mirror of
https://github.com/LizardByte/Sunshine.git
synced 2024-12-29 12:16:08 +00:00
Generated app id with hashed input data (#715)
This commit is contained in:
parent
effa98f76a
commit
9b6d0b7a06
@ -375,7 +375,6 @@ set(SUNSHINE_TARGET_FILES
|
||||
src/network.cpp
|
||||
src/network.h
|
||||
src/move_by_copy.h
|
||||
src/rand.h
|
||||
src/task_pool.h
|
||||
src/thread_pool.h
|
||||
src/thread_safe.h
|
||||
|
@ -30,7 +30,6 @@
|
||||
#include "network.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "rand.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
@ -303,11 +302,6 @@ void saveApp(resp_https_t response, req_https_t request) {
|
||||
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;
|
||||
|
||||
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(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()) {
|
||||
inputTree.erase("prep-cmd");
|
||||
}
|
||||
|
168
src/process.cpp
168
src/process.cpp
@ -9,11 +9,16 @@
|
||||
#include <vector>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/crc.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/program_options/parsers.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "crypto.h"
|
||||
#include "main.h"
|
||||
#include "platform/common.h"
|
||||
#include "utility.h"
|
||||
@ -23,6 +28,8 @@
|
||||
#include <share.h>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_APP_IMAGE_PATH SUNSHINE_ASSETS_DIR "/box.png"
|
||||
|
||||
namespace proc {
|
||||
using namespace std::literals;
|
||||
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 http content-type header compatible image type.
|
||||
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) {
|
||||
return app.id == std::to_string(app_id);
|
||||
});
|
||||
auto app_image_path = iter == _apps.end() ? std::string() : iter->image_path;
|
||||
|
||||
if(app_image_path.empty()) {
|
||||
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;
|
||||
return validate_app_image_path(app_image_path);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
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>());
|
||||
}
|
||||
|
||||
int app_index = 1; // Start at 1, 0 indicates no app running
|
||||
std::set<std::string> ids;
|
||||
std::vector<proc::ctx_t> apps;
|
||||
int i = 0;
|
||||
for(auto &[_, app_node] : apps_node) {
|
||||
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 image_path = app_node.get_optional<std::string>("image-path"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;
|
||||
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);
|
||||
}
|
||||
|
||||
if(id) {
|
||||
ctx.id = parse_env_val(this_env, *id);
|
||||
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
|
||||
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 {
|
||||
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
|
||||
app_index++;
|
||||
ids.insert(ctx.id);
|
||||
|
||||
ctx.name = std::move(name);
|
||||
ctx.prep_cmds = std::move(prep_cmds);
|
||||
|
@ -97,10 +97,15 @@ private:
|
||||
file_t _pipe;
|
||||
std::vector<cmd_t>::const_iterator _undo_it;
|
||||
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);
|
||||
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
|
Loading…
Reference in New Issue
Block a user