Jacob Schmidt 236dec1d35
All checks were successful
Build / Build (push) Successful in 30s
feat(client): Trim quotes from hashset value
This commit enhances the handling of string values in the `DragonflyHashSetAsync` function. Specifically, it adds `.Trim('"')` to the second argument (the value being set) to remove any surrounding quotes. This ensures that the value is correctly stored and retrieved, preventing potential issues with data integrity.

Key changes:

*   **String Value Trimming:** Added `.Trim('"')` to the value argument in `DragonflyHashSetAsync` within `Main.cs`.
*   **Binary Updates:** Updated the compiled DLL and SO files.
2025-03-29 15:35:20 -05:00

653 lines
27 KiB
C#

using System.Runtime.InteropServices;
using System.Text;
using System.Xml.Linq;
#pragma warning disable IDE0130 // Namespace does not match folder structure
namespace ArmaDragonflyClient
#pragma warning restore IDE0130 // Namespace does not match folder structure
{
public class CallContext
{
public ulong SteamId { get; set; }
public string FileSource { get; set; }
public string MissionName { get; set; }
public string ServerName { get; set; }
public short RemoteExecutedOwner { get; set; }
}
public class Main
{
private const string ADC_VERSION = "1.0.0";
public const int ADC_BUFFERSIZE = 10240;
public static string ADC_HOST {get; private set; } = "127.0.0.1";
public static int ADC_PORT {get; private set; } = 6379;
public static string ADC_PASSWORD {get; private set; } = "xyz123";
public static string ADC_LOGFOLDER {get; private set; } = $"{Path.DirectorySeparatorChar}@dragonfly{Path.DirectorySeparatorChar}logs";
public static bool ADC_DEBUG {get; private set; } = false;
public static bool ADC_INITCHECK {get; private set; } = false;
public static string STEAMID {get; private set; } = "";
public static bool ADC_CONTEXTLOG {get; private set; } = false;
public static IntPtr ADC_CONTEXT { get; private set; }
private static int numCalls = 0;
public static ExtensionCallback Callback { get; private set; }
public delegate int ExtensionCallback([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string function, [MarshalAs(UnmanagedType.LPStr)] string data);
public static void ADC_Init()
{
char[] separator = ['='];
string[] commandLineArgs = Environment.GetCommandLineArgs();
string str = "";
for (int index = 0; index < commandLineArgs.Length; index++)
{
string[] strArray = commandLineArgs[index].Split(separator, StringSplitOptions.RemoveEmptyEntries);
if (strArray[0].Equals("-adc", StringComparison.CurrentCultureIgnoreCase))
{
str = strArray[1];
break;
}
}
if (str == "")
str = $"{Path.DirectorySeparatorChar}@dragonfly{Path.DirectorySeparatorChar}config.xml";
if (File.Exists(Environment.CurrentDirectory + $"{Path.DirectorySeparatorChar}{str}"))
{
List<string> strList = [];
List<string> list = [.. XElement.Load(Environment.CurrentDirectory + $"{Path.DirectorySeparatorChar}{str}").Elements().Select(eintrag => (string)eintrag)];
ADC_HOST = list[0];
ADC_PORT = Convert.ToInt32(list[1]);
ADC_PASSWORD = list[2];
if (bool.TryParse(list[3], out bool res1))
ADC_CONTEXTLOG = res1;
if (bool.TryParse(list[4], out bool res2))
ADC_DEBUG = res2;
Log($"Config file found! Context Mode: {ADC_CONTEXTLOG}! Debug Mode: {ADC_DEBUG}! Changed Server Settings to: {ADC_HOST}:{ADC_PORT}:{ADC_PASSWORD}!", "action");
}
else
{
Log("Config file not found! Default Settings Loaded.", "action");
}
ADC_INITCHECK = true;
}
public static void Log(string msg, string logType)
{
if (!ADC_DEBUG)
return;
string logFileName = logType + ".log";
string path = Environment.CurrentDirectory + ADC_LOGFOLDER;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
using (StreamWriter streamWriter = new(Path.Combine(path, logFileName), true))
{
streamWriter.WriteLine(DateTime.Now.ToString() + " >> " + msg);
};
}
/// <summary>
/// Called only once when Arma 3 loads the extension.
/// </summary>
/// <param name="func">Pointer to Arma 3's callback function</param>
[UnmanagedCallersOnly(EntryPoint = "RVExtensionRegisterCallback")]
public unsafe static void RVExtensionRegisterCallback(nint func)
{
Callback = (Marshal.GetDelegateForFunctionPointer<ExtensionCallback>(func));
}
/// <summary>
/// Called only once when Arma 3 loads the extension.
/// The output will be written in the RPT logs.
/// </summary>
/// <param name="output">A pointer to the output buffer</param>
/// <param name="outputSize">The maximum length of the buffer (always 32 for this particular method)</param>
[UnmanagedCallersOnly(EntryPoint = "RVExtensionVersion")]
#pragma warning disable IDE0060 // Remove unused parameter
public unsafe static void RVExtensionVersion(char* output, int outputSize)
#pragma warning restore IDE0060 // Remove unused parameter
{
WriteOutput(output, ADC_VERSION);
}
/// <summary>
/// Called before every RVExtension/RVExtensionArgs execution, providing context about where the call came from.
/// </summary>
/// <param name="argv">
/// Array of pointers to null-terminated strings containing:
/// [0] - Steam 64bit UserID
/// [1] - FileSource
/// [2] - MissionName
/// [3] - ServerName
/// [4] - Machine Network ID
/// </param>
/// <param name="argc">Number of arguments in the argv array (always 5).</param>
[UnmanagedCallersOnly(EntryPoint = "RVExtensionContext")]
public unsafe static int RVExtensionContext(char** argv, int argc)
{
ADC_CONTEXT = (IntPtr)argv;
STEAMID = Marshal.PtrToStringAnsi((IntPtr)argv[0]);
string steamID = Marshal.PtrToStringAnsi((IntPtr)argv[0]);
string fileSource = Marshal.PtrToStringAnsi((IntPtr)argv[1]);
string missionName = Marshal.PtrToStringAnsi((IntPtr)argv[2]);
string serverName = Marshal.PtrToStringAnsi((IntPtr)argv[3]);
short remoteExecutedOwner = short.Parse(Marshal.PtrToStringAnsi((IntPtr)argv[4]));
if (ADC_CONTEXTLOG)
{
Log($"SteamID: {steamID}", "context");
Log($"FileSource: {fileSource}", "context");
Log($"MissionName: {missionName}", "context");
Log($"ServerName: {serverName}", "context");
Log($"RemoteExecutedOwner: {remoteExecutedOwner}", "context");
}
return 100;
}
/// <summary>
/// The entry point for the default "callExtension" command.
/// </summary>
/// <param name="output">A pointer to the output buffer</param>
/// <param name="outputSize">The maximum size of the buffer (20480 bytes)</param>
/// <param name="function">The function identifier passed in "callExtension"</param>
[UnmanagedCallersOnly(EntryPoint = "RVExtension")]
#pragma warning disable IDE0060 // Remove unused parameter
public unsafe static void RVExtension(char* output, int outputSize, char* function)
#pragma warning restore IDE0060 // Remove unused parameter
{
if (!ADC_INITCHECK)
ADC_Init();
numCalls++;
var func = GetString(function);
switch (func.ToLower())
{
case "time":
DateTime timeNow = DateTime.Now;
string formattedTime = timeNow.ToString("dd:MM:yyyy HH:mm:ss");
WriteOutput(output, formattedTime);
break;
default:
WriteOutput(output, func);
break;
}
}
/// <summary>
/// The entry point for the "callExtension" command with function arguments.
/// </summary>
/// <param name="output">A pointer to the output buffer</param>
/// <param name="outputSize">The maximum size of the buffer (20480 bytes)</param>
/// <param name="function">The function identifier passed in "callExtension"</param>
/// <param name="argv">The args passed to "callExtension" as a string array</param>
/// <param name="argc">Number of elements in "argv"</param>
/// <returns>The return code</returns>
[UnmanagedCallersOnly(EntryPoint = "RVExtensionArgs")]
public unsafe static int RVExtensionArgs(char* output, int outputSize, char* function, char** argv, int argc)
{
if (!ADC_INITCHECK)
ADC_Init();
numCalls++;
var func = GetString(function);
long _id = Utils.GetUniqueId();
List<string> argsArr = [];
for (int i = 0; i < argc; i++)
{
argsArr.Add(GetString(argv[i]));
}
switch (func.ToLower())
{
case "get":
HandleGetOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_get\",{argsArr[1]}]");
return 100;
case "set":
HandleSetOperation(argsArr);
WriteOutput(output, "Async");
return 200;
case "del":
HandleDelOperation(argsArr);
WriteOutput(output, "Async");
return 200;
case "listadd":
HandleListAddOperation(argsArr, argc);
WriteOutput(output, "Async");
return 200;
case "listidx":
HandleListIdxOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_listidx\",{argsArr[2]}]");
return 100;
case "listlen":
HandleListLenOperation(_id, argsArr);
WriteOutput(output, $"[\"{_id}_listlen\"]");
return 100;
case "listrem":
HandleListRemOperation(argsArr);
WriteOutput(output, "Async");
return 200;
case "listrng":
HandleListRngOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_listrng\",{argsArr[3]}]");
return 100;
case "listset":
HandleListSetOperation(argsArr);
WriteOutput(output, "Async");
return 200;
case "hset":
HandleHSetOperation(argsArr, argc);
WriteOutput(output, "Async");
return 200;
case "hsetid":
HandleHSetIdOperation(argsArr, argc);
WriteOutput(output, "Async");
return 200;
case "hget":
HandleHGetOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_hget\",{argsArr[1]}]");
return 100;
case "hgetid":
HandleHGetIdOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_hgetid\",{argsArr[2]}]");
return 100;
case "hgetall":
HandleHGetAllOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_hgetall\",{argsArr[0]}]");
return 100;
case "hgetallid":
HandleHGetAllIdOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_hgetallid\",{argsArr[1]}]");
return 100;
case "hdel":
HandleHDelOperation(argsArr);
WriteOutput(output, "Async");
return 200;
case "hdelid":
HandleHDelIdOperation(argsArr);
WriteOutput(output, "Async");
return 200;
case "publish":
HandlePublishOperation(_id, argsArr);
WriteOutput(output, $"[\"{_id}_publish\"]");
return 100;
case "subscribe":
HandleSubscribeOperation(_id, argsArr, argc);
WriteOutput(output, $"[\"{_id}_subscribe\"]");
return 100;
case "savedb":
HandleSaveDBOperation();
WriteOutput(output, "Async");
return 200;
case "setup":
HandleSetupOperation(argsArr, argc);
WriteOutput(output, "Connection settings updated");
return 100;
case "version":
WriteOutput(output, ADC_VERSION);
return 100;
default:
WriteOutput(output, "Available Functions: get, set, del, listadd, listidx, listlen, listrem, listrng, listset, hset, hsetid, hget, hgetid, hgetall, hgetallid, hdel, hdelid, publish, subscribe, savedb, setup, version");
return -1;
}
}
/// <summary>
/// Reads a string from the given pointer.
/// If the pointer is null, a default value will be returned instead.
/// </summary>
/// <param name="pointer">The pointer to read</param>
/// <param name="defaultValue">The string's default value</param>
/// <returns>The marshalled string</returns>
private unsafe static string GetString(char* pointer, string defaultValue = "")
{
return Marshal.PtrToStringAnsi((nint)pointer) ?? defaultValue;
}
/// <summary>
/// Serializes a list of strings into a string representing a valid Arma 3 array.
/// </summary>
/// <param name="list">The list of strings to serialize</param>
/// <returns>A string representing an Arma 3 array</returns>
private static string SerializeList(List<string> list)
{
var content = string.Join(",", [.. list]);
return string.Format("[{0}]", content);
}
/// <summary>
/// Writes the given payload to the output buffer using the provided pointer.
/// Ensures that the payload string is always null terminated.
/// </summary>
/// <param name="output">A pointer to the output buffer</param>
/// <param name="payload">The payload to write</param>
private unsafe static void WriteOutput(char* output, string payload)
{
byte[] bytes = Encoding.ASCII.GetBytes(payload + '\0');
Marshal.Copy(bytes, 0, (nint)output, bytes.Length);
}
private static void HandleGetOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_get";
switch (argsCnt)
{
case 2:
await DragonflyDB.DragonflyGetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID);
break;
case 3:
await DragonflyDB.DragonflyGetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID, argsArr[2].Trim('"'));
break;
case 4:
await DragonflyDB.DragonflyGetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID, argsArr[2].Trim('"'), argsArr[3].Trim('"'));
break;
}
});
}
private static void HandleSetOperation(List<string> argsArr)
{
Task.Run(async () =>
{
await DragonflyDB.DragonflySetAsync(argsArr[0].Trim('"'), argsArr[1]);
});
}
private static void HandleDelOperation(List<string> argsArr)
{
Task.Run(async () =>
{
await DragonflyDB.DragonflyDeleteAsync(argsArr[0].Trim('"'));
});
}
private static void HandleListAddOperation(List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
switch (argsCnt)
{
case > 2:
for (var i = 0; i < argsCnt; i++)
{
await DragonflyDB.DragonflyListAddAsync(argsArr[0].Trim('"'), argsArr[i]);
}
break;
default:
await DragonflyDB.DragonflyListAddAsync(argsArr[0].Trim('"'), argsArr[1]);
break;
}
});
}
private static void HandleListIdxOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_listidx";
switch (argsCnt)
{
case 3:
await DragonflyDB.DragonflyListIndexAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID);
break;
case 4:
await DragonflyDB.DragonflyListIndexAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID, argsArr[3].Trim('"'));
break;
case 5:
await DragonflyDB.DragonflyListIndexAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID, argsArr[3].Trim('"'), argsArr[4].Trim('"'));
break;
}
});
}
private static void HandleListLenOperation(long _id, List<string> argsArr)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_listlen";
await DragonflyDB.DragonflyListLengthAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID);
});
}
private static void HandleListRngOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_listrng";
switch (argsCnt)
{
case 4:
await DragonflyDB.DragonflyListRangeAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), argsArr[3].Trim('"'), _uniqueID);
break;
case 5:
await DragonflyDB.DragonflyListRangeAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), argsArr[3].Trim('"'), _uniqueID, argsArr[4].Trim('"'));
break;
case 6:
await DragonflyDB.DragonflyListRangeAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), argsArr[3].Trim('"'), _uniqueID, argsArr[4].Trim('"'), argsArr[5].Trim('"'));
break;
}
});
}
private static void HandleListRemOperation(List<string> argsArr)
{
Task.Run(async () =>
{
await DragonflyDB.DragonflyListRemoveAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'));
});
}
private static void HandleListSetOperation(List<string> argsArr)
{
Task.Run(async () =>
{
await DragonflyDB.DragonflyListSetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2]);
});
}
private static void HandleHSetOperation(List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
switch (argsCnt)
{
case > 2:
for (var i = 0; i < argsCnt; i += 2)
{
await DragonflyDB.DragonflyHashSetAsync(STEAMID, argsArr[i].Trim('"'), argsArr[i + 1]);
}
break;
default:
await DragonflyDB.DragonflyHashSetAsync(STEAMID, argsArr[0].Trim('"'), argsArr[1]);
break;
}
});
}
private static void HandleHSetIdOperation(List<string> argsArr, int argsCnt)
{
Task.Run(async () => {
switch (argsCnt)
{
case > 4:
for (var i = 1; i < argsCnt; i += 2)
{
await DragonflyDB.DragonflyHashSetAsync(argsArr[0].Trim('"'), argsArr[i].Trim('"'), argsArr[i + 1]);
}
break;
default:
await DragonflyDB.DragonflyHashSetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2]);
break;
}
});
}
private static void HandleHGetOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_hget";
switch (argsCnt)
{
case 2:
await DragonflyDB.DragonflyHashGetAsync(STEAMID, argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID);
break;
case 3:
await DragonflyDB.DragonflyHashGetAsync(STEAMID, argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID, argsArr[2].Trim('"'));
break;
case 4:
await DragonflyDB.DragonflyHashGetAsync(STEAMID, argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID, argsArr[2].Trim('"'), argsArr[3].Trim('"'));
break;
}
});
}
private static void HandleHGetIdOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_hgetid";
switch (argsCnt)
{
case 3:
await DragonflyDB.DragonflyHashGetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID);
break;
case 4:
await DragonflyDB.DragonflyHashGetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID, argsArr[3].Trim('"'));
break;
case 5:
await DragonflyDB.DragonflyHashGetAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID, argsArr[3].Trim('"'), argsArr[4].Trim('"'));
break;
}
});
}
private static void HandleHGetAllOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_hgetall";
switch (argsCnt)
{
case 1:
await DragonflyDB.DragonflyHashGetAllAsync(STEAMID, argsArr[0].Trim('"'), _uniqueID);
break;
case 2:
await DragonflyDB.DragonflyHashGetAllAsync(STEAMID, argsArr[0].Trim('"'), _uniqueID, argsArr[1].Trim('"'));
break;
case 3:
await DragonflyDB.DragonflyHashGetAllAsync(STEAMID, argsArr[0].Trim('"'), _uniqueID, argsArr[1].Trim('"'), argsArr[2].Trim('"'));
break;
}
});
}
private static void HandleHGetAllIdOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_hgetallid";
switch (argsCnt)
{
case 2:
await DragonflyDB.DragonflyHashGetAllAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID);
break;
case 3:
await DragonflyDB.DragonflyHashGetAllAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID, argsArr[2].Trim('"'));
break;
case 4:
await DragonflyDB.DragonflyHashGetAllAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID, argsArr[2].Trim('"'), argsArr[3].Trim('"'));
break;
}
});
}
private static void HandleHDelOperation(List<string> argsArr)
{
Task.Run(async () =>
{
await DragonflyDB.DragonflyHashDeleteAsync(STEAMID, argsArr[0].Trim('"'));
});
}
private static void HandleHDelIdOperation(List<string> argsArr)
{
Task.Run(async () =>
{
await DragonflyDB.DragonflyHashDeleteAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'));
});
}
private static void HandleSaveDBOperation()
{
Task.Run(DragonflyDB.DragonflySaveDBAsync);
}
private static void HandleSetupOperation(List<string> argsArr, int argsCnt)
{
if (argsCnt != 3)
{
Log("Invalid number of arguments for 'setup' function", "error");
return;
}
string host = argsArr[0].Trim('"');
if (!int.TryParse(argsArr[1].Trim('"'), out int port))
{
Log("Invalid port number", "error");
return;
}
string password = argsArr[2].Trim('"');
if (string.IsNullOrEmpty(password))
{
Log("Warning: Setting up DragonflyDB without authentication is not recommended", "action");
password = "";
}
ADC_HOST = host;
ADC_PORT = port;
ADC_PASSWORD = password;
Log($"DragonflyDB connection settings updated - Host: '{host}', Port: '{port}', Password: '{password}'", "action");
}
private static void HandlePublishOperation(long _id, List<string> argsArr)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_publish";
await DragonflyDB.DragonflyPublishAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), _uniqueID);
});
}
private static void HandleSubscribeOperation(long _id, List<string> argsArr, int argsCnt)
{
Task.Run(async () =>
{
string _uniqueID = $"{_id}_subscribe";
switch (argsCnt)
{
case 3:
await DragonflyDB.DragonflySubscribeAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID);
break;
case 4:
await DragonflyDB.DragonflySubscribeAsync(argsArr[0].Trim('"'), argsArr[1].Trim('"'), argsArr[2].Trim('"'), _uniqueID, argsArr[3].Trim('"'));
break;
}
});
}
}
}