internal ClientConnection(Socket socket, TlsSecurityInformation tlsSecInfo, APCIParameters apciParameters, ApplicationLayerParameters parameters, Server server, ASDUQueue asduQueue, bool debugOutput) { connectionsCounter++; connectionID = connectionsCounter; this.remoteEndpoint = (IPEndPoint)socket.RemoteEndPoint; this.apciParameters = apciParameters; this.alParameters = parameters; this.server = server; this.asduQueue = asduQueue; this.debugOutput = debugOutput; ResetT3Timeout(); maxSentASDUs = apciParameters.K; this.sentASDUs = new SentASDU[maxSentASDUs]; //receivedASDUs = new ConcurrentQueue<ASDU>(); receivedASDUs = new Queue <ASDU>(); waitingASDUsHighPrio = new Queue <BufferFrame>(); socketStream = new NetworkStream(socket); this.socket = socket; this.tlsSecInfo = tlsSecInfo; this.fileServer = new FileServer(this, server.GetAvailableFiles(), DebugLog); Thread workerThread = new Thread(HandleConnection); workerThread.Start(); }
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence) { base.Encode(frame, parameters, isSequence); frame.SetNextByte((byte)(value % 0x100)); frame.SetNextByte((byte)((value / 0x100) % 0x100)); frame.SetNextByte((byte)((value / 0x10000) % 0x100)); frame.SetNextByte((byte)(value / 0x1000000)); }
public Server(APCIParameters apciParameters, ApplicationLayerParameters alParameters, TlsSecurityInformation securityInfo) { this.apciParameters = apciParameters; this.alParameters = alParameters; this.securityInfo = securityInfo; if (securityInfo != null) { this.localPort = 19998; } }
private Integer32Object(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence) : base(parameters, msg, startIndex, isSequence) { if (!isSequence) { startIndex += parameters.SizeOfIOA; /* skip IOA */ } value = msg [startIndex++]; value += ((int)msg [startIndex++] * 0x100); value += ((int)msg [startIndex++] * 0x10000); value += ((int)msg [startIndex++] * 0x1000000); }
public ASDUQueue(int maxQueueSize, ApplicationLayerParameters parameters, Action <string> DebugLog) { enqueuedASDUs = new ASDUQueueEntry[maxQueueSize]; for (int i = 0; i < maxQueueSize; i++) { enqueuedASDUs[i].asdu = new BufferFrame(new byte[260], 6); enqueuedASDUs[i].state = QueueEntryState.NOT_USED; } this.oldestQueueEntry = -1; this.latestQueueEntry = -1; this.numberOfAsduInQueue = 0; this.maxQueueSize = maxQueueSize; this.parameters = parameters; this.DebugLog = DebugLog; }
private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi) { Console.WriteLine("Interrogation for group " + qoi); ApplicationLayerParameters cp = connection.GetApplicationLayerParameters(); connection.SendACT_CON(asdu, false); // send sequence of information objects ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 1, 1, true); rfile( ); ToServer(ref newAsdu); connection.SendASDU(newAsdu); connection.SendACT_TERM(asdu); return(true); }
InformationObject IPrivateIOFactory.Decode(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence) { return(new Integer32Object(parameters, msg, startIndex, isSequence)); }
AsduReceivedHandler(object parameter, IMasterConnection connection, ASDU asdu) { var srv = IEC10Xconns[(int)parameter]; var conNameStr = srv.name + " - "; Log(conNameStr + asdu.ToString(), LogLevelDetailed); try { if (IsMongoLive) { var DB = Client.GetDatabase(JSConfig.mongoDatabaseName); var collection_rtd = DB .GetCollection <rtData>(RealtimeDataCollectionName); Double val = 0; Int32 objaddr = 0; Int32 dur = 0; Boolean isselect = false; Boolean cmdhastime = false; DateTime cmdtime = new DateTime(); Double dstkconv1 = 1; Double dstkconv2 = 0; Boolean dstsbo = false; Int32 dstdur = 0; Double srcval = 0; Int32 srcobjaddr = 0; Int32 srcconn = 0; Boolean srcsbo = false; Int32 srcdur = 0; Int32 srcasdu = 0; Int32 srcca = 0; Int32 srcpointkey = 0; String srctag = ""; Double srckconv1 = 1; Double srckconv2 = 0; switch (asdu.TypeId) { case TypeID.C_SC_NA_1: // 45 { var cmd = (SingleCommand)asdu.GetElement(0); isselect = cmd.Select; dur = cmd.QU; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.State); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_SC_TA_1: // 58 { var cmd = (SingleCommandWithCP56Time2a)asdu.GetElement(0); isselect = cmd.Select; dur = cmd.QU; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.State); cmdtime = cmd.Timestamp.GetDateTime(); cmdhastime = true; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_DC_NA_1: // 46 { var cmd = (DoubleCommand)asdu.GetElement(0); isselect = cmd.Select; dur = cmd.QU; objaddr = cmd.ObjectAddress; if (cmd.State != DoubleCommand.ON && cmd.State != DoubleCommand.OFF) { connection.SendACT_CON(asdu, true); // activation confirm negative Log(conNameStr + " Invalid double state command " + cmd.State, LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } val = cmd.State == DoubleCommand.ON ? 1 : 0; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_DC_TA_1: // 59 { var cmd = (DoubleCommandWithCP56Time2a)asdu.GetElement(0); isselect = cmd.Select; dur = cmd.QU; objaddr = cmd.ObjectAddress; if (cmd.State != DoubleCommand.ON && cmd.State != DoubleCommand.OFF) { connection.SendACT_CON(asdu, true); // activation confirm negative Log(conNameStr + " Invalid double state command " + cmd.State, LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } cmdtime = cmd.Timestamp.GetDateTime(); cmdhastime = true; val = cmd.State == DoubleCommand.ON ? 1 : 0; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_RC_NA_1: // 47 { var cmd = (StepCommand)asdu.GetElement(0); isselect = cmd.Select; dur = cmd.QU; objaddr = cmd.ObjectAddress; if (cmd.State != StepCommandValue.HIGHER && cmd.State != StepCommandValue.LOWER) { connection.SendACT_CON(asdu, true); // activation confirm negative Log(conNameStr + " Invalid step state command " + cmd.State, LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } val = cmd.State == StepCommandValue.HIGHER ? 1 : 0; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_RC_TA_1: // 60 { var cmd = (StepCommandWithCP56Time2a)asdu.GetElement(0); isselect = cmd.Select; dur = cmd.QU; objaddr = cmd.ObjectAddress; if (cmd.State != StepCommandValue.HIGHER && cmd.State != StepCommandValue.LOWER) { connection.SendACT_CON(asdu, true); // activation confirm negative Log(conNameStr + " Invalid step state command " + cmd.State, LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } cmdtime = cmd.Timestamp.GetDateTime(); cmdhastime = true; val = cmd.State == StepCommandValue.HIGHER ? 1 : 0; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_SE_NA_1: // 48 { var cmd = (SetpointCommandNormalized)asdu.GetElement(0); isselect = cmd.QOS.Select; dur = cmd.QOS.QL; objaddr = cmd.ObjectAddress; val = cmd.NormalizedValue; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_SE_TA_1: // 61 { var cmd = (SetpointCommandNormalizedWithCP56Time2a)asdu.GetElement(0); isselect = cmd.QOS.Select; dur = cmd.QOS.QL; objaddr = cmd.ObjectAddress; cmdtime = cmd.Timestamp.GetDateTime(); cmdhastime = true; val = cmd.NormalizedValue; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_SE_NB_1: // 49 { var cmd = (SetpointCommandScaled)asdu.GetElement(0); isselect = cmd.QOS.Select; dur = cmd.QOS.QL; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.ScaledValue); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_SE_TB_1: // 62 { var cmd = (SetpointCommandScaledWithCP56Time2a)asdu.GetElement(0); isselect = cmd.QOS.Select; dur = cmd.QOS.QL; objaddr = cmd.ObjectAddress; cmdtime = cmd.Timestamp.GetDateTime(); cmdhastime = true; val = System.Convert.ToDouble(cmd.ScaledValue); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_SE_NC_1: // 50 { var cmd = (SetpointCommandShort)asdu.GetElement(0); isselect = cmd.QOS.Select; dur = cmd.QOS.QL; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.Value); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_SE_TC_1: // 63 { var cmd = (SetpointCommandShortWithCP56Time2a)asdu.GetElement(0); isselect = cmd.QOS.Select; dur = cmd.QOS.QL; objaddr = cmd.ObjectAddress; cmdtime = cmd.Timestamp.GetDateTime(); cmdhastime = true; val = System.Convert.ToDouble(cmd.Value); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_BO_NA_1: // 51 { var cmd = (Bitstring32Command)asdu.GetElement(0); isselect = false; dur = 0; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.Value); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_BO_TA_1: // 64 { var cmd = (Bitstring32CommandWithCP56Time2a)asdu.GetElement(0); isselect = false; dur = 0; objaddr = cmd.ObjectAddress; cmdtime = cmd.Timestamp.GetDateTime(); cmdhastime = true; val = System.Convert.ToDouble(cmd.Value); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.P_ME_NA_1: // 110 { var cmd = (ParameterNormalizedValue)asdu.GetElement(0); isselect = false; dur = cmd.QPM; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.NormalizedValue); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.P_ME_NB_1: // 111 { var cmd = (ParameterScaledValue)asdu.GetElement(0); isselect = false; dur = cmd.QPM; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.ScaledValue); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.P_ME_NC_1: // 112 { var cmd = (ParameterFloatValue)asdu.GetElement(0); isselect = false; dur = cmd.QPM; objaddr = cmd.ObjectAddress; val = System.Convert.ToDouble(cmd.Value); Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.P_AC_NA_1: // 113 { var cmd = (ParameterActivation)asdu.GetElement(0); isselect = false; dur = cmd.QPA; objaddr = cmd.ObjectAddress; val = 0; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_RP_NA_1: // 105 reset process { var cmd = (ResetProcessCommand)asdu.GetElement(0); isselect = false; dur = cmd.QRP; objaddr = cmd.ObjectAddress; val = 0; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; // case TypeID.C_IC_NA_1: // 100 already managed by interrogation handler function case TypeID.C_TS_NA_1: // 104 test command { Log(conNameStr + " Test command C_TS_NA_1", LogLevelDetailed); connection.SendACT_CON(asdu, false); // activation confirm positive } return(true); case TypeID.C_TS_TA_1: // 107 test command { Log(conNameStr + " Test command C_TS_TA_1", LogLevelDetailed); connection.SendACT_CON(asdu, false); // activation confirm positive } return(true); case TypeID.C_CI_NA_1: // 101 { var cmd = (CounterInterrogationCommand)asdu.GetElement(0); isselect = false; dur = cmd.QCC; objaddr = cmd.ObjectAddress; // should be zero val = 0; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_RD_NA_1: // 102 { var cmd = (ReadCommand)asdu.GetElement(0); objaddr = cmd.ObjectAddress; Log(conNameStr + " " + cmd.ToString() + " Obj Address " + objaddr, LogLevelBasic); } break; case TypeID.C_CS_NA_1: // 103 clock sync { ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement(0); connection.SendACT_CON(asdu, false); // activation confirm positive Log(conNameStr + " Received clock sync command with time " + qsc.NewTime.ToString(), LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } default: connection.SendACT_CON(asdu, true); Log(conNameStr + " Not implemented type of ASDU received: " + asdu.TypeId, LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } var filter1 = Builders <rtData> .Filter.Eq("protocolDestinations.protocolDestinationConnectionNumber", srv.protocolConnectionNumber); var filter2 = Builders <rtData> .Filter.Eq("protocolDestinations.protocolDestinationCommonAddress", asdu.Ca); var filter3 = Builders <rtData> .Filter.Eq("protocolDestinations.protocolDestinationObjectAddress", objaddr); var filter4 = Builders <rtData> .Filter.Eq("protocolDestinations.protocolDestinationASDU", asdu.TypeId); var filter = Builders <rtData> .Filter .And(filter1, filter2, filter3, filter4); if (asdu.TypeId == TypeID.C_RD_NA_1) { // READ COMMAND, look for object by object address of any type to send it filter = Builders <rtData> .Filter .And(filter1, filter2, filter3); } var list = collection_rtd.Find(filter).ToList(); if (list.Count > 0) { Log(conNameStr + " Command found.", LogLevelBasic); foreach (var dst in list[0].protocolDestinations) { if (dst.protocolDestinationConnectionNumber == srv.protocolConnectionNumber) { dstkconv1 = dst.protocolDestinationKConv1.ToDouble(); dstkconv2 = dst.protocolDestinationKConv2.ToDouble(); dstsbo = dst.protocolDestinationCommandUseSBO.ToBoolean(); dstdur = dst.protocolDestinationCommandDuration.ToInt32(); if (asdu.TypeId == TypeID.C_RD_NA_1) { // READ REQUEST connection.SendACT_CON(asdu, false); // activation confirm positive ApplicationLayerParameters cp = srv.server.GetApplicationLayerParameters(); var newAsdu = new ASDU(cp, CauseOfTransmission.REQUEST, false, false, System.Convert.ToByte(dst.protocolDestinationCommonAddress), dst.protocolDestinationCommonAddress.ToInt32(), true); var quality = new QualityDescriptor(); quality.Invalid = list[0].invalid.ToBoolean() || list[0].overflow.ToBoolean() || list[0].transient.ToBoolean(); quality.Substituted = list[0].substituted.ToBoolean(); quality.Blocked = false; quality.NonTopical = false; InformationObject io = BuildInfoObj( System.Convert.ToInt32(dst.protocolDestinationASDU), System.Convert.ToInt32(dst.protocolDestinationObjectAddress), list[0].value.ToDouble(), false, 0, quality ); if (io != null) { newAsdu.AddInformationObject(io); srv.server.EnqueueASDU(newAsdu); } return(true); } if (isselect && !dstsbo) { // tried a select when there is no select connection.SendACT_CON(asdu, true); // activation confirm negative Log(conNameStr + " Select tried but not expected!", LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } if (dur != dstdur) { // duration spec different than expected, reject commmand connection.SendACT_CON(asdu, true); // activation confirm negative Log(conNameStr + " QU/QL command qualifier not expected: " + dur + ", " + dstdur + " wanted ", LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } srcconn = list[0].protocolSourceConnectionNumber.ToInt32(); srcdur = list[0].protocolSourceCommandDuration.ToInt32(); srcobjaddr = list[0].protocolSourceObjectAddress.ToInt32(); srcasdu = list[0].protocolSourceASDU.ToInt32(); srcca = list[0].protocolSourceCommonAddress.ToInt32(); srckconv1 = list[0].kconv1.ToDouble(); srckconv2 = list[0].kconv2.ToDouble(); srcpointkey = list[0]._id.ToInt32(); srctag = list[0].tag.ToString(); break; } } } else { connection.SendACT_CON(asdu, true); // activation confirm negative if (asdu.TypeId == TypeID.C_RD_NA_1) { Log(conNameStr + " Request to read object not found, address: " + objaddr, LogLevelBasic); } else { Log(conNameStr + " Command not found!", LogLevelBasic); } LastPointKeySelectedOk = 0; return(true); } if (srcasdu == 0) { Log(conNameStr + " Command rejected!", LogLevelBasic); connection.SendACT_CON(asdu, true); // activation confirm negative LastPointKeySelectedOk = 0; return(true); } if (cmdhastime) // check command time { if (DateTime.Now.Subtract(cmdtime).TotalSeconds > timeToExpireCommandsWithTime) { // expired Log(conNameStr + " Command with time expired after " + timeToExpireCommandsWithTime + "s, diff: " + (DateTime.Now.Subtract(cmdtime).TotalSeconds - timeToExpireCommandsWithTime) + "s", LogLevelBasic); connection.SendACT_CON(asdu, true); // activation confirm negative LastPointKeySelectedOk = 0; return(true); } } connection.SendACT_CON(asdu, false); // activation confirm positive if (isselect) { LastPointKeySelectedOk = srcpointkey; // flag selected point Log(conNameStr + " Select!", LogLevelBasic); return(true); // do not forward a select } if (!isselect && dstsbo && LastPointKeySelectedOk != srcpointkey) { // tried execute without select first when there is select expected connection.SendACT_CON(asdu, true); Log(conNameStr + " Tried execute without select first!", LogLevelBasic); LastPointKeySelectedOk = 0; return(true); } LastPointKeySelectedOk = 0; Log(conNameStr + " Execute (forward to queue)!", LogLevelBasic); switch (asdu.TypeId) { case TypeID.C_SC_NA_1: // 45 case TypeID.C_SC_TA_1: // 58 case TypeID.C_DC_NA_1: // 46 case TypeID.C_DC_TA_1: // 59 case TypeID.C_RC_NA_1: // 47 case TypeID.C_RC_TA_1: // 60 if (dstkconv1 == -1) // invert digital for kconv1 -1 { val = val == 0 ? 1 : 0; } break; case TypeID.C_SE_NA_1: // 48 case TypeID.C_SE_TA_1: // 61 case TypeID.C_SE_NB_1: // 49 case TypeID.C_SE_TB_1: // 62 case TypeID.C_SE_NC_1: // 50 case TypeID.C_SE_TC_1: // 63 case TypeID.P_ME_NA_1: // 110 case TypeID.P_ME_NB_1: // 111 case TypeID.P_ME_NC_1: // 112 case TypeID.P_AC_NA_1: // 113 case TypeID.C_RP_NA_1: // 105 val = val * dstkconv1 + dstkconv2; break; case TypeID.C_BO_NA_1: // 51 case TypeID.C_BO_TA_1: // 64 if (dstkconv1 == -1) // invert digital bits for kconv1 -1 { val = System.Convert.ToInt32(val); } break; default: break; } switch ((TypeID)srcasdu) { case TypeID.C_SC_NA_1: // 45 case TypeID.C_SC_TA_1: // 58 case TypeID.C_DC_NA_1: // 46 case TypeID.C_DC_TA_1: // 59 case TypeID.C_RC_NA_1: // 47 case TypeID.C_RC_TA_1: // 60 if (srckconv1 == -1) // invert digital for kconv1 -1 { srcval = val == 0 ? 1 : 0; } else { srcval = val; } break; case TypeID.C_SE_NA_1: // 48 case TypeID.C_SE_TA_1: // 61 case TypeID.C_SE_NB_1: // 49 case TypeID.C_SE_TB_1: // 62 case TypeID.C_SE_NC_1: // 50 case TypeID.C_SE_TC_1: // 63 case TypeID.P_ME_NA_1: // 110 case TypeID.P_ME_NB_1: // 111 case TypeID.P_ME_NC_1: // 112 case TypeID.P_AC_NA_1: // 113 case TypeID.C_RP_NA_1: // 105 srcval = val * srckconv1 + srckconv2; break; case TypeID.C_BO_NA_1: // 51 case TypeID.C_BO_TA_1: // 64 if (srckconv1 == -1) // invert digital bits for kconv1 -1 { srcval = ~System.Convert.ToInt32(val); } else { srcval = System.Convert.ToInt32(val); } break; default: break; } // not sure how to detect the client connection as can be more than one String orgip = ""; foreach (var conn in srv.clientConnections) { orgip = orgip + srv.clientConnections[0].RemoteEndpoint.ToString() + " "; } var collection_cmd = DB .GetCollection <rtCommandNoAck>(CommandsQueueCollectionName); var rtcmd = new rtCommandNoAck { protocolSourceConnectionNumber = srcconn, protocolSourceCommonAddress = srcca, protocolSourceObjectAddress = srcobjaddr, protocolSourceASDU = srcasdu, protocolSourceCommandDuration = srcdur, protocolSourceCommandUseSBO = srcsbo, pointKey = srcpointkey, tag = srctag, value = srcval, valueString = srcval.ToString(), originatorUserName = "******" + srv.name, originatorIpAddress = orgip, timeTag = DateTime.Now }; collection_cmd.InsertOne(rtcmd); } } catch (Exception e) { Log(" Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); } return(true); }
/// <summary> /// Create a new server using the provided connection parameters. /// </summary> /// <param name="parameters">Connection parameters</param> public Server(APCIParameters apciParameters, ApplicationLayerParameters alParameters) { this.apciParameters = apciParameters; this.alParameters = alParameters; }
static void Main(string[] args) { Log("{json:scada} IEC60870-5-104 Server Driver - Copyright 2020 RLO"); Log("Driver version " + DriverVersion); Log("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString()); if ( args.Length > 0 // first argument in number of the driver instance ) { int num; bool res = int.TryParse(args[0], out num); if (res) { ProtocolDriverInstanceNumber = num; } } if ( args.Length > 1 // second argument is logLevel ) { int num; bool res = int.TryParse(args[1], out num); if (res) { LogLevel = num; } } string fname = JsonConfigFilePath; if (args.Length > 2) // third argument is config file name { if (File.Exists(args[2])) { fname = args[2]; } } if (!File.Exists(fname)) { fname = JsonConfigFilePathAlt; } if (!File.Exists(fname)) { Log("Missing config file " + JsonConfigFilePath); Environment.Exit(-1); } Log("Reading config file " + fname); string json = File.ReadAllText(fname); JSConfig = JsonSerializer.Deserialize <JSONSCADAConfig>(json); if ( JSConfig.mongoConnectionString == "" || JSConfig.mongoConnectionString == null ) { Log("Missing MongoDB connection string in JSON config file " + fname); Environment.Exit(-1); } // Log("MongoDB connection string: " + JSConfig.mongoConnectionString); if ( JSConfig.mongoDatabaseName == "" || JSConfig.mongoDatabaseName == null ) { Log("Missing MongoDB database name in JSON config file " + fname); Environment.Exit(-1); } Log("MongoDB database name: " + JSConfig.mongoDatabaseName); if (JSConfig.nodeName == "" || JSConfig.nodeName == null) { Log("Missing nodeName parameter in JSON config file " + fname); Environment.Exit(-1); } Log("Node name: " + JSConfig.nodeName); // connect to MongoDB Database server Client = ConnectMongoClient(JSConfig); var DB = Client.GetDatabase(JSConfig.mongoDatabaseName); // read and process instances configuration var collinsts = DB .GetCollection <protocolDriverInstancesClass >(ProtocolDriverInstancesCollectionName); var instances = collinsts .Find(inst => inst.protocolDriver == ProtocolDriverName && inst.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && inst.enabled == true) .ToList(); var foundInstance = false; foreach (protocolDriverInstancesClass inst in instances) { if ( ProtocolDriverName == inst.protocolDriver && ProtocolDriverInstanceNumber == inst.protocolDriverInstanceNumber ) { foundInstance = true; if (!inst.enabled) { Log("Driver instance [" + ProtocolDriverInstanceNumber.ToString() + "] disabled!"); Environment.Exit(-1); } Log("Instance: " + inst.protocolDriverInstanceNumber.ToString()); var nodefound = false; foreach (var name in inst.nodeNames) { if (JSConfig.nodeName == name) { nodefound = true; } } if (!nodefound) { Log("Node '" + JSConfig.nodeName + "' not found in instances configuration!"); Environment.Exit(-1); } DriverInstance = inst; break; } break; // process just first result } if (!foundInstance) { Log("Driver instance [" + ProtocolDriverInstanceNumber + "] not found in configuration!"); Environment.Exit(-1); } // read and process connections configuration for this driver instance var collconns = DB .GetCollection <IEC10X_connection>(ProtocolConnectionsCollectionName); var conns = collconns .Find(conn => conn.protocolDriver == ProtocolDriverName && conn.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && conn.enabled == true) .ToList(); foreach (IEC10X_connection isrv in conns) { IEC10Xconns.Add(isrv); Log(isrv.name.ToString()); } if (IEC10Xconns.Count == 0) { Log("No connections found!"); Environment.Exit(-1); } // start thread to dequeue iec data and send to connections Thread thrDeqIecInfo = new Thread(() => DequeueIecInfo()); thrDeqIecInfo.Start(); // start thread to watch for commands in the database using a change stream Thread thrMongoCS = new Thread(() => ProcessMongoCS(JSConfig)); thrMongoCS.Start(); Log("Setting up IEC Connections & ASDU handlers..."); int cntIecSrv = 0; foreach (IEC10X_connection srv in IEC10Xconns) { var apcipars = new APCIParameters(); apcipars.K = srv.k; apcipars.W = srv.w; apcipars.T0 = srv.t0; apcipars.T1 = srv.t1; apcipars.T2 = srv.t2; apcipars.T3 = srv.t3; var alpars = new ApplicationLayerParameters(); alpars.SizeOfCOT = srv.sizeOfCOT; alpars.SizeOfCA = srv.sizeOfCA; alpars.SizeOfIOA = srv.sizeOfIOA; alpars.OA = srv.localLinkAddress; var server = new Server(apcipars, alpars); srv.server = server; if (srv.serverModeMultiActive) { server.ServerMode = ServerMode.CONNECTION_IS_REDUNDANCY_GROUP; } else { server.ServerMode = ServerMode.SINGLE_REDUNDANCY_GROUP; } var localBindIpAddress = "0.0.0.0"; var tcpPort = 2404; string[] ipAddrPort = srv.ipAddressLocalBind.Split(':'); if (ipAddrPort.Length > 0) { if (ipAddrPort[0] != "") { localBindIpAddress = ipAddrPort[0]; } } if (ipAddrPort.Length > 1) { if (int.TryParse(ipAddrPort[1], out _)) { tcpPort = System.Convert.ToInt32(ipAddrPort[1]); } } server.SetLocalAddress(localBindIpAddress); server.SetLocalPort(tcpPort); //RedundancyGroup redGroup = new RedundancyGroup("catch all"); //server.AddRedundancyGroup(redGroup); if (LogLevel >= LogLevelDebug) { server.DebugOutput = true; } server.MaxQueueSize = srv.maxQueueSize; server.MaxOpenConnections = srv.maxClientConnections; Log(srv.name + " - Max Queue Size: " + server.MaxQueueSize); Log(srv.name + " - Max Client Connections: " + server.MaxOpenConnections); server.SetConnectionRequestHandler( ConnectionRequestHandler, cntIecSrv ); server.SetConnectionEventHandler( ConnectionEventHandler, cntIecSrv ); server.SetInterrogationHandler( InterrogationHandler, cntIecSrv ); server.SetASDUHandler(AsduReceivedHandler, cntIecSrv); server.Start(); Log(srv.name + " - New server listening on " + localBindIpAddress + ":" + tcpPort); cntIecSrv++; } Thread.Sleep(1000); bool running = true; Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; running = false; }; Log("Press [CTRL]+[C] to terminate..."); do { try { if (Client == null) { // retry connection Client = new MongoClient(JSConfig.mongoConnectionString); DB = Client.GetDatabase(JSConfig.mongoDatabaseName); } IsMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(10000); if (!IsMongoLive) { throw new Exception("Error on MongoDB connection "); } //foreach (IEC104_connection srv in IEC10Xconns) //{ //} } catch (Exception e) { // Disconnects to retry after some time Client = null; Log("Exception Mongo"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); } Thread.Sleep(1000); }while (running); Log("Exiting application!"); Environment.Exit(0); /* Synchronize clock of the controlled station */ //con.SendClockSyncCommand(1 /* CA */, new CP56Time2a(DateTime.Now)); }
InterrogationHandler( object parameter, IMasterConnection connection, ASDU asdu, byte qoi ) { var srv = IEC10Xconns[(int)parameter]; var conNameStr = srv.name + " - "; Log(conNameStr + "[" + qoi + "] Group interrogation BEGIN", LogLevelBasic); try { var Client = new MongoClient(JSConfig.mongoConnectionString); var DB = Client.GetDatabase(JSConfig.mongoDatabaseName); var collection = DB.GetCollection <rtData>(RealtimeDataCollectionName); ApplicationLayerParameters cp = connection.GetApplicationLayerParameters(); // query mongodb for all data to distribute in this connection // for group 20 (general interogation) request filter by all in destination connection but those marked with group -1 // for other groups requests filter by by those marked with group this same group var filter_conn = Builders <rtData> .Filter.Eq("protocolDestinations.protocolDestinationConnectionNumber", srv.protocolConnectionNumber); var filter_cmd = Builders <rtData> .Filter.Ne("origin", "command"); var filter_gen_int = Builders <rtData> .Filter.And(filter_conn, filter_cmd, Builders <rtData> .Filter.Ne("protocolDestinations.protocolDestinationGroup", -1)); var filter_oth_grp = Builders <rtData> .Filter.And(filter_conn, filter_cmd, Builders <rtData> .Filter.Or( Builders <rtData> .Filter.Eq("protocolDestinations.protocolDestinationGroup", qoi), // accept 20-36 or 0-16 Builders <rtData> .Filter.Eq("protocolDestinations.protocolDestinationGroup", qoi - 20) ) ); var sort = Builders <rtData> .Sort.Descending("$natural"); var options = new FindOptions <rtData, rtData>(); options.Sort = sort; var filter = filter_gen_int; if (qoi != 20) { filter = filter_oth_grp; } var list = collection.Find(filter).ToList(); int CompareASDUInDest(rtData x, rtData y) { var asdu1 = x.protocolDestinations[0].protocolDestinationASDU; foreach (var dst in x.protocolDestinations) { if (dst.protocolDestinationConnectionNumber == srv.protocolConnectionNumber) { asdu1 = dst.protocolDestinationASDU; } } var asdu2 = y.protocolDestinations[0].protocolDestinationASDU; foreach (var dst in y.protocolDestinations) { if (dst.protocolDestinationConnectionNumber == srv.protocolConnectionNumber) { asdu2 = dst.protocolDestinationASDU; } } if (asdu1 == asdu2) { return(0); } if (asdu1 > asdu2) { return(1); } return(-1); } list.Sort(CompareASDUInDest); // order by ASDU Log(conNameStr + "[" + qoi + "] Group request, " + list.Count() + " objects to send.", LogLevelBasic); connection.SendACT_CON(asdu, false); // confirm positive var lastasdu = -1; var cntasduobj = 0; ASDU nwasdu = null; foreach (rtData entry in list) { Log(conNameStr + "[" + qoi + "] " + entry.tag.ToString() + " " + entry.value + " Key " + entry._id, LogLevelDetailed); foreach (var dst in entry.protocolDestinations) { var q = new QualityDescriptor(); q.Invalid = entry.invalid.ToBoolean(); q.Substituted = entry.substituted.ToBoolean(); q.NonTopical = false; q.Blocked = false; q.Overflow = entry.overflow.ToBoolean(); if (dst.protocolDestinationConnectionNumber == srv.protocolConnectionNumber) { if ((lastasdu != dst.protocolDestinationASDU && cntasduobj > 0) || cntasduobj >= 30) { if (nwasdu != null) { Log(conNameStr + "[" + qoi + "] Send ASDU TI:" + System.Convert.ToByte(nwasdu.TypeId) + "." + nwasdu.TypeId + " CA:" + nwasdu.Ca + " with " + nwasdu.NumberOfElements + " objects."); connection.SendASDU(nwasdu); nwasdu = null; cntasduobj = 0; lastasdu = -1; } } switch (dst.protocolDestinationASDU.ToInt32()) { case 1: case 30: if (cntasduobj == 0) { nwasdu = new ASDU(cp, (CauseOfTransmission)qoi, false, false, System.Convert.ToByte(cp.OA), System.Convert.ToInt32(dst.protocolDestinationCommonAddress.ToDouble()), false); } if (nwasdu != null) { var bval = entry.value != 0 ? true : false; if (dst.protocolDestinationKConv1 == -1) { bval = !bval; } nwasdu .AddInformationObject(new SinglePointInformation(System.Convert.ToInt32(dst.protocolDestinationObjectAddress.ToDouble()), bval, q)); cntasduobj++; } break; case 3: case 31: if (cntasduobj == 0) { nwasdu = new ASDU(cp, (CauseOfTransmission)qoi, false, false, System.Convert.ToByte(cp.OA), System.Convert.ToInt32(dst.protocolDestinationCommonAddress.ToDouble()), false); } if (nwasdu != null) { var dpval = entry.value != 0 ? DoublePointValue.ON : DoublePointValue.OFF; if (dst.protocolDestinationKConv1 == -1) { dpval = entry.value != 0 ? DoublePointValue.OFF : DoublePointValue.ON; } if (entry.transient.ToBoolean()) { dpval = DoublePointValue.INTERMEDIATE; } nwasdu .AddInformationObject(new DoublePointInformation(System.Convert.ToInt32(dst.protocolDestinationObjectAddress.ToDouble()), dpval, q)); cntasduobj++; } break; case 5: case 32: if (cntasduobj == 0) { nwasdu = new ASDU(cp, (CauseOfTransmission)qoi, false, false, System.Convert.ToByte(cp.OA), System.Convert.ToInt32(dst.protocolDestinationCommonAddress.ToDouble()), false); } if (nwasdu != null) { var val = dst.protocolDestinationKConv1.ToDouble() * System.Convert.ToDouble(entry.value) + dst.protocolDestinationKConv2.ToDouble(); if (val > 63) { val = 63; q.Overflow = true; } else if (val < -64) { val = -64; q.Overflow = true; } nwasdu .AddInformationObject(new StepPositionInformation(System.Convert.ToInt32(dst.protocolDestinationObjectAddress.ToDouble()), System.Convert.ToInt16(val), entry.transient.ToBoolean(), q)); cntasduobj++; } break; case 9: case 34: if (cntasduobj == 0) { nwasdu = new ASDU(cp, (CauseOfTransmission)qoi, false, false, System.Convert.ToByte(cp.OA), System.Convert.ToInt32(dst.protocolDestinationCommonAddress.ToDouble()), false); } if (nwasdu != null) { var val = dst.protocolDestinationKConv1.ToDouble() * System.Convert.ToDouble(entry.value) + dst.protocolDestinationKConv2.ToDouble(); if (val > 32767) { val = 32767; q.Overflow = true; } else if (val < -32768) { val = -32768; q.Overflow = true; } nwasdu .AddInformationObject(new MeasuredValueNormalized(System.Convert.ToInt32(dst.protocolDestinationObjectAddress.ToDouble()), System.Convert.ToInt16(val), new QualityDescriptor())); cntasduobj++; } break; case 11: case 35: if (cntasduobj == 0) { nwasdu = new ASDU(cp, (CauseOfTransmission)qoi, false, false, System.Convert.ToByte(cp.OA), System.Convert.ToInt32(dst.protocolDestinationCommonAddress.ToDouble()), false); } if (nwasdu != null) { var val = dst.protocolDestinationKConv1.ToDouble() * System.Convert.ToDouble(entry.value) + dst.protocolDestinationKConv2.ToDouble(); if (val > 32767) { val = 32767; q.Overflow = true; } else if (val < -32768) { val = -32768; q.Overflow = true; } nwasdu .AddInformationObject(new MeasuredValueScaled(System.Convert.ToInt32(dst.protocolDestinationObjectAddress.ToDouble()), System.Convert.ToInt16(val), new QualityDescriptor())); cntasduobj++; } break; case 13: case 36: if (cntasduobj == 0) { nwasdu = new ASDU(cp, (CauseOfTransmission)qoi, false, false, System.Convert.ToByte(cp.OA), System.Convert.ToInt32(dst.protocolDestinationCommonAddress.ToDouble()), false); } if (nwasdu != null) { var val = dst.protocolDestinationKConv1.ToDouble() * System.Convert.ToDouble(entry.value) + dst.protocolDestinationKConv2.ToDouble(); nwasdu .AddInformationObject(new MeasuredValueShort(System.Convert.ToInt32(dst.protocolDestinationObjectAddress.ToDouble()), System.Convert.ToSingle(val), new QualityDescriptor())); cntasduobj++; } break; default: break; } lastasdu = dst.protocolDestinationASDU.ToInt32(); break; } } } if (nwasdu != null) { Log(conNameStr + "[" + qoi + "] Send ASDU TI:" + System.Convert.ToByte(nwasdu.TypeId) + "." + nwasdu.TypeId + " CA:" + nwasdu.Ca + " with " + nwasdu.NumberOfElements + " objects.", LogLevelBasic); connection.SendASDU(nwasdu); nwasdu = null; } connection.SendACT_TERM(asdu); Log(conNameStr + "[" + qoi + "] Group interrogation END", LogLevelBasic); } catch (Exception e) { if (e.Message == "Connection not active") { return(true); } Log("Exception on Interrogation"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); connection.SendACT_CON(asdu, true); // negative confirm return(true); } return(true); }
// a process to dequeue iec data and send to connections static void DequeueIecInfo() { do { var sentSomething = false; foreach (IEC10X_connection srv in IEC10Xconns ) { InfoCA ica; if (srv.infoCAQueue.TryDequeue(out ica)) { sentSomething = true; var conNameStr = srv.name + " - "; ApplicationLayerParameters alp = srv.server.Parameters; var newAsdu = new ASDU(alp, CauseOfTransmission.SPONTANEOUS, false, false, System.Convert.ToByte(alp.OA), ica.ca, false); newAsdu.AddInformationObject(ica.io); var cnt = 1; InfoCA ica2; // keep the same asdu while same type and common address, and size fits while (srv.infoCAQueue.TryPeek(out ica2)) // do not remove from queue { if (ica2.ca == ica.ca && ica2.io.Type == ica.io.Type && cnt < 30) // check for same data type/ca and size { if (srv.infoCAQueue.TryDequeue(out ica2)) // will remove from queue only to send { newAsdu.AddInformationObject(ica2.io); cnt++; } else { break; // could not remove from queue, move on to enqueue asdu } } else { // changed type or common address, move on to enqueue asdu break; } } srv.server.EnqueueUserDataClass1(newAsdu); if (LogLevel >= LogLevelBasic) { Log(conNameStr + "Spont ASDU Type: " + ica.io.Type + " with " + newAsdu.NumberOfElements + " objects", LogLevelBasic); } } } if (!sentSomething) // if nothing was sent { Thread.Sleep(200); // let's give some time to wait for more data } } while (true); }
static void Main(string[] args) { Log("{json:scada} IEC60870-5-104 Driver - Copyright 2020 RLO"); Log("Driver version " + DriverVersion); Log("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString()); if (args.Length > 0) // first argument in number of the driver instance { int num; bool res = int.TryParse(args[0], out num); if (res) { ProtocolDriverInstanceNumber = num; } } if (args.Length > 1) // second argument is logLevel { int num; bool res = int.TryParse(args[1], out num); if (res) { LogLevel = num; } } string fname = JsonConfigFilePath; if (args.Length > 2) // third argument is config file name { if (File.Exists(args[2])) { fname = args[2]; } } if (!File.Exists(fname)) { fname = JsonConfigFilePathAlt; } if (!File.Exists(fname)) { Log("Missing config file " + JsonConfigFilePath); Environment.Exit(-1); } Log("Reading config file " + fname); string json = File.ReadAllText(fname); JSConfig = JsonSerializer.Deserialize <JSONSCADAConfig>(json); if ( JSConfig.mongoConnectionString == "" || JSConfig.mongoConnectionString == null ) { Log("Missing MongoDB connection string in JSON config file " + fname); Environment.Exit(-1); } // Log("MongoDB connection string: " + JSConfig.mongoConnectionString); if ( JSConfig.mongoDatabaseName == "" || JSConfig.mongoDatabaseName == null ) { Log("Missing MongoDB database name in JSON config file " + fname); Environment.Exit(-1); } Log("MongoDB database name: " + JSConfig.mongoDatabaseName); if (JSConfig.nodeName == "" || JSConfig.nodeName == null) { Log("Missing nodeName parameter in JSON config file " + fname); Environment.Exit(-1); } Log("Node name: " + JSConfig.nodeName); var Client = ConnectMongoClient(JSConfig); var DB = Client.GetDatabase(JSConfig.mongoDatabaseName); // read and process instances configuration var collinsts = DB .GetCollection <protocolDriverInstancesClass >(ProtocolDriverInstancesCollectionName); var instances = collinsts .Find(inst => inst.protocolDriver == ProtocolDriverName && inst.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && inst.enabled == true) .ToList(); var foundInstance = false; foreach (protocolDriverInstancesClass inst in instances) { if ( ProtocolDriverName == inst.protocolDriver && ProtocolDriverInstanceNumber == inst.protocolDriverInstanceNumber ) { foundInstance = true; if (!inst.enabled) { Log("Driver instance [" + ProtocolDriverInstanceNumber.ToString() + "] disabled!"); Environment.Exit(-1); } Log("Instance: " + inst.protocolDriverInstanceNumber.ToString()); var nodefound = false; foreach (var name in inst.nodeNames) { if (JSConfig.nodeName == name) { nodefound = true; } } if (!nodefound) { Log("Node '" + JSConfig.nodeName + "' not found in instances configuration!"); Environment.Exit(-1); } DriverInstance = inst; break; } break; // process just first result } if (!foundInstance) { Log("Driver instance [" + ProtocolDriverInstanceNumber + "] not found in configuration!"); Environment.Exit(-1); } // read and process connections configuration for this driver instance var collconns = DB .GetCollection <IEC10X_connection>(ProtocolConnectionsCollectionName); var conns = collconns .Find(conn => conn.protocolDriver == ProtocolDriverName && conn.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && conn.enabled == true) .ToList(); foreach (IEC10X_connection isrv in conns) { if (isrv.ipAddresses.Length < 1) { Log("Missing ipAddresses list!"); Environment.Exit(-1); } IEC10Xconns.Add(isrv); Log(isrv.name.ToString() + " - New Connection"); } if (IEC10Xconns.Count == 0) { Log("No connections found!"); Environment.Exit(-1); } // start thread to process redundancy control Thread thrMongoRedundacy = new Thread(() => ProcessRedundancyMongo(JSConfig)); thrMongoRedundacy.Start(); // start thread to update acquired data to database Thread thrMongo = new Thread(() => ProcessMongo(JSConfig)); thrMongo.Start(); // start thread to watch for commands in the database using a change stream Thread thrMongoCmd = new Thread(() => ProcessMongoCmd(JSConfig)); thrMongoCmd.Start(); Log("Setting up IEC Connections & ASDU handlers..."); int cntIecSrv = 0; foreach (IEC10X_connection srv in IEC10Xconns) { var apcipars = new APCIParameters(); apcipars.K = srv.k; apcipars.W = srv.w; apcipars.T0 = srv.t0; apcipars.T1 = srv.t1; apcipars.T2 = srv.t2; apcipars.T3 = srv.t3; var alpars = new ApplicationLayerParameters(); alpars.SizeOfCOT = srv.sizeOfCOT; alpars.SizeOfCA = srv.sizeOfCA; alpars.SizeOfIOA = srv.sizeOfIOA; alpars.OA = srv.localLinkAddress; TlsSecurityInformation secInfo = null; if (srv.localCertFilePath != "") { try { // Own certificate has to be a pfx file that contains the private key X509Certificate2 ownCertificate = new X509Certificate2(srv.localCertFilePath); // Create a new security information object to configure TLS secInfo = new TlsSecurityInformation(null, ownCertificate); // Add allowed server certificates - not required when AllowOnlySpecificCertificates == false secInfo.AddAllowedCertificate(new X509Certificate2(srv.peerCertFilePath)); // Add a CA certificate to check the certificate provided by the server - not required when ChainValidation == false secInfo.AddCA(new X509Certificate2(srv.rootCertFilePath)); // Check if the certificate is signed by a provided CA secInfo.ChainValidation = srv.chainValidation; // Check that the shown server certificate is in the list of allowed certificates secInfo.AllowOnlySpecificCertificates = srv.allowOnlySpecificCertificates; } catch (Exception e) { Log(srv.name + " - Error configuring TLS certficates."); Log(srv.name + " - " + e.Message); Environment.Exit(1); } } var tcpPort = 2404; string[] ipAddrPort = srv.ipAddresses[0].Split(':'); if (ipAddrPort.Length > 1) { if (int.TryParse(ipAddrPort[1], out _)) { tcpPort = System.Convert.ToInt32(ipAddrPort[1]); } } var con = new Connection(ipAddrPort[0], tcpPort, apcipars, alpars); con.Parameters.OA = srv.localLinkAddress; srv.conn1 = con; srv.conn2 = con; srv.connection = con; srv.CntGI = srv.giInterval - 3; srv.CntTestCommand = srv.testCommandInterval - 1; srv.CntTimeSync = 0; srv.CntTestCommandSeq = 0; if (LogLevel >= LogLevelDebug) { con.DebugOutput = true; } con.SetASDUReceivedHandler(AsduReceivedHandler, cntIecSrv); con.SetConnectionHandler(ConnectionHandler, cntIecSrv); if (srv.ipAddresses.Length > 1) // is there a secondary server ? { string[] ipAddrPort2 = srv.ipAddresses[1].Split(':'); if (ipAddrPort2.Length > 1) { if (int.TryParse(ipAddrPort2[1], out _)) { tcpPort = System.Convert.ToInt32(ipAddrPort2[1]); } } var c2 = new Connection(ipAddrPort2[0], tcpPort, apcipars, alpars); con.Parameters.OA = srv.localLinkAddress; srv.conn2 = c2; srv.connection = c2; // force initial swap to primary server if (LogLevel >= LogLevelDebug) { c2.DebugOutput = true; } c2.SetASDUReceivedHandler(AsduReceivedHandler, cntIecSrv); c2.SetConnectionHandler(ConnectionHandler, cntIecSrv); } if (srv.localCertFilePath != "" && secInfo != null) { srv.conn1.SetTlsSecurity(secInfo); srv.conn2.SetTlsSecurity(secInfo); } // create timer to increment counters each second srv.TimerCnt = new System.Timers.Timer(); srv.TimerCnt.Interval = 1000; srv.TimerCnt.Elapsed += (sender, e) => MyElapsedMethod(sender, e, srv);
public static void Main(string[] args) { bool running = true; Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; running = false; }; // specify application layer parameters (CS 101 and CS 104) var alParams = new ApplicationLayerParameters(); alParams.SizeOfCA = 2; alParams.SizeOfIOA = 3; alParams.MaxAsduLength = 249; // specify APCI parameters (only CS 104) var apciParameters = new APCIParameters(); apciParameters.K = 12; apciParameters.W = 8; apciParameters.T0 = 10; apciParameters.T1 = 15; apciParameters.T2 = 10; apciParameters.T3 = 20; Server server = new Server(apciParameters, alParams); server.DebugOutput = true; server.MaxQueueSize = 10; server.EnqueueMode = EnqueueMode.REMOVE_OLDEST; server.SetInterrogationHandler(interrogationHandler, null); server.SetASDUHandler(asduHandler, null); server.Start(); ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false); EndOfInitialization eoi = new EndOfInitialization(0); newAsdu.AddInformationObject(eoi); server.EnqueueASDU(newAsdu); int waitTime = 1000; while (running) { Thread.Sleep(100); if (waitTime > 0) { waitTime -= 100; } else { newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false); newAsdu.AddInformationObject(new MeasuredValueScaled(110, -1, new QualityDescriptor())); server.EnqueueASDU(newAsdu); waitTime = 1000; } } Console.WriteLine("Stop server"); server.Stop(); }
// This process watches (via change stream) for point updates // Forward data to its connections static async void ProcessMongoCS(JSONSCADAConfig jsConfig) { do { try { var Client = ConnectMongoClient(jsConfig); var DB = Client.GetDatabase(jsConfig.mongoDatabaseName); var collection = DB .GetCollection <rtData>(RealtimeDataCollectionName); bool isMongoLive = DB .RunCommandAsync((Command <BsonDocument>) "{ping:1}") .Wait(1000); if (!isMongoLive) { throw new Exception("Error on connection " + jsConfig.mongoConnectionString); } Log("MongoDB CMD CS - Start listening for realtime data updates via changestream..."); // observe updates and replaces, avoid updates with sourceDataUpdateField (those are handled by cs_data_processor.js) var filter = "{ $or: [{ $and:[{ 'updateDescription.updatedFields.sourceDataUpdate': { $exists: false } },{ operationType: 'update' }] }, { operationType: 'replace'}] }"; var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <rtData > >().Match(filter); var changeStreamOptions = new ChangeStreamOptions { FullDocument = ChangeStreamFullDocumentOption.UpdateLookup }; using (var cursor = await collection.WatchAsync(pipeline, changeStreamOptions)) { await cursor .ForEachAsync(change => { // process change event, only process updates and replaces if ( change.OperationType == ChangeStreamOperationType.Update || change.OperationType == ChangeStreamOperationType.Replace ) { if (change.FullDocument != null) { if (change.FullDocument.protocolDestinations != null) { foreach (var dst in change.FullDocument.protocolDestinations) { foreach (IEC10X_connection srv in IEC10Xconns ) { if (dst.protocolDestinationConnectionNumber == srv.protocolConnectionNumber) { var conNameStr = srv.name + " - "; ApplicationLayerParameters alp = srv.server.GetApplicationLayerParameters(); var quality = new QualityDescriptor(); quality.Invalid = change.FullDocument.invalid.ToBoolean() || change.FullDocument.overflow.ToBoolean() || change.FullDocument.transient.ToBoolean(); quality.Substituted = change.FullDocument.substituted.ToBoolean(); quality.Blocked = false; quality.NonTopical = false; CP56Time2a cp56timesrc = null; if (change.FullDocument.timeTagAtSource != null) { cp56timesrc = new CP56Time2a(System.Convert.ToDateTime(change.FullDocument.timeTagAtSource).AddHours(dst.protocolDestinationHoursShift.ToDouble())); cp56timesrc.Invalid = false; if (change.FullDocument.timeTagAtSourceOk != null) { cp56timesrc.Invalid = !change.FullDocument.timeTagAtSourceOk.ToBoolean(); } else { cp56timesrc.Invalid = true; } } var io = BuildInfoObj( dst.protocolDestinationASDU.ToInt32(), dst.protocolDestinationObjectAddress.ToInt32(), change.FullDocument.value.ToDouble(), false, 0, quality, cp56timesrc ); if (io != null) { // queue data to make possible to assemble an ASDU with many elements, will send on DequeueIecInfo InfoCA ica = new InfoCA() { io = io, ca = dst.protocolDestinationCommonAddress.ToInt32() }; srv.infoCAQueue.Enqueue(ica); if (LogLevel >= LogLevelDetailed) { Log(conNameStr + "Spont Tag:" + change.FullDocument.tag + " Value:" + change.FullDocument.value + " Key:" + change.FullDocument._id + " TI:" + dst.protocolDestinationASDU.ToInt32() + " CA:" + dst.protocolDestinationCommonAddress + (cp56timesrc == null ? "" : " " + cp56timesrc.ToString()), LogLevelDetailed); } } } } } } } } }); } } catch (Exception e) { Log("Exception MongoCmd"); Log(e); Log(e .ToString() .Substring(0, e.ToString().IndexOf(Environment.NewLine))); System.Threading.Thread.Sleep(3000); } }while (true); }
public Server(uint port) { this.localPort = (int)port; this.apciParameters = new APCIParameters(); this.alParameters = new ApplicationLayerParameters(); }
private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi) { Console.WriteLine("Interrogation for group " + qoi); ApplicationLayerParameters cp = connection.GetApplicationLayerParameters(); connection.SendACT_CON(asdu, false); // send information objects ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false); newAsdu.AddInformationObject(new MeasuredValueScaled(100, -1, new QualityDescriptor())); newAsdu.AddInformationObject(new MeasuredValueScaled(101, 23, new QualityDescriptor())); newAsdu.AddInformationObject(new MeasuredValueScaled(102, 2300, new QualityDescriptor())); connection.SendASDU(newAsdu); newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false); newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor(), new CP56Time2a(DateTime.Now))); connection.SendASDU(newAsdu); newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false); newAsdu.AddInformationObject(new SinglePointWithCP56Time2a(104, true, new QualityDescriptor(), new CP56Time2a(DateTime.Now))); connection.SendASDU(newAsdu); // send sequence of information objects newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true); newAsdu.AddInformationObject(new SinglePointInformation(200, true, new QualityDescriptor())); newAsdu.AddInformationObject(new SinglePointInformation(201, false, new QualityDescriptor())); newAsdu.AddInformationObject(new SinglePointInformation(202, true, new QualityDescriptor())); newAsdu.AddInformationObject(new SinglePointInformation(203, false, new QualityDescriptor())); newAsdu.AddInformationObject(new SinglePointInformation(204, true, new QualityDescriptor())); newAsdu.AddInformationObject(new SinglePointInformation(205, false, new QualityDescriptor())); newAsdu.AddInformationObject(new SinglePointInformation(206, true, new QualityDescriptor())); newAsdu.AddInformationObject(new SinglePointInformation(207, false, new QualityDescriptor())); connection.SendASDU(newAsdu); newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(300, -1.0f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(301, -0.5f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(302, -0.1f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(303, .0f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(304, 0.1f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(305, 0.2f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(306, 0.5f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(307, 0.7f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(308, 0.99f)); newAsdu.AddInformationObject(new MeasuredValueNormalizedWithoutQuality(309, 1f)); connection.SendASDU(newAsdu); connection.SendACT_TERM(asdu); return(true); }
/// <summary> /// Create a new server using default connection parameters /// </summary> public Server() { this.apciParameters = new APCIParameters(); this.alParameters = new ApplicationLayerParameters(); }
static void Main(string[] args) { Log("{json:scada} IEC60870-5-104 Driver - Copyright 2020 RLO"); Log("Driver version " + DriverVersion); Log("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString()); if (args.Length > 0) // first argument in number of the driver instance { int num; bool res = int.TryParse(args[0], out num); if (res) { ProtocolDriverInstanceNumber = num; } } if (args.Length > 1) // second argument is logLevel { int num; bool res = int.TryParse(args[1], out num); if (res) { LogLevel = num; } } string fname = JsonConfigFilePath; if (!File.Exists(fname)) { fname = JsonConfigFilePathAlt; } if (!File.Exists(fname)) { Log("Missing config file " + JsonConfigFilePath); Environment.Exit(-1); } Log("Reading config file " + fname); string json = File.ReadAllText(fname); JSConfig = JsonSerializer.Deserialize <JSONSCADAConfig>(json); if ( JSConfig.mongoConnectionString == "" || JSConfig.mongoConnectionString == null ) { Log("Missing MongoDB connection string in JSON config file " + fname); Environment.Exit(-1); } // Log("MongoDB connection string: " + JSConfig.mongoConnectionString); if ( JSConfig.mongoDatabaseName == "" || JSConfig.mongoDatabaseName == null ) { Log("Missing MongoDB database name in JSON config file " + fname); Environment.Exit(-1); } Log("MongoDB database name: " + JSConfig.mongoDatabaseName); if (JSConfig.nodeName == "" || JSConfig.nodeName == null) { Log("Missing nodeName parameter in JSON config file " + fname); Environment.Exit(-1); } Log("Node name: " + JSConfig.nodeName); var Client = ConnectMongoClient(JSConfig); var DB = Client.GetDatabase(JSConfig.mongoDatabaseName); // read and process instances configuration var collinsts = DB .GetCollection <protocolDriverInstancesClass >(ProtocolDriverInstancesCollectionName); var instances = collinsts .Find(inst => inst.protocolDriver == ProtocolDriverName && inst.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && inst.enabled == true) .ToList(); var foundInstance = false; foreach (protocolDriverInstancesClass inst in instances) { if ( ProtocolDriverName == inst.protocolDriver && ProtocolDriverInstanceNumber == inst.protocolDriverInstanceNumber ) { foundInstance = true; if (!inst.enabled) { Log("Driver instance [" + ProtocolDriverInstanceNumber.ToString() + "] disabled!"); Environment.Exit(-1); } Log("Instance: " + inst.protocolDriverInstanceNumber.ToString()); var nodefound = false; foreach (var name in inst.nodeNames) { if (JSConfig.nodeName == name) { nodefound = true; } } if (!nodefound) { Log("Node '" + JSConfig.nodeName + "' not found in instances configuration!"); Environment.Exit(-1); } DriverInstance = inst; break; } break; // process just first result } if (!foundInstance) { Log("Driver instance [" + ProtocolDriverInstanceNumber + "] not found in configuration!"); Environment.Exit(-1); } // read and process connections configuration for this driver instance var collconns = DB .GetCollection <IEC10X_connection>(ProtocolConnectionsCollectionName); var conns = collconns .Find(conn => conn.protocolDriver == ProtocolDriverName && conn.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && conn.enabled == true) .ToList(); foreach (IEC10X_connection isrv in conns) { if (isrv.ipAddresses.Length < 1) { Log("Missing ipAddresses list!"); Environment.Exit(-1); } IEC10Xconns.Add(isrv); Log(isrv.name.ToString() + " - New Connection"); } if (IEC10Xconns.Count == 0) { Log("No connections found!"); Environment.Exit(-1); } // start thread to process redundancy control Thread thrMongoRedundacy = new Thread(() => ProcessRedundancyMongo(JSConfig)); thrMongoRedundacy.Start(); // start thread to update acquired data to database Thread thrMongo = new Thread(() => ProcessMongo(JSConfig)); thrMongo.Start(); // start thread to watch for commands in the database using a change stream Thread thrMongoCmd = new Thread(() => ProcessMongoCmd(JSConfig)); thrMongoCmd.Start(); Log("Setting up IEC Connections & ASDU handlers..."); int cntIecSrv = 0; foreach (IEC10X_connection srv in IEC10Xconns) { var apcipars = new APCIParameters(); apcipars.K = srv.k; apcipars.W = srv.w; apcipars.T0 = srv.t0; apcipars.T1 = srv.t1; apcipars.T2 = srv.t2; apcipars.T3 = srv.t3; var alpars = new ApplicationLayerParameters(); alpars.SizeOfCOT = srv.sizeOfCOT; alpars.SizeOfCA = srv.sizeOfCA; alpars.SizeOfIOA = srv.sizeOfIOA; alpars.OA = srv.localLinkAddress; var tcpPort = 2404; string[] ipAddrPort = srv.ipAddresses[0].Split(':'); if (ipAddrPort.Length > 1) { if (int.TryParse(ipAddrPort[1], out _)) { tcpPort = System.Convert.ToInt32(ipAddrPort[1]); } } var con = new Connection(ipAddrPort[0], tcpPort, apcipars, alpars); con.Parameters.OA = srv.localLinkAddress; srv.connection = con; srv.CntGI = srv.giInterval - 3; srv.CntTestCommand = srv.testCommandInterval - 1; srv.CntTimeSync = 0; srv.CntTestCommandSeq = 0; if (LogLevel >= LogLevelDebug) { con.DebugOutput = true; } con.SetASDUReceivedHandler(AsduReceivedHandler, cntIecSrv); con.SetConnectionHandler(ConnectionHandler, cntIecSrv); // create timer to increment counters each second srv.TimerCnt = new System.Timers.Timer(); srv.TimerCnt.Interval = 1000; srv.TimerCnt.Elapsed += (sender, e) => MyElapsedMethod(sender, e, srv);
public static void Main(string[] args) { Log("{json:scada} IEC60870-5-101 Driver - Copyright 2020 RLO"); Log("Driver version " + DriverVersion); Log("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString()); if (args.Length > 0) // first argument in number of the driver instance { int num; bool res = int.TryParse(args[0], out num); if (res) { ProtocolDriverInstanceNumber = num; } } if (args.Length > 1) // second argument is logLevel { int num; bool res = int.TryParse(args[1], out num); if (res) { LogLevel = num; } } string fname = JsonConfigFilePath; if (args.Length > 2) // third argument is config file name { if (File.Exists(args[2])) { fname = args[2]; } } if (!File.Exists(fname)) { fname = JsonConfigFilePathAlt; } if (!File.Exists(fname)) { Log("Missing config file " + JsonConfigFilePath); Environment.Exit(-1); } Log("Reading config file " + fname); string json = File.ReadAllText(fname); JSConfig = JsonSerializer.Deserialize <JSONSCADAConfig>(json); if ( JSConfig.mongoConnectionString == "" || JSConfig.mongoConnectionString == null ) { Log("Missing MongoDB connection string in JSON config file " + fname); Environment.Exit(-1); } // Log("MongoDB connection string: " + JSConfig.mongoConnectionString); if ( JSConfig.mongoDatabaseName == "" || JSConfig.mongoDatabaseName == null ) { Log("Missing MongoDB database name in JSON config file " + fname); Environment.Exit(-1); } Log("MongoDB database name: " + JSConfig.mongoDatabaseName); if (JSConfig.nodeName == "" || JSConfig.nodeName == null) { Log("Missing nodeName parameter in JSON config file " + fname); Environment.Exit(-1); } Log("Node name: " + JSConfig.nodeName); // connect to MongoDB Database server var Client = ConnectMongoClient(JSConfig); var DB = Client.GetDatabase(JSConfig.mongoDatabaseName); // read and process instances configuration var collinsts = DB .GetCollection <protocolDriverInstancesClass >(ProtocolDriverInstancesCollectionName); var instances = collinsts .Find(inst => inst.protocolDriver == ProtocolDriverName && inst.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && inst.enabled == true) .ToList(); var foundInstance = false; foreach (protocolDriverInstancesClass inst in instances) { if ( ProtocolDriverName == inst.protocolDriver && ProtocolDriverInstanceNumber == inst.protocolDriverInstanceNumber ) { foundInstance = true; if (!inst.enabled) { Log("Driver instance [" + ProtocolDriverInstanceNumber.ToString() + "] disabled!"); Environment.Exit(-1); } Log("Instance: " + inst.protocolDriverInstanceNumber.ToString()); var nodefound = false; foreach (var name in inst.nodeNames) { if (JSConfig.nodeName == name) { nodefound = true; } } if (!nodefound) { Log("Node '" + JSConfig.nodeName + "' not found in instances configuration!"); Environment.Exit(-1); } DriverInstance = inst; break; } break; // process just first result } if (!foundInstance) { Log("Driver instance [" + ProtocolDriverInstanceNumber + "] not found in configuration!"); Environment.Exit(-1); } // read and process connections configuration for this driver instance var collconns = DB .GetCollection <IEC10X_connection>(ProtocolConnectionsCollectionName); var conns = collconns .Find(conn => conn.protocolDriver == ProtocolDriverName && conn.protocolDriverInstanceNumber == ProtocolDriverInstanceNumber && conn.enabled == true) .ToList(); foreach (IEC10X_connection isrv in conns) { IEC10Xconns.Add(isrv); Log(isrv.name.ToString() + " - New Connection"); } if (IEC10Xconns.Count == 0) { Log("No connections found!"); Environment.Exit(-1); } // start thread to process redundancy control Thread thrMongoRedundacy = new Thread(() => ProcessRedundancyMongo(JSConfig)); thrMongoRedundacy.Start(); // start thread to update acquired data to database Thread thrMongo = new Thread(() => ProcessMongo(JSConfig)); thrMongo.Start(); // start thread to watch for commands in the database using a change stream Thread thrMongoCmd = new Thread(() => ProcessMongoCmd(JSConfig)); thrMongoCmd.Start(); Log("Setting up IEC Connections & ASDU handlers..."); int cntIecSrv = 0; foreach (IEC10X_connection srv in IEC10Xconns) { TcpClientVirtualSerialPort virtualPort = null; SerialPort port = null; if (srv.portName.Contains(":")) { var hostport = srv.portName.Split(":"); virtualPort = new TcpClientVirtualSerialPort(hostport[0], System.Convert.ToInt32(hostport[1])); if (LogLevel >= LogLevelDebug) { virtualPort.DebugOutput = true; } virtualPort.Start(); } else { port = new SerialPort(); port.PortName = srv.portName; port.BaudRate = srv.baudRate; switch (srv.parity.ToLower()) { default: // Even is the starndard parity for 101 case "even": port.Parity = Parity.Even; break; case "none": port.Parity = Parity.None; break; case "odd": port.Parity = Parity.Odd; break; case "mark": port.Parity = Parity.Mark; break; case "space": port.Parity = Parity.Space; break; } switch (srv.stopBits.ToLower()) { default: case "one": port.StopBits = StopBits.One; break; case "one5": case "onepointfive": port.StopBits = StopBits.OnePointFive; break; case "two": port.StopBits = StopBits.Two; break; } switch (srv.handshake.ToLower()) { default: case "none": port.Handshake = Handshake.None; break; case "xon": case "xonxoff": port.Handshake = Handshake.XOnXOff; break; case "rts": case "requesttosend": port.Handshake = Handshake.RequestToSend; break; case "rtsxon": case "requesttosendxonxoff": port.Handshake = Handshake.RequestToSendXOnXOff; break; } port.Open(); port.DiscardInBuffer(); } LinkLayerParameters llParameters = new LinkLayerParameters(); llParameters.AddressLength = srv.sizeOfLinkAddress; llParameters.TimeoutForACK = srv.timeoutForACK; llParameters.TimeoutRepeat = srv.timeoutRepeat; llParameters.UseSingleCharACK = srv.useSingleCharACK; ApplicationLayerParameters alpars = new ApplicationLayerParameters(); alpars.SizeOfCOT = srv.sizeOfCOT; alpars.SizeOfCA = srv.sizeOfCA; alpars.SizeOfIOA = srv.sizeOfIOA; alpars.OA = srv.localLinkAddress; CS101Master master; if (port != null) { Log("Serial Port: " + srv.portName); master = new CS101Master(port, LinkLayerMode.UNBALANCED, llParameters, alpars); } else { Log("Virtual Serial Port: " + srv.portName); master = new CS101Master(virtualPort, LinkLayerMode.UNBALANCED, llParameters, alpars); } // master.OwnAddress = srv.localLinkAddress; master.SetTimeouts(srv.timeoutMessage, srv.timeoutCharacter); master.AddSlave(srv.remoteLinkAddress); master.SlaveAddress = srv.remoteLinkAddress; srv.master = master; srv.CntGI = srv.giInterval - 5; srv.CntTestCommand = srv.testCommandInterval - 2; srv.CntTimeSync = srv.timeSyncInterval; if (LogLevel >= LogLevelDebug) { master.DebugOutput = true; } master.SetASDUReceivedHandler(AsduReceivedHandlerPre, cntIecSrv); master.SetLinkLayerStateChangedHandler(linkLayerStateChanged, cntIecSrv); master.SetReceivedRawMessageHandler(rcvdRawMessageHandler, cntIecSrv); master.Start(); // create timer to increment counters each second srv.TimerCnt = new System.Timers.Timer(); srv.TimerCnt.Interval = 1000; srv.TimerCnt.Elapsed += (sender, e) => MyElapsedMethod(sender, e, srv);