Skip to content

Commit

Permalink
Pubsub JSON updates preview (#2768)
Browse files Browse the repository at this point in the history
Add support for Compact and Verbose JSON encodings.

Other bug fixes: 
- Defaults for non reversible forces the namespace Uri in the NodeId encoding and all default values are encoded. This behavior is according to spec. Old behavior can be changed reestablished with properties on creation of the JSON encoders.
- Compact and Verbose are preview for the next spec release. Main difference is to always encode NodeIds and ExpandedNodeIds as string with NamespaceUri. Same for QualifiedName. Both are considered reversible encodings.
- JSON decoder accepts any of the new and existing JSOn encodings no matter if the NodeId are encoded as string or JSON object.

Co-authored-by: Randy Armstrong <randy.armstrong@opcfoundation.org>
  • Loading branch information
mregen and Randy Armstrong authored Sep 26, 2024
1 parent d7680fa commit 8131a02
Show file tree
Hide file tree
Showing 23 changed files with 3,583 additions and 678 deletions.
10 changes: 5 additions & 5 deletions Applications/ConsoleReferenceClient/ClientSamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ Task FetchReferenceIdTypesAsync(ISession session)

if (ServiceResult.IsNotBad(value.StatusCode))
{
var valueString = ClientSamples.FormatValueAsJson(uaClient.Session.MessageContext, variableId.ToString(), value, true);
var valueString = ClientSamples.FormatValueAsJson(uaClient.Session.MessageContext, variableId.ToString(), value, JsonEncodingType.Compact);
m_output.WriteLine(valueString);
}
else
Expand All @@ -992,7 +992,7 @@ Task FetchReferenceIdTypesAsync(ISession session)
{
if (ServiceResult.IsNotBad(errors[ii]))
{
var valueString = ClientSamples.FormatValueAsJson(uaClient.Session.MessageContext, variableIds[ii].ToString(), value, true);
var valueString = ClientSamples.FormatValueAsJson(uaClient.Session.MessageContext, variableIds[ii].ToString(), value, JsonEncodingType.Compact);
m_output.WriteLine(valueString);
}
else
Expand Down Expand Up @@ -1102,15 +1102,15 @@ public async Task SubscribeAllValuesAsync(
/// </summary>
/// <param name="name">The key of the Json value.</param>
/// <param name="value">The DataValue.</param>
/// <param name="jsonReversible">Use reversible encoding.</param>
/// <param name="jsonEncodingType">Use reversible encoding.</param>
public static string FormatValueAsJson(
IServiceMessageContext messageContext,
string name,
DataValue value,
bool jsonReversible)
JsonEncodingType jsonEncodingType)
{
string textbuffer;
using (var jsonEncoder = new JsonEncoder(messageContext, jsonReversible))
using (var jsonEncoder = new JsonEncoder(messageContext, jsonEncodingType))
{
jsonEncoder.WriteDataValue(name, value);
textbuffer = jsonEncoder.CloseAndReturnText();
Expand Down
2 changes: 2 additions & 0 deletions Libraries/Opc.Ua.PubSub/Encoding/JsonDataSetMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ private void EncodeField(IJsonEncoder encoder, Field field)
valueToEncode = field.Value.StatusCode;
}

#pragma warning disable CS0618 // Type or member is obsolete
switch (m_fieldTypeEncoding)
{
case FieldTypeEncodingMask.Variant:
Expand Down Expand Up @@ -519,6 +520,7 @@ private void EncodeField(IJsonEncoder encoder, Field field)
encoder.UsingReversibleEncoding(encoder.WriteDataValue, fieldName, dataValue, false);
break;
}
#pragma warning restore CS0618 // Type or member is obsolete
}
#endregion

Expand Down
2 changes: 1 addition & 1 deletion Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<ProjectReference Include="..\..\Stack\Opc.Ua.Core\Opc.Ua.Core.csproj" />
<ProjectReference Include="..\Opc.Ua.Configuration\Opc.Ua.Configuration.csproj" />
</ItemGroup>

<Target Name="GetPackagingOutputs" />

</Project>
192 changes: 159 additions & 33 deletions Stack/Opc.Ua.Core/Types/BuiltIn/ExpandedNodeId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public ExpandedNodeId(NodeId nodeId, string namespaceUri)
m_nodeId = new NodeId(nodeId);
}

if (!String.IsNullOrEmpty(namespaceUri))
if (!string.IsNullOrEmpty(namespaceUri))
{
SetNamespaceUri(namespaceUri);
}
Expand All @@ -133,7 +133,7 @@ public ExpandedNodeId(NodeId nodeId, string namespaceUri, uint serverIndex)
m_nodeId = new NodeId(nodeId);
}

