Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace NextGenSoftware.Holochain.HoloNET.Client
{
/// <summary>
/// Defines the supported versions of Holochain for protocol compatibility.
/// </summary>
public enum HolochainVersion
{
/// <summary>
/// Legacy Holochain version (0.0.x series) - Redux protocol
/// </summary>
Redux,

/// <summary>
/// RSM version (0.0.x series) - RSM protocol
/// </summary>
RSM,

/// <summary>
/// Latest stable Holochain version (0.5.2) - JSON-RPC 2.0 protocol
/// </summary>
Holochain_0_5_2
}
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using MessagePack;
using NextGenSoftware.Logging;
using NextGenSoftware.Holochain.HoloNET.Client.Interfaces;
using System.Text.Json;

namespace NextGenSoftware.Holochain.HoloNET.Client
{
Expand All @@ -18,7 +19,16 @@ public abstract partial class HoloNETClientBase : IHoloNETClientBase
/// <returns></returns>
public virtual async Task SendHoloNETRequestAsync(HoloNETData holoNETData, HoloNETRequestType requestType, string id = "")
{
await SendHoloNETRequestAsync(MessagePackSerializer.Serialize(holoNETData), requestType, id);
// Check if we should use JSON-RPC 2.0 protocol (Holochain 0.5.2)
if (HoloNETDNA?.HolochainVersion == HolochainVersion.Holochain_0_5_2)
{
await SendHoloNETRequestAsyncHolochain052(holoNETData, requestType, id);
}
else
{
// Use legacy MessagePack protocol (Redux/RSM)
await SendHoloNETRequestAsync(MessagePackSerializer.Serialize(holoNETData), requestType, id);
}
}

/// <summary>
Expand Down Expand Up @@ -84,5 +94,103 @@ protected virtual string GetRequestId()
_currentId++;
return _currentId.ToString();
}
}
}

