diff --git a/CHANGELOG.md b/CHANGELOG.md index de41215..7cb3da5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [Unreleased] +## [v1.1.0] - 2024-05-21 ### Changed - Adopt `bool isConnected()` from `Connection` interface ([#7](https://github.com/matth-x/MicroOcppMongoose/pull/7)) - Do not copy cert into heap memory ([#10](https://github.com/matth-x/MicroOcppMongoose/pull/10)) +- Migrate to base64-encoder in Mongoose ([#4](https://github.com/matth-x/MicroOcppMongoose/pull/4)) ### Added @@ -18,6 +19,10 @@ - FTP moved into a new project [MicroFtp](https://github.com/matth-x/MicroFtp) ([#5](https://github.com/matth-x/MicroOcppMongoose/pull/5)) - Custom config `Cst_CaCert` ([#10](https://github.com/matth-x/MicroOcppMongoose/pull/10)) +### Fixed + +- Encode AuthorizationKey in Hex ([#4](https://github.com/matth-x/MicroOcppMongoose/pull/4)) + ## [1.0.0] - 2023-10-20 _First release._ diff --git a/README.md b/README.md index 81fddac..612b68e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # MicroOcppMongoose Mongoose WebSocket adapter for MicroOcpp -**Formerly ArduinoOcppMongoose**: *see the statement on the former ArduinoOcpp [project site](https://github.com/matth-x/MicroOcpp)* - ## Dependencies The following projects must be available on the include path: @@ -19,8 +17,6 @@ The setup is done if the following include statements work: #include ``` -The last dependency is [base64-converter by Densaugeo](https://github.com/Densaugeo/base64_arduino), but it is already included in this repository. Thanks to [Densaugeo](https://github.com/Densaugeo) for providing it! - ## License This project is licensed under the GPL as it uses the [Mongoose Embedded Networking Library](https://github.com/cesanta/mongoose). If you have a proprietary license of Mongoose, then the [MIT License](https://github.com/matth-x/MicroOcpp/blob/master/LICENSE) applies. diff --git a/src/MicroOcppMongooseClient.cpp b/src/MicroOcppMongooseClient.cpp index 4071c20..24bdbd8 100644 --- a/src/MicroOcppMongooseClient.cpp +++ b/src/MicroOcppMongooseClient.cpp @@ -3,7 +3,6 @@ // GPL-3.0 License (see LICENSE) #include "MicroOcppMongooseClient.h" -#include "base64.hpp" #include #include @@ -14,6 +13,10 @@ #define MO_MG_F_IS_MOcppMongooseClient MG_F_USER_2 #endif +namespace MicroOcpp { +bool validateAuthorizationKeyHex(const char *auth_key_hex); +} + using namespace MicroOcpp; void ws_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data); @@ -21,7 +24,7 @@ void ws_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data); MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr, const char *backend_url_factory, const char *charge_box_id_factory, - const char *auth_key_factory, + unsigned char *auth_key_factory, size_t auth_key_factory_len, const char *ca_certificate, std::shared_ptr filesystem, ProtocolVersion protocolVersion) : mgr(mgr), protocolVersion(protocolVersion) { @@ -43,8 +46,22 @@ MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr, MO_CONFIG_EXT_PREFIX "BackendUrl", backend_url_factory, MO_WSCONN_FN, readonly, true); setting_cb_id_str = declareConfiguration( MO_CONFIG_EXT_PREFIX "ChargeBoxId", charge_box_id_factory, MO_WSCONN_FN, readonly, true); - setting_auth_key_str = declareConfiguration( - "AuthorizationKey", auth_key_factory, MO_WSCONN_FN, readonly, true); + + if (auth_key_factory_len > MO_AUTHKEY_LEN_MAX) { + MO_DBG_WARN("auth_key_factory too long - will be cropped"); + auth_key_factory_len = MO_AUTHKEY_LEN_MAX; + } + char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1]; + auth_key_hex[0] = '\0'; + if (auth_key_factory) { + for (size_t i = 0; i < auth_key_factory_len; i += 2) { + snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key_factory[i]); + } + } + setting_auth_key_hex_str = declareConfiguration( + "AuthorizationKey", auth_key_hex, MO_WSCONN_FN, readonly, true); + registerConfigurationValidator("AuthorizationKey", validateAuthorizationKeyHex); + ws_ping_interval_int = declareConfiguration( "WebSocketPingInterval", 5, MO_WSCONN_FN); reconnect_interval_int = declareConfiguration( @@ -67,6 +84,24 @@ MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr, maintainWsConn(); } +MOcppMongooseClient::MOcppMongooseClient(struct mg_mgr *mgr, + const char *backend_url_factory, + const char *charge_box_id_factory, + const char *auth_key_factory, + const char *ca_certificate, + std::shared_ptr filesystem, + ProtocolVersion protocolVersion) : + + MOcppMongooseClient(mgr, + backend_url_factory, + charge_box_id_factory, + (unsigned char *)auth_key_factory, auth_key_factory ? strlen(auth_key_factory) : 0, + ca_certificate, + filesystem, + protocolVersion) { + +} + MOcppMongooseClient::~MOcppMongooseClient() { MO_DBG_DEBUG("destruct MOcppMongooseClient"); if (websocket) { @@ -156,6 +191,59 @@ void MOcppMongooseClient::maintainWsConn() { last_reconnection_attempt = mocpp_tick_ms(); + /* + * determine auth token + */ + + std::string basic_auth64; + + if (auth_key_len > 0) { + + #if MO_DBG_LEVEL >= MO_DL_DEBUG + { + char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1]; + auth_key_hex[0] = '\0'; + for (size_t i = 0; i < auth_key_len; i++) { + snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key[i]); + } + MO_DBG_DEBUG("auth Token=%s:%s (key will be converted to non-hex)", cb_id.c_str(), auth_key_hex); + } + #endif //MO_DBG_LEVEL >= MO_DL_DEBUG + + unsigned char *token = new unsigned char[cb_id.length() + 1 + auth_key_len]; //cb_id:auth_key + if (!token) { + //OOM + return; + } + size_t len = 0; + memcpy(token, cb_id.c_str(), cb_id.length()); + len += cb_id.length(); + token[len++] = (unsigned char) ':'; + memcpy(token + len, auth_key, auth_key_len); + len += auth_key_len; + + int base64_length = ((len + 2) / 3) * 4; //3 bytes base256 get encoded into 4 bytes base64. --> base64_len = ceil(len/3) * 4 + char *base64 = new char[base64_length + 1]; + if (!base64) { + //OOM + delete[] token; + return; + } + + // mg_base64_encode() places a null terminator automatically, because the output is a c-string + mg_base64_encode(token, len, base64); + delete[] token; + + MO_DBG_DEBUG("auth64 len=%u, auth64 Token=%s", base64_length, base64); + + basic_auth64 = &base64[0]; + + delete[] base64; + } else { + MO_DBG_DEBUG("no authentication"); + (void) 0; + } + #if defined(MO_MG_VERSION_614) struct mg_connect_opts opts; @@ -177,7 +265,7 @@ void MOcppMongooseClient::maintainWsConn() { char extra_headers [128] = {'\0'}; - if (!auth_key.empty()) { + if (!basic_auth64.empty()) { auto ret = snprintf(extra_headers, 128, "Authorization: Basic %s\r\n", basic_auth64.c_str()); if (ret < 0 || ret >= 128) { MO_DBG_ERR("Basic Authentication failed: %d", ret); @@ -258,8 +346,23 @@ void MOcppMongooseClient::setAuthKey(const char *auth_key_cstr) { return; } - if (setting_auth_key_str) { - setting_auth_key_str->setString(auth_key_cstr); + return setAuthKey((const unsigned char*)auth_key_cstr, strlen(auth_key_cstr)); +} + +void MOcppMongooseClient::setAuthKey(const unsigned char *auth_key, size_t len) { + if (!auth_key || len > MO_AUTHKEY_LEN_MAX) { + MO_DBG_ERR("invalid argument"); + return; + } + + char auth_key_hex [2 * MO_AUTHKEY_LEN_MAX + 1]; + auth_key_hex[0] = '\0'; + for (size_t i = 0; i < len; i += 2) { + snprintf(auth_key_hex + 2 * i, 3, "%02X", auth_key[i]); + } + + if (setting_auth_key_hex_str) { + setting_auth_key_hex_str->setString(auth_key_hex); configuration_save(); } } @@ -283,48 +386,46 @@ void MOcppMongooseClient::reloadConfigs() { cb_id = setting_cb_id_str->getString(); } - if (setting_auth_key_str) { - auth_key = setting_auth_key_str->getString(); + if (setting_auth_key_hex_str) { + auto auth_key_hex = setting_auth_key_hex_str->getString(); + + #if MO_MG_VERSION_614 + cs_from_hex((char*)auth_key, auth_key_hex, strlen(auth_key_hex)); + #else + mg_unhex(auth_key_hex, strlen(auth_key_hex), auth_key); + #endif + + auth_key_len = strlen(setting_auth_key_hex_str->getString()) / 2; + auth_key[auth_key_len] = '\0'; //need null-termination as long as deprecated `const char *getAuthKey()` exists } /* - * determine new URL and auth token with updated WS credentials + * determine new URL with updated WS credentials */ url.clear(); - basic_auth64.clear(); if (backend_url.empty()) { MO_DBG_DEBUG("empty URL closes connection"); return; - } else { - url = backend_url; - - if (url.back() != '/' && !cb_id.empty()) { - url.append("/"); - } - - url.append(cb_id); } - if (!auth_key.empty()) { - std::string token = cb_id + ":" + auth_key; - - MO_DBG_DEBUG("auth Token=%s", token.c_str()); + url = backend_url; - unsigned int base64_length = encode_base64_length(token.length()); - std::vector base64 (base64_length + 1); - - // encode_base64() places a null terminator automatically, because the output is a string - base64_length = encode_base64((const unsigned char*) token.c_str(), token.length(), &base64[0]); - - MO_DBG_DEBUG("auth64 len=%u, auth64 Token=%s", base64_length, &base64[0]); + if (url.back() != '/' && !cb_id.empty()) { + url.append("/"); + } + url.append(cb_id); +} - basic_auth64 = (const char*) &base64[0]; - } else { - MO_DBG_DEBUG("no authentication"); - (void) 0; +int MOcppMongooseClient::printAuthKey(unsigned char *buf, size_t size) { + if (!buf || size < auth_key_len) { + MO_DBG_ERR("invalid argument"); + return -1; } + + memcpy(buf, auth_key, auth_key_len); + return (int)auth_key_len; } void MOcppMongooseClient::setConnectionOpen(bool open) { @@ -474,3 +575,31 @@ void ws_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { } } #endif + +bool MicroOcpp::validateAuthorizationKeyHex(const char *auth_key_hex) { + if (!auth_key_hex) { + return true; //nullptr (or "") means disable Auth + } + bool valid = true; + size_t i = 0; + while (i <= 2 * MO_AUTHKEY_LEN_MAX && auth_key_hex[i] != '\0') { + //check if character is in 0-9, a-f, or A-F + if ( (auth_key_hex[i] >= '0' && auth_key_hex[i] <= '9') || + (auth_key_hex[i] >= 'a' && auth_key_hex[i] <= 'f') || + (auth_key_hex[i] >= 'A' && auth_key_hex[i] <= 'F')) { + //yes, it is + i++; + } else { + //no, it isn't + valid = false; + break; + } + } + valid &= auth_key_hex[i] == '\0'; + valid &= (i % 2) == 0; + if (!valid) { + MO_DBG_ERR("AuthorizationKey must be hex with at most 20 octets"); + (void)0; + } + return valid; +} diff --git a/src/MicroOcppMongooseClient.h b/src/MicroOcppMongooseClient.h index 7bc43fa..1160230 100644 --- a/src/MicroOcppMongooseClient.h +++ b/src/MicroOcppMongooseClient.h @@ -21,6 +21,8 @@ #define MO_WSCONN_FN (MO_FILENAME_PREFIX "ws-conn.jsn") #endif +#define MO_AUTHKEY_LEN_MAX 20 //AuthKey in Bytes. Hex value has double length + namespace MicroOcpp { class FilesystemAdapter; @@ -33,12 +35,12 @@ class MOcppMongooseClient : public MicroOcpp::Connection { std::string backend_url; std::string cb_id; std::string url; //url = backend_url + '/' + cb_id - std::string auth_key; - std::string basic_auth64; + unsigned char auth_key [MO_AUTHKEY_LEN_MAX + 1]; //AuthKey in bytes encoding ("FF01" = {0xFF, 0x01}) + size_t auth_key_len; const char *ca_cert; //zero-copy. The host system must ensure that this pointer remains valid during the lifetime of this class std::shared_ptr setting_backend_url_str; std::shared_ptr setting_cb_id_str; - std::shared_ptr setting_auth_key_str; + std::shared_ptr setting_auth_key_hex_str; unsigned long last_status_dbg_msg {0}, last_recv {0}; std::shared_ptr reconnect_interval_int; //minimum time between two connect trials in s unsigned long last_reconnection_attempt {-1UL / 2UL}; @@ -57,6 +59,15 @@ class MOcppMongooseClient : public MicroOcpp::Connection { void maintainWsConn(); public: + MOcppMongooseClient(struct mg_mgr *mgr, + const char *backend_url_factory, + const char *charge_box_id_factory, + unsigned char *auth_key_factory, size_t auth_key_factory_len, + const char *ca_cert = nullptr, //zero-copy, the string must outlive this class and mg_mgr. Forwards this string to Mongoose as ssl_ca_cert (see https://github.com/cesanta/mongoose/blob/ab650ec5c99ceb52bb9dc59e8e8ec92a2724932b/mongoose.h#L4192) + std::shared_ptr filesystem = nullptr, + ProtocolVersion protocolVersion = ProtocolVersion(1,6)); + + //DEPRECATED: will be removed in a future release MOcppMongooseClient(struct mg_mgr *mgr, const char *backend_url_factory = nullptr, const char *charge_box_id_factory = nullptr, @@ -82,14 +93,16 @@ class MOcppMongooseClient : public MicroOcpp::Connection { //update WS configs. To apply the updates, call `reloadConfigs()` afterwards void setBackendUrl(const char *backend_url); void setChargeBoxId(const char *cb_id); - void setAuthKey(const char *auth_key); + void setAuthKey(const char *auth_key); //DEPRECATED: will be removed in a future release + void setAuthKey(const unsigned char *auth_key, size_t len); //set the auth key in bytes-encoded format void setCaCert(const char *ca_cert); //forwards this string to Mongoose as ssl_ca_cert (see https://github.com/cesanta/mongoose/blob/ab650ec5c99ceb52bb9dc59e8e8ec92a2724932b/mongoose.h#L4192) void reloadConfigs(); const char *getBackendUrl() {return backend_url.c_str();} const char *getChargeBoxId() {return cb_id.c_str();} - const char *getAuthKey() {return auth_key.c_str();} + const char *getAuthKey() {return (const char*)auth_key;} //DEPRECATED: will be removed in a future release + int printAuthKey(unsigned char *buf, size_t size); const char *getCaCert() {return ca_cert ? ca_cert : "";} const char *getUrl() {return url.c_str();} diff --git a/src/MicroOcppMongooseClient_c.cpp b/src/MicroOcppMongooseClient_c.cpp index bcc5070..c696d31 100644 --- a/src/MicroOcppMongooseClient_c.cpp +++ b/src/MicroOcppMongooseClient_c.cpp @@ -1,5 +1,5 @@ // matth-x/MicroOcppMongoose -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // GPL-3.0 License (see LICENSE) #include "MicroOcppMongooseClient_c.h" diff --git a/src/MicroOcppMongooseClient_c.h b/src/MicroOcppMongooseClient_c.h index 324296f..efa6c65 100644 --- a/src/MicroOcppMongooseClient_c.h +++ b/src/MicroOcppMongooseClient_c.h @@ -1,9 +1,9 @@ // matth-x/MicroOcppMongoose -// Copyright Matthias Akstaller 2019 - 2023 +// Copyright Matthias Akstaller 2019 - 2024 // GPL-3.0 License (see LICENSE) -#ifndef AOCPPMONGOOSECLIENT_C_H -#define AOCPPMONGOOSECLIENT_C_H +#ifndef MO_MONGOOSECLIENT_C_H +#define MO_MONGOOSECLIENT_C_H #if defined(__cplusplus) && defined(ARDUINO) //fix for conflicting defitions of IPAddress on Arduino #include diff --git a/src/base64.hpp b/src/base64.hpp deleted file mode 100644 index 2f12ccb..0000000 --- a/src/base64.hpp +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Base64 encoding and decoding of strings. Uses '+' for 62, '/' for 63, '=' for padding - * - * https://github.com/Densaugeo/base64_arduino - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Densaugeo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef BASE64_H_INCLUDED -#define BASE64_H_INCLUDED - -/* binary_to_base64: - * Description: - * Converts a single byte from a binary value to the corresponding base64 character - * Parameters: - * v - Byte to convert - * Returns: - * ascii code of base64 character. If byte is >= 64, then there is not corresponding base64 character - * and 255 is returned - */ -unsigned char binary_to_base64(unsigned char v); - -/* base64_to_binary: - * Description: - * Converts a single byte from a base64 character to the corresponding binary value - * Parameters: - * c - Base64 character (as ascii code) - * Returns: - * 6-bit binary value - */ -unsigned char base64_to_binary(unsigned char c); - -/* encode_base64_length: - * Description: - * Calculates length of base64 string needed for a given number of binary bytes - * Parameters: - * input_length - Amount of binary data in bytes - * Returns: - * Number of base64 characters needed to encode input_length bytes of binary data - */ -unsigned int encode_base64_length(unsigned int input_length); - -/* decode_base64_length: - * Description: - * Calculates number of bytes of binary data in a base64 string - * Variant that does not use input_length no longer used within library, retained for API compatibility - * Parameters: - * input - Base64-encoded null-terminated string - * input_length (optional) - Number of bytes to read from input pointer - * Returns: - * Number of bytes of binary data in input - */ -unsigned int decode_base64_length(const unsigned char input[]); -unsigned int decode_base64_length(const unsigned char input[], unsigned int input_length); - -/* encode_base64: - * Description: - * Converts an array of bytes to a base64 null-terminated string - * Parameters: - * input - Pointer to input data - * input_length - Number of bytes to read from input pointer - * output - Pointer to output string. Null terminator will be added automatically - * Returns: - * Length of encoded string in bytes (not including null terminator) - */ -unsigned int encode_base64(const unsigned char input[], unsigned int input_length, unsigned char output[]); - -/* decode_base64: - * Description: - * Converts a base64 null-terminated string to an array of bytes - * Parameters: - * input - Pointer to input string - * input_length (optional) - Number of bytes to read from input pointer - * output - Pointer to output array - * Returns: - * Number of bytes in the decoded binary - */ -unsigned int decode_base64(const unsigned char input[], unsigned char output[]); -unsigned int decode_base64(const unsigned char input[], unsigned int input_length, unsigned char output[]); - -unsigned char binary_to_base64(unsigned char v) { - // Capital letters - 'A' is ascii 65 and base64 0 - if(v < 26) return v + 'A'; - - // Lowercase letters - 'a' is ascii 97 and base64 26 - if(v < 52) return v + 71; - - // Digits - '0' is ascii 48 and base64 52 - if(v < 62) return v - 4; - - #ifdef BASE64_URL - // '-' is ascii 45 and base64 62 - if(v == 62) return '-'; - #else - // '+' is ascii 43 and base64 62 - if(v == 62) return '+'; - #endif - - #ifdef BASE64_URL - // '_' is ascii 95 and base64 62 - if(v == 63) return '_'; - #else - // '/' is ascii 47 and base64 63 - if(v == 63) return '/'; - #endif - - return 64; -} - -unsigned char base64_to_binary(unsigned char c) { - // Capital letters - 'A' is ascii 65 and base64 0 - if('A' <= c && c <= 'Z') return c - 'A'; - - // Lowercase letters - 'a' is ascii 97 and base64 26 - if('a' <= c && c <= 'z') return c - 71; - - // Digits - '0' is ascii 48 and base64 52 - if('0' <= c && c <= '9') return c + 4; - - #ifdef BASE64_URL - // '-' is ascii 45 and base64 62 - if(c == '-') return 62; - #else - // '+' is ascii 43 and base64 62 - if(c == '+') return 62; - #endif - - #ifdef BASE64_URL - // '_' is ascii 95 and base64 62 - if(c == '_') return 63; - #else - // '/' is ascii 47 and base64 63 - if(c == '/') return 63; - #endif - - return 255; -} - -unsigned int encode_base64_length(unsigned int input_length) { - return (input_length + 2)/3*4; -} - -unsigned int decode_base64_length(const unsigned char input[]) { - return decode_base64_length(input, -1); -} - -unsigned int decode_base64_length(const unsigned char input[], unsigned int input_length) { - const unsigned char *start = input; - - while(base64_to_binary(input[0]) < 64 && (unsigned int) (input - start) < input_length) { - ++input; - } - - input_length = (unsigned int) (input - start); - return input_length/4*3 + (input_length % 4 ? input_length % 4 - 1 : 0); -} - -unsigned int encode_base64(const unsigned char input[], unsigned int input_length, unsigned char output[]) { - unsigned int full_sets = input_length/3; - - // While there are still full sets of 24 bits... - for(unsigned int i = 0; i < full_sets; ++i) { - output[0] = binary_to_base64( input[0] >> 2); - output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4); - output[2] = binary_to_base64((input[1] & 0x0F) << 2 | input[2] >> 6); - output[3] = binary_to_base64( input[2] & 0x3F); - - input += 3; - output += 4; - } - - switch(input_length % 3) { - case 0: - output[0] = '\0'; - break; - case 1: - output[0] = binary_to_base64( input[0] >> 2); - output[1] = binary_to_base64((input[0] & 0x03) << 4); - output[2] = '='; - output[3] = '='; - output[4] = '\0'; - break; - case 2: - output[0] = binary_to_base64( input[0] >> 2); - output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4); - output[2] = binary_to_base64((input[1] & 0x0F) << 2); - output[3] = '='; - output[4] = '\0'; - break; - } - - return encode_base64_length(input_length); -} - -unsigned int decode_base64(const unsigned char input[], unsigned char output[]) { - return decode_base64(input, -1, output); -} - -unsigned int decode_base64(const unsigned char input[], unsigned int input_length, unsigned char output[]) { - unsigned int output_length = decode_base64_length(input, input_length); - - // While there are still full sets of 24 bits... - for(unsigned int i = 2; i < output_length; i += 3) { - output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; - output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; - output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]); - - input += 4; - output += 3; - } - - switch(output_length % 3) { - case 1: - output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; - break; - case 2: - output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; - output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; - break; - } - - return output_length; -} - -#endif // ifndef