forked from locka99/opcua
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Framework for running tests against an external server, in this case written in .NET. Rust and .NET communicates over stdio, by just writing JSON to stdin/stdout. Right now we don't use it for a lot, but it's pretty much a necessity to have communication if we want to write tests that run a .NET client against an OPC-UA server. Only a few tests are written for now, but there is plenty of room to expand.
- Loading branch information
Showing
26 changed files
with
1,181 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,8 @@ log/ | |
**/pki-server | ||
3rd-party/open62541/build/ | ||
lib/pki* | ||
lib/certs | ||
lib/certs | ||
|
||
**/bin | ||
**/obj | ||
**/pki-ext-client |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,18 @@ | ||
# TODO | ||
|
||
This is a pending rewrite of the OPC-UA stack, following up on the rewrite of the client to be async all the way through. | ||
This is a list of things that are known to be missing, or ideas that could be implemented. Feel free to pick up any of these if you wish to contribute. | ||
|
||
The following is a list of tasks, with progress indicated where relevant. | ||
|
||
- Rewrite the server to be async, and a great deal more flexible, making it possible to create _really_ advanced servers using this SDK. | ||
- **~100%** done with the initial scope, barring any bugs or details that need fixing. | ||
- Some features are left out: | ||
- Diagnostics, both as diagnosticsInfo from services, and general session diagnostics. There's a skeleton for this in the DiagnosticsNodeManager. | ||
- Events are _implemented_ but incredibly cumbersome to write, so there is nothing fancy implemented for them. See below. | ||
- Audit events are taken out due to the above. | ||
- The web server is removed. Likely forever, a better solution is to use the `metrics` library to hook into the rust metrics ecosystem. | ||
- A smattering of TODO's, most are somehow blocked by other tasks. | ||
- **100%** (The big stuff is merged) Merge most recent PRs on the main repo, especially the one migrating away from OpenSSL. | ||
- **100%** Split the library into parts again. | ||
- Initially into types, client, core, and server. | ||
- This is needed for other features. | ||
- **70%** Write a codegen/macro library. Initially this should just replace all the JS codegen, later on it will do _more_. | ||
- It would be best if this could be written in such a way that it can either be used as a core for a macro library, or as a standalone build.rs codegen module. | ||
- **70%** Implement sophisticated event support, using a macro to create event types. | ||
- **100%?** Investigate decoding. There are several things that would be interesting to do here. | ||
- Capture request-id/request-handle for error reporting during decoding. This will allow us to fatally fail much less often, but will require major changes to codegen. | ||
- **100%** See if there is a way to avoid needing to pass the ID when decoding ExtensionObjects. This info should be available, either in the object itself or as part of the type being decoded. | ||
- Flesh out the server and client SDK with tooling for ease if use. | ||
- **100%** I had an idea of a "request builder" framework for the client SDK, which might be really useful. | ||
- The server should be possible to set up in such a way that it is no harder to use than before. A specialized node manager would be ideal for this. | ||
- There are probably lots of neat logic we can add as utility methods that make it easier to implement node managers. | ||
- Go through the standard and implement _more_ of the core stuff. Diagnostics (**done-ish**), server management methods, etc. | ||
- Implement a better framework for security checks. (?) | ||
- Make it even easier to implement custom node managers. | ||
- Write a generic `Browser` for the client, to make it easier to recursively browse node hierarchies. This should be made super flexible, perhaps a trait based approach where the browser is generic over something that handles the response from a browse request and returns what nodes to browse next... | ||
- Automate fetching data type definitions from the server for custom structs. | ||
- Support for StructureWithOptionalFields and Union in the encoding macros. | ||
- Implement Part 4 7.41.2.3, encrypted secrets. We currently only support legacy secrets. We should also support more encryption algorithms for secrets. | ||
- Write some form of support for IssuedToken based authentication on the client. | ||
- Implement a better framework for security checks on the server. | ||
- Write a sophisticated server example with a persistent store. This would be a great way to verify the flexibility of the server. | ||
- Write some "bad ideas" servers, it would be nice to showcase how flexible this is. | ||
- **N/A, clippy warns about this** Look into using non-send locks, to eliminate a source of deadlocks. | ||
- Re-implement XML. The current approach using roxmltree is easy to write, but not actually what we need if we really wanted to implement OPC-UA XML encoding. A stream-based low level XML parser like `quick-xml` would probably be a better option. An implementation could probably borrow a lot from the JSON implementation. | ||
- Write a framework for method calls. The foundation for this has been laid with `TryFromVariant`, if we really wanted to we could use clever trait magic to let users simply define a rust method that takes in values that each implement a trait `MethodArg`, with a blanket impl for `TryFromVariant`, and return a tuple of results. Could be really powerful, but methods are a little niche. | ||
- Implement `Query`. I never got around to this, because the service is just so complex. Currently there is no way to actually implement it, since it won't work unless _all_ node managers implement it, and the core node managers don't. | ||
- Look into running certain services concurrently. Currently they are sequential because that makes everything much simpler, but the services that don't have any cross node-manager interaction could run on all node managers concurrently. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="System.Text.Json" Version="8.0.5" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
using System.Runtime.CompilerServices; | ||
using System.Text; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace Common; | ||
|
||
public static class Comms | ||
{ | ||
private static JsonSerializerOptions MakeOptions() | ||
{ | ||
var res = new JsonSerializerOptions | ||
{ | ||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower | ||
}; | ||
res.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower, false)); | ||
res.Converters.Add(new OutMessageConverter()); | ||
res.Converters.Add(new InMessageConverter()); | ||
return res; | ||
} | ||
|
||
private static readonly JsonSerializerOptions options = MakeOptions(); | ||
|
||
public static void Send(IOutMessage message) | ||
{ | ||
using var stdout = Console.OpenStandardOutput(); | ||
stdout.Write(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message, options))); | ||
stdout.WriteByte(0); | ||
stdout.Flush(); | ||
} | ||
|
||
public static async IAsyncEnumerable<IInMessage> ListenToInput([EnumeratorCancellation] CancellationToken token) | ||
{ | ||
using var stream = Console.OpenStandardInput(); | ||
|
||
var batch = new List<byte>(); | ||
var buffer = new byte[1024]; | ||
while (!token.IsCancellationRequested) | ||
{ | ||
var len = await stream.ReadAsync(buffer, token); | ||
foreach (var b in buffer.Take(len)) | ||
{ | ||
if (b == 0) | ||
{ | ||
var str = Encoding.UTF8.GetString([.. batch]); | ||
var msg = JsonSerializer.Deserialize<IInMessage>(str, options) ?? throw new Exception("Got null message"); | ||
yield return msg; | ||
batch.Clear(); | ||
} | ||
else | ||
{ | ||
batch.Add(b); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public static void LogToRust(string message) | ||
{ | ||
Send(new LogMessage { Message = message }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System.Text.Json; | ||
|
||
namespace Common; | ||
|
||
public interface IInMessage | ||
{ | ||
InMessageType Type { get; } | ||
} | ||
|
||
public class ShutdownMessage : IInMessage | ||
{ | ||
public InMessageType Type { get; set; } = InMessageType.Shutdown; | ||
} | ||
|
||
public enum InMessageType | ||
{ | ||
Shutdown, | ||
} | ||
|
||
class InMessageConverter : TaggedUnionConverter<IInMessage, InMessageType> | ||
{ | ||
protected override string TagName => "type"; | ||
|
||
protected override IInMessage? FromEnum(JsonDocument document, JsonSerializerOptions options, InMessageType type) | ||
{ | ||
return type switch | ||
{ | ||
InMessageType.Shutdown => document.Deserialize<ShutdownMessage>(options), | ||
_ => throw new JsonException("Unknown type variant") | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using System.Text.Json; | ||
|
||
namespace Common; | ||
|
||
|
||
public interface IOutMessage | ||
{ | ||
OutMessageType Type { get; } | ||
} | ||
|
||
public class LogMessage : IOutMessage | ||
{ | ||
public OutMessageType Type { get; set; } = OutMessageType.Log; | ||
public string? Message { get; set; } | ||
} | ||
|
||
public class ErrorMessage : IOutMessage | ||
{ | ||
public OutMessageType Type { get; set; } = OutMessageType.Error; | ||
public string? Message { get; set; } | ||
} | ||
|
||
public class ReadyMessage : IOutMessage | ||
{ | ||
public OutMessageType Type { get; set; } = OutMessageType.Ready; | ||
} | ||
|
||
public enum OutMessageType | ||
{ | ||
Log, | ||
Ready, | ||
Payload, | ||
Error, | ||
} | ||
|
||
public class GeneralMessage : IOutMessage | ||
{ | ||
public OutMessageType Type { get; set; } = OutMessageType.Payload; | ||
public JsonDocument? Payload { get; set; } | ||
} | ||
|
||
class OutMessageConverter : TaggedUnionConverter<IOutMessage, OutMessageType> | ||
{ | ||
protected override string TagName => "type"; | ||
|
||
protected override IOutMessage? FromEnum(JsonDocument document, JsonSerializerOptions options, OutMessageType type) | ||
{ | ||
return type switch | ||
{ | ||
OutMessageType.Log => document.Deserialize<LogMessage>(options), | ||
OutMessageType.Ready => document.Deserialize<ReadyMessage>(options), | ||
OutMessageType.Payload => document.Deserialize<GeneralMessage>(options), | ||
OutMessageType.Error => document.Deserialize<ErrorMessage>(options), | ||
_ => throw new JsonException("Unknown type variant") | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace Common; | ||
|
||
/// <summary> | ||
/// Utility for working with rust style internally-tagged unions. | ||
/// | ||
/// Requires specifying an enum, and an interface where each enum variant corresponds to | ||
/// an implementor of the interface. | ||
/// </summary> | ||
public abstract class TaggedUnionConverter<TInterface, TEnum> : JsonConverter<TInterface> | ||
where TInterface : class | ||
where TEnum : struct, Enum | ||
{ | ||
protected abstract string TagName { get; } | ||
|
||
protected abstract TInterface? FromEnum(JsonDocument document, JsonSerializerOptions options, TEnum type); | ||
|
||
public override TInterface? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||
{ | ||
using var doc = JsonDocument.ParseValue(ref reader); | ||
|
||
var prop = doc.RootElement.GetProperty(TagName).GetString(); | ||
if (string.IsNullOrEmpty(prop)) | ||
{ | ||
throw new JsonException($"Missing tag \"{TagName}\""); | ||
} | ||
if (!Enum.TryParse<TEnum>(prop, true, out var type)) | ||
{ | ||
throw new JsonException($"Invalid tag \"{TagName}\""); | ||
} | ||
return FromEnum(doc, options, type); | ||
} | ||
|
||
public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options) | ||
{ | ||
JsonSerializer.Serialize(writer, value, value.GetType(), options); | ||
} | ||
} |
Oops, something went wrong.