Pyrogenesis HEAD
Pyrogenesis, a RTS Engine
ModIo Class Reference

mod.io API interfacing code. More...

#include <ModIo.h>

Collaboration diagram for ModIo:

Public Member Functions

 ModIo ()
 
 ~ModIo ()
 
void StartGetGameId ()
 
void StartListMods ()
 
void StartDownloadMod (u32 idx)
 
bool AdvanceRequest (const ScriptInterface &scriptInterface)
 Advance the current async request and perform final steps if the download is complete. More...
 
void CancelRequest ()
 Cancel the current async request and clean things up. More...
 
const std::vector< ModIoModData > & GetMods () const
 
const DownloadProgressDataGetDownloadProgress () const
 

Private Member Functions

 NONCOPYABLE (ModIo)
 
CURLMcode SetupRequest (const std::string &url, bool fileDownload)
 
void TearDownRequest ()
 
bool ParseGameId (const ScriptInterface &scriptInterface, std::string &err)
 
bool ParseMods (const ScriptInterface &scriptInterface, std::string &err)
 
void DeleteDownloadedFile ()
 
bool VerifyDownloadedFile (std::string &err)
 

Static Private Member Functions

static size_t ReceiveCallback (void *buffer, size_t size, size_t nmemb, void *userp)
 
static size_t DownloadCallback (void *buffer, size_t size, size_t nmemb, void *userp)
 
