330 lines
6.5 KiB
C++
330 lines
6.5 KiB
C++
#include <iostream>
|
|
#include <list>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "httplib.h"
|
|
#include "json.hpp"
|
|
|
|
#include "helpers.h"
|
|
#include "TRMNL.h"
|
|
|
|
using nlohmann::json;
|
|
|
|
using namespace std;
|
|
|
|
constexpr int API_KEY_LENGHT = 16;
|
|
|
|
string generate_api_key(int len = API_KEY_LENGHT)
|
|
{
|
|
string result = "";
|
|
std::random_device rand;
|
|
std::uniform_int_distribution distribution(0, 63);
|
|
int single;
|
|
char symbol;
|
|
|
|
if (len < 0)
|
|
{
|
|
len = API_KEY_LENGHT;
|
|
}
|
|
|
|
for (int i = 0; i < len; ++i)
|
|
{
|
|
single = distribution(rand);
|
|
if (single <= 9)
|
|
{
|
|
symbol = '0' + single;
|
|
}
|
|
else if ((single >= 10) && (single <= 35))
|
|
{
|
|
symbol = 'A' + single - 10;
|
|
}
|
|
else if ((single >= 36) && (single <= 61))
|
|
{
|
|
symbol = 'a' + single - 36;
|
|
}
|
|
else if (62 == single)
|
|
{
|
|
symbol = '+';
|
|
}
|
|
else if (63 == single)
|
|
{
|
|
symbol = '/';
|
|
}
|
|
// This should never happen
|
|
else
|
|
{
|
|
symbol = '?';
|
|
}
|
|
|
|
result += symbol;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void reload_container(TRMNLContainer& container, const string& filename)
|
|
{
|
|
json j;
|
|
bool ok = read_file_json(j, filename, &cout);
|
|
if (!ok)
|
|
{
|
|
return;
|
|
}
|
|
container.clear();
|
|
if (j.is_array())
|
|
{
|
|
for (int i = 0; i < j.size(); ++i)
|
|
{
|
|
container.add_device(j[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
string config_filename = "config.json";
|
|
string devices_filename = "devices.json";
|
|
string host = "";
|
|
string folder_images = "images";
|
|
string cert_file = "";
|
|
string key_file = "";
|
|
uint16_t port = 0;
|
|
shared_ptr<httplib::Server> server = nullptr;
|
|
|
|
bool ok;
|
|
json cfg;
|
|
json devs;
|
|
TRMNLContainer container;
|
|
|
|
if (argc > 2)
|
|
{
|
|
config_filename = argv[1];
|
|
}
|
|
|
|
ok = read_file_json(cfg, config_filename, &cout);
|
|
if (!ok)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
json_extract(cfg, "devices_filename", devices_filename);
|
|
json_extract(cfg, "host", host);
|
|
json_extract(cfg, "port", port);
|
|
json_extract(cfg, "folder_images", folder_images);
|
|
// json_extract(cfg, "cert_file", cert_file);
|
|
// json_extract(cfg, "key_file", cert_file);
|
|
|
|
if (host.empty())
|
|
{
|
|
cout << "host not provided" << endl;
|
|
return -1;
|
|
}
|
|
|
|
if (0 == port)
|
|
{
|
|
cout << "port number not provided" << endl;
|
|
return -1;
|
|
}
|
|
|
|
if (folder_images.empty())
|
|
{
|
|
cout << "folder for images is empty" << endl;
|
|
return -1;
|
|
}
|
|
|
|
if (!cert_file.empty() && !key_file.empty())
|
|
{
|
|
// TODO: Implement SSL Server Properly
|
|
}
|
|
else
|
|
{
|
|
server = make_shared<httplib::Server>();
|
|
}
|
|
|
|
ok = read_file_json(devs, devices_filename, &cout);
|
|
if (!ok)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
reload_container(container, devices_filename);
|
|
|
|
auto setup_handler = [&container, &devices_filename](const httplib::Request& req, httplib::Response& res)
|
|
{
|
|
json response;
|
|
|
|
if (!req.has_header("ID"))
|
|
{
|
|
// Bad Request - No ID header
|
|
res.status = 400;
|
|
|
|
response["status"] = 400;
|
|
response["error"] = "No ID header";
|
|
|
|
res.set_header("Content-Type", "application/json");
|
|
res.body = response.dump();
|
|
|
|
return;
|
|
}
|
|
|
|
// Refresh data from file
|
|
// Someone might have put a new device in
|
|
reload_container(container, devices_filename);
|
|
|
|
string id = req.get_header_value("ID");
|
|
TRMNL* trmnl = container.get_device_by_id(id);
|
|
|
|
if (nullptr == trmnl)
|
|
{
|
|
res.status = 404;
|
|
|
|
response["status"] = 404;
|
|
response["api_key"] = nullptr;
|
|
response["friendly_id"] = nullptr;
|
|
response["image_url"] = nullptr;
|
|
response["filename"] = nullptr;
|
|
|
|
res.set_header("Content-Type", "application/json");
|
|
res.body = response.dump();
|
|
return;
|
|
}
|
|
|
|
bool should_dump = false;
|
|
|
|
res.status = 200;
|
|
if (trmnl->friendly_id().empty())
|
|
{
|
|
should_dump = true;
|
|
trmnl->friendly_id(TRMNL::friendly_from_id(id));
|
|
}
|
|
|
|
if (trmnl->api_key().empty())
|
|
{
|
|
should_dump = true;
|
|
trmnl->api_key(generate_api_key());
|
|
}
|
|
|
|
response["status"] = 200;
|
|
response["api_key"] = trmnl->api_key();
|
|
response["friendly_id"] = trmnl->friendly_id();
|
|
// TODO: Check for image in folder
|
|
response["image_url"] = "https://trmnl.com/images/setup/setup-logo.bmp";
|
|
response["filename"] = "welcome";
|
|
|
|
res.set_header("Content-Type", "application/json");
|
|
res.body = response.dump();
|
|
|
|
if (should_dump)
|
|
{
|
|
write_file_json(container, devices_filename, &cout);
|
|
}
|
|
};
|
|
|
|
auto display_handler = [&container, &devices_filename](const httplib::Request& req, httplib::Response& res)
|
|
{
|
|
json response;
|
|
|
|
if (!req.has_header("ID") || !req.has_header("Access-Token"))
|
|
{
|
|
// Bad Request
|
|
res.status = 400;
|
|
|
|
response["status"] = 400;
|
|
response["error"] = "No ID and Access-Token headers set";
|
|
|
|
res.body = response.dump();
|
|
res.set_header("Content-Type", "application/json");
|
|
return;
|
|
}
|
|
|
|
// Refresh data from file
|
|
// Someone might have put a new device in
|
|
reload_container(container, devices_filename);
|
|
|
|
string id = req.get_header_value("ID");
|
|
string api_key = req.get_header_value("Access-Token");
|
|
TRMNL* trmnl = container.get_device_by_id(id);
|
|
|
|
if (nullptr == trmnl)
|
|
{
|
|
// Not Found
|
|
res.status = 404;
|
|
|
|
response["status"] = 404;
|
|
response["error"] = "Device not found";
|
|
|
|
res.set_header("Content-Type", "application/json");
|
|
res.body = response.dump();
|
|
return;
|
|
}
|
|
|
|
if (trmnl->api_key() != api_key)
|
|
{
|
|
// Forbidden
|
|
res.status = 403;
|
|
|
|
response["status"] = 403;
|
|
response["error"] = "Wrong credentials for this device";
|
|
|
|
res.set_header("Content-Type", "application/json");
|
|
res.body = response.dump();
|
|
return;
|
|
}
|
|
|
|
// TODO: Check for image in folder
|
|
// string image;
|
|
|
|
// The ID and api_key match here
|
|
res.status = 200;
|
|
|
|
// From docs: will be 202 if no user_id is attached to device
|
|
response["status"] = 0;
|
|
response["image_url"] = "https://trmnl.com/images/setup/setup-logo.bmp";
|
|
response["filename"] = "2024-09-20T00:00:00";
|
|
response["update_firmware"] = false;
|
|
response["firmware_url"] = nullptr;
|
|
response["refresh_rate"] = to_string(trmnl->refresh_rate());
|
|
response["reset_firmware"] = false;
|
|
|
|
res.set_header("Content-Type", "application/json");
|
|
res.body = response.dump();
|
|
};
|
|
|
|
auto log_handler = [](const httplib::Request& req, httplib::Response& res)
|
|
{
|
|
try
|
|
{
|
|
json j = json::parse(req.body);
|
|
cout << j.dump(4) << endl;
|
|
}
|
|
catch (const exception& e)
|
|
{
|
|
cout << req.body << endl;
|
|
}
|
|
res.status = 200;
|
|
};
|
|
|
|
|
|
server->Get("/api/setup/*", setup_handler);
|
|
server->Get("/api/display/*", display_handler);
|
|
server->Post("/api/log/*", log_handler);
|
|
|
|
ok = server->set_mount_point("/images", folder_images);
|
|
if (!ok)
|
|
{
|
|
cout << "Could not mount images folder" << endl;
|
|
return -1;
|
|
}
|
|
|
|
cout << "Server listening on " << host << ":" << port << endl;
|
|
server->listen(host, port);
|
|
|
|
return 0;
|
|
}
|