if (!String.IsNullOrEmpty(namespaceUri))
if (!string.IsNullOrEmpty(namespaceUri))
{
SetNamespaceUri(namespaceUri);
}
Expand Down Expand Up @@ -417,7 +417,7 @@ public bool IsNull
{
get
{
if (!String.IsNullOrEmpty(m_namespaceUri))
if (!string.IsNullOrEmpty(m_namespaceUri))
{
return false;
}
Expand All @@ -441,7 +441,7 @@ public bool IsAbsolute
{
get
{
if (!String.IsNullOrEmpty(m_namespaceUri) || m_serverIndex > 0)
if (!string.IsNullOrEmpty(m_namespaceUri) || m_serverIndex > 0)
{
return true;
}
Expand Down Expand Up @@ -565,31 +565,10 @@ public static void Format(
buffer.AppendFormat(formatProvider, "svr={0};", serverIndex);
}

if (!String.IsNullOrEmpty(namespaceUri))
if (!string.IsNullOrEmpty(namespaceUri))
{
buffer.Append("nsu=");

for (int ii = 0; ii < namespaceUri.Length; ii++)
{
char ch = namespaceUri[ii];

switch (ch)
{
case ';':
case '%':
{
buffer.AppendFormat(formatProvider, "%{0:X2}", Convert.ToInt16(ch));
break;
}

default:
{
buffer.Append(ch);
break;
}
}
}

buffer.Append(Utils.EscapeUri(namespaceUri));
buffer.Append(';');
}

Expand Down Expand Up @@ -617,7 +596,7 @@ public static ExpandedNodeId Parse(string text, NamespaceTable currentNamespaces
// translate the namespace uri.
ushort namespaceIndex = 0;

if (!String.IsNullOrEmpty(uri))
if (!string.IsNullOrEmpty(uri))
{
int index = targetNamespaces.GetIndex(uri);

Expand Down Expand Up @@ -662,7 +641,7 @@ public static ExpandedNodeId Parse(string text)
try
{
// check for null.
if (String.IsNullOrEmpty(text))
if (string.IsNullOrEmpty(text))
{
return ExpandedNodeId.Null;
}
Expand Down Expand Up @@ -698,7 +677,7 @@ internal static void UnescapeUri(string text, int start, int index, StringBuilde

ushort value = 0;

int digit = kHexDigits.IndexOf(Char.ToUpperInvariant(text[++ii]));
int digit = kHexDigits.IndexOf(char.ToUpperInvariant(text[++ii]));

if (digit == -1)
{
Expand All @@ -708,7 +687,7 @@ internal static void UnescapeUri(string text, int start, int index, StringBuilde
value += (ushort)digit;
value <<= 4;

digit = kHexDigits.IndexOf(Char.ToUpperInvariant(text[++ii]));
digit = kHexDigits.IndexOf(char.ToUpperInvariant(text[++ii]));

if (digit == -1)
{
Expand Down Expand Up @@ -791,7 +770,7 @@ public int CompareTo(object obj)
{
if (this.NamespaceUri != null)
{
return String.CompareOrdinal(NamespaceUri, expandedId.NamespaceUri);
return string.CompareOrdinal(NamespaceUri, expandedId.NamespaceUri);
}

return -1;
Expand Down Expand Up @@ -1004,7 +983,7 @@ public static NodeId ToNodeId(ExpandedNodeId nodeId, NamespaceTable namespaceTab
}

// return a reference to the internal node id object.
if (String.IsNullOrEmpty(nodeId.m_namespaceUri) && nodeId.m_serverIndex == 0)
if (string.IsNullOrEmpty(nodeId.m_namespaceUri) && nodeId.m_serverIndex == 0)
{
return nodeId.m_nodeId;
}
Expand Down Expand Up @@ -1060,6 +1039,153 @@ internal void SetServerIndex(uint serverIndex)
#endregion

#region Static Members
/// <summary>
/// Parses an ExpandedNodeId formatted as a string and converts it a local NodeId.
/// </summary>
/// <param name="context">The current context,</param>
/// <param name="text">The text to parse.</param>
/// <param name="options">The options to use when parsing the ExpandedNodeId.</param>
/// <returns>The local identifier.</returns>
/// <exception cref="ServiceResultException">Thrown if the namespace URI is not in the namespace table.</exception>
public static ExpandedNodeId Parse(IServiceMessageContext context, string text, NodeIdParsingOptions options = null)
{
if (string.IsNullOrEmpty(text))
{
return Null;
}

var originalText = text;
int serverIndex = 0;

if (text.StartsWith("svu=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"Invalid ExpandedNodeId ({originalText}).");
}

var serverUri = Utils.UnescapeUri(text.Substring(4, index - 4));
serverIndex = (options?.UpdateTables == true) ? context.ServerUris.GetIndexOrAppend(serverUri) : context.ServerUris.GetIndex(serverUri);

if (serverIndex < 0)
{
throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"No mapping to ServerIndex for ServerUri ({serverUri}).");
}

text = text.Substring(index + 1);
}

if (text.StartsWith("svr=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"Invalid ExpandedNodeId ({originalText}).");
}

if (ushort.TryParse(text.Substring(4, index - 4), out ushort ns))
{
serverIndex = ns;

if (options.ServerMappings != null && options?.NamespaceMappings.Length < ns)
{
serverIndex = options.NamespaceMappings[ns];
}
}

text = text.Substring(index + 1);
}

int namespaceIndex = 0;
string namespaceUri = null;

if (text.StartsWith("nsu=", StringComparison.Ordinal))
{
int index = text.IndexOf(';', 4);

if (index < 0)
{
throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"Invalid ExpandedNodeId ({originalText}).");
}

namespaceUri = Utils.UnescapeUri(text.Substring(4, index - 4));
namespaceIndex = (options?.UpdateTables == true) ? context.NamespaceUris.GetIndexOrAppend(namespaceUri) : context.NamespaceUris.GetIndex(namespaceUri);

text = text.Substring(index + 1);
}

var nodeId = NodeId.Parse(context, text, options);

if (namespaceIndex > 0)
{
return new ExpandedNodeId(
nodeId.Identifier,
(ushort)namespaceIndex,
null,
(uint)serverIndex);
}

return new ExpandedNodeId(nodeId, namespaceUri, (uint)serverIndex);
}

/// <summary>
/// Formats a NodeId as a string.
/// </summary>
/// <param name="context">The current context.</param>
/// <param name="useUris">The NamespaceUri and/or ServerUri is used instead of the indexes.</param>
/// <returns>The formatted identifier.</returns>
public string Format(IServiceMessageContext context, bool useUris = false)
{
if (NodeId.IsNull(m_nodeId))
{
return null;
}

var buffer = new StringBuilder();

if (m_serverIndex > 0)
{
if (useUris)
{
var serverUri = context.ServerUris.GetString(m_serverIndex);

if (!string.IsNullOrEmpty(serverUri))
{
buffer.Append("svu=");
buffer.Append(Utils.EscapeUri(serverUri));
buffer.Append(';');
}
else
{
buffer.Append("svr=");
buffer.Append(m_serverIndex);
buffer.Append(';');
}
}
else
{
buffer.Append("svr=");
buffer.Append(m_serverIndex);
buffer.Append(';');
}
}

if (!string.IsNullOrEmpty(m_namespaceUri))
{
buffer.Append("nsu=");
buffer.Append(Utils.EscapeUri(m_namespaceUri));
buffer.Append(';');
}

var id = m_nodeId.Format(context, useUris);
buffer.Append(id);

return buffer.ToString();
}

/// <summary>
/// Parses an absolute NodeId formatted as a string and converts it a local NodeId.
/// </summary>
Expand Down
Loading

0 comments on commit 8131a02

Please sign in to comment.