Publish LumaOps source

This commit is contained in:
LumaOps release export
2026-09-03 01:18:36 +02:00
commit 7f1c0e5f71
2363 changed files with 501543 additions and 0 deletions
@@ -0,0 +1,352 @@
/*---------------------------------------------------------*\
| GoveeController.cpp |
| |
| Driver for Govee wireless lighting devices |
| |
| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <algorithm>
#include <nlohmann/json.hpp>
#include "base64.hpp"
#include "GoveeController.h"
using json = nlohmann::json;
using namespace std::chrono_literals;
base64::byte CalculateXorChecksum(std::vector<base64::byte> packet)
{
base64::byte checksum = 0;
for(unsigned int i = 0; i < packet.size(); i++)
{
checksum ^= packet[i];
}
return(checksum);
}
GoveeController::GoveeController(std::string ip)
{
/*-----------------------------------------------------*\
| Fill in location string with device's IP address |
\*-----------------------------------------------------*/
ip_address = ip;
/*-----------------------------------------------------*\
| Register callback for receiving broadcasts |
\*-----------------------------------------------------*/
RegisterReceiveBroadcastCallback(this);
broadcast_received = false;
/*-----------------------------------------------------*\
| Request device information |
\*-----------------------------------------------------*/
SendScan();
/*-----------------------------------------------------*\
| Wait up to 5s for device information to be received |
\*-----------------------------------------------------*/
for(unsigned int wait_count = 0; wait_count < 500; wait_count++)
{
if(broadcast_received)
{
break;
}
std::this_thread::sleep_for(10ms);
}
/*-----------------------------------------------------*\
| Open a UDP client sending to the Govee device IP, |
| port 4003 |
\*-----------------------------------------------------*/
port.udp_client(ip_address.c_str(), "4003");
}
GoveeController::~GoveeController()
{
UnregisterReceiveBroadcastCallback(this);
}
std::string GoveeController::GetLocation()
{
return("IP: " + ip_address);
}
std::string GoveeController::GetSku()
{
return(sku);
}
std::string GoveeController::GetVersion()
{
return("BLE Hardware Version: " + bleVersionHard + "\r\n" +
"BLE Software Version: " + bleVersionSoft + "\r\n" +
"WiFi Hardware Version: " + wifiVersionHard + "\r\n" +
"WiFI Software Version: " + wifiVersionSoft + "\r\n");
}
void GoveeController::ReceiveBroadcast(char* recv_buf, int size)
{
if(broadcast_received)
{
return;
}
/*-----------------------------------------------------*\
| Responses are not null-terminated, so add termination |
\*-----------------------------------------------------*/
recv_buf[size] = '\0';
/*-----------------------------------------------------*\
| Convert null-terminated response to JSON |
\*-----------------------------------------------------*/
json response = json::parse(recv_buf);
/*-----------------------------------------------------*\
| Check if the response contains the method name |
\*-----------------------------------------------------*/
if(response.contains("msg"))
{
/*-------------------------------------------------*\
| Handle responses for scan command |
| This command's response should contain a msg |
| object containing a data member with ip, device, |
| sku, among others. |
\*-------------------------------------------------*/
if(response["msg"].contains("cmd"))
{
if(response["msg"]["cmd"] == "scan")
{
if(response["msg"].contains("data"))
{
if(response["msg"]["data"].contains("ip"))
{
if(response["msg"]["data"]["ip"] == ip_address)
{
if(response["msg"]["data"].contains("sku"))
{
sku = response["msg"]["data"]["sku"];
}
if(response["msg"]["data"].contains("bleVersionHard"))
{
bleVersionHard = response["msg"]["data"]["bleVersionHard"];
}
if(response["msg"]["data"].contains("bleVersionSoft"))
{
bleVersionSoft = response["msg"]["data"]["bleVersionSoft"];
}
if(response["msg"]["data"].contains("wifiVersionHard"))
{
wifiVersionHard = response["msg"]["data"]["wifiVersionHard"];
}
if(response["msg"]["data"].contains("wifiVersionSoft"))
{
wifiVersionSoft = response["msg"]["data"]["wifiVersionSoft"];
}
broadcast_received = true;
}
}
}
}
}
}
}
void GoveeController::SetColor(unsigned char red, unsigned char green, unsigned char blue)
{
json command;
command["msg"]["cmd"] = "colorwc";
command["msg"]["data"]["color"]["r"] = red;
command["msg"]["data"]["color"]["g"] = green;
command["msg"]["data"]["color"]["b"] = blue;
command["msg"]["data"]["colorTemInKelvin"] = "0";
/*-----------------------------------------------------*\
| Convert the JSON object to a string and write it |
\*-----------------------------------------------------*/
std::string command_str = command.dump();
port.udp_write((char *)command_str.c_str(), (int)command_str.length() + 1);
}
void GoveeController::SendRazerData(RGBColor* colors, unsigned int size)
{
/*-----------------------------------------------------*\
| Do not send an empty frame (this was producing |
| length=2, count=0) |
\*-----------------------------------------------------*/
if(size == 0)
{
return;
}
/*-----------------------------------------------------*\
| PT payload: BB [len_hi] [len_lo] B0 [gradient_off=1] |
| [led_count] (RGB * N) [xor] |
| length = 2 + 3*N (bytes after 0xB0: gradient_off + |
| led_count + RGB*count) |
\*-----------------------------------------------------*/
const unsigned int count = std::min(size, 255u);
const unsigned int payload_len = 2 + (3 * count);
/*-----------------------------------------------------*\
| Create buffer with fixed size and fill sequentially |
\*-----------------------------------------------------*/
std::vector<base64::byte> pkt;
pkt.reserve(7 + (3 * count));
pkt.push_back(0xBB);
pkt.push_back(static_cast<base64::byte>((payload_len >> 8) & 0xFF)); /* len_hi */
pkt.push_back(static_cast<base64::byte>(payload_len & 0xFF)); /* len_lo */
pkt.push_back(0xB0); /* subcommand */
pkt.push_back(0x01); /* gradient_off = 1 */
pkt.push_back(static_cast<base64::byte>(count)); /* led_count */
for(std::size_t led_idx = 0; led_idx < count; led_idx++)
{
pkt.push_back(RGBGetRValue(colors[led_idx]));
pkt.push_back(RGBGetGValue(colors[led_idx]));
pkt.push_back(RGBGetBValue(colors[led_idx]));
}
pkt.push_back(CalculateXorChecksum(pkt));
json command;
command["msg"]["cmd"] = "razer";
command["msg"]["data"]["pt"] = base64::encode(pkt);
/*-----------------------------------------------------*\
| Convert the JSON object to a string and write it |
\*-----------------------------------------------------*/
std::string command_str = command.dump();
port.udp_write((char *)command_str.c_str(), (int)command_str.length() + 1);
}
void GoveeController::SendRazerDisable()
{
const std::vector<base64::byte> pkt = { 0xBB, 0x00, 0x01, 0xB1, 0x00, 0x0B };
json command;
command["msg"]["cmd"] = "razer";
command["msg"]["data"]["pt"] = base64::encode(pkt);
/*-----------------------------------------------------*\
| Convert the JSON object to a string and write it |
\*-----------------------------------------------------*/
std::string command_str = command.dump();
port.udp_write((char *)command_str.c_str(), (int)command_str.length() + 1);
}
void GoveeController::SendRazerEnable()
{
const std::vector<base64::byte> pkt = { 0xBB, 0x00, 0x01, 0xB1, 0x01, 0x0A };
json command;
command["msg"]["cmd"] = "razer";
command["msg"]["data"]["pt"] = base64::encode(pkt);
/*-----------------------------------------------------*\
| Convert the JSON object to a string and write it |
\*-----------------------------------------------------*/
std::string command_str = command.dump();
port.udp_write((char *)command_str.c_str(), (int)command_str.length() + 1);
}
void GoveeController::SendScan()
{
json command;
command["msg"]["cmd"] = "scan";
/*-----------------------------------------------------*\
| Matches what Govee devices commonly accept for LAN |
| scan |
\*-----------------------------------------------------*/
command["msg"]["data"]["account_topic"] = "reserve";
/*-----------------------------------------------------*\
| Convert the JSON object to a string and write it |
\*-----------------------------------------------------*/
std::string command_str = command.dump();
broadcast_port.udp_write((char *)command_str.c_str(), (int)command_str.length() + 1);
}
/*---------------------------------------------------------*\
| Static class members for shared broadcast receiver |
\*---------------------------------------------------------*/
net_port GoveeController::broadcast_port;
std::vector<GoveeController*> GoveeController::callbacks;
std::thread* GoveeController::ReceiveThread;
std::atomic<bool> GoveeController::ReceiveThreadRun;
void GoveeController::ReceiveBroadcastThreadFunction()
{
char recv_buf[1024];
broadcast_port.set_receive_timeout(1, 0);
while(ReceiveThreadRun.load())
{
/*-------------------------------------------------*\
| Receive up to 1024 bytes from the device with a |
| 1s timeout |
\*-------------------------------------------------*/
int size = broadcast_port.udp_listen(recv_buf, 1024);
/*-------------------------------------------------*\
| If data was received, loop through registered |
| callback controllers and call the |
| ReceiveBroadcast function for the controller |
| matching the received data |
| |
| NOTE: As implemented, it doesn't actually match |
| the intended controller and just calls all |
| registered controllers. As they are all called |
| sequence, this should work, but if parallel calls |
| are ever needed, receives should be filtered by |
| IP address |
\*-------------------------------------------------*/
if(size > 0)
{
for(std::size_t callback_idx = 0; callback_idx < callbacks.size(); callback_idx++)
{
GoveeController* controller = callbacks[callback_idx];
controller->ReceiveBroadcast(recv_buf, size);
}
}
}
}
void GoveeController::RegisterReceiveBroadcastCallback(GoveeController* controller_ptr)
{
callbacks.push_back(controller_ptr);
}
void GoveeController::UnregisterReceiveBroadcastCallback(GoveeController* controller_ptr)
{
for(std::size_t callback_idx = 0; callback_idx < callbacks.size(); callback_idx++)
{
if(callbacks[callback_idx] == controller_ptr)
{
callbacks.erase(callbacks.begin() + callback_idx);
break;
}
}
}
@@ -0,0 +1,71 @@
/*---------------------------------------------------------*\
| GoveeController.h |
| |
| Driver for Govee wireless lighting devices |
| |
| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <thread>
#include <vector>
#include "RGBController.h"
#include "net_port.h"
class GoveeController
{
public:
GoveeController(std::string ip);
~GoveeController();
std::string GetLocation();
std::string GetSku();
std::string GetVersion();
void ReceiveBroadcast(char* recv_buf, int size);
void SendRazerData(RGBColor* colors, unsigned int size);
void SendRazerDisable();
void SendRazerEnable();
void SendScan();
void SetColor(unsigned char red, unsigned char green, unsigned char blue);
private:
std::string firmware_version;
std::string ip_address;
std::string module_name;
std::string module_mac;
std::string sku;
std::string bleVersionHard;
std::string bleVersionSoft;
std::string wifiVersionHard;
std::string wifiVersionSoft;
bool broadcast_received;
net_port port;
public:
/*-----------------------------------------------------*\
| One receive thread is shared among all instances of |
| GoveeController, so the receive thread function is |
| static and the thread is initialized in the detector |
| if any GoveeControllers are created. |
\*-----------------------------------------------------*/
static net_port broadcast_port;
static std::vector<GoveeController*> callbacks;
static std::thread* ReceiveThread;
static std::atomic<bool> ReceiveThreadRun;
static void ReceiveBroadcastThreadFunction();
static void RegisterReceiveBroadcastCallback(GoveeController* controller_ptr);
static void UnregisterReceiveBroadcastCallback(GoveeController* controller_ptr);
};
@@ -0,0 +1,92 @@
/*---------------------------------------------------------*\
| GoveeControllerDetect.cpp |
| |
| Detector for Govee wireless lighting devices |
| |
| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include "Detector.h"
#include "GoveeController.h"
#include "RGBController.h"
#include "RGBController_Govee.h"
#include "SettingsManager.h"
/******************************************************************************************\
* *
* DetectGoveeControllers *
* *
* Detect Govee devices *
* *
\******************************************************************************************/
void DetectGoveeControllers()
{
json govee_settings;
/*-----------------------------------------------------*\
| Get Govee settings from settings manager |
\*-----------------------------------------------------*/
govee_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("GoveeDevices");
/*-----------------------------------------------------*\
| If the Govee settings contains devices, process |
\*-----------------------------------------------------*/
if(govee_settings.contains("devices"))
{
GoveeController::ReceiveThreadRun = false;
if(govee_settings["devices"].size() > 0)
{
/*---------------------------------------------*\
| Open a UDP client sending to and receiving |
| from the Govee Multicast IP, send port 4001 |
| and receive port 4002 |
\*---------------------------------------------*/
GoveeController::broadcast_port.udp_client("239.255.255.250", "4001", "4002");
GoveeController::broadcast_port.udp_join_multicast_group("239.255.255.250");
/*---------------------------------------------*\
| Start a thread to handle responses received |
| from the Govee device |
\*---------------------------------------------*/
GoveeController::ReceiveThreadRun = true;
GoveeController::ReceiveThread = new std::thread(&GoveeController::ReceiveBroadcastThreadFunction);
}
for(unsigned int device_idx = 0; device_idx < govee_settings["devices"].size(); device_idx++)
{
if(govee_settings["devices"][device_idx].contains("ip"))
{
std::string govee_ip = govee_settings["devices"][device_idx]["ip"];
GoveeController* controller = new GoveeController(govee_ip);
RGBController_Govee* rgb_controller = new RGBController_Govee(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
/*-------------------------------------------------*\
| All controllers have been created, the broadcast |
| receiver thread is no longer needed and can be |
| shut down |
\*-------------------------------------------------*/
if(GoveeController::ReceiveThreadRun)
{
GoveeController::ReceiveThreadRun = false;
GoveeController::ReceiveThread->join();
delete GoveeController::ReceiveThread;
GoveeController::broadcast_port.tcp_close();
}
}
} /* DetectGoveeControllers() */
REGISTER_DETECTOR("Govee", DetectGoveeControllers);
@@ -0,0 +1,228 @@
/*---------------------------------------------------------*\
| RGBController_Govee.cpp |
| |
| RGBController for Govee wireless lighting devices |
| |
| Adam Honse (calcprogrammer1@gmail.com) 27 Dec 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <algorithm>
#include <map>
#include <string>
#include "RGBController_Govee.h"
using namespace std::chrono_literals;
struct GoveeHardwareInfo
{
unsigned int led_count;
unsigned int matrix_row_len; // 0 = linear
};
const unsigned int GOVEE_FALLBACK_LED_COUNT = 20;
static std::map<std::string, GoveeHardwareInfo> govee_hardware_info
{
{ "H6022", { 132, 12 } }, // Govee Smart Table Lamp 2
{ "H612F", { 12, 0 } }, // Govee Strip Light S (3m)
{ "H619A", { 20, 0 } }, // Govee RGBIC Led Strip Lights
{ "H70B1", { 20, 0 } }, // Govee LED Curtain Lights
{ "H607C", { 174, 0 } }, // Govee Floor Lamp 2
};
RGBController_Govee::RGBController_Govee(GoveeController* controller_ptr)
{
controller = controller_ptr;
name = "Govee " + controller->GetSku();
vendor = "Govee";
type = DEVICE_TYPE_LIGHT;
description = "Govee Device";
location = controller->GetLocation();
version = controller->GetVersion();
mode Static;
Static.name = "Static";
Static.value = 1;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors_min = 1;
Static.colors_max = 1;
Static.colors.resize(1);
modes.push_back(Static);
mode Direct;
Direct.name = "Direct";
Direct.value = 0;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
keepalive_thread_run = 1;
keepalive_thread = new std::thread(&RGBController_Govee::KeepaliveThread, this);
}
RGBController_Govee::~RGBController_Govee()
{
keepalive_thread_run = 0;
keepalive_thread->join();
delete keepalive_thread;
delete controller;
}
void RGBController_Govee::SetupZones()
{
GoveeHardwareInfo hw = { GOVEE_FALLBACK_LED_COUNT, 0 };
bool resizable = true;
std::map<std::string, GoveeHardwareInfo>::iterator it = govee_hardware_info.find(controller->GetSku());
if(it != govee_hardware_info.end())
{
hw = it->second;
resizable = false;
}
zone strip;
strip.leds_count = hw.led_count;
strip.leds_min = resizable ? 0 : hw.led_count;
strip.leds_max = resizable ? 255 : hw.led_count;
if(hw.matrix_row_len == 0)
{
strip.name = "Govee Strip";
strip.type = ZONE_TYPE_LINEAR;
strip.matrix_map = NULL;
}
else
{
strip.name = "Govee Matrix";
strip.type = ZONE_TYPE_MATRIX;
unsigned int width = hw.matrix_row_len;
unsigned int height = hw.led_count / width;
strip.matrix_map = new matrix_map_type;
strip.matrix_map->height = height;
strip.matrix_map->width = width;
strip.matrix_map->map = new unsigned int[hw.led_count];
/*-----------------------------------------------------*\
| On H6022, LEDs indexed bottom to top, alternating |
| clockwise and counterclockwise for each row. |
\*-----------------------------------------------------*/
for(unsigned int y = 0; y < height; y++)
{
/*-------------------------------------------------*\
| LEDs numbered bottom to top, opposite of matrix |
| clockwise and counterclockwise for each row. |
\*-------------------------------------------------*/
unsigned int led_y = (height - 1) - y;
for(unsigned int x = 0; x < width; x++)
{
/*---------------------------------------------*\
| LED is right-to-left for even rows, including |
| first one |
\*---------------------------------------------*/
unsigned int led_x = led_y & 1 ? x : (width - 1) - x;
strip.matrix_map->map[y * width + x] = led_y * width + led_x;
}
}
}
zones.push_back(strip);
for(std::size_t led_idx = 0; led_idx < strip.leds_count; led_idx++)
{
led strip_led;
strip_led.name = "Govee LED " + std::to_string(led_idx);
leds.push_back(strip_led);
}
SetupColors();
}
void RGBController_Govee::ResizeZone(int zone, int new_size)
{
if(zones[zone].type == ZONE_TYPE_MATRIX)
{
return;
}
if(zone < 0 || zone >= (int)zones.size() || new_size <= 0)
{
return;
}
new_size = std::max(1, std::min(255, new_size));
zones[zone].leds_count = new_size;
zones[zone].leds_min = 1;
zones[zone].leds_max = 255;
leds.clear();
leds.resize(new_size);
for(int i = 0; i < new_size; ++i)
{
leds[i].name = "Govee LED " + std::to_string(i);
}
SetupColors(); /* re-sync color buffers with LED count */
DeviceUpdateLEDs(); /* push an updated frame */
}
void RGBController_Govee::DeviceUpdateLEDs()
{
last_update_time = std::chrono::steady_clock::now();
if(modes[active_mode].color_mode == MODE_COLORS_PER_LED)
{
if(!colors.empty())
{
controller->SendRazerData(&colors[0], (unsigned int)colors.size());
}
}
}
void RGBController_Govee::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_Govee::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_Govee::DeviceUpdateMode()
{
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
unsigned char red = RGBGetRValue(modes[active_mode].colors[0]);
unsigned char grn = RGBGetGValue(modes[active_mode].colors[0]);
unsigned char blu = RGBGetBValue(modes[active_mode].colors[0]);
controller->SetColor(red, grn, blu);
}
else
{
controller->SendRazerEnable();
DeviceUpdateLEDs();
}
}
void RGBController_Govee::KeepaliveThread()
{
while(keepalive_thread_run.load())
{
if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::seconds(30))
{
DeviceUpdateLEDs();
}
std::this_thread::sleep_for(10s);
}
}
@@ -0,0 +1,39 @@
/*---------------------------------------------------------*\
| RGBController_Govee.h |
| |
| RGBController for Govee wireless lighting devices |
| |
| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "GoveeController.h"
class RGBController_Govee : public RGBController
{
public:
RGBController_Govee(GoveeController* controller_ptr);
~RGBController_Govee();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void KeepaliveThread();
private:
GoveeController* controller;
std::thread* keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
};
+111
View File
@@ -0,0 +1,111 @@
#pragma once
#include <string>
#include <vector>
#include <stdexcept>
#include <cstdint>
namespace base64
{
inline static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
inline static const char kPadCharacter = '=';
using byte = std::uint8_t;
inline std::string encode(const std::vector<byte>& input)
{
std::string encoded;
encoded.reserve(((input.size() / 3) + (input.size() % 3 > 0)) * 4);
std::uint32_t temp{};
auto it = input.begin();
for(std::size_t i = 0; i < input.size() / 3; ++i)
{
temp = (*it++) << 16;
temp += (*it++) << 8;
temp += (*it++);
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6 ]);
encoded.append(1, kEncodeLookup[(temp & 0x0000003F) ]);
}
switch(input.size() % 3)
{
case 1:
temp = (*it++) << 16;
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(2, kPadCharacter);
break;
case 2:
temp = (*it++) << 16;
temp += (*it++) << 8;
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6 ]);
encoded.append(1, kPadCharacter);
break;
}
return encoded;
}
inline std::vector<byte> decode(const std::string& input)
{
if(input.length() % 4)
throw std::runtime_error("Invalid base64 length!");
std::size_t padding{};
if(input.length())
{
if(input[input.length() - 1] == kPadCharacter) padding++;
if(input[input.length() - 2] == kPadCharacter) padding++;
}
std::vector<byte> decoded;
decoded.reserve(((input.length() / 4) * 3) - padding);
std::uint32_t temp{};
auto it = input.begin();
while(it < input.end())
{
for(std::size_t i = 0; i < 4; ++i)
{
temp <<= 6;
if (*it >= 0x41 && *it <= 0x5A) temp |= *it - 0x41;
else if(*it >= 0x61 && *it <= 0x7A) temp |= *it - 0x47;
else if(*it >= 0x30 && *it <= 0x39) temp |= *it + 0x04;
else if(*it == 0x2B) temp |= 0x3E;
else if(*it == 0x2F) temp |= 0x3F;
else if(*it == kPadCharacter)
{
switch(input.end() - it)
{
case 1:
decoded.push_back((temp >> 16) & 0x000000FF);
decoded.push_back((temp >> 8 ) & 0x000000FF);
return decoded;
case 2:
decoded.push_back((temp >> 10) & 0x000000FF);
return decoded;
default:
throw std::runtime_error("Invalid padding in base64!");
}
}
else throw std::runtime_error("Invalid character in base64!");
++it;
}
decoded.push_back((temp >> 16) & 0x000000FF);
decoded.push_back((temp >> 8 ) & 0x000000FF);
decoded.push_back((temp ) & 0x000000FF);
}
return decoded;
}
}