aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/database_postgres.cc411
-rw-r--r--src/database_sqlite.cc4
-rw-r--r--src/query.cc14
-rw-r--r--src/querymanager.cc165
-rw-r--r--src/querymanager.hh33
-rw-r--r--src/sha256.cc44
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;