From aec77536e80e678883e509ae5e6503a26b671a80 Mon Sep 17 00:00:00 2001 From: caran Date: Mon, 3 Jun 2024 15:05:10 +0200 Subject: [PATCH] RPC works --- YangParser/Generator/YangGenerator.cs | 52 +++- YangParser/SemanticModel/Action.cs | 35 ++- .../Builtins/BuiltinTypeReference.cs | 16 +- YangParser/SemanticModel/Case.cs | 2 +- YangParser/SemanticModel/Choice.cs | 2 +- YangParser/SemanticModel/Input.cs | 2 +- YangParser/SemanticModel/Leaf.cs | 11 + YangParser/SemanticModel/Notification.cs | 13 +- YangParser/SemanticModel/Output.cs | 3 +- YangParser/SemanticModel/Rpc.cs | 44 +-- YangParser/SemanticModel/Statement.cs | 285 ++++++++++++------ YangSourceTests/BfdIpMhTests.cs | 3 +- YangSourceTests/RpcTests.cs | 114 +++++++ yang-compiler.sln.DotSettings.user | 2 + 14 files changed, 429 insertions(+), 155 deletions(-) create mode 100644 YangSourceTests/RpcTests.cs diff --git a/YangParser/Generator/YangGenerator.cs b/YangParser/Generator/YangGenerator.cs index 98aaf5d..d76aaa2 100644 --- a/YangParser/Generator/YangGenerator.cs +++ b/YangParser/Generator/YangGenerator.cs @@ -324,6 +324,7 @@ private void AddAttributesClass(IncrementalGeneratorPostInitializationContext co var fileName = "YangModules/Attributes/Yang.Attributes.cs"; var contents = """ using System; + using System.Xml; namespace Yang.Attributes; [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class RevisionAttribute(string date) : Attribute @@ -413,15 +414,64 @@ public class InstanceIdentifier(string path) { public string Path { get; } = path; public static InstanceIdentifier Parse(string id) => new(id); + public override string ToString() => Path; } public interface IChannel { - string Send(string xml); + Task Send(string xml); } public interface IXMLSource { string ToXML(); } + public static class SerializationHelper + { + public static XmlWriterSettings GetStandardWriterSettings() + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.OmitXmlDeclaration = true; + settings.NewLineOnAttributes = false; + settings.Async = true; + return settings; + } + public static XmlReaderSettings GetStandardReaderSettings() + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.Async = true; + settings.ConformanceLevel = ConformanceLevel.Fragment; + settings.IgnoreWhitespace = true; + settings.IgnoreComments = true; + settings.IgnoreProcessingInstructions = true; + return settings; + } + public static async Task ExpectOkRpcReply(XmlReader reader, int messageID) + { + await reader.ReadAsync(); + if(reader.NodeType != XmlNodeType.Element || reader.Name != "rpc-reply" || reader.NamespaceURI != "urn:ietf:params:xml:ns:netconf:base:1.0" || reader["message-id"] != messageID.ToString()) + { + throw new Exception($"Expected stream to start with a element with message id {messageID} & \"urn:ietf:params:xml:ns:netconf:base:1.0\" but got {reader.NodeType}: {reader.Name} in {reader.NamespaceURI}"); + } + await reader.ReadAsync(); + while(reader.NodeType == XmlNodeType.Whitespace) + { + await reader.ReadAsync(); + } + if(reader.NodeType != XmlNodeType.Element || reader.Name != "ok") + { + throw new Exception($"Expected element {reader.NodeType}: {reader.Name}"); + } + await reader.ReadAsync(); + while(reader.NodeType == XmlNodeType.Whitespace) + { + await reader.ReadAsync(); + } + if(reader.NodeType != XmlNodeType.EndElement) + { + throw new Exception($"Expected closing element {reader.NodeType}: {reader.Name}"); + } + } + } """; context.AddSource(fileName, contents); diff --git a/YangParser/SemanticModel/Action.cs b/YangParser/SemanticModel/Action.cs index 4f0843e..9c7d077 100644 --- a/YangParser/SemanticModel/Action.cs +++ b/YangParser/SemanticModel/Action.cs @@ -45,13 +45,8 @@ public override string ToCode() $"public async {returnType} {MakeName(Argument)}(IChannel channel, int messageID{inputType})"); builder.AppendLine(""" { - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Indent = true; - settings.OmitXmlDeclaration = true; - settings.NewLineOnAttributes = true; - settings.Async = true; StringBuilder stringBuilder = new StringBuilder(); - using XmlWriter writer = XmlWriter.Create(stringBuilder, settings); + using XmlWriter writer = XmlWriter.Create(stringBuilder, SerializationHelper.GetStandardWriterSettings()); await writer.WriteStartElementAsync(null,"rpc","urn:ietf:params:xml:ns:netconf:base:1.0"); await writer.WriteAttributeStringAsync(null,"message-id",null,messageID.ToString()); await writer.WriteStartElementAsync(null,"action","urn:ietf:params:xml:ns:yang:1"); @@ -63,21 +58,31 @@ public override string ToCode() builder.AppendLine(""" await WriteXMLAsync(writer); await writer.WriteEndElementAsync(); - await writer.WriteEndElementAsync(); await writer.FlushAsync(); - var xml = stringBuilder.ToString(); + var response = await channel.Send(stringBuilder.ToString()); """); - builder.AppendLine(returnType != "Task" - ? "\tvar response = channel.Send(xml);" - : "\tchannel.Send(xml);"); builder.AppendLine(Ingoing is not null ? $"\tthis.{MakeName(Argument)}InputValue = null;" : $"\tthis.{MakeName(Argument)}Active = false;"); - if (returnType != "Task") - { - builder.AppendLine($"\treturn {outputType}.Parse(response);"); - } + builder.AppendLine(returnType != "Task" + ? $$""" + using XmlReader reader = XmlReader.Create(response,SerializationHelper.GetStandardReaderSettings()); + await reader.ReadAsync(); + if(reader.NodeType != XmlNodeType.Element || reader.Name != "rpc-reply" || reader.NamespaceURI != "urn:ietf:params:xml:ns:netconf:base:1.0" || reader["message-id"] != messageID.ToString()) + { + throw new Exception($"Expected stream to start with a element with message id {messageID} & \"urn:ietf:params:xml:ns:netconf:base:1.0\" but got {reader.NodeType}: {reader.Name} in {reader.NamespaceURI}"); + } + var value = await {{outputType}}.ParseAsync(reader); + response.Dispose(); + return value; + """ + : """ + using XmlReader reader = XmlReader.Create(response,SerializationHelper.GetStandardReaderSettings()); + await reader.ReadAsync(); + await SerializationHelper.ExpectOkRpcReply(reader, messageID); + response.Dispose(); + """); builder.AppendLine("}"); if (Outgoing is not null) diff --git a/YangParser/SemanticModel/Builtins/BuiltinTypeReference.cs b/YangParser/SemanticModel/Builtins/BuiltinTypeReference.cs index 87d5a25..72b2157 100644 --- a/YangParser/SemanticModel/Builtins/BuiltinTypeReference.cs +++ b/YangParser/SemanticModel/Builtins/BuiltinTypeReference.cs @@ -145,6 +145,7 @@ public override string ToString() private static string GetText(string argument) => $$""" await reader.ReadAsync(); + while(reader.NodeType == XmlNodeType.Whitespace) await reader.ReadAsync(); if(reader.NodeType != XmlNodeType.Text) { throw new Exception($"Expected token in ParseCall for '{{argument}}' to be text, but was '{reader.NodeType}'"); @@ -152,10 +153,15 @@ private static string GetText(string argument) => $$""" """; private static string EndElement(string argument) => $$""" - await reader.ReadAsync(); - if(reader.NodeType != XmlNodeType.EndElement) + if(!reader.IsEmptyElement) { - throw new Exception($"Expected token in ParseCall for '{{argument}}' to be an element closure, but was '{reader.NodeType}'"); + + await reader.ReadAsync(); + while(reader.NodeType == XmlNodeType.Whitespace) await reader.ReadAsync(); + if(reader.NodeType != XmlNodeType.EndElement) + { + throw new Exception($"Expected token in ParseCall for '{{argument}}' to be an element closure, but was '{reader.NodeType}'"); + } } """; @@ -273,7 +279,9 @@ public static string ValueParsing(Type type, string typeName) var p = prefix.Contains('.') ? prefix : prefix + ":"; return $"return {p}Get{local}Value(value);"; } - Log.Write($"Specified typeName {typeName} did not match source type name {chosen.Name}, defaulting to .Parse"); + + Log.Write( + $"Specified typeName {typeName} did not match source type name {chosen.Name}, defaulting to .Parse"); return $"return {typeName}.Parse(value);"; default: return $"return {typeName}.Parse(value);"; diff --git a/YangParser/SemanticModel/Case.cs b/YangParser/SemanticModel/Case.cs index fbaf689..b6a515f 100644 --- a/YangParser/SemanticModel/Case.cs +++ b/YangParser/SemanticModel/Case.cs @@ -43,7 +43,7 @@ public class {{ClassName}} { {{Indent(string.Join("\n", nodes))}} {{Indent(WriteFunctionInvisibleSelf())}} - {{Indent(ReadFunction())}} + {{Indent(ReadFunctionWithInvisibleSelf())}} } """; } diff --git a/YangParser/SemanticModel/Choice.cs b/YangParser/SemanticModel/Choice.cs index 932ced0..4f316df 100644 --- a/YangParser/SemanticModel/Choice.cs +++ b/YangParser/SemanticModel/Choice.cs @@ -47,7 +47,7 @@ public class {{TargetName}}Choice { {{string.Join("\n\t", nodes.Select(Indent))}} {{Indent(WriteFunctionInvisibleSelf())}} - {{Indent(ReadFunction())}} + {{Indent(ReadFunctionWithInvisibleSelf())}} } """; } diff --git a/YangParser/SemanticModel/Input.cs b/YangParser/SemanticModel/Input.cs index a73bba3..1c8915b 100644 --- a/YangParser/SemanticModel/Input.cs +++ b/YangParser/SemanticModel/Input.cs @@ -36,7 +36,7 @@ public override string ToCode() public class {{ClassName}} { {{string.Join("\n\t", Children.Select(child => Indent(child.ToCode())))}} - {{Indent(WriteFunction())}} + {{Indent(WriteFunctionInvisibleSelf())}} {{Indent(ReadFunction())}} } """; diff --git a/YangParser/SemanticModel/Leaf.cs b/YangParser/SemanticModel/Leaf.cs index dbffaa7..742fdf3 100644 --- a/YangParser/SemanticModel/Leaf.cs +++ b/YangParser/SemanticModel/Leaf.cs @@ -119,6 +119,17 @@ public string WriteCall """; } + if (Type.GetBaseType(out _, out _) is "empty") + { + return $$""" + if({{TargetName}} != default) + { + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); + await writer.WriteEndElementAsync(); + } + """; + } + return $$""" if({{TargetName}} != default) { diff --git a/YangParser/SemanticModel/Notification.cs b/YangParser/SemanticModel/Notification.cs index 465dc16..78813ab 100644 --- a/YangParser/SemanticModel/Notification.cs +++ b/YangParser/SemanticModel/Notification.cs @@ -44,13 +44,8 @@ public class {{MakeName(Argument)}} {{string.Join("\n\t", nodes.Select(Indent))}} public async Task ToXML() { - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Indent = true; - settings.OmitXmlDeclaration = true; - settings.NewLineOnAttributes = true; - settings.Async = true; StringBuilder stringBuilder = new StringBuilder(); - using XmlWriter writer = XmlWriter.Create(stringBuilder, settings); + using XmlWriter writer = XmlWriter.Create(stringBuilder, SerializationHelper.GetStandardWriterSettings()); await writer.WriteStartElementAsync(null,"notification","urn:ietf:params:xml:ns:netconf:notification:1.0"); await writer.WriteStartElementAsync(null,"eventTime",null); await writer.WriteStringAsync(DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ")); @@ -62,9 +57,7 @@ public async Task ToXML() } public static async Task<{{MakeName(Argument)}}> ParseAsync(global::System.IO.Stream xmlStream) { - XmlReaderSettings settings = new XmlReaderSettings(); - settings.Async = true; - using XmlReader reader = XmlReader.Create(xmlStream,settings); + using XmlReader reader = XmlReader.Create(xmlStream,SerializationHelper.GetStandardReaderSettings()); await reader.ReadAsync(); if(reader.NodeType != XmlNodeType.Element || reader.Name != "notification" || reader.NamespaceURI != "urn:ietf:params:xml:ns:netconf:notification:1.0") { @@ -113,8 +106,6 @@ public async Task ToXML() throw new Exception($"Expected closing element {reader.NodeType}: {reader.Name}"); } return value; - - } {{Indent(WriteFunction())}} {{Indent(ReadFunction(MakeName(Argument)))}} diff --git a/YangParser/SemanticModel/Output.cs b/YangParser/SemanticModel/Output.cs index 77ffc0a..4cad9d5 100644 --- a/YangParser/SemanticModel/Output.cs +++ b/YangParser/SemanticModel/Output.cs @@ -34,12 +34,13 @@ public Output(YangStatement statement) : base(statement) public override string ToCode() { + Argument = "rpc-reply"; return $$""" public class {{ClassName}} { {{string.Join("\n\t", Children.Select(child => Indent(child.ToCode())))}} - public static {{ClassName}} Parse(string xml) => default!; //TODO {{ReadFunction()}} + {{WriteFunctionInvisibleSelf()}} } """; } diff --git a/YangParser/SemanticModel/Rpc.cs b/YangParser/SemanticModel/Rpc.cs index 955cfc0..976a220 100644 --- a/YangParser/SemanticModel/Rpc.cs +++ b/YangParser/SemanticModel/Rpc.cs @@ -56,37 +56,41 @@ public override string ToCode() $"public static async {returnType} {MakeName(Argument)}(IChannel channel, int messageID{inputType})"); builder.AppendLine($$""" { - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Indent = true; - settings.OmitXmlDeclaration = true; - settings.NewLineOnAttributes = true; - settings.Async = true; StringBuilder stringBuilder = new StringBuilder(); - using XmlWriter writer = XmlWriter.Create(stringBuilder, settings); + using XmlWriter writer = XmlWriter.Create(stringBuilder, SerializationHelper.GetStandardWriterSettings()); await writer.WriteStartElementAsync(null,"rpc","urn:ietf:params:xml:ns:netconf:base:1.0"); await writer.WriteAttributeStringAsync(null,"message-id",null,messageID.ToString()); - await writer.WriteStartElementAsync("{{XmlNamespace?.Prefix}}","{{Argument}}","{{XmlNamespace?.Namespace}}"); + await writer.WriteStartElementAsync("{{Prefix}}","{{Argument}}","{{Namespace}}"); """); - var ns = $"xmlns:{XmlNamespace?.Prefix}=\\\"" + XmlNamespace?.Namespace + "\\\""; if (inputType != string.Empty) { builder.AppendLine("\tawait input.WriteXMLAsync(writer);"); } - builder.AppendLine($$""" - await writer.WriteEndElementAsync(); - await writer.WriteEndElementAsync(); - await writer.FlushAsync(); - var xml = stringBuilder.ToString(); - """); + builder.AppendLine(""" + await writer.WriteEndElementAsync(); + await writer.WriteEndElementAsync(); + await writer.FlushAsync(); + var response = await channel.Send(stringBuilder.ToString()); + """); builder.AppendLine(returnType != "Task" - ? "\tvar response = channel.Send(xml);" - : "\tchannel.Send(xml);"); - if (returnType != "Task") - { - builder.AppendLine($"\treturn {outputType}.Parse(response);"); - } + ? $$""" + using XmlReader reader = XmlReader.Create(response,SerializationHelper.GetStandardReaderSettings()); + await reader.ReadAsync(); + if(reader.NodeType != XmlNodeType.Element || reader.Name != "rpc-reply" || reader.NamespaceURI != "urn:ietf:params:xml:ns:netconf:base:1.0" || reader["message-id"] != messageID.ToString()) + { + throw new Exception($"Expected stream to start with a element with message id {messageID} & \"urn:ietf:params:xml:ns:netconf:base:1.0\" but got {reader.NodeType}: {reader.Name} in {reader.NamespaceURI}"); + } + var value = await {{outputType}}.ParseAsync(reader); + response.Dispose(); + return value; + """ + : """ + using XmlReader reader = XmlReader.Create(response,SerializationHelper.GetStandardReaderSettings()); + await SerializationHelper.ExpectOkRpcReply(reader, messageID); + response.Dispose(); + """); builder.AppendLine("}"); if (Outgoing is not null) diff --git a/YangParser/SemanticModel/Statement.cs b/YangParser/SemanticModel/Statement.cs index 8248849..a2d5d6f 100644 --- a/YangParser/SemanticModel/Statement.cs +++ b/YangParser/SemanticModel/Statement.cs @@ -60,103 +60,7 @@ protected string ReadFunction(string type) List declarations = new List(); List assignments = new List(); List cases = new List(); - foreach (var child in Children) - { - var hasCondition = child.TryGetChild(out var when); - var booleanStatement = hasCondition - ? $" when string.IsNullOrEmpty(\"{SingleLine(when!.Argument.Replace("\"", "\\\""))}\") /*TODO: Properly implement WHEN*/" - : string.Empty; - switch (child) - { - case IXMLParseable xml: - if (xml.TargetName != null) - { - declarations.Add($"{xml.ClassName} _{xml.TargetName} = default!;"); - assignments.Add($"{xml.TargetName} = _{xml.TargetName},"); - } - - switch (xml) - { - case Choice choice: - { - StringBuilder builder = new(); - bool added = false; - foreach (var c in choice.SubTargets) - { - added = true; - builder.AppendLine($"case \"{c}\"{booleanStatement}:"); - } - - if (!added) - { - builder.AppendLine($"case \"{choice.Argument}\"{booleanStatement}:"); - } - - builder.AppendLine($""" - _{xml.TargetName} = await {xml.ClassName}.ParseAsync(reader); - continue; - """); - cases.Add(builder.ToString()); - break; - } - case Case ChoiceCase: - { - StringBuilder builder = new(); - bool added = false; - foreach (var c in ChoiceCase.SubTargets) - { - added = true; - builder.AppendLine($"case \"{c}\"{booleanStatement}:"); - } - - if (!added) - { - builder.AppendLine($"case \"{ChoiceCase.Argument}\"{booleanStatement}:"); - } - - builder.AppendLine($""" - _{xml.TargetName} = await {xml.ClassName}.ParseAsync(reader); - continue; - """); - cases.Add(builder.ToString()); - break; - } - default: - cases.Add( - $""" - case "{xml.Argument}"{booleanStatement}: - _{xml.TargetName} = await {xml.ClassName}.ParseAsync(reader); - continue; - """); - break; - } - - break; - case IXMLReadValue xmlValue: - if (xmlValue.TargetName != null) - { - declarations.Add(child is List - ? $"List<{xmlValue.ClassName}> _{xmlValue.TargetName} = default!;" - : $"{xmlValue.ClassName} _{xmlValue.TargetName} = default!;"); - - assignments.Add($"{xmlValue.TargetName} = _{xmlValue.TargetName},"); - } - - cases.Add($""" - case "{xmlValue.Argument}"{booleanStatement}: - {Indent(xmlValue.ParseCall)} - continue; - """); - break; - case IXMLAction action: - cases.Add($""" - case "{action.Argument}"{booleanStatement}: - {Indent(action.ParseCall)} - continue; - """); - break; - } - } + CollectParsingChildren(declarations, assignments, cases, "continue"); return $$""" public static async Task<{{type}}> ParseAsync(XmlReader reader) @@ -172,12 +76,12 @@ protected string ReadFunction(string type) {{Indent(Indent(Indent(Indent(Indent(string.Join("\n", cases))))))}} default: throw new Exception($"Unexpected element '{reader.Name}' under '{{Argument}}'"); } - case XmlNodeType.EndElement: + case XmlNodeType.EndElement when reader.Name == "{{Argument}}": return new {{type}}{ {{Indent(Indent(Indent(Indent(Indent(string.Join("\n", assignments))))))}} }; case XmlNodeType.Whitespace: break; - default: throw new Exception($"Unexpected node type '{reader.NodeType}' under '{{Argument}}'"); + default: throw new Exception($"Unexpected node type '{reader.NodeType}' : '{reader.Name}' under '{{Argument}}'"); } } throw new Exception("Reached end-of-readability without ever returning from {{type}}.ParseAsync"); @@ -185,6 +89,189 @@ protected string ReadFunction(string type) """; } + protected string ReadFunctionWithInvisibleSelf() + { + var type = string.Empty; + switch (this) + { + case IXMLReadValue xmlReadValue: + type = xmlReadValue.ClassName; + break; + case IXMLParseable xmlReadValue: + type = xmlReadValue.ClassName; + break; + } + + if (type == string.Empty) + { + throw new InvalidOperationException($"ReadFunction called from invalid provider {GetType()}"); + } + + return ReadFunctionWithInvisibleSelf(type); + } + + protected string ReadFunctionWithInvisibleSelf(string type) + { + List declarations = new List(); + List assignments = new List(); + List cases = new List(); + CollectParsingChildren(declarations, assignments, cases, "break"); + + if (cases.Count > 0) + { + return $$""" + public static async global::System.Threading.Tasks.Task<{{type}}> ParseAsync(XmlReader reader) + { + {{Indent(string.Join("\n", declarations))}} + switch(reader.Name) + { + {{((Indent(Indent(Indent(string.Join("\n", cases))))))}} + default: throw new Exception($"Unexpected element '{reader.Name}' under '{{Argument}}'"); + } + return new {{type}}{ + {{((Indent(Indent(Indent(string.Join("\n", assignments))))))}} + }; + } + """; + } + + return $$""" + public static global::System.Threading.Tasks.Task<{{type}}> ParseAsync(XmlReader reader) + { + return global::System.Threading.Tasks.Task.FromResult(new {{type}}()); + } + """; + } + + private void CollectParsingChildren(List declarations, List assignments, List cases, + string escapeKeyword) + { + foreach (var child in Children) + { + var hasCondition = child.TryGetChild(out var when); + var booleanStatement = hasCondition + ? $" when string.IsNullOrEmpty(\"{SingleLine(when!.Argument.Replace("\"", "\\\""))}\") /*TODO: Properly implement WHEN*/" + : string.Empty; + switch (child) + { + case IXMLParseable xml: + HandleParseables(declarations, assignments, cases, xml, booleanStatement, escapeKeyword); + + break; + case IXMLReadValue xmlValue: + HandleReadValues(declarations, assignments, cases, xmlValue, child, booleanStatement, + escapeKeyword); + break; + case IXMLAction action: + cases.Add($""" + case "{action.Argument}"{booleanStatement}: + {Indent(action.ParseCall)} + {escapeKeyword}; + """); + break; + } + } + } + + private static void HandleReadValues(List declarations, List assignments, List cases, + IXMLReadValue xmlValue, + IStatement child, string booleanStatement, string escapeKeyword) + { + if (xmlValue.TargetName != null) + { + declarations.Add(child is List + ? $"List<{xmlValue.ClassName}> _{xmlValue.TargetName} = default!;" + : $"{xmlValue.ClassName} _{xmlValue.TargetName} = default!;"); + + assignments.Add($"{xmlValue.TargetName} = _{xmlValue.TargetName},"); + } + + cases.Add($""" + case "{xmlValue.Argument}"{booleanStatement}: + {Indent(xmlValue.ParseCall)} + {escapeKeyword}; + """); + } + + private static void HandleParseables(List declarations, List assignments, List cases, + IXMLParseable xml, + string booleanStatement, string escapeKeyword) + { + if (xml.TargetName != null) + { + declarations.Add($"{xml.ClassName} _{xml.TargetName} = default!;"); + assignments.Add($"{xml.TargetName} = _{xml.TargetName},"); + } + + switch (xml) + { + case Choice choice: + { + HandleChoice(cases, xml, booleanStatement, choice, escapeKeyword); + break; + } + case Case ChoiceCase: + { + HandleCase(cases, xml, booleanStatement, ChoiceCase, escapeKeyword); + break; + } + default: + cases.Add( + $""" + case "{xml.Argument}"{booleanStatement}: + _{xml.TargetName} = await {xml.ClassName}.ParseAsync(reader); + {escapeKeyword}; + """); + break; + } + } + + private static void HandleCase(List cases, IXMLParseable xml, string booleanStatement, Case ChoiceCase, + string escapeKeyword) + { + StringBuilder builder = new(); + bool added = false; + foreach (var c in ChoiceCase.SubTargets) + { + added = true; + builder.AppendLine($"case \"{c}\"{booleanStatement}:"); + } + + if (!added) + { + builder.AppendLine($"case \"{ChoiceCase.Argument}\"{booleanStatement}:"); + } + + builder.AppendLine($""" + _{xml.TargetName} = await {xml.ClassName}.ParseAsync(reader); + {escapeKeyword}; + """); + cases.Add(builder.ToString()); + } + + private static void HandleChoice(List cases, IXMLParseable xml, string booleanStatement, Choice choice, + string escapeKeyword) + { + StringBuilder builder = new(); + bool added = false; + foreach (var c in choice.SubTargets) + { + added = true; + builder.AppendLine($"case \"{c}\"{booleanStatement}:"); + } + + if (!added) + { + builder.AppendLine($"case \"{choice.Argument}\"{booleanStatement}:"); + } + + builder.AppendLine($""" + _{xml.TargetName} = await {xml.ClassName}.ParseAsync(reader); + {escapeKeyword}; + """); + cases.Add(builder.ToString()); + } + protected string WriteFunctionInvisibleSelf() { var writeCalls = Children.OfType() diff --git a/YangSourceTests/BfdIpMhTests.cs b/YangSourceTests/BfdIpMhTests.cs index 26e940f..edddb67 100644 --- a/YangSourceTests/BfdIpMhTests.cs +++ b/YangSourceTests/BfdIpMhTests.cs @@ -29,7 +29,8 @@ public async Task NotificationDeserializationTest() var result = await notification.ToXML(); using var ms = new MemoryStream(Encoding.UTF8.GetBytes(result)); var newNotification = await Ietf.Bfd.Ip.Mh.YangNode.MultihopNotification.ParseAsync(ms); - Assert.Equal(notification.DestAddr!.Ipv4AddressValue!.WrittenValue, newNotification.DestAddr!.Ipv4AddressValue!.WrittenValue); + Assert.Equal(notification.DestAddr!.Ipv4AddressValue!.WrittenValue, + newNotification.DestAddr!.Ipv4AddressValue!.WrittenValue); Assert.Equal(notification.NewState, newNotification.NewState); Assert.Equal(notification.LocalDiscr, newNotification.LocalDiscr); } diff --git a/YangSourceTests/RpcTests.cs b/YangSourceTests/RpcTests.cs new file mode 100644 index 0000000..de0e87a --- /dev/null +++ b/YangSourceTests/RpcTests.cs @@ -0,0 +1,114 @@ +using System.Text; +using System.Xml; +using Ietf.Inet.Types; +using Xunit.Abstractions; +using Yang.Attributes; + +namespace YangSourceTests; + +public class RpcTests(ITestOutputHelper outputHelper) +{ + private static readonly Ietf.Connection.Oriented.Oam.YangNode.TracerouteOutput output = new() + { + Response = new List + { + new() + { + Mip = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteOutput.ResponseEntry.MipContainer + { + MipAddress = + new Ietf.Connection.Oriented.Oam.YangNode.TracerouteOutput.ResponseEntry.MipContainer. + MipAddressChoice + { + IpAddressCaseValue = + new Ietf.Connection.Oriented.Oam.YangNode.TracerouteOutput.ResponseEntry. + MipContainer.MipAddressChoice.IpAddressCaseValueCase + { + IpAddress = new YangNode.IpAddress( + new YangNode.Ipv4Address("12.23.34.45")) + } + } + } + }, + new() + { + Ttl = 1, + MonitorStats = + new Ietf.Connection.Oriented.Oam.YangNode.TracerouteOutput.ResponseEntry.MonitorStatsChoice + { + MonitorNullCaseValue = + new Ietf.Connection.Oriented.Oam.YangNode.TracerouteOutput.ResponseEntry. + MonitorStatsChoice. + MonitorNullCaseValueCase + { + MonitorNull = new object() + } + } + } + } + }; + + private class TestChannel : IChannel + { + public string? LastXML { get; private set; } + public string? LastWritten { get; private set; } + + public async Task Send(string xml) + { + LastXML = xml; + var stream = new MemoryStream(); + await using var writer = XmlWriter.Create(stream, SerializationHelper.GetStandardWriterSettings()); + await writer.WriteStartElementAsync(null, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0"); + await writer.WriteAttributeStringAsync(null, "message-id", null, MessageID.ToString()); + await output.WriteXMLAsync(writer); + await writer.WriteEndElementAsync(); + await writer.FlushAsync(); + stream.Seek(0, SeekOrigin.Begin); + LastWritten = Encoding.UTF8.GetString(stream.GetBuffer()); + return stream; + } + + public int MessageID { get; set; } + } + + [Fact] + public async Task RpcSend() + { + var channel = new TestChannel(); + channel.MessageID = Random.Shared.Next(); + var reply = await Ietf.Connection.Oriented.Oam.YangNode.Traceroute(channel, channel.MessageID, + new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput + { + MaNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MaNameString(), + MdNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MdNameString(), + Ttl = 2, + Count = 4, + Interval = 6, + CosId = 2, + DestinationMep = new() + { + MepAddress = new() + { + IpAddressCaseValue = new() + { + IpAddress = new(new YangNode.Ipv4Address("1.2.3.4")) + } + } + }, + SourceMepValue = new(), + CommandSubTypeValue = + new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.CommandSubType("some-command") + }); + outputHelper.WriteLine(channel.LastXML); + outputHelper.WriteLine("_____________________________________"); + outputHelper.WriteLine(channel.LastWritten); + Assert.Equal( + reply.Response![0].Mip!.MipAddress!.IpAddressCaseValue!.IpAddress!.Ipv4AddressValue! + .WrittenValue, + output.Response![0].Mip!.MipAddress!.IpAddressCaseValue!.IpAddress!.Ipv4AddressValue! + .WrittenValue); + Assert.Equal( + reply.Response![1].Ttl, + output.Response![1].Ttl); + } +} \ No newline at end of file diff --git a/yang-compiler.sln.DotSettings.user b/yang-compiler.sln.DotSettings.user index f0e64d3..24e0231 100644 --- a/yang-compiler.sln.DotSettings.user +++ b/yang-compiler.sln.DotSettings.user @@ -5,6 +5,8 @@ <TestAncestor> <TestId>xUnit::D7772430-87F7-4707-8BC1-4F5C601C4AD3::net8.0::YangSourceTests.BfdIpMhTests.NotificationSerializationTest</TestId> <TestId>xUnit::D7772430-87F7-4707-8BC1-4F5C601C4AD3::net8.0::YangSourceTests.BfdIpMhTests.NotificationDeserializationTest</TestId> + <TestId>xUnit::D7772430-87F7-4707-8BC1-4F5C601C4AD3::net8.0::YangSourceTests.BfdIpMhTests</TestId> + <TestId>xUnit::D7772430-87F7-4707-8BC1-4F5C601C4AD3::net8.0::YangSourceTests.RpcTests.RpcSend</TestId> </TestAncestor> </Or> </SessionState> \ No newline at end of file