mirror of
https://github.com/m5stack/meshtastic-device-ui.git
synced 2026-05-20 11:51:03 -07:00
restructure code into sub-directories
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#ifdef USE_ILOG
|
||||
|
||||
#define ILOG_DEBUG(...) ILog::logger()->log_debug(__VA_ARGS__)
|
||||
#define ILOG_INFO(...) ILog::logger()->log_info(__VA_ARGS__)
|
||||
#define ILOG_WARN(...) ILog::logger()->log_warn(__VA_ARGS__)
|
||||
#define ILOG_ERROR(...) ILog::logger()->log_error(__VA_ARGS__)
|
||||
#define ILOG_CRIT(...) ILog::logger()->log_crit(__VA_ARGS__)
|
||||
#define ILOG_TRACE(...) ILog::logger()->log_trace(__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* abstract class to inject debug logging into the library
|
||||
*/
|
||||
class ILog
|
||||
{
|
||||
public:
|
||||
virtual void log_debug(const char *format, ...) = 0;
|
||||
virtual void log_info(const char *format, ...) = 0;
|
||||
virtual void log_warn(const char *format, ...) = 0;
|
||||
virtual void log_error(const char *format, ...) = 0;
|
||||
virtual void log_crit(const char *format, ...) = 0;
|
||||
virtual void log_trace(const char *format, ...) = 0;
|
||||
|
||||
static ILog *logger() { return _logger; }
|
||||
virtual ~ILog() {}
|
||||
|
||||
protected:
|
||||
ILog(ILog *logger) { _logger = logger; }
|
||||
|
||||
private:
|
||||
static ILog *_logger;
|
||||
};
|
||||
|
||||
#elif defined(USE_LOG_DEBUG)
|
||||
|
||||
// alternative approach to directly use LOG_DEBUG macros
|
||||
#include LOG_DEBUG_INC
|
||||
#define ILOG_DEBUG(...) LOG_DEBUG("[DeviceUI] " __VA_ARGS__)
|
||||
#define ILOG_INFO(...) LOG_INFO("[DeviceUI] " __VA_ARGS__)
|
||||
#define ILOG_WARN(...) LOG_WARN("[DeviceUI] " __VA_ARGS__)
|
||||
#define ILOG_ERROR(...) LOG_ERROR("[DeviceUI] " __VA_ARGS__)
|
||||
#define ILOG_CRIT(...) LOG_CRIT("[DeviceUI] " __VA_ARGS__)
|
||||
#define ILOG_TRACE(...) LOG_TRACE("[DeviceUI] " __VA_ARGS__)
|
||||
|
||||
#else // no logging
|
||||
|
||||
#define ILOG_DEBUG(...)
|
||||
#define ILOG_INFO(...)
|
||||
#define ILOG_WARN(...)
|
||||
#define ILOG_ERROR(...)
|
||||
#define ILOG_CRIT(...)
|
||||
#define ILOG_TRACE(...)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Generic interface base class for any log entries (stored via class LogRotate)
|
||||
*/
|
||||
class ILogEntry
|
||||
{
|
||||
public:
|
||||
virtual size_t size(void) const = 0;
|
||||
virtual size_t serialize(std::function<size_t(const uint8_t *, size_t)> write) const = 0;
|
||||
virtual size_t deserialize(std::function<size_t(uint8_t *, size_t)> read) = 0;
|
||||
virtual ~ILogEntry() = default;
|
||||
|
||||
protected:
|
||||
ILogEntry(void) = default;
|
||||
|
||||
private:
|
||||
ILogEntry(const ILogEntry &) = delete;
|
||||
ILogEntry &operator=(const ILogEntry &) = delete;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class LinuxHelper
|
||||
{
|
||||
public:
|
||||
static uint32_t getAvailableMem(void);
|
||||
static uint32_t getFreeMem(void);
|
||||
static uint32_t getTotalMem(void);
|
||||
|
||||
protected:
|
||||
static uint32_t getMem(const char *entry);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "ILogEntry.h"
|
||||
#include <assert.h>
|
||||
#include <ctime>
|
||||
#include <memory.h>
|
||||
|
||||
constexpr uint32_t messagePayloadSize = 233;
|
||||
|
||||
/**
|
||||
* @brief Header for storing message logs containing the actual size of the payload
|
||||
* Note: this struct does have vtable pointers, i.e. sizeof(LogMessageHeader)-8 is the real data size
|
||||
*/
|
||||
struct LogMessageHeader : public ILogEntry {
|
||||
uint16_t _size;
|
||||
time_t time;
|
||||
uint32_t from;
|
||||
uint32_t to;
|
||||
uint8_t ch;
|
||||
enum MsgStatus : uint8_t { eNone, eDefault, eHeard, eNoResponse, eAcked, eFailed, eDeleted, eUnread } status;
|
||||
bool trashFlag;
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Structure for storing message logs containing the actual payload
|
||||
*/
|
||||
struct LogMessage : public LogMessageHeader {
|
||||
uint8_t bytes[messagePayloadSize];
|
||||
};
|
||||
|
||||
/**
|
||||
* Log message envelope that implements the actual interface for ILogEntry
|
||||
* (size, serialize and deserialize)
|
||||
*/
|
||||
class LogMessageEnv : public LogMessage
|
||||
{
|
||||
public:
|
||||
LogMessageEnv(void) = default;
|
||||
LogMessageEnv(uint32_t _from, uint32_t _to, uint16_t _ch, time_t _time, MsgStatus _status, bool _trashFlag, uint32_t _len,
|
||||
const uint8_t *msg)
|
||||
{
|
||||
assert(_len < messagePayloadSize);
|
||||
_size = (uint16_t)_len;
|
||||
time = _time;
|
||||
from = _from;
|
||||
to = _to;
|
||||
ch = _ch;
|
||||
status = _status;
|
||||
trashFlag = _trashFlag;
|
||||
reserved = 0;
|
||||
memcpy(bytes, msg, _len);
|
||||
}
|
||||
|
||||
size_t size(void) const override { return sizeof(LogMessageHeader) - 8 + _size; }
|
||||
|
||||
virtual size_t serialize(std::function<size_t(const uint8_t *, size_t)> write) const override
|
||||
{
|
||||
return write((uint8_t *)&_size, sizeof(LogMessageHeader) - 8) + write(bytes, _size);
|
||||
}
|
||||
|
||||
virtual size_t deserialize(std::function<size_t(uint8_t *, size_t)> read) override
|
||||
{
|
||||
return read((uint8_t *)&_size, sizeof(LogMessageHeader) - 8) + read(bytes, _size);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "FS.h"
|
||||
#include "ILogEntry.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Generic LogRotate class that writes log-rotation like files into (arduino) FS storage file system
|
||||
* @param fs arduino file system FS/LittleFS or derived classes
|
||||
* @param logDir directory to store the logs (absolute path)
|
||||
* @param maxLen the maximum length of the variable log entry length
|
||||
* The maximum storage is limited by:
|
||||
* @param maxSize the total storage in bytes (default is 200kB)
|
||||
* @param maxFiles number of log files (default is 50)
|
||||
* @param maxFileSize per log file (default size is 4000 bytes to fit into a physical block
|
||||
* including fs descriptor data)
|
||||
*
|
||||
* If the maximum storage is exceeded then old files are deleted to fit the new log entry.
|
||||
* Note: for performance reasons the logs are not renumbered
|
||||
*/
|
||||
class LogRotate
|
||||
{
|
||||
public:
|
||||
LogRotate(fs::FS &fs, const char *logDir, uint32_t maxLen, uint32_t maxSize = 102400, uint32_t maxFiles = 25,
|
||||
uint32_t maxFileSize = 4000);
|
||||
// uint32_t maxSize = 4096, uint32_t maxFiles = 10, uint32_t maxFileSize = 400);
|
||||
|
||||
// initialize the log directory
|
||||
void init(void);
|
||||
// write a log entry to fs
|
||||
bool write(const ILogEntry &entry);
|
||||
// read the next log entry from fs
|
||||
bool readNext(ILogEntry &entry);
|
||||
// remove all logs from fs
|
||||
bool clear(void);
|
||||
// request log count
|
||||
uint32_t count(void);
|
||||
// request current log number
|
||||
uint32_t current(void);
|
||||
|
||||
private:
|
||||
LogRotate(const LogRotate &) = delete;
|
||||
LogRotate &operator=(const LogRotate &) = delete;
|
||||
|
||||
// create filename from number
|
||||
String logFileName(uint32_t num);
|
||||
// remove oldest log and return freed size
|
||||
size_t removeLog(void);
|
||||
// scan all files in logdir to get min/max log
|
||||
void scanLogDir(uint32_t &num, uint32_t &minLog, uint32_t &maxLog, uint32_t &size, uint32_t &total);
|
||||
|
||||
const uint32_t c_maxLen; // maximum size a single log entry could be
|
||||
const uint32_t c_maxSize; // max storage size in bytes (default is 100kB)
|
||||
const uint32_t c_maxFiles; // max log files number (default is 50)
|
||||
const uint32_t c_maxFileSize; // max file size per log file
|
||||
|
||||
fs::FS &_fs;
|
||||
File rootDir; // directory (for reading logs)
|
||||
File currentFile; // current file (when reading)
|
||||
String rootDirName; // path of log directory
|
||||
String currentLogName; // current log file name (when writing)
|
||||
uint32_t numFiles; // number of log files
|
||||
uint32_t minLogNum; // logfile with smallest number
|
||||
uint32_t maxLogNum; // logfile with largest number after init()
|
||||
uint32_t currentLogRead; // current log number (when reading)
|
||||
uint32_t currentLogWrite; // current log number (when writing)
|
||||
uint32_t currentSize; // size of current written log file
|
||||
uint32_t totalSize; // size of all logs
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* Polymorphic packets that can be moved into and out of packet queues.
|
||||
*/
|
||||
class Packet
|
||||
{
|
||||
public:
|
||||
using PacketPtr = std::unique_ptr<Packet>;
|
||||
|
||||
Packet(int packetId) : id(packetId) {}
|
||||
|
||||
// virtual move constructor
|
||||
virtual PacketPtr move() { return PacketPtr(new Packet(std::move(*this))); }
|
||||
|
||||
// Disable copying
|
||||
Packet(const Packet &) = delete;
|
||||
Packet &operator=(const Packet &) = delete;
|
||||
|
||||
virtual ~Packet() {}
|
||||
|
||||
int getPacketId() const { return id; }
|
||||
|
||||
protected:
|
||||
// Enable moving
|
||||
Packet(Packet &&) = default;
|
||||
Packet &operator=(Packet &&) = default;
|
||||
|
||||
private:
|
||||
int id;
|
||||
};
|
||||
|
||||
/**
|
||||
* generic packet type class
|
||||
*/
|
||||
template <typename PacketType> class DataPacket : public Packet
|
||||
{
|
||||
public:
|
||||
template <typename... Args> DataPacket(int id, Args &&...args) : Packet(id), data(new PacketType(std::forward<Args>(args)...))
|
||||
{
|
||||
}
|
||||
|
||||
PacketPtr move() override { return PacketPtr(new DataPacket(std::move(*this))); }
|
||||
|
||||
// Disable copying
|
||||
DataPacket(const DataPacket &) = delete;
|
||||
DataPacket &operator=(const DataPacket &) = delete;
|
||||
|
||||
virtual ~DataPacket() {}
|
||||
|
||||
const PacketType &getData() const { return *data; }
|
||||
|
||||
protected:
|
||||
// Enable moving
|
||||
DataPacket(DataPacket &&) = default;
|
||||
DataPacket &operator=(DataPacket &&) = default;
|
||||
|
||||
private:
|
||||
std::unique_ptr<PacketType> data;
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#ifdef BLOCKING_PACKET_QUEUE
|
||||
#include <condition_variable>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Generic platform independent and re-entrant queue wrapper that can be used to
|
||||
* safely pass (generic) movable objects between threads.
|
||||
*/
|
||||
template <typename T> class PacketQueue
|
||||
{
|
||||
public:
|
||||
PacketQueue() {}
|
||||
|
||||
PacketQueue(PacketQueue const &other) = delete;
|
||||
|
||||
/**
|
||||
* Push movable object into queue
|
||||
*/
|
||||
void push(T &&packet)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
queue.push(packet.move());
|
||||
#ifdef BLOCKING_PACKET_QUEUE
|
||||
cond.notify_one();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef BLOCKING_PACKET_QUEUE
|
||||
/**
|
||||
* Pop movable object from queue (blocking)
|
||||
*/
|
||||
std::unique_ptr<T> pop(void)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
cond.wait(lock, [this] { return !queue.empty(); });
|
||||
T packet = queue.front()->move();
|
||||
queue.pop();
|
||||
return packet;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Pop movable object from queue (non-blocking)
|
||||
*/
|
||||
std::unique_ptr<T> try_pop()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (queue.empty())
|
||||
return {nullptr};
|
||||
auto packet = queue.front()->move();
|
||||
queue.pop();
|
||||
return packet;
|
||||
}
|
||||
|
||||
uint32_t size() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::queue<std::unique_ptr<T>> queue;
|
||||
#ifdef BLOCKING_PACKET_QUEUE
|
||||
std::condition_variable cond;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
Note: files in this folder MUST NOT have any kind of dependency to any other files outside of this directory!!
|
||||
Goal is to put all files of this directory into a separate library for re-use in other projects.
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "Packet.h"
|
||||
#include "PacketQueue.h"
|
||||
|
||||
/**
|
||||
* @brief Queue wrapper that aggregates two thread queues (namely client and server)
|
||||
* for bidirectional packet transfer between two threads or processes.
|
||||
*
|
||||
* This queue may also be created in shared memory (e.g. in Linux for inter-process communication)
|
||||
*/
|
||||
class SharedQueue
|
||||
{
|
||||
public:
|
||||
SharedQueue();
|
||||
virtual ~SharedQueue();
|
||||
|
||||
// server methods
|
||||
virtual bool serverSend(Packet &&p);
|
||||
virtual Packet::PacketPtr serverReceive();
|
||||
virtual size_t serverQueueSize() const;
|
||||
|
||||
// client methods
|
||||
virtual bool clientSend(Packet &&p);
|
||||
virtual Packet::PacketPtr clientReceive();
|
||||
virtual size_t clientQueueSize() const;
|
||||
|
||||
private:
|
||||
// the server pushes into serverQueue and the client pushes into clientQueue
|
||||
// receiving is done from the opposite queue, respectively
|
||||
PacketQueue<Packet> serverQueue;
|
||||
PacketQueue<Packet> clientQueue;
|
||||
};
|
||||
|
||||
extern SharedQueue *sharedQueue;
|
||||
@@ -0,0 +1,118 @@
|
||||
#ifndef _MACARON_BASE64_H_
|
||||
#define _MACARON_BASE64_H_
|
||||
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2016-2024 tomykaira
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace macaron
|
||||
{
|
||||
|
||||
class Base64
|
||||
{
|
||||
public:
|
||||
static std::string Encode(const uint8_t *data, uint32_t size)
|
||||
{
|
||||
static constexpr char sEncodingTable[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
|
||||
|
||||
size_t in_len = size;
|
||||
size_t out_len = 4 * ((in_len + 2) / 3);
|
||||
std::string ret(out_len, '\0');
|
||||
size_t i;
|
||||
char *p = const_cast<char *>(ret.c_str());
|
||||
|
||||
for (i = 0; in_len > 2 && i < in_len - 2; i += 3) {
|
||||
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
|
||||
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
|
||||
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int)(data[i + 2] & 0xC0) >> 6)];
|
||||
*p++ = sEncodingTable[data[i + 2] & 0x3F];
|
||||
}
|
||||
if (i < in_len) {
|
||||
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
|
||||
if (i == (in_len - 1)) {
|
||||
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
|
||||
*p++ = '=';
|
||||
} else {
|
||||
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
|
||||
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
|
||||
}
|
||||
*p++ = '=';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::string Decode(const std::string &input, std::string &out)
|
||||
{
|
||||
static constexpr unsigned char kDecodingTable[] = {
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
|
||||
64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
|
||||
22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
||||
45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64};
|
||||
|
||||
size_t in_len = input.size();
|
||||
if (in_len % 4 != 0)
|
||||
return "Input data size is not a multiple of 4";
|
||||
|
||||
size_t out_len = in_len / 4 * 3;
|
||||
if (in_len >= 1 && input[in_len - 1] == '=')
|
||||
out_len--;
|
||||
if (in_len >= 2 && input[in_len - 2] == '=')
|
||||
out_len--;
|
||||
|
||||
out.resize(out_len);
|
||||
|
||||
for (size_t i = 0, j = 0; i < in_len;) {
|
||||
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||
|
||||
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
|
||||
|
||||
if (j < out_len)
|
||||
out[j++] = (triple >> 2 * 8) & 0xFF;
|
||||
if (j < out_len)
|
||||
out[j++] = (triple >> 1 * 8) & 0xFF;
|
||||
if (j < out_len)
|
||||
out[j++] = (triple >> 0 * 8) & 0xFF;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace macaron
|
||||
|
||||
#endif /* _MACARON_BASE64_H_ */
|
||||
Reference in New Issue
Block a user