static int DownloadProgressCallback (void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
 
static bool ParseGameIdResponse (const ScriptInterface &scriptInterface, const std::string &responseData, int &id, std::string &err)
 Parses the current content of m_ResponseData to extract m_GameId. More...
 
static bool ParseModsResponse (const ScriptInterface &scriptInterface, const std::string &responseData, std::vector< ModIoModData > &modData, const PKStruct &pk, std::string &err)
 Parses the current content of m_ResponseData into m_ModData. More...
 
static bool ParseSignature (const std::vector< std::string > &minisigs, SigStruct &sig, const PKStruct &pk, std::string &err)
 Parse signatures to find one that matches the public key, and has a valid global signature. More...
 

Private Attributes

std::string m_BaseUrl
 
std::string m_GamesRequest
 
std::string m_GameId
 
std::string m_ApiKey
 
std::string m_IdQuery
 
CURL * m_Curl
 
CURLM * m_CurlMulti
 
curl_slist * m_Headers
 
char m_ErrorBuffer [CURL_ERROR_SIZE]
 
std::string m_ResponseData
 
int m_DownloadModID
 
OsPath m_DownloadFilePath
 
DownloadCallbackDatam_CallbackData
 
DownloadProgressData m_DownloadProgressData
 
PKStruct m_pk
 
std::vector< ModIoModDatam_ModData
 

Friends

class TestModIo
 

Detailed Description

mod.io API interfacing code.

Overview

This class interfaces with a remote API provider that returns a list of mod files. These can then be downloaded after some cursory checking of well-formedness of the returned metadata. Downloaded files are checked for well formedness by validating that they fit the size and hash indicated by the API, then we check if the file is actually signed by a trusted key, and only if all of that is success the file is actually possible to be loaded as a mod.

Security considerations

This both distrusts the loaded JS mods, and the API as much as possible. We do not want a malicious mod to use this to download arbitrary files, nor do we want the API to make us download something we have not verified. Therefore we only allow mods to download one of the mods returned by this class (using indices).

This (mostly) necessitates parsing the API responses here, as opposed to in JS. One could alternatively parse the responses in a locked down JS context, but that would require storing that code in here, or making sure nobody can overwrite it. Also this would possibly make some of the needed accesses for downloading and verifying files a bit more complicated.

Everything downloaded from the API has its signature verified against our public key. This is a requirement, as otherwise a compromise of the API would result in users installing possibly malicious files. So a compromised API can just serve old files that we signed, so in that case there would need to be an issue in that old file that was missed.

To limit the extend to how old those files could be the signing key should be rotated regularly (e.g. every release). To allow old versions of the engine to still use the API files can be signed by both the old and the new key for some amount of time, that however only makes sense in case a mod is compatible with both engine versions.

Note that this does not prevent all possible attacks a package manager/update system should defend against. This is intentionally not an update system since proper package managers already exist. However there is some possible overlap in attack vectors and these should be evalutated whether they apply and to what extend we can fix that on our side (or how to get the API provider to help us do so). For a list of some possible issues see: https://github.com/theupdateframework/specification/blob/master/tuf-spec.md

The mod.io settings are also locked down such that only mods that have been authorized by us show up in API queries. This is both done so that all required information (dependencies) are stored for the files, and that only mods that have been checked for being ok are actually shown to users.

Constructor & Destructor Documentation

◆ ModIo()

ModIo::ModIo ( )

◆ ~ModIo()

ModIo::~ModIo ( )

Member Function Documentation

◆ AdvanceRequest()

bool ModIo::AdvanceRequest ( const ScriptInterface scriptInterface)

Advance the current async request and perform final steps if the download is complete.

Parameters
scriptInterfaceused for parsing the data and possibly install the mod.
Returns
true if the download is complete (successful or not), false otherwise.

◆ CancelRequest()

void ModIo::CancelRequest ( )

Cancel the current async request and clean things up.

◆ DeleteDownloadedFile()

void ModIo::DeleteDownloadedFile ( )
private

◆ DownloadCallback()

size_t ModIo::DownloadCallback ( void *  buffer,
size_t  size,
size_t  nmemb,
void *  userp 
)
staticprivate

◆ DownloadProgressCallback()

int ModIo::DownloadProgressCallback ( void *  clientp,
curl_off_t  dltotal,
curl_off_t  dlnow,
curl_off_t  ultotal,
curl_off_t  ulnow 
)
staticprivate

◆ GetDownloadProgress()

const DownloadProgressData & ModIo::GetDownloadProgress ( ) const
inline

◆ GetMods()

const std::vector< ModIoModData > & ModIo::GetMods ( ) const
inline

◆ NONCOPYABLE()

ModIo::NONCOPYABLE ( ModIo  )
private

◆ ParseGameId()

bool ModIo::ParseGameId ( const ScriptInterface scriptInterface,
std::string &  err 
)
private

◆ ParseGameIdResponse()

bool ModIo::ParseGameIdResponse ( const ScriptInterface scriptInterface,
const std::string &  responseData,
int &  id,
std::string &  err 
)
staticprivate

Parses the current content of m_ResponseData to extract m_GameId.

The JSON data is expected to look like { "data": [{"id": 42, ...}, ...], ... } where we are only interested in the value of the id property.

Returns
true iff it successfully parsed the id.

◆ ParseMods()

bool ModIo::ParseMods ( const ScriptInterface scriptInterface,
std::string &  err 
)
private

◆ ParseModsResponse()

bool ModIo::ParseModsResponse ( const ScriptInterface scriptInterface,
const std::string &  responseData,
std::vector< ModIoModData > &  modData,
const PKStruct pk,
std::string &  err 
)
staticprivate

Parses the current content of m_ResponseData into m_ModData.

The JSON data is expected to look like { data: [modobj1, modobj2, ...], ... (including result_count) } where modobjN has the following structure { homepage_url: "url", name: "displayname", nameid: "short-non-whitespace-name", summary: "short desc.", modfile: { version: "1.2.4", filename: "asdf.zip", filehash: { md5: "deadbeef" }, filesize: 1234, download: { binary_url: "someurl", ... } }, ... }. Only the listed properties are of interest to consumers, and we flatten the modfile structure as that simplifies handling and there are no conflicts.

◆ ParseSignature()

bool ModIo::ParseSignature ( const std::vector< std::string > &  minisigs,
SigStruct sig,
const PKStruct pk,
std::string &  err 
)
staticprivate

Parse signatures to find one that matches the public key, and has a valid global signature.

Returns true and sets

Parameters
sigto the valid matching signature.

◆ ReceiveCallback()

size_t ModIo::ReceiveCallback ( void *  buffer,
size_t  size,
size_t  nmemb,
void *  userp 
)
staticprivate

◆ SetupRequest()

CURLMcode ModIo::SetupRequest ( const std::string &  url,
bool  fileDownload 
)
private

◆ StartDownloadMod()

void ModIo::StartDownloadMod ( u32  idx)

◆ StartGetGameId()

void ModIo::StartGetGameId ( )

◆ StartListMods()

void ModIo::StartListMods ( )

◆ TearDownRequest()

void ModIo::TearDownRequest ( )
private

◆ VerifyDownloadedFile()

bool ModIo::VerifyDownloadedFile ( std::string &  err)
private

Friends And Related Function Documentation

◆ TestModIo

friend class TestModIo
friend

Member Data Documentation

◆ m_ApiKey

std::string ModIo::m_ApiKey
private

◆ m_BaseUrl

std::string ModIo::m_BaseUrl
private

◆ m_CallbackData

DownloadCallbackData* ModIo::m_CallbackData
private

◆ m_Curl

CURL* ModIo::m_Curl
private

◆ m_CurlMulti

CURLM* ModIo::m_CurlMulti
private

◆ m_DownloadFilePath

OsPath ModIo::m_DownloadFilePath
private

◆ m_DownloadModID

int ModIo::m_DownloadModID
private

◆ m_DownloadProgressData

DownloadProgressData ModIo::m_DownloadProgressData
private

◆ m_ErrorBuffer

char ModIo::m_ErrorBuffer[CURL_ERROR_SIZE]
private

◆ m_GameId

std::string ModIo::m_GameId
private

◆ m_GamesRequest

std::string ModIo::m_GamesRequest
private

◆ m_Headers

curl_slist* ModIo::m_Headers
private

◆ m_IdQuery

std::string ModIo::m_IdQuery
private

◆ m_ModData

std::vector<ModIoModData> ModIo::m_ModData
private

◆ m_pk

PKStruct ModIo::m_pk
private

◆ m_ResponseData

std::string ModIo::m_ResponseData
private

The documentation for this class was generated from the following files: