Generated app id with hashed input data (#715)
This commit is contained in:
@@ -375,7 +375,6 @@ set(SUNSHINE_TARGET_FILES
|
|||||||
src/network.cpp
|
src/network.cpp
|
||||||
src/network.h
|
src/network.h
|
||||||
src/move_by_copy.h
|
src/move_by_copy.h
|
||||||
src/rand.h
|
|
||||||
src/task_pool.h
|
src/task_pool.h
|
||||||
src/thread_pool.h
|
src/thread_pool.h
|
||||||
src/thread_safe.h
|
src/thread_safe.h
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include "nvhttp.h"
|
#include "nvhttp.h"
|
||||||
#include "platform/common.h"
|
#include "platform/common.h"
|
||||||
#include "rand.h"
|
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
#include "uuid.h"
|
#include "uuid.h"
|
||||||
@@ -303,11 +302,6 @@ void saveApp(resp_https_t response, req_https_t request) {
|
|||||||
response->write(data.str());
|
response->write(data.str());
|
||||||
});
|
});
|
||||||
|
|
||||||
std::set<std::string> ids;
|
|
||||||
for(auto const &app : proc::proc.get_apps()) {
|
|
||||||
ids.insert(app.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pt::ptree inputTree, fileTree;
|
pt::ptree inputTree, fileTree;
|
||||||
|
|
||||||
BOOST_LOG(fatal) << config::stream.file_apps;
|
BOOST_LOG(fatal) << config::stream.file_apps;
|
||||||
@@ -316,14 +310,6 @@ void saveApp(resp_https_t response, req_https_t request) {
|
|||||||
pt::read_json(ss, inputTree);
|
pt::read_json(ss, inputTree);
|
||||||
pt::read_json(config::stream.file_apps, fileTree);
|
pt::read_json(config::stream.file_apps, fileTree);
|
||||||
|
|
||||||
// Moonlight checks the id of an item to determine if an item was changed
|
|
||||||
// Needs to be a 32-bit positive integer due to client limitations, "0" indicates no app
|
|
||||||
auto id = util::generate_int32(1, std::numeric_limits<std::int32_t>::max());
|
|
||||||
while(ids.count(std::to_string(id)) > 0) {
|
|
||||||
id = util::generate_int32(1, std::numeric_limits<std::int32_t>::max());
|
|
||||||
}
|
|
||||||
inputTree.put("id", id);
|
|
||||||
|
|
||||||
if(inputTree.get_child("prep-cmd").empty()) {
|
if(inputTree.get_child("prep-cmd").empty()) {
|
||||||
inputTree.erase("prep-cmd");
|
inputTree.erase("prep-cmd");
|
||||||
}
|
}
|
||||||
|
|||||||
168
src/process.cpp
168
src/process.cpp
@@ -9,11 +9,16 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include <boost/crc.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/program_options/parsers.hpp>
|
#include <boost/program_options/parsers.hpp>
|
||||||
#include <boost/property_tree/json_parser.hpp>
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
#include <boost/property_tree/ptree.hpp>
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#include "crypto.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "platform/common.h"
|
#include "platform/common.h"
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
@@ -23,6 +28,8 @@
|
|||||||
#include <share.h>
|
#include <share.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define DEFAULT_APP_IMAGE_PATH SUNSHINE_ASSETS_DIR "/box.png"
|
||||||
|
|
||||||
namespace proc {
|
namespace proc {
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace bp = boost::process;
|
namespace bp = boost::process;
|
||||||
@@ -233,47 +240,12 @@ std::vector<ctx_t> &proc_t::get_apps() {
|
|||||||
// Returns default image if image configuration is not set.
|
// Returns default image if image configuration is not set.
|
||||||
// Returns http content-type header compatible image type.
|
// Returns http content-type header compatible image type.
|
||||||
std::string proc_t::get_app_image(int app_id) {
|
std::string proc_t::get_app_image(int app_id) {
|
||||||
auto default_image = SUNSHINE_ASSETS_DIR "/box.png";
|
|
||||||
|
|
||||||
auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) {
|
auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) {
|
||||||
return app.id == std::to_string(app_id);
|
return app.id == std::to_string(app_id);
|
||||||
});
|
});
|
||||||
auto app_image_path = iter == _apps.end() ? std::string() : iter->image_path;
|
auto app_image_path = iter == _apps.end() ? std::string() : iter->image_path;
|
||||||
|
|
||||||
if(app_image_path.empty()) {
|
return validate_app_image_path(app_image_path);
|
||||||
BOOST_LOG(warning) << "Couldn't find app image for ID ["sv << app_id << ']';
|
|
||||||
return default_image;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the image extension and convert it to lowercase
|
|
||||||
auto image_extension = std::filesystem::path(app_image_path).extension().string();
|
|
||||||
boost::to_lower(image_extension);
|
|
||||||
|
|
||||||
// return the default box image if extension is not "png"
|
|
||||||
if(image_extension != ".png") {
|
|
||||||
return default_image;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if image is in assets directory
|
|
||||||
auto full_image_path = std::filesystem::path(SUNSHINE_ASSETS_DIR) / app_image_path;
|
|
||||||
if(std::filesystem::exists(full_image_path)) {
|
|
||||||
return full_image_path.string();
|
|
||||||
}
|
|
||||||
else if(app_image_path == "./assets/steam.png") {
|
|
||||||
// handle old default steam image definition
|
|
||||||
return SUNSHINE_ASSETS_DIR "/steam.png";
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if specified image exists
|
|
||||||
std::error_code code;
|
|
||||||
if(!std::filesystem::exists(app_image_path, code)) {
|
|
||||||
// return default box image if image does not exist
|
|
||||||
return default_image;
|
|
||||||
}
|
|
||||||
|
|
||||||
// image is a png, and not in assets directory
|
|
||||||
// return only "content-type" http header compatible image type
|
|
||||||
return app_image_path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proc_t::~proc_t() {
|
proc_t::~proc_t() {
|
||||||
@@ -355,6 +327,114 @@ std::string parse_env_val(bp::native_environment &env, const std::string_view &v
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string validate_app_image_path(std::string app_image_path) {
|
||||||
|
if(app_image_path.empty()) {
|
||||||
|
return DEFAULT_APP_IMAGE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the image extension and convert it to lowercase
|
||||||
|
auto image_extension = std::filesystem::path(app_image_path).extension().string();
|
||||||
|
boost::to_lower(image_extension);
|
||||||
|
|
||||||
|
// return the default box image if extension is not "png"
|
||||||
|
if(image_extension != ".png") {
|
||||||
|
return DEFAULT_APP_IMAGE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if image is in assets directory
|
||||||
|
auto full_image_path = std::filesystem::path(SUNSHINE_ASSETS_DIR) / app_image_path;
|
||||||
|
if(std::filesystem::exists(full_image_path)) {
|
||||||
|
return full_image_path.string();
|
||||||
|
}
|
||||||
|
else if(app_image_path == "./assets/steam.png") {
|
||||||
|
// handle old default steam image definition
|
||||||
|
return SUNSHINE_ASSETS_DIR "/steam.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if specified image exists
|
||||||
|
std::error_code code;
|
||||||
|
if(!std::filesystem::exists(app_image_path, code)) {
|
||||||
|
// return default box image if image does not exist
|
||||||
|
BOOST_LOG(warning) << "Couldn't find app image at path ["sv << app_image_path << ']';
|
||||||
|
return DEFAULT_APP_IMAGE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// image is a png, and not in assets directory
|
||||||
|
// return only "content-type" http header compatible image type
|
||||||
|
return app_image_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> calculate_sha256(const std::string &filename) {
|
||||||
|
crypto::md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||||
|
if(!ctx) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file and update calculated SHA
|
||||||
|
char buf[1024 * 16];
|
||||||
|
std::ifstream file(filename, std::ifstream::binary);
|
||||||
|
while(file.good()) {
|
||||||
|
file.read(buf, sizeof(buf));
|
||||||
|
if(!EVP_DigestUpdate(ctx.get(), buf, file.gcount())) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
unsigned char result[SHA256_DIGEST_LENGTH];
|
||||||
|
if(!EVP_DigestFinal_ex(ctx.get(), result, nullptr)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform byte-array to string
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::setfill('0');
|
||||||
|
for(const auto &byte : result) {
|
||||||
|
ss << std::setw(2) << (int)byte;
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t calculate_crc32(const std::string &input) {
|
||||||
|
boost::crc_32_type result;
|
||||||
|
result.process_bytes(input.data(), input.length());
|
||||||
|
return result.checksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<std::string, std::string> calculate_app_id(const std::string &app_name, std::string app_image_path, int index) {
|
||||||
|
// Generate id by hashing name with image data if present
|
||||||
|
std::vector<std::string> to_hash;
|
||||||
|
to_hash.push_back(app_name);
|
||||||
|
auto file_path = validate_app_image_path(app_image_path);
|
||||||
|
if(file_path != DEFAULT_APP_IMAGE_PATH) {
|
||||||
|
auto file_hash = calculate_sha256(file_path);
|
||||||
|
if(file_hash) {
|
||||||
|
to_hash.push_back(file_hash.value());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Fallback to just hashing image path
|
||||||
|
to_hash.push_back(file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create combined strings for hash
|
||||||
|
std::stringstream ss;
|
||||||
|
for_each(to_hash.begin(), to_hash.end(), [&ss](const std::string &s) { ss << s; });
|
||||||
|
auto input_no_index = ss.str();
|
||||||
|
ss << index;
|
||||||
|
auto input_with_index = ss.str();
|
||||||
|
|
||||||
|
// CRC32 then truncate to signed 32-bit range due to client limitations
|
||||||
|
auto id_no_index = std::to_string(abs((int32_t)calculate_crc32(input_no_index)));
|
||||||
|
auto id_with_index = std::to_string(abs((int32_t)calculate_crc32(input_with_index)));
|
||||||
|
|
||||||
|
return std::make_tuple(id_no_index, id_with_index);
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<proc::proc_t> parse(const std::string &file_name) {
|
std::optional<proc::proc_t> parse(const std::string &file_name) {
|
||||||
pt::ptree tree;
|
pt::ptree tree;
|
||||||
|
|
||||||
@@ -370,8 +450,9 @@ std::optional<proc::proc_t> parse(const std::string &file_name) {
|
|||||||
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
|
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
int app_index = 1; // Start at 1, 0 indicates no app running
|
std::set<std::string> ids;
|
||||||
std::vector<proc::ctx_t> apps;
|
std::vector<proc::ctx_t> apps;
|
||||||
|
int i = 0;
|
||||||
for(auto &[_, app_node] : apps_node) {
|
for(auto &[_, app_node] : apps_node) {
|
||||||
proc::ctx_t ctx;
|
proc::ctx_t ctx;
|
||||||
|
|
||||||
@@ -382,7 +463,6 @@ std::optional<proc::proc_t> parse(const std::string &file_name) {
|
|||||||
auto cmd = app_node.get_optional<std::string>("cmd"s);
|
auto cmd = app_node.get_optional<std::string>("cmd"s);
|
||||||
auto image_path = app_node.get_optional<std::string>("image-path"s);
|
auto image_path = app_node.get_optional<std::string>("image-path"s);
|
||||||
auto working_dir = app_node.get_optional<std::string>("working-dir"s);
|
auto working_dir = app_node.get_optional<std::string>("working-dir"s);
|
||||||
auto id = app_node.get_optional<std::string>("id"s);
|
|
||||||
|
|
||||||
std::vector<proc::cmd_t> prep_cmds;
|
std::vector<proc::cmd_t> prep_cmds;
|
||||||
if(prep_nodes_opt) {
|
if(prep_nodes_opt) {
|
||||||
@@ -428,14 +508,16 @@ std::optional<proc::proc_t> parse(const std::string &file_name) {
|
|||||||
ctx.image_path = parse_env_val(this_env, *image_path);
|
ctx.image_path = parse_env_val(this_env, *image_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id) {
|
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
|
||||||
ctx.id = parse_env_val(this_env, *id);
|
if(ids.count(std::get<0>(possible_ids)) == 0) {
|
||||||
|
// Avoid using index to generate id if possible
|
||||||
|
ctx.id = std::get<0>(possible_ids);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ctx.id = std::to_string(app_index);
|
// Fallback to include index on collision
|
||||||
|
ctx.id = std::get<1>(possible_ids);
|
||||||
}
|
}
|
||||||
// Always increment index to avoid order shuffling in moonlight
|
ids.insert(ctx.id);
|
||||||
app_index++;
|
|
||||||
|
|
||||||
ctx.name = std::move(name);
|
ctx.name = std::move(name);
|
||||||
ctx.prep_cmds = std::move(prep_cmds);
|
ctx.prep_cmds = std::move(prep_cmds);
|
||||||
|
|||||||
@@ -97,10 +97,15 @@ private:
|
|||||||
file_t _pipe;
|
file_t _pipe;
|
||||||
std::vector<cmd_t>::const_iterator _undo_it;
|
std::vector<cmd_t>::const_iterator _undo_it;
|
||||||
std::vector<cmd_t>::const_iterator _undo_begin;
|
std::vector<cmd_t>::const_iterator _undo_begin;
|
||||||
|
|
||||||
int app_index_from_id(int app_id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate a stable id based on name and image data
|
||||||
|
* @return tuple of id calculated without index (for use if no collision) and one with
|
||||||
|
*/
|
||||||
|
std::tuple<std::string, std::string> calculate_app_id(const std::string &app_name, std::string app_image_path, int index);
|
||||||
|
|
||||||
|
std::string validate_app_image_path(std::string app_image_path);
|
||||||
void refresh(const std::string &file_name);
|
void refresh(const std::string &file_name);
|
||||||
std::optional<proc::proc_t> parse(const std::string &file_name);
|
std::optional<proc::proc_t> parse(const std::string &file_name);
|
||||||
|
|
||||||
|
|||||||
23
src/rand.h
23
src/rand.h
@@ -1,23 +0,0 @@
|
|||||||
#ifndef SUNSHINE_RAND_H
|
|
||||||
#define SUNSHINE_RAND_H
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
namespace util {
|
|
||||||
|
|
||||||
static int32_t generate_int32(std::default_random_engine &engine, int32_t min, int32_t max) {
|
|
||||||
std::uniform_int_distribution<std::int32_t> dist(min, max);
|
|
||||||
|
|
||||||
return dist(engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t generate_int32(int32_t min, int32_t max) {
|
|
||||||
std::random_device r;
|
|
||||||
|
|
||||||
std::default_random_engine engine { r() };
|
|
||||||
|
|
||||||
return util::generate_int32(engine, min, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace util
|
|
||||||
#endif // SUNSHINE_RAND_H
|
|
||||||
Reference in New Issue
Block a user