/// <summary>
/// Sends a request using JSON-RPC 2.0 protocol (Holochain 0.5.2)
/// </summary>
protected virtual async Task SendHoloNETRequestAsyncHolochain052(HoloNETData holoNETData, HoloNETRequestType requestType, string id = "")
{
try
{
if (string.IsNullOrEmpty(id))
id = GetRequestId();

// Create JSON-RPC 2.0 request
var jsonRpcRequest = new
{
jsonrpc = "2.0",
id = id,
method = GetJsonRpcMethod(requestType),
@params = new
{
data = holoNETData
}
};

// Serialize to JSON
string jsonString = JsonSerializer.Serialize(jsonRpcRequest, new JsonSerializerOptions
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

Logger.Log($"JSON-RPC 2.0 Request: {jsonString}", LogType.Debug);

// Convert to bytes
byte[] jsonData = System.Text.Encoding.UTF8.GetBytes(jsonString);

if (HoloNETDNA.EnforceRequestToResponseIdMatchingBehaviour != EnforceRequestToResponseIdMatchingBehaviour.Ignore)
_pendingRequests.Add(id);

_requestTypeLookup[id] = requestType;

if (WebSocket.State == WebSocketState.Open)
{
Logger.Log("Sending JSON-RPC 2.0 Request to Holochain Conductor...", LogType.Info, true);
await WebSocket.SendRawDataAsync(jsonData);
Logger.Log("JSON-RPC 2.0 Request Successfully Sent To Holochain Conductor.", LogType.Info, false);
}
}
catch (Exception ex)
{
HandleError("Error occurred in HoloNETClient.SendHoloNETRequestAsyncHolochain052 method.", ex);
}
}

/// <summary>
/// Maps HoloNET request types to JSON-RPC 2.0 method names
/// </summary>
protected virtual string GetJsonRpcMethod(HoloNETRequestType requestType)
{
switch (requestType)
{
case HoloNETRequestType.ZomeCall:
return "call_zome";
case HoloNETRequestType.AppInfo:
return "app_info";
case HoloNETRequestType.AdminGenerateAgentPubKey:
return "admin_generate_agent_pub_key";
case HoloNETRequestType.AdminInstallApp:
return "admin_install_app";
case HoloNETRequestType.AdminUninstallApp:
return "admin_uninstall_app";
case HoloNETRequestType.AdminEnableApp:
return "admin_enable_app";
case HoloNETRequestType.AdminDisableApp:
return "admin_disable_app";
case HoloNETRequestType.AdminGrantZomeCallCapability:
return "admin_grant_zome_call_capability";
case HoloNETRequestType.AdminAttachAppInterface:
return "admin_attach_app_interface";
case HoloNETRequestType.AdminListApps:
return "admin_list_apps";
case HoloNETRequestType.AdminListDnas:
return "admin_list_dnas";
case HoloNETRequestType.AdminListCellIds:
return "admin_list_cell_ids";
case HoloNETRequestType.AdminListAppInterfaces:
return "admin_list_app_interfaces";
case HoloNETRequestType.AdminRegisterDna:
return "admin_register_dna";
case HoloNETRequestType.AdminGetDnaDefinition:
return "admin_get_dna_definition";
case HoloNETRequestType.AdminAgentInfo:
return "admin_agent_info";
case HoloNETRequestType.AdminAddAgentInfo:
return "admin_add_agent_info";
default:
return "unknown";
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using NextGenSoftware.Utilities;
using NextGenSoftware.Utilities.ExtentionMethods;
using NextGenSoftware.Holochain.HoloNET.Client.Interfaces;
using System.Text.Json;

namespace NextGenSoftware.Holochain.HoloNET.Client
{
Expand Down Expand Up @@ -84,6 +85,123 @@ protected virtual IHoloNETResponse DecodeDataReceived(byte[] rawBinaryData, WebS
IHoloNETResponse response = null;
HoloNETDataReceivedEventArgs holoNETDataReceivedEventArgs = new HoloNETDataReceivedEventArgs();

try
{
// Check if we should use JSON-RPC 2.0 protocol (Holochain 0.5.2)
if (HoloNETDNA?.HolochainVersion == HolochainVersion.Holochain_0_5_2)
{
return DecodeDataReceivedHolochain052(rawBinaryData, dataReceivedEventArgs);
}
else
{
// Use legacy MessagePack protocol (Redux/RSM)
return DecodeDataReceivedLegacy(rawBinaryData, dataReceivedEventArgs);
}
}
catch (Exception ex)
{
string msg = "Error in HoloNETClient.DecodeDataReceived method.";
HandleError(msg, ex);
}

return response;
}

/// <summary>
/// Decodes data received using JSON-RPC 2.0 protocol (Holochain 0.5.2)
/// </summary>
protected virtual IHoloNETResponse DecodeDataReceivedHolochain052(byte[] rawBinaryData, WebSocket.DataReceivedEventArgs dataReceivedEventArgs)
{
try
{
// Convert binary data to string for JSON parsing
string jsonString = DataHelper.DecodeBinaryDataAsUTF8(rawBinaryData);
Logger.Log($"JSON-RPC 2.0 Response: {jsonString}", LogType.Debug);

// Parse JSON-RPC 2.0 response
var jsonResponse = JsonSerializer.Deserialize<JsonElement>(jsonString);

// Create HoloNET response object
var response = new HoloNETResponse();

// Extract JSON-RPC 2.0 fields
if (jsonResponse.TryGetProperty("id", out var idElement))
{
// Handle different id types (string, number, etc.)
if (idElement.ValueKind == JsonValueKind.String)
{
string idString = idElement.GetString();
if (ulong.TryParse(idString, out ulong idValue))
{
response.id = idValue;
}
else
{
response.id = 0; // Default value if parsing fails
}
}
else if (idElement.ValueKind == JsonValueKind.Number)
{
response.id = idElement.GetUInt64();
}
else
{
response.id = 0; // Default value for unknown types
}
}

if (jsonResponse.TryGetProperty("result", out var resultElement))
{
response.type = "success";
response.data = rawBinaryData; // Store original data for compatibility

// Parse the result to determine response type
if (resultElement.TryGetProperty("type", out var typeElement))
{
string resultType = typeElement.GetString();
switch (resultType)
{
case "zome_response":
response.HoloNETResponseType = HoloNETResponseType.ZomeResponse;
break;
case "signal":
response.HoloNETResponseType = HoloNETResponseType.Signal;
break;
case "app_info":
response.HoloNETResponseType = HoloNETResponseType.AppInfo;
break;
default:
response.HoloNETResponseType = HoloNETResponseType.Error;
break;
}
}
}
else if (jsonResponse.TryGetProperty("error", out var errorElement))
{
response.type = "error";
response.HoloNETResponseType = HoloNETResponseType.Error;
response.data = rawBinaryData;
}

Logger.Log($"JSON-RPC 2.0 Decoded - ID: {response.id}, Type: {response.type}, ResponseType: {response.HoloNETResponseType}", LogType.Info);
return response;
}
catch (Exception ex)
{
Logger.Log($"Error parsing JSON-RPC 2.0 response: {ex.Message}", LogType.Error);
// Fallback to legacy parsing if JSON parsing fails
return DecodeDataReceivedLegacy(rawBinaryData, dataReceivedEventArgs);
}
}

/// <summary>
/// Decodes data received using legacy MessagePack protocol (Redux/RSM)
/// </summary>
protected virtual IHoloNETResponse DecodeDataReceivedLegacy(byte[] rawBinaryData, WebSocket.DataReceivedEventArgs dataReceivedEventArgs)
{
IHoloNETResponse response = null;
HoloNETDataReceivedEventArgs holoNETDataReceivedEventArgs = new HoloNETDataReceivedEventArgs();

try
{
string id = "";
Expand All @@ -93,7 +211,6 @@ protected virtual IHoloNETResponse DecodeDataReceived(byte[] rawBinaryData, WebS
byte[] data = rawBinaryData.ToArray();
rawBinaryData.CopyTo(data, 0);


response = MessagePackSerializer.Deserialize<HoloNETResponse>(data, messagePackSerializerOptions);
AppResponse appResponse = MessagePackSerializer.Deserialize<AppResponse>(response.data, messagePackSerializerOptions);

Expand All @@ -102,8 +219,6 @@ protected virtual IHoloNETResponse DecodeDataReceived(byte[] rawBinaryData, WebS
rawBinaryDataAfterMessagePackDecodeDecoded = DataHelper.DecodeBinaryDataAsUTF8(response.data);

Logger.Log($"Id: {response.id} Type: {response.type}: {response.type} Internal Type: {appResponse.type}", LogType.Info);
//Logger.Log(string.Concat("Raw Data Bytes Received After MessagePack Decode: ", rawBinaryDataAfterMessagePackDecodeAsString), LogType.Debug);
//Logger.Log(string.Concat("Raw Data Bytes Decoded After MessagePack Decode: ", rawBinaryDataAfterMessagePackDecodeDecoded), LogType.Debug);

switch (appResponse.type)
{
Expand Down
Loading