Skip to content

Commit 779820e

Browse files
committed
Cleanup base64::encode functions
The implementation choice here using libb64 is generally good as it is a relatively fast implementation, however the adaptation to use PROGMEM for the translation function was a bad choice, as reading randomly PROGMEM with byte-wide access is very very very slow. Doing a naive if-snake is between 20% and 55% faster and uses less flash (about 120 bytes less) and also for reasons I don't understand 8 bytes less data RAM (maybe the removal of static?). In addition the base64::encode function was allocating for larger input a huge amount of memory (twice the total size). we can reduce that by doing a chunk-wise conversation to base64.
1 parent 1e17ddd commit 779820e

File tree

3 files changed

+53
-35
lines changed

3 files changed

+53
-35
lines changed

cores/esp8266/base64.cpp

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
#include "Arduino.h"
2626
extern "C" {
27-
#include "libb64/cdecode.h"
2827
#include "libb64/cencode.h"
2928
}
3029
#include "base64.h"
@@ -35,37 +34,43 @@ extern "C" {
3534
* @param length size_t
3635
* @return String
3736
*/
38-
String base64::encode(const uint8_t * data, size_t length, bool doNewLines) {
37+
String base64::encode(const uint8_t * data, size_t length, bool doNewLines)
38+
{
39+
String base64;
40+
3941
// base64 needs more size then the source data, use cencode.h macros
40-
size_t size = ((doNewLines ? base64_encode_expected_len(length)
41-
: base64_encode_expected_len_nonewlines(length)) + 1);
42-
char * buffer = (char *) malloc(size);
43-
if(buffer) {
42+
size_t size = ((doNewLines ? base64_encode_expected_len( length )
43+
: base64_encode_expected_len_nonewlines( length )) + 1);
44+
45+
if (base64.reserve(size))
46+
{
47+
4448
base64_encodestate _state;
45-
if(doNewLines)
49+
if (doNewLines)
4650
{
4751
base64_init_encodestate(&_state);
4852
}
4953
else
5054
{
5155
base64_init_encodestate_nonewlines(&_state);
5256
}
53-
int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state);
54-
len = base64_encode_blockend((buffer + len), &_state);
5557

56-
String base64 = String(buffer);
57-
free(buffer);
58-
return base64;
58+
constexpr size_t BUFSIZE = 48;
59+
char buf[BUFSIZE + 1 /* newline */ + 1 /* NUL */];
60+
for (size_t len = 0; len < length; len += BUFSIZE * 3 / 4)
61+
{
62+
size_t blocklen = base64_encode_block((const char*) data + len,
63+
std::min( BUFSIZE * 3 / 4, length - len ), buf, &_state);
64+
buf[blocklen] = '\0';
65+
base64 += buf;
66+
}
67+
if (base64_encode_blockend(buf, &_state))
68+
base64 += buf;
69+
}
70+
else
71+
{
72+
base64 = F("-FAIL-");
5973
}
60-
return String("-FAIL-");
61-
}
6274

63-
/**
64-
* convert input data to base64
65-
* @param text const String&
66-
* @return String
67-
*/
68-
String base64::encode(const String& text, bool doNewLines) {
69-
return base64::encode((const uint8_t *) text.c_str(), text.length(), doNewLines);
75+
return base64;
7076
}
71-

cores/esp8266/base64.h

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@
2525
#ifndef CORE_BASE64_H_
2626
#define CORE_BASE64_H_
2727

28-
class base64 {
29-
public:
30-
// NOTE: The default behaviour of backend (lib64)
31-
// is to add a newline every 72 (encoded) characters output.
32-
// This may 'break' longer uris and json variables
33-
static String encode(const uint8_t * data, size_t length, bool doNewLines = true);
34-
static String encode(const String& text, bool doNewLines = true);
35-
private:
28+
class base64
29+
{
30+
public:
31+
// NOTE: The default behaviour of backend (lib64)
32+
// is to add a newline every 72 (encoded) characters output.
33+
// This may 'break' longer uris and json variables
34+
static String encode(const uint8_t * data, size_t length, bool doNewLines = true);
35+
static String inline encode(const String& text, bool doNewLines = true)
36+
{
37+
return encode( (const uint8_t *) text.c_str(), text.length(), doNewLines );
38+
}
39+
private:
3640
};
3741

3842

cores/esp8266/libb64/cencode.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ This is part of the libb64 project, and has been placed in the public domain.
55
For details, see http://sourceforge.net/projects/libb64
66
*/
77

8-
#include <pgmspace.h>
98
#include "cencode.h"
109

1110
extern "C" {
@@ -23,10 +22,20 @@ void base64_init_encodestate_nonewlines(base64_encodestate* state_in){
2322
state_in->stepsnewline = -1;
2423
}
2524

26-
char base64_encode_value(char value_in){
27-
static const char encoding[] PROGMEM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
28-
if (value_in > 63) return '=';
29-
return pgm_read_byte( &encoding[(int)value_in] );
25+
char base64_encode_value(const char n) {
26+
char r;
27+
28+
if (n < 26)
29+
r = n + 'A';
30+
else if (n < 26 + 26)
31+
r = n - 26 + 'a';
32+
else if (n < 26 + 26 + 10 )
33+
r = n - 26 - 26 + '0';
34+
else if (n == 62 )
35+
r = '+';
36+
else
37+
r = '/';
38+
return r;
3039
}
3140

3241
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in){

0 commit comments

Comments
 (0)