OPCFoundation / UA-.NETStandard

OPC Unified Architecture .NET Standard
Other
1.96k stars 946 forks source link

NullReferenceException in Session.Open() #2812

Open bbmmcodegirl opened 1 day ago

bbmmcodegirl commented 1 day ago

Type of issue

Current Behavior

Opening a client session, Synchronous Open() , throws a NullReferenceException in a logging function. The session is not opened.

System.NullReferenceException: Object reference not set to an instance of an object.
   at Opc.Ua.Utils.Log(LogLevel logLevel, EventId eventId, Exception exception, String message, Object[] args)
   at Opc.Ua.Utils.LogInfo(String message, Object[] args)
   at Opc.Ua.Client.Session.Open(String sessionName, UInt32 sessionTimeout, IUserIdentity identity, IList`1 preferredLocales, Boolean checkDomain)
   at <myConnectMethod> 

Expected Behavior

The session should open (if the server allows anonymous access and SecurityPolicy None) and not throw an exception.

Steps To Reproduce

  1. Create a console application targeting .Net Framework 4.72 that references the packages OPCFoundation.NetStandard.Opc.Ua.Client Version 1.5.374.126 and OPCFoundation.NetStandard.Opc.Ua.Core Version 1.5.374.126.
  2. Copy the example code into the main program.
  3. Define the ServerUrl variable to have some useful value.
  4. Create a configuration from the configuration xml provided in this issue, replacing the '...' values with some appropriate values.
  5. Have a server ready that accepts SecurityPolicy None and listens on that ServerUrl. But I suspect that the call does not even go to the actual connection, so you may not need a server running.
  6. Run the application.

The Open call will throw a NullReferenceException from a logging method, of all things.

Environment

- OS: Windows 11 Enterprise
- Environment: Visual Studio 2022
- Runtime: .NET Framework4.72
- Nuget Version: NuGet Package Manager 6.11.0
- Component: Client
- Server: Custom server built using the same stack
- Client: Custom client -- the error occurs in this client

Anything else?

This is how the session is created:

var serverAddress = ServerUrl.ToString();
_logger.Debug($"Connecting to {serverAddress}");
var endpointConfig = EndpointConfiguration.Create();
endpointConfig.UseBinaryEncoding = true;
var endpointDescription = new EndpointDescription
{
    EndpointUrl = serverAddress,
    SecurityMode = MessageSecurityMode.None,
    SecurityPolicyUri = SecurityPolicies.None
};
endpointDescription.Server.ApplicationName = _serverDescription;
endpointDescription.Server.ApplicationType = ApplicationType.Server;
endpointDescription.Server.ApplicationUri = serverAddress;
endpointDescription.UserIdentityTokens.Add(new UserTokenPolicy(UserTokenType.Anonymous));
endpointDescription.ServerCertificate = null;
var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfig);

// copy the message context.
_logger.Trace($"Creating message context");
var messageContext = _configuration.CreateMessageContext(true);

// create the channel.

_logger.Trace($"Creating transport channel");
ITransportChannel channel = SessionChannel.Create(
    _configuration,
    endpoint.Description,
    endpoint.Configuration,
    null,
    null,
    messageContext);
// create the session.
_logger.Trace($"Creating session");
Session session = new Session(channel, _configuration, endpoint, null)
{
    ReturnDiagnostics = DiagnosticsMasks.All
};
try
{
    _logger.Trace($"Opening session");
    // open the session.
    session.Open("My Session", (uint)0, null, null, false);    <---------- this call throws
}
catch (ServiceResultException sre)
{
    _logger.Error($"Error connecting to {ServerUrl}", sre);
}
catch (Exception exception)
{
    _logger.Error($"Error connecting to {ServerUrl}", exception);
}

The configuration that is passed in looks something like tis:

<ApplicationConfiguration xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <ApplicationName>My OpcUa Client</ApplicationName>
  <ApplicationUri>urn:localhost:My:Client</ApplicationUri>
  <ProductUri>uri:myCompany.com:My:Client</ProductUri>
  <ApplicationType>Client_1</ApplicationType>
  <SecurityConfiguration>
    <ApplicationCertificate>
      <StoreType>Directory</StoreType>
      <StorePath>...</StorePath>
      <SubjectName>...</SubjectName>
    </ApplicationCertificate>
    <TrustedIssuerCertificates>
      <StoreType>Directory</StoreType>
      <StorePath>..</StorePath>
      <TrustedCertificates/>
    </TrustedIssuerCertificates>
    <TrustedPeerCertificates>
      <StoreType>Directory</StoreType>
      <StorePath>...</StorePath>
      <TrustedCertificates/>
    </TrustedPeerCertificates>
    <NonceLength>32</NonceLength>
    <RejectedCertificateStore>
      <StoreType>Directory</StoreType>
      <StorePath>...</StorePath>
    </RejectedCertificateStore>
    <MaxRejectedCertificates>5</MaxRejectedCertificates>
    <AutoAcceptUntrustedCertificates>true</AutoAcceptUntrustedCertificates>
    <UserRoleDirectory i:nil="true"/>
    <RejectSHA1SignedCertificates>true</RejectSHA1SignedCertificates>
    <MinimumCertificateKeySize>2048</MinimumCertificateKeySize>
    <AddAppCertToTrustedStore>true</AddAppCertToTrustedStore>
    <SendCertificateChain>true</SendCertificateChain>
  </SecurityConfiguration>
  <TransportConfigurations/>
  <TransportQuotas>
    <OperationTimeout>600000</OperationTimeout>
    <MaxStringLength>1048576</MaxStringLength>
    <MaxByteStringLength>1048576</MaxByteStringLength>
    <MaxArrayLength>65535</MaxArrayLength>
    <MaxMessageSize>4194304</MaxMessageSize>
    <MaxBufferSize>65535</MaxBufferSize>
    <MaxEncodingNestingLevels>200</MaxEncodingNestingLevels>
    <MaxDecoderRecoveries>0</MaxDecoderRecoveries>
    <ChannelLifetime>300000</ChannelLifetime>
    <SecurityTokenLifetime>3600000</SecurityTokenLifetime>
  </TransportQuotas>
  <ClientConfiguration>
    <DefaultSessionTimeout>60000</DefaultSessionTimeout>
    <WellKnownDiscoveryUrls xmlns:a="http://opcfoundation.org/UA/2008/02/Types.xsd">
      <a:String>opc.tcp://{0}:4840</a:String>
      <a:String>http://{0}:52601/UADiscovery</a:String>
      <a:String>http://{0}/UADiscovery/Default.svc</a:String>
    </WellKnownDiscoveryUrls>
    <DiscoveryServers xmlns:a="http://opcfoundation.org/UA/2008/02/Types.xsd"/>
    <EndpointCacheFilePath>My.Client.Endpoints.xml</EndpointCacheFilePath>
    <MinSubscriptionLifetime>10000</MinSubscriptionLifetime>
    <ReverseConnect i:nil="true"/>
    <OperationLimits>
      <MaxNodesPerRead>0</MaxNodesPerRead>
      <MaxNodesPerHistoryReadData>0</MaxNodesPerHistoryReadData>
      <MaxNodesPerHistoryReadEvents>0</MaxNodesPerHistoryReadEvents>
      <MaxNodesPerWrite>0</MaxNodesPerWrite>
      <MaxNodesPerHistoryUpdateData>0</MaxNodesPerHistoryUpdateData>
      <MaxNodesPerHistoryUpdateEvents>0</MaxNodesPerHistoryUpdateEvents>
      <MaxNodesPerMethodCall>0</MaxNodesPerMethodCall>
      <MaxNodesPerBrowse>0</MaxNodesPerBrowse>
      <MaxNodesPerRegisterNodes>0</MaxNodesPerRegisterNodes>
      <MaxNodesPerTranslateBrowsePathsToNodeIds>0</MaxNodesPerTranslateBrowsePathsToNodeIds>
      <MaxNodesPerNodeManagement>0</MaxNodesPerNodeManagement>
      <MaxMonitoredItemsPerCall>0</MaxMonitoredItemsPerCall>
    </OperationLimits>
  </ClientConfiguration>
  <Extensions xmlns:a="http://opcfoundation.org/UA/2008/02/Types.xsd"/>
  <TraceConfiguration>
    <OutputFilePath>my logfile location</OutputFilePath>
    <DeleteOnLoad>false</DeleteOnLoad>
    <TraceMasks>523</TraceMasks>
  </TraceConfiguration>
</ApplicationConfiguration>
bbmmcodegirl commented 1 day ago

The NRE happens in this code:

[Event(ServiceCallStartId, Keywords = Keywords.Services, Message = ServiceCallStartMessage, Level = EventLevel.Verbose, Task = Tasks.ServiceCallTask)]
public void ServiceCallStart(string serviceName, int requestHandle, int pendingRequestCount)
{
    if (IsEnabled())
    {
        WriteEvent(ServiceCallStartId, serviceName, requestHandle, pendingRequestCount);
    }
    else if (Utils.Logger.IsEnabled(LogLevel.Trace)) <------ Utils.Logger is null.
    {
        Utils.Log(LogLevel.Trace, ServiceCallStartEventId, ServiceCallStartMessage, serviceName, requestHandle, pendingRequestCount);
    }
}

The library should obviously be resilient against such a case.

mregen commented 14 hours ago

Hi @bbmmcodegirl, thanks for the report. My suggestion is to use the Opc.Ua.Configuration library to build theconfiguration with the fluent API or to use the file API to load the configuration. They ensure all is properly configured. Utils.Logger is used across the board and having null checks all over the place adds quite some overhead that can be avoided. I suggest to initialize it properly.