Skip to content

Commit a3b956f

Browse files
Copilotmarcschier
andauthored
Fix: Allow calling methods with only output parameters (#3354)
* Fix CallMethodRequest to handle null InputArguments for methods with only output parameters Co-authored-by: marcschier <[email protected]> * Address code review: Use explicit VariantCollection() instead of collection expressions Co-authored-by: marcschier <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: marcschier <[email protected]>
1 parent f31d37b commit a3b956f

File tree

2 files changed

+136
-5
lines changed

2 files changed

+136
-5
lines changed

Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3691,11 +3691,9 @@ protected ServiceResult ValidateCallRequestItem(
36913691
return StatusCodes.BadMethodInvalid;
36923692
}
36933693

3694-
// check input arguments
3695-
if (callMethodRequest.InputArguments == null)
3696-
{
3697-
return StatusCodes.BadStructureMissing;
3698-
}
3694+
// Initialize input arguments to empty collection if null.
3695+
// Methods with only output parameters (no input parameters) are valid.
3696+
callMethodRequest.InputArguments ??= new VariantCollection();
36993697

37003698
return StatusCodes.Good;
37013699
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/* ========================================================================
2+
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
3+
*
4+
* OPC Foundation MIT License 1.00
5+
*
6+
* Permission is hereby granted, free of charge, to any person
7+
* obtaining a copy of this software and associated documentation
8+
* files (the "Software"), to deal in the Software without
9+
* restriction, including without limitation the rights to use,
10+
* copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the
12+
* Software is furnished to do so, subject to the following
13+
* conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be
16+
* included in all copies or substantial portions of the Software.
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19+
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24+
* OTHER DEALINGS IN THE SOFTWARE.
25+
*
26+
* The complete license agreement can be found here:
27+
* http://opcfoundation.org/License/MIT/1.00/
28+
* ======================================================================*/
29+
30+
using System.IO;
31+
using NUnit.Framework;
32+
using Opc.Ua.Tests;
33+
using Assert = NUnit.Framework.Legacy.ClassicAssert;
34+
35+
namespace Opc.Ua.Core.Tests.Types.BuiltIn
36+
{
37+
/// <summary>
38+
/// Tests for the CallMethodRequest class.
39+
/// </summary>
40+
[TestFixture, Category("CallMethodRequest")]
41+
[SetCulture("en-us")]
42+
[Parallelizable]
43+
public class CallMethodRequestTests
44+
{
45+
/// <summary>
46+
/// Verify that InputArguments is never null after construction.
47+
/// </summary>
48+
[Test]
49+
public void InputArgumentsInitializedAfterConstruction()
50+
{
51+
var request = new CallMethodRequest();
52+
Assert.IsNotNull(request.InputArguments);
53+
Assert.AreEqual(0, request.InputArguments.Count);
54+
}
55+
56+
/// <summary>
57+
/// Verify that InputArguments is initialized to empty collection after decode
58+
/// when the wire format indicates no arguments.
59+
/// This tests the fix for calling methods with only output parameters (no input parameters).
60+
/// </summary>
61+
[Test]
62+
public void InputArgumentsInitializedAfterDecode()
63+
{
64+
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
65+
var context = new ServiceMessageContext(telemetry);
66+
var originalRequest = new CallMethodRequest
67+
{
68+
ObjectId = new NodeId(1000),
69+
MethodId = new NodeId(2000),
70+
InputArguments = new VariantCollection() // Empty collection
71+
};
72+
73+
// Encode
74+
using (var stream = new MemoryStream())
75+
{
76+
using (var encoder = new BinaryEncoder(stream, context, leaveOpen: true))
77+
{
78+
originalRequest.Encode(encoder);
79+
}
80+
81+
// Decode
82+
stream.Position = 0;
83+
using (var decoder = new BinaryDecoder(stream, context))
84+
{
85+
var decodedRequest = new CallMethodRequest();
86+
decodedRequest.Decode(decoder);
87+
88+
// InputArguments should not be null
89+
Assert.IsNotNull(decodedRequest.InputArguments);
90+
Assert.AreEqual(0, decodedRequest.InputArguments.Count);
91+
}
92+
}
93+
}
94+
95+
/// <summary>
96+
/// Verify that InputArguments with values are correctly encoded and decoded.
97+
/// </summary>
98+
[Test]
99+
public void InputArgumentsWithValuesEncodeDecode()
100+
{
101+
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
102+
var context = new ServiceMessageContext(telemetry);
103+
var originalRequest = new CallMethodRequest
104+
{
105+
ObjectId = new NodeId(1000),
106+
MethodId = new NodeId(2000),
107+
InputArguments = new VariantCollection { new Variant(42), new Variant("test") }
108+
};
109+
110+
// Encode
111+
using (var stream = new MemoryStream())
112+
{
113+
using (var encoder = new BinaryEncoder(stream, context, leaveOpen: true))
114+
{
115+
originalRequest.Encode(encoder);
116+
}
117+
118+
// Decode
119+
stream.Position = 0;
120+
using (var decoder = new BinaryDecoder(stream, context))
121+
{
122+
var decodedRequest = new CallMethodRequest();
123+
decodedRequest.Decode(decoder);
124+
125+
Assert.IsNotNull(decodedRequest.InputArguments);
126+
Assert.AreEqual(2, decodedRequest.InputArguments.Count);
127+
Assert.AreEqual(42, decodedRequest.InputArguments[0].Value);
128+
Assert.AreEqual("test", decodedRequest.InputArguments[1].Value);
129+
}
130+
}
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)