Skip to content

Commit 14262af

Browse files
dirkmuellerearlephilhower
authored andcommitted
Cleanup base64::encode functions (#6607)
* 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. * Create authorisation base64 encoded string without newlines Rather than first creating a string with newlines and then stripping it away in the fast path of constructing the query, we can call the right method and trust that the result does not have newlines anymore.
1 parent 348c58b commit 14262af

File tree

4 files changed

+56
-38
lines changed

4 files changed

+56
-38
lines changed

cores/esp8266/base64.cpp

+27-22
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

+12-8
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

+14-5
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){

libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol
319319
// auth info
320320
String auth = host.substring(0, index);
321321
host.remove(0, index + 1); // remove auth part including @
322-
_base64Authorization = base64::encode(auth);
322+
_base64Authorization = base64::encode(auth, false /* doNewLines */);
323323
}
324324

325325
// get port
@@ -504,7 +504,7 @@ void HTTPClient::setAuthorization(const char * user, const char * password)
504504
String auth = user;
505505
auth += ':';
506506
auth += password;
507-
_base64Authorization = base64::encode(auth);
507+
_base64Authorization = base64::encode(auth, false /* doNewLines */);
508508
}
509509
}
510510

@@ -516,6 +516,7 @@ void HTTPClient::setAuthorization(const char * auth)
516516
{
517517
if(auth) {
518518
_base64Authorization = auth;
519+
_base64Authorization.replace(String('\n'), emptyString);
519520
}
520521
}
521522

@@ -1243,7 +1244,6 @@ bool HTTPClient::sendHeader(const char * type)
12431244
}
12441245

12451246
if(_base64Authorization.length()) {
1246-
_base64Authorization.replace("\n", "");
12471247
header += F("Authorization: Basic ");
12481248
header += _base64Authorization;
12491249
header += "\r\n";

0 commit comments

Comments
 (0)