diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/database_postgres.cc | 411 | ||||
| -rw-r--r-- | src/database_sqlite.cc | 4 | ||||
| -rw-r--r-- | src/query.cc | 14 | ||||
| -rw-r--r-- | src/querymanager.cc | 165 | ||||
| -rw-r--r-- | src/querymanager.hh | 33 | ||||
| -rw-r--r-- | src/sha256.cc | 44 |
6 files changed, 472 insertions, 199 deletions
diff --git a/src/database_postgres.cc b/src/database_postgres.cc index 7c0f3f9..daa19fb 100644 --- a/src/database_postgres.cc +++ b/src/database_postgres.cc @@ -2,6 +2,12 @@ #include "querymanager.hh" #include "libpq-fe.h" +// IMPORTANT(fusion): With PostgreSQL being a distributed database, we cannot +// rely on automatic schema upgrades like in the case of SQLite. It must be +// managed manually and there must be an agreement on the current version +// which is why there is a `SchemaInfo` table. +#define POSTGRESQL_SCHEMA_VERSION 1 + // IMPORTANT(fusion): These are the OIDs for a few of built-in data types in // PostgreSQL. They're taken from `catalog/pg_type_d.h` which is not included // with libpq but should be STABLE across different versions and are needed @@ -37,6 +43,244 @@ struct TDatabase{ TCachedStatement *CachedStatements; }; +// Internal Helpers +//============================================================================== +struct AutoResultClear{ +private: + PGresult *m_Result; + +public: + AutoResultClear(PGresult *Result){ + m_Result = Result; + } + + ~AutoResultClear(void){ + if(m_Result != NULL){ + PQclear(m_Result); + m_Result = NULL; + } + } +}; + +static bool GetResultBool(PGresult *Result, int Row, int Col){ + bool Value = false; + int Format = PQfformat(Result, Col); + Oid Type = PQftype(Result, Col); + if(Format == 0){ // TEXT FORMAT + if(!ParseBoolean(&Value, PQgetvalue(Result, Row, Col))){ + LOG_ERR("Failed to properly parse column (%d) %s as BOOLEAN", + Col, PQfname(Result, Col)); + } + }else if(Format == 1){ // BINARY FORMAT + switch(Type){ + case BOOLOID:{ + ASSERT(PQgetlength(Result, Row, Col) == 1); + Value = (BufferRead8((const uint8*)PQgetvalue(Result, Row, Col)) != 0); + break; + } + + case INT8OID:{ + ASSERT(PQgetlength(Result, Row, Col) == 8); + Value = (BufferRead64BE((const uint8*)PQgetvalue(Result, Row, Col)) != 0); + break; + } + + case INT2OID:{ + ASSERT(PQgetlength(Result, Row, Col) == 2); + Value = (BufferRead16BE((const uint8*)PQgetvalue(Result, Row, Col)) != 0); + break; + } + + case INT4OID:{ + ASSERT(PQgetlength(Result, Row, Col) == 4); + Value = (BufferRead32BE((const uint8*)PQgetvalue(Result, Row, Col)) != 0); + break; + } + + case TEXTOID: + case VARCHAROID:{ + if(!ParseBoolean(&Value, PQgetvalue(Result, Row, Col))){ + LOG_WARN("Failed to properly convert column (%d) %s from TEXT to BOOLEAN", + Col, PQfname(Result, Col)); + } + break; + } + + default:{ + LOG_ERR("Column (%d) %s has OID %d which is not convertible to BOOLEAN", + Col, PQfname(Result, Col), Type); + break; + } + } + } + return Value; +} + +static int GetResultInt(PGresult *Result, int Row, int Col){ + int Value = 0; + int Format = PQfformat(Result, Col); + Oid Type = PQftype(Result, Col); + ASSERT(Format == 0 || Format == 1); + if(Format == 0){ // TEXT FORMAT + if(!ParseInteger(&Value, PQgetvalue(Result, Row, Col))){ + LOG_ERR("Failed to properly parse column (%d) %s as INT4", + Col, PQfname(Result, Col)); + } + }else if(Format == 1){ // BINARY FORMAT + switch(Type){ + case BOOLOID:{ + ASSERT(PQgetlength(Result, Row, Col) == 1); + Value = BufferRead8((const uint8*)PQgetvalue(Result, Row, Col)); + break; + } + + case INT8OID:{ + ASSERT(PQgetlength(Result, Row, Col) == 8); + int64 Temp = (int64)BufferRead64BE((const uint8*)PQgetvalue(Result, Row, Col)); + if(Temp < INT_MIN || Temp > INT_MAX){ + LOG_WARN("Lossy conversion of column (%d) %s from INT8 to INT4", + Col, PQfname(Result, Col)); + } + + Value = (int)Temp; + break; + } + + case INT2OID:{ + ASSERT(PQgetlength(Result, Row, Col) == 2); + Value = (int16)BufferRead16BE((const uint8*)PQgetvalue(Result, Row, Col)); + break; + } + + case INT4OID:{ + ASSERT(PQgetlength(Result, Row, Col) == 4); + Value = (int)BufferRead32BE((const uint8*)PQgetvalue(Result, Row, Col)); + break; + } + + case TEXTOID: + case VARCHAROID:{ + if(!ParseInteger(&Value, PQgetvalue(Result, Row, Col))){ + LOG_WARN("Failed to properly convert column (%d) %s from TEXT to INT4", + Col, PQfname(Result, Col)); + } + break; + } + + default:{ + LOG_ERR("Column (%d) %s has OID %d which is not convertible to INT4", + Col, PQfname(Result, Col), Type); + break; + } + } + } + return Value; +} + +static const char *GetResultText(PGresult *Result, int Row, int Col){ + const char *Text = ""; + int Format = PQfformat(Result, Col); + Oid Type = PQftype(Result, Col); + ASSERT(Format == 0 || Format == 1); + if(Format == 0){ // TEXT FORMAT + Text = PQgetvalue(Result, Row, Col); + }else if(Format == 1){ // BINARY FORMAT + switch(Type){ + case TEXTOID: + case VARCHAROID:{ + Text = PQgetvalue(Result, Row, Col); + break; + } + + default:{ + // IMPORTANT(fusion): There is no trivial way to convert whatever + // value we received back to string. We'd either need to allocate + // or modify the prototype of this function to accept an output + // buffer. + // The fact is, we shouldn't expect implicit conversions to work + // when using the binary format, PERIOD. + LOG_ERR("Column (%d) %s has OID %d which is not trivially convertible to TEXT", + Col, PQfname(Result, Col), Type); + break; + } + } + } + return Text; +} + +static int GetResultByteA(PGresult *Result, int Row, int Col, uint8 *Buffer, int BufferSize){ + int Size = 0; + int Format = PQfformat(Result, Col); + ASSERT(Format == 0 || Format == 1); + if(Format == 0){ // TEXT FORMAT + const char *String = PQgetvalue(Result, Row, Col); + if(String[0] != '\\' && String[1] != 'x'){ + LOG_ERR("Column (%d) %s (OID %d) doesn't contain a valid BYTEA literal", + Col, PQfname(Result, Col), PQftype(Result, Col)); + return -1; + } + + Size = ParseHexString(Buffer, BufferSize, String + 2); + if(Size == -1){ + return -1; + } + }else if(Format == 1){ // BINARY FORMAT + Size = PQgetlength(Result, Row, Col); + if(Size > BufferSize){ + return -1; + } + memcpy(Buffer, PQgetvalue(Result, Row, Col), Size); + } + + ASSERT(Size <= BufferSize); + return Size; +} + +static bool ExecInternal(TDatabase *Database, const char *Format, ...) ATTR_PRINTF(2, 3); +static bool ExecInternal(TDatabase *Database, const char *Format, ...){ + va_list ap; + va_start(ap, Format); + char Text[1024]; + int Written = vsnprintf(Text, sizeof(Text), Format, ap); + va_end(ap); + + if(Written >= (int)sizeof(Text)){ + LOG_ERR("Query is too long"); + return false; + } + + PGresult *Result = PQexec(Database->Handle, Text); + AutoResultClear ResultGuard(Result); + bool Status = PQresultStatus(Result) == PGRES_COMMAND_OK + || PQresultStatus(Result) == PGRES_TUPLES_OK; + if(!Status){ + char Preview[30]; + StringBufCopyEllipsis(Preview, Text); + LOG_ERR("Failed to execute query \"%s\": %s", + Preview, PQerrorMessage(Database->Handle)); + } + return Status; +} + +static bool GetSchemaVersion(TDatabase *Database, int *Version){ + PGresult *Result = PQexec(Database->Handle, + "SELECT Value FROM SchemaInfo WHERE Key = 'VERSION'"); + AutoResultClear ResultGuard(Result); + if(PQresultStatus(Result) != PGRES_TUPLES_OK){ + LOG_ERR("Failed to execute query: %s", + PQerrorMessage(Database->Handle)); + return false; + } + + if(PQntuples(Result) == 0){ + LOG_ERR("Query returned no rows"); + return false; + } + + *Version = GetResultInt(Result, 0, 0); + return true; +} + // Statement Cache //============================================================================== // NOTE(fusion): Prepared statements are stored server-side and only referenced @@ -71,10 +315,6 @@ void DeleteStatementCache(TDatabase *Database){ ASSERT(Database->MaxCachedStatements > 0); for(int i = 0; i < Database->MaxCachedStatements; i += 1){ TCachedStatement *Entry = &Database->CachedStatements[i]; - // NOTE(fusion): There is little reason to use `PQclosePrepared` here - // because this function would usually be called with `PQreset` or - // `PQfinish` which should already clear any prepared statements - // created for the session. if(Entry->Text != NULL){ free(Entry->Text); Entry->LastUsed = 0; @@ -83,9 +323,15 @@ void DeleteStatementCache(TDatabase *Database){ } } - // TODO(fusion): We might not need to use `PQclosePrepared` but it could - // be a good idea to do some ExecInternal("DEALLOCATE ALL"), which would - // clear all prepared statements from the current session. + // NOTE(fusion): This function would usually be called along with `PQreset` + // or `PQfinish` but it's probably a good idea to close all prepared statements + // if the connection is still going. There is no libpq wrapper but we can + // execute `DEALLOCATE ALL`. + if(PQstatus(Database->Handle) == CONNECTION_OK){ + if(!ExecInternal(Database, "DEALLOCATE ALL")){ + LOG_WARN("Failed to close all prepared statements"); + } + } free(Database->CachedStatements); Database->MaxCachedStatements = 0; @@ -128,27 +374,26 @@ const char *PrepareQuery(TDatabase *Database, const char *Text){ if(Stmt->Text != NULL){ PGresult *Result = PQclosePrepared(Database->Handle, Stmt->Name); + AutoResultClear ResultGuard(Result); if(PQresultStatus(Result) != PGRES_COMMAND_OK){ char OldPreview[30]; StringBufCopyEllipsis(OldPreview, Stmt->Text); LOG_ERR("Failed to close prepared query \"%s\": %s", OldPreview, PQerrorMessage(Database->Handle)); } - PQclear(Result); free(Stmt->Text); } { PGresult *Result = PQprepare(Database->Handle, Stmt->Name, Text, 0, NULL); + AutoResultClear ResultGuard(Result); if(PQresultStatus(Result) != PGRES_COMMAND_OK){ char NewPreview[30]; StringBufCopyEllipsis(NewPreview, Text); LOG_ERR("Failed to prepare query \"%s\": %s", NewPreview, PQerrorMessage(Database->Handle)); - PQclear(Result); return NULL; } - PQclear(Result); } @@ -164,6 +409,7 @@ const char *PrepareQuery(TDatabase *Database, const char *Text){ LOG("New statement cached: \"%s\"", Preview); PGresult *Result = PQdescribePrepared(Database->Handle, Stmt->Name); + AutoResultClear ResultGuard(Result); if(PQresultStatus(Result) == PGRES_COMMAND_OK){ LOG(" PARAM OIDs:"); for(int i = 0; i < PQnparams(Result); i += 1){ @@ -175,7 +421,6 @@ const char *PrepareQuery(TDatabase *Database, const char *Text){ LOG(" (%d) %s: %d", i, PQfname(Result, i), PQftype(Result, i)); } } - PQclear(Result); } #endif } @@ -183,6 +428,53 @@ const char *PrepareQuery(TDatabase *Database, const char *Text){ return Stmt->Name; } +// TransactionScope +//============================================================================== +TransactionScope::TransactionScope(const char *Context){ + m_Context = (Context != NULL ? Context : "NOCONTEXT"); + m_Database = NULL; +} + +TransactionScope::~TransactionScope(void){ + if(m_Database != NULL){ + if(!ExecInternal(m_Database, "ROLLBACK")){ + LOG_ERR("Failed to rollback transaction (%s)", m_Context); + } + + m_Database = NULL; + } +} + +bool TransactionScope::Begin(TDatabase *Database){ + if(m_Database != NULL){ + LOG_ERR("Transaction (%s) already running", m_Context); + return false; + } + + if(!ExecInternal(Database, "BEGIN")){ + LOG_ERR("Failed to begin transaction (%s)", m_Context); + return false; + } + + m_Database = Database; + return true; +} + +bool TransactionScope::Commit(void){ + if(m_Database == NULL){ + LOG_ERR("Transaction (%s) not running", m_Context); + return false; + } + + if(!ExecInternal(m_Database, "COMMIT")){ + LOG_ERR("Failed to commit transaction (%s)", m_Context); + return false; + } + + m_Database = NULL; + return true; +} + // Database Management //============================================================================== void DatabaseClose(TDatabase *Database){ @@ -239,52 +531,21 @@ TDatabase *DatabaseOpen(void){ return NULL; } -//===== - // TODO(fusion): REMOVE. - { - const char *Stmt = PrepareQuery(Database, - "SELECT Value::INTEGER FROM SchemaInfo WHERE Key = $1::TEXT"); - if(Stmt == NULL){ - LOG_ERR("Failed to prepare query"); - DatabaseClose(Database); - return NULL; - } - - const char *ParamValues[] = { "VERSION" }; - PGresult *Result = PQexecPrepared(Database->Handle, Stmt, 1, ParamValues, NULL, NULL, 1); - if(PQresultStatus(Result) != PGRES_TUPLES_OK){ - LOG_ERR("Failed to execute query: %s", PQerrorMessage(Database->Handle)); - PQclear(Result); - DatabaseClose(Database); - return NULL; - } - - LOG("VERSION: %d", BufferRead32BE((const uint8*)PQgetvalue(Result, 0, 0))); - PQclear(Result); + int SchemaVersion; + if(!GetSchemaVersion(Database, &SchemaVersion)){ + LOG_ERR("Failed to retrieve schema version..." + " Database schema may not have been initialized"); + DatabaseClose(Database); + return NULL; } - const char *Stmt = PrepareQuery(Database, "SELECT Value FROM SchemaInfo WHERE Key = 'VERSION'"); - if(Stmt == NULL){ - LOG_ERR("Failed to prepare query"); + if(SchemaVersion != POSTGRESQL_SCHEMA_VERSION){ + LOG_ERR("Schema version MISMATCH (expected %d, got %d)", + POSTGRESQL_SCHEMA_VERSION, SchemaVersion); DatabaseClose(Database); return NULL; } - int i = 0; - do{ - PGresult *Result = PQexecPrepared(Database->Handle, Stmt, 0, NULL, NULL, NULL, 1); - if(PQresultStatus(Result) != PGRES_TUPLES_OK){ - LOG_ERR("Failed to execute query: %s", PQerrorMessage(Database->Handle)); - PQclear(Result); - DatabaseClose(Database); - return NULL; - } - LOG("VERSION: OID = %u, LEN = %d", PQftype(Result, 0), PQgetlength(Result, 0, 0)); - PQclear(Result); - //PQreset(Database->Handle); - }while(i++ < 2); -//===== - return Database; } @@ -309,35 +570,35 @@ int DatabaseMaxConcurrency(void){ return INT_MAX; } -// TransactionScope -//============================================================================== -TransactionScope::TransactionScope(const char *Context){ - m_Context = (Context != NULL ? Context : "NOCONTEXT"); - m_Database = NULL; -} - -TransactionScope::~TransactionScope(void){ - // TODO -} - -bool TransactionScope::Begin(TDatabase *Database){ - // TODO - return false; -} - -bool TransactionScope::Commit(void){ - // TODO - return false; -} - // Primary Tables //============================================================================== bool GetWorldID(TDatabase *Database, const char *World, int *WorldID){ -// const Oid ParamTypes[] = { TEXTOID }; const char *Stmt = PrepareQuery(Database, "SELECT WorldID FROM Worlds WHERE Name = $1::TEXT"); -// - return false; + if(Stmt == NULL){ + LOG_ERR("Failed to prepare query"); + return false; + } + + // TODO(fusion): In this specific case it doesn't make a difference but + // we'll probably need some helper struct to organize query parameters. + // It would have an internal buffer which would be used as an arena. + //PGParams Params(1); + //Params.PushText(World); + //Params.PushX(...); // PANIC: Too many parameters pushed... + //PGResult *Result = PQexecPrepared(Database->Handle, Stmt, Params.Count, + // Params.Values, Params.Lengths, Params.Formats, 1); + + const char *ParamValues[] = {World}; + PGresult *Result = PQexecPrepared(Database->Handle, Stmt, 1, ParamValues, NULL, NULL, 1); + AutoResultClear ResultGuard(Result); + if(PQresultStatus(Result) != PGRES_TUPLES_OK){ + LOG_ERR("Failed to execute query: %s", PQerrorMessage(Database->Handle)); + return false; + } + + *WorldID = (PQntuples(Result) > 0 ? GetResultInt(Result, 0, 0) : 0); + return true; } bool GetWorlds(TDatabase *Database, DynamicArray<TWorld> *Worlds){ diff --git a/src/database_sqlite.cc b/src/database_sqlite.cc index 50f8bda..85d8fc7 100644 --- a/src/database_sqlite.cc +++ b/src/database_sqlite.cc @@ -35,7 +35,7 @@ public: } ~AutoStmtReset(void){ - if(m_Stmt){ + if(m_Stmt != NULL){ sqlite3_reset(m_Stmt); m_Stmt = NULL; } @@ -190,6 +190,7 @@ static bool ExecFile(TDatabase *Database, const char *FileName){ return Result; } +static bool ExecInternal(TDatabase *Database, const char *Format, ...) ATTR_PRINTF(2, 3); static bool ExecInternal(TDatabase *Database, const char *Format, ...){ va_list ap; va_start(ap, Format); @@ -434,7 +435,6 @@ bool TransactionScope::Commit(void){ return false; } - // TODO(fusion): Does the transaction automatically rollback if commiting fails? if(!ExecInternal(m_Database, "COMMIT")){ LOG_ERR("Failed to commit transaction (%s)", m_Context); return false; diff --git a/src/query.cc b/src/query.cc index 6c2512a..2cf74ea 100644 --- a/src/query.cc +++ b/src/query.cc @@ -545,7 +545,7 @@ void ProcessCheckAccountPassword(TDatabase *Database, TQuery *Query){ Request.ReadString(IPString, sizeof(IPString)); int IPAddress = 0; - QUERY_FAIL_IF(!ParseIPAddress(IPString, &IPAddress)); + QUERY_FAIL_IF(!ParseIPAddress(&IPAddress, IPString)); // NOTE(fusion): Same as `ProcessLoginGame`. CheckAccountPasswordTx(Database, Query, AccountID, Password, IPAddress); @@ -604,7 +604,7 @@ void ProcessLoginAccount(TDatabase *Database, TQuery *Query){ Request.ReadString(IPString, sizeof(IPString)); int IPAddress = 0; - QUERY_FAIL_IF(!ParseIPAddress(IPString, &IPAddress)); + QUERY_FAIL_IF(!ParseIPAddress(&IPAddress, IPString)); // NOTE(fusion): Same as `ProcessLoginGame`. LoginAccountTx(Database, Query, AccountID, Password, IPAddress); @@ -732,7 +732,7 @@ void ProcessLoginGame(TDatabase *Database, TQuery *Query){ bool GamemasterRequired = Request.ReadFlag(); int IPAddress; - QUERY_FAIL_IF(!ParseIPAddress(IPString, &IPAddress)); + QUERY_FAIL_IF(!ParseIPAddress(&IPAddress, IPString)); // IMPORTANT(fusion): We need to insert login attempts outside the login game // transaction or we could end up not having it recorded at all due to rollbacks. @@ -778,7 +778,7 @@ void ProcessSetNamelock(TDatabase *Database, TQuery *Query){ Request.ReadString(Comment, sizeof(Comment)); int IPAddress = 0; - QUERY_FAIL_IF(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)); + QUERY_FAIL_IF(!StringEmpty(IPString) && !ParseIPAddress(&IPAddress, IPString)); TransactionScope Tx("SetNamelock"); QUERY_STOP_IF(!Tx.Begin(Database)); @@ -816,7 +816,7 @@ void ProcessBanishAccount(TDatabase *Database, TQuery *Query){ bool FinalWarning = Request.ReadFlag(); int IPAddress = 0; - QUERY_FAIL_IF(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)); + QUERY_FAIL_IF(!StringEmpty(IPString) && !ParseIPAddress(&IPAddress, IPString)); TransactionScope Tx("BanishAccount"); QUERY_STOP_IF(!Tx.Begin(Database)); @@ -863,7 +863,7 @@ void ProcessSetNotation(TDatabase *Database, TQuery *Query){ Request.ReadString(Comment, sizeof(Comment)); int IPAddress = 0; - QUERY_FAIL_IF(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)); + QUERY_FAIL_IF(!StringEmpty(IPString) && !ParseIPAddress(&IPAddress, IPString)); TransactionScope Tx("SetNotation"); QUERY_STOP_IF(!Tx.Begin(Database)); @@ -987,7 +987,7 @@ void ProcessBanishIpAddress(TDatabase *Database, TQuery *Query){ Request.ReadString(Comment, sizeof(Comment)); int IPAddress; - QUERY_FAIL_IF(!ParseIPAddress(IPString, &IPAddress)); + QUERY_FAIL_IF(!ParseIPAddress(&IPAddress, IPString)); TransactionScope Tx("BanishIP"); QUERY_STOP_IF(!Tx.Begin(Database)); diff --git a/src/querymanager.cc b/src/querymanager.cc index 773ebb3..bdb27ae 100644 --- a/src/querymanager.cc +++ b/src/querymanager.cc @@ -209,7 +209,47 @@ uint32 HashString(const char *String){ return Hash; } -bool ParseIPAddress(const char *String, int *OutAddr){ +int HexDigit(int Ch){ + if(Ch >= '0' && Ch <= '9'){ + return (Ch - '0'); + }else if(Ch >= 'A' && Ch <= 'F'){ + return (Ch - 'A') + 10; + }else if(Ch >= 'a' && Ch <= 'f'){ + return (Ch - 'a') + 10; + }else{ + return -1; + } +} + +int ParseHexString(uint8 *Dest, int DestCapacity, const char *String){ + int StringLen = (int)strlen(String); + if(StringLen % 2 != 0){ + LOG_ERR("Expected even number of characters"); + return -1; + } + + int NumBytes = (StringLen / 2); + if(NumBytes > DestCapacity){ + LOG_ERR("Supplied buffer is too small (Size: %d, Required: %d)", + DestCapacity, NumBytes); + return -1; + } + + for(int i = 0; i < StringLen; i += 2){ + int DigitHi = HexDigit(String[i + 0]); + int DigitLo = HexDigit(String[i + 1]); + if(DigitHi == -1 || DigitLo == -1){ + LOG_ERR("Invalid hex digit at offset %d", i); + return -1; + } + + Dest[i/2] = ((uint8)DigitHi << 4) | (uint8)DigitLo; + } + + return NumBytes; +} + +bool ParseIPAddress(int *Dest, const char *String){ if(StringEmpty(String)){ LOG_ERR("Empty IP Address"); return false; @@ -229,8 +269,8 @@ bool ParseIPAddress(const char *String, int *OutAddr){ return false; } - if(OutAddr){ - *OutAddr = ((int)Addr[0] << 24) + if(Dest){ + *Dest = ((int)Addr[0] << 24) | ((int)Addr[1] << 16) | ((int)Addr[2] << 8) | ((int)Addr[3] << 0); @@ -239,24 +279,29 @@ bool ParseIPAddress(const char *String, int *OutAddr){ return true; } -bool ReadBooleanConfig(bool *Dest, const char *Val){ - ASSERT(Dest && Val); - *Dest = StringEqCI(Val, "true"); - return *Dest || StringEqCI(Val, "false"); +bool ParseBoolean(bool *Dest, const char *String){ + ASSERT(Dest && String); + *Dest = StringEqCI(String, "true") + || StringEqCI(String, "on") + || StringEqCI(String, "yes"); + return *Dest + || StringEqCI(String, "false") + || StringEqCI(String, "off") + || StringEqCI(String, "no"); } -bool ReadIntegerConfig(int *Dest, const char *Val){ - ASSERT(Dest && Val); - const char *ValEnd; - *Dest = (int)strtol(Val, (char**)&ValEnd, 0); - return ValEnd > Val; +bool ParseInteger(int *Dest, const char *String){ + ASSERT(Dest && String); + const char *StringEnd; + *Dest = (int)strtol(String, (char**)&StringEnd, 0); + return StringEnd > String; } -bool ReadDurationConfig(int *Dest, const char *Val){ - ASSERT(Dest && Val); +bool ParseDuration(int *Dest, const char *String){ + ASSERT(Dest && String); const char *Suffix; - *Dest = (int)strtol(Val, (char**)&Suffix, 0); - if(Suffix == Val){ + *Dest = (int)strtol(String, (char**)&Suffix, 0); + if(Suffix == String){ return false; } @@ -275,11 +320,11 @@ bool ReadDurationConfig(int *Dest, const char *Val){ return true; } -bool ReadSizeConfig(int *Dest, const char *Val){ - ASSERT(Dest && Val); +bool ParseSize(int *Dest, const char *String){ + ASSERT(Dest && String); const char *Suffix; - *Dest = (int)strtol(Val, (char**)&Suffix, 0); - if(Suffix == Val){ + *Dest = (int)strtol(String, (char**)&Suffix, 0); + if(Suffix == String){ return false; } @@ -296,21 +341,21 @@ bool ReadSizeConfig(int *Dest, const char *Val){ return true; } -bool ReadStringConfig(char *Dest, int DestCapacity, const char *Val){ - ASSERT(Dest && DestCapacity > 0 && Val); - int ValStart = 0; - int ValEnd = (int)strlen(Val); - if(ValEnd >= 2){ - if((Val[0] == '"' && Val[ValEnd - 1] == '"') - || (Val[0] == '\'' && Val[ValEnd - 1] == '\'') - || (Val[0] == '`' && Val[ValEnd - 1] == '`')){ - ValStart += 1; - ValEnd -= 1; +bool ParseString(char *Dest, int DestCapacity, const char *String){ + ASSERT(Dest && DestCapacity > 0 && String); + int StringStart = 0; + int StringEnd = (int)strlen(String); + if(StringEnd >= 2){ + if((String[0] == '"' && String[StringEnd - 1] == '"') + || (String[0] == '\'' && String[StringEnd - 1] == '\'') + || (String[0] == '`' && String[StringEnd - 1] == '`')){ + StringStart += 1; + StringEnd -= 1; } } return StringCopyN(Dest, DestCapacity, - &Val[ValStart], (ValEnd - ValStart)); + &String[StringStart], (StringEnd - StringStart)); } bool ReadConfig(const char *FileName, TConfig *Config){ @@ -413,68 +458,68 @@ bool ReadConfig(const char *FileName, TConfig *Config){ } if(StringEqCI(Key, "MaxCachedHostNames")){ - ReadIntegerConfig(&Config->MaxCachedHostNames, Val); + ParseInteger(&Config->MaxCachedHostNames, Val); }else if(StringEqCI(Key, "HostNameExpireTime")){ - ReadDurationConfig(&Config->HostNameExpireTime, Val); + ParseDuration(&Config->HostNameExpireTime, Val); #if DATABASE_SQLITE }else if(StringEqCI(Key, "SQLite.File")){ - ReadStringBufConfig(Config->SQLite.File, Val); + ParseStringBuf(Config->SQLite.File, Val); }else if(StringEqCI(Key, "SQLite.MaxCachedStatements")){ - ReadIntegerConfig(&Config->SQLite.MaxCachedStatements, Val); + ParseInteger(&Config->SQLite.MaxCachedStatements, Val); #elif DATABASE_POSTGRESQL }else if(StringEqCI(Key, "PostgreSQL.Host")){ - ReadStringBufConfig(Config->PostgreSQL.Host, Val); + ParseStringBuf(Config->PostgreSQL.Host, Val); }else if(StringEqCI(Key, "PostgreSQL.Port")){ - ReadStringBufConfig(Config->PostgreSQL.Port, Val); + ParseStringBuf(Config->PostgreSQL.Port, Val); }else if(StringEqCI(Key, "PostgreSQL.DBName")){ - ReadStringBufConfig(Config->PostgreSQL.DBName, Val); + ParseStringBuf(Config->PostgreSQL.DBName, Val); }else if(StringEqCI(Key, "PostgreSQL.User")){ - ReadStringBufConfig(Config->PostgreSQL.User, Val); + ParseStringBuf(Config->PostgreSQL.User, Val); }else if(StringEqCI(Key, "PostgreSQL.Password")){ - ReadStringBufConfig(Config->PostgreSQL.Password, Val); + ParseStringBuf(Config->PostgreSQL.Password, Val); }else if(StringEqCI(Key, "PostgreSQL.ConnectTimeout")){ - ReadStringBufConfig(Config->PostgreSQL.ConnectTimeout, Val); + ParseStringBuf(Config->PostgreSQL.ConnectTimeout, Val); }else if(StringEqCI(Key, "PostgreSQL.ClientEncoding")){ - ReadStringBufConfig(Config->PostgreSQL.ClientEncoding, Val); + ParseStringBuf(Config->PostgreSQL.ClientEncoding, Val); }else if(StringEqCI(Key, "PostgreSQL.ApplicationName")){ - ReadStringBufConfig(Config->PostgreSQL.ApplicationName, Val); + ParseStringBuf(Config->PostgreSQL.ApplicationName, Val); }else if(StringEqCI(Key, "PostgreSQL.SSLMode")){ - ReadStringBufConfig(Config->PostgreSQL.SSLMode, Val); + ParseStringBuf(Config->PostgreSQL.SSLMode, Val); }else if(StringEqCI(Key, "PostgreSQL.SSLRootCert")){ - ReadStringBufConfig(Config->PostgreSQL.SSLRootCert, Val); + ParseStringBuf(Config->PostgreSQL.SSLRootCert, Val); }else if(StringEqCI(Key, "PostgreSQL.MaxCachedStatements")){ - ReadIntegerConfig(&Config->PostgreSQL.MaxCachedStatements, Val); + ParseInteger(&Config->PostgreSQL.MaxCachedStatements, Val); #elif DATABASE_MYSQL }else if(StringEqCI(Key, "MySQL.Host")){ - ReadStringBufConfig(Config->MySQL.Host, Val); + ParseStringBuf(Config->MySQL.Host, Val); }else if(StringEqCI(Key, "MySQL.Port")){ - ReadStringBufConfig(Config->MySQL.Port, Val); + ParseStringBuf(Config->MySQL.Port, Val); }else if(StringEqCI(Key, "MySQL.DBName")){ - ReadStringBufConfig(Config->MySQL.DBName, Val); + ParseStringBuf(Config->MySQL.DBName, Val); }else if(StringEqCI(Key, "MySQL.User")){ - ReadStringBufConfig(Config->MySQL.User, Val); + ParseStringBuf(Config->MySQL.User, Val); }else if(StringEqCI(Key, "MySQL.Password")){ - ReadStringBufConfig(Config->MySQL.Password, Val); + ParseStringBuf(Config->MySQL.Password, Val); }else if(StringEqCI(Key, "MySQL.UnixSocket")){ - ReadStringBufConfig(Config->MySQL.UnixSocket, Val); + ParseStringBuf(Config->MySQL.UnixSocket, Val); }else if(StringEqCI(Key, "MySQL.MaxCachedStatements")){ - ReadIntegerConfig(&Config->MySQL.MaxCachedStatements, Val); + ParseInteger(&Config->MySQL.MaxCachedStatements, Val); #endif }else if(StringEqCI(Key, "QueryManagerPort")){ - ReadIntegerConfig(&Config->QueryManagerPort, Val); + ParseInteger(&Config->QueryManagerPort, Val); }else if(StringEqCI(Key, "QueryManagerPassword")){ - ReadStringBufConfig(Config->QueryManagerPassword, Val); + ParseStringBuf(Config->QueryManagerPassword, Val); }else if(StringEqCI(Key, "QueryWorkerThreads")){ - ReadIntegerConfig(&Config->QueryWorkerThreads, Val); + ParseInteger(&Config->QueryWorkerThreads, Val); }else if(StringEqCI(Key, "QueryBufferSize") || StringEqCI(Key, "MaxConnectionPacketSize")){ - ReadSizeConfig(&Config->QueryBufferSize, Val); + ParseSize(&Config->QueryBufferSize, Val); }else if(StringEqCI(Key, "QueryMaxAttempts")){ - ReadIntegerConfig(&Config->QueryMaxAttempts, Val); + ParseInteger(&Config->QueryMaxAttempts, Val); }else if(StringEqCI(Key, "MaxConnections")){ - ReadIntegerConfig(&Config->MaxConnections, Val); + ParseInteger(&Config->MaxConnections, Val); }else if(StringEqCI(Key, "MaxConnectionIdleTime")){ - ReadDurationConfig(&Config->MaxConnectionIdleTime, Val); + ParseDuration(&Config->MaxConnectionIdleTime, Val); }else{ LOG_WARN("Unknown config \"%s\"", Key); } diff --git a/src/querymanager.hh b/src/querymanager.hh index 8f7d638..2b2b053 100644 --- a/src/querymanager.hh +++ b/src/querymanager.hh @@ -14,6 +14,7 @@ #include <algorithm> typedef uint8_t uint8; +typedef int16_t int16; typedef uint16_t uint16; typedef uint32_t uint32; typedef int64_t int64; @@ -103,7 +104,7 @@ struct TConfig{ #elif DATABASE_POSTGRESQL struct{ // NOTE(fusion): Most of these are stored as strings because that is the - // format the connector expects for connect parameters. + // format libpq expects for connection parameters. char Host[100]; char Port[30]; char DBName[30]; @@ -159,12 +160,14 @@ bool StringCopy(char *Dest, int DestCapacity, const char *Src); void StringCopyEllipsis(char *Dest, int DestCapacity, const char *Src); bool StringFormat(char *Dest, int DestCapacity, const char *Format, ...) ATTR_PRINTF(3, 4); uint32 HashString(const char *String); -bool ParseIPAddress(const char *String, int *OutAddr); -bool ReadBooleanConfig(bool *Dest, const char *Val); -bool ReadIntegerConfig(int *Dest, const char *Val); -bool ReadSizeConfig(int *Dest, const char *Val); -bool ReadStringConfig(char *Dest, int DestCapacity, const char *Val); +int HexDigit(int Ch); +int ParseHexString(uint8 *Dest, int DestCapacity, const char *String); +bool ParseIPAddress(int *Dest, const char *String); +bool ParseBoolean(bool *Dest, const char *String); +bool ParseInteger(int *Dest, const char *String); +bool ParseSize(int *Dest, const char *String); +bool ParseString(char *Dest, int DestCapacity, const char *String); bool ReadConfig(const char *FileName, TConfig *Config); // IMPORTANT(fusion): These macros should only be used when `Dest` is a char array @@ -174,7 +177,8 @@ bool ReadConfig(const char *FileName, TConfig *Config); #define StringBufCopyEllipsis(Dest, Src) StringCopyEllipsis(Dest, sizeof(Dest), Src); #define StringBufCopy(Dest, Src) StringCopy(Dest, sizeof(Dest), Src) #define StringBufCopyN(Dest, Src, SrcLength) StringCopyN(Dest, sizeof(Dest), Src, SrcLength) -#define ReadStringBufConfig(Dest, Val) ReadStringConfig(Dest, sizeof(Dest), Val) +#define ParseStringBuf(Dest, String) ParseString(Dest, sizeof(Dest), String) +#define ParseHexStringBuf(Dest, String) ParseHexString(Dest, sizeof(Dest), String); // AtomicInt //============================================================================== @@ -794,13 +798,9 @@ struct TOnlineCharacter{ char Profession[30]; }; -// NOTE(fusion): Database Management +// NOTE(fusion): The database struct is OPAQUE and dependent on the current +// active database driver. struct TDatabase; -void DatabaseClose(TDatabase *Database); -TDatabase *DatabaseOpen(void); -int DatabaseChanges(TDatabase *Database); -bool DatabaseCheckpoint(TDatabase *Database); -int DatabaseMaxConcurrency(void); // NOTE(fusion): TransactionScope struct TransactionScope{ @@ -815,6 +815,13 @@ public: bool Commit(void); }; +// NOTE(fusion): Database Management +void DatabaseClose(TDatabase *Database); +TDatabase *DatabaseOpen(void); +int DatabaseChanges(TDatabase *Database); +bool DatabaseCheckpoint(TDatabase *Database); +int DatabaseMaxConcurrency(void); + // NOTE(fusion): Primary Tables bool GetWorldID(TDatabase *Database, const char *World, int *WorldID); bool GetWorlds(TDatabase *Database, DynamicArray<TWorld> *Worlds); diff --git a/src/sha256.cc b/src/sha256.cc index 9e2bb23..f86d053 100644 --- a/src/sha256.cc +++ b/src/sha256.cc @@ -166,46 +166,6 @@ bool GenerateAuth(const char *Password, uint8 *Auth, int AuthSize){ // CheckSHA256 //============================================================================== -static int HexDigit(int Ch){ - if(Ch >= '0' && Ch <= '9'){ - return (Ch - '0'); - }else if(Ch >= 'A' && Ch <= 'F'){ - return (Ch - 'A') + 10; - }else if(Ch >= 'a' && Ch <= 'f'){ - return (Ch - 'a') + 10; - }else{ - return -1; - } -} - -static int ParseHexString(uint8 *Buffer, int BufferSize, const char *String){ - int StringLen = (int)strlen(String); - if(StringLen % 2 != 0){ - LOG_ERR("Expected even number of characters"); - return -1; - } - - int NumBytes = (StringLen / 2); - if(NumBytes > BufferSize){ - LOG_ERR("Supplied buffer is too small (Size: %d, Required: %d)", - BufferSize, NumBytes); - return -1; - } - - for(int i = 0; i < NumBytes; i += 1){ - int Digit0 = HexDigit(String[i * 2 + 0]); - int Digit1 = HexDigit(String[i * 2 + 1]); - if(Digit0 == -1 || Digit1 == -1){ - LOG_ERR("Invalid hex digit at offset %d", i * 2); - return -1; - } - - Buffer[i] = ((uint8)Digit0 << 4) | (uint8)Digit1; - } - - return NumBytes; -} - bool CheckSHA256(void){ // NOTE(fusion): We're using only a few NIST test vectors. This is to make // sure there are no blatant implementation errors but we'd ideally run it @@ -246,8 +206,8 @@ bool CheckSHA256(void){ uint8 Expected[32]; uint8 Digest[32]; for(int i = 0; i < NARRAY(Tests); i += 1){ - int InputBytes = ParseHexString(Input, sizeof(Input), Tests[i].Input); - int ExpectedBytes = ParseHexString(Expected, sizeof(Expected), Tests[i].Expected); + int InputBytes = ParseHexStringBuf(Input, Tests[i].Input); + int ExpectedBytes = ParseHexStringBuf(Expected, Tests[i].Expected); if(InputBytes == -1 || ExpectedBytes != sizeof(Expected)){ LOG_ERR("Invalid test vector %d", i); return false; |
