GithubHelp home page GithubHelp logo

mz-automation / lib60870.net Goto Github PK

View Code? Open in Web Editor NEW
121.0 21.0 74.0 1.02 MB

Official repository for lib60870.NET an implementation of the IEC 60870-5-101/104 protocols in C#

Home Page: https://www.mz-automation.de/communication-protocols/iec-60870-5-101-104-c-net-source-code-library/

License: GNU General Public License v3.0

C# 100.00%
protocol 60870-5-104 60870-5-101

lib60870.net's People

Contributors

andrepf avatar eroke avatar mzillgith avatar pangweishen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lib60870.net's Issues

Maintain ClientConnection after "Failed to parse ASDU"

The following ASDU leads to the termination of the ClientConnection:

CS104 SLAVE CONNECTION 348: Handle received ASDU
CS104 SLAVE CONNECTION 348: RCVD: 68-24-0C-00-00-00-87-01-14-00-FC-19-0D-94-01-0D-14-FE-FC-19-02-83-DE-C0-04-01-01-09-00-00-5C-44-03-8D-63-08-16-FF
CS104 SLAVE CONNECTION 348: Received I frame: N(S) = 6 N(R) = 0
CS104 SLAVE CONNECTION 348: Enqueue received I-message for processing
CS104 SLAVE CONNECTION 348: Failed to parse ASDU --> close connection
CS104 SLAVE CONNECTION 348: CLOSE CONNECTION!
CS104 SLAVE CONNECTION 348: CONNECTION CLOSED!

This ASDU (TypeID = 135) is not supported by your stack.
Is there a possibility to maintain the connection in case of an unknown ASDU and to handle the ASDU manually or is this behaviour desired and if yes, with which reasoning?

In theory, one would need a RawASDUHandler to decide how to handle these ASDUs to prevent the connection from being terminated.

Exception when call Server.Stop()

Hello,
I catch this exception all the time, when server has connection or no connection to client.
it is from the line of listeningSocket.Shutdown(SocketShutdown.Both);

the server is turned off but exception is still happen at this line.
Please support to check and update.
Thank you.

`System.Net.Sockets.SocketException
HResult=0x80004005
Message=A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied.
Source=System.Net.Sockets
StackTrace:
at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, String callerName)
at System.Net.Sockets.Socket.Shutdown(SocketShutdown how)
at lib60870.CS104.Server.Stop() in D:\work\C#\iec104\lib60870.NET\lib60870\CS104\Server.cs:line 943

This exception was originally thrown at this call stack:
[External Code]
lib60870.CS104.Server.Stop() in Server.cs`

SocketException error codes are not OS specific

I was wondering why I was getting nonsensical SocketException error messages and I traced it back to his line:

https://github.com/mz-automation/lib60870.NET/blob/d291d38961816f8a56e757f7ff8e62e9482245f3/lib60870/CS104/Connection.cs#L1490C40-L1490C40

throw new SocketException(87); // wrong argument

The error code is hardcoded even though it is different among OSs.
Error 87 is "Too many users" on Linux and "The parameter is incorrect." on Windows.

I was wondering why I was getting "Too many users" on Linux even though it was my 1st connection attempt.

Wrong encoding of negative ScaledValue

When a ScaledValue is created with a negative value, it's not encoded correctly to a byte array, so the returned value is invalid. Example code:

// scaledValue.Value is 255 instead of -1
var scaledValue = new ScaledValue(-1);

The problem is located on file ScaledValue.cs, when the Value/ShortValue properties are set.

Single variable Read

I want to read

M_ME_TF_1(36) | Short measured value (FLOAT32) with CP56Time2a asdu adress:2063259
from iec60870 server with c# form application but i don't read. Because i don't understand function of lib60870 like a asdureceivedhandler.

Briefly i want to read one variable (m_me_tf_1(36)) and i want this value assign to label1.text. How can i do this?

Thanks for all....

Best regards...

Talos Security Advisory for MZ Automation GmbH

Hello,
The Cisco Talos team found a security vulnerability affecting MZ Automation. As this is a sensitive security issue, this message is to request an email contact/maintainer handling and PGP key for further communication. If there's an option to mark this issue as private, we can place the details here.

For further information about the Cisco Vendor Vulnerability Reporting and Disclosure Policy please refer to this document which also links to our public PGP key. https://tools.cisco.com/security/center/resources/vendor_vulnerability_policy.html

Please CC [email protected] on all correspondence related to this issue.

###Tested Versions
MZ Automation GmbH lib60870.NET 2.2.0

how to clear ASDUReceivedHandler ??

Hi,
I have developed Dotnet Core Web API using this library when I'm giving the request to the API I'm getting the IEC104 parameters data in API response now the issues is when I'm requesting the API again a second time or third time it is not giving the current updated records it is giving me the old records and new records both but I don't want the old records I want only new records.

My code:

using System;
using System.Collections.Generic;
using System.Threading;
using lib60870;
using lib60870.CS101;
using lib60870.CS104;
using ResultModels;

namespace IEC104Functions
{
    class IEC104FunctionsClass
    {
        public static List<dynamic> resultvqt = new List<dynamic>();

        private static void ConnectionHandler(object parameter, ConnectionEvent connectionEvent)
        {
            switch (connectionEvent)
            {
                case ConnectionEvent.OPENED:
                    Console.WriteLine("Connected");
                    break;
                case ConnectionEvent.CLOSED:
                    Console.WriteLine("Connection closed");
                    break;
                case ConnectionEvent.STARTDT_CON_RECEIVED:
                    Console.WriteLine("STARTDT CON received");
                    break;
                case ConnectionEvent.STOPDT_CON_RECEIVED:
                    Console.WriteLine("STOPDT CON received");
                    break;
            }
        }

        private static bool asduReceivedHandler(object parameter, ASDU asdu)
        {
            switch (asdu.TypeId)
            {
                case TypeID.M_SP_NA_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (SinglePointInformation)asdu.GetElement(i);
                        ResultAsduVQT datavq = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = DateTime.Now
                        };
                        resultvqt.Add(datavq);
                    }
                    break;
                case TypeID.M_SP_TB_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (SinglePointWithCP56Time2a)asdu.GetElement(i);
                        ResultAsduVQT datavqt = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = data.Timestamp.GetDateTime()
                        };
                        resultvqt.Add(datavqt);
                    }
                    break;
                case TypeID.M_DP_NA_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (DoublePointInformation)asdu.GetElement(i);
                        ResultAsduVQT datavq = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = DateTime.Now
                        };
                        resultvqt.Add(datavq);
                    }
                    break;
                case TypeID.M_DP_TB_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (DoublePointWithCP56Time2a)asdu.GetElement(i);
                        ResultAsduVQT datavqt = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = data.Timestamp.GetDateTime()
                        };
                        resultvqt.Add(datavqt);
                    }
                    break;
                case TypeID.M_ST_NA_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (StepPositionInformation)asdu.GetElement(i);
                        ResultAsduVQT datavq = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = DateTime.Now
                        };
                        resultvqt.Add(datavq);
                    }
                    break;
                case TypeID.M_ST_TB_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (StepPositionWithCP56Time2a)asdu.GetElement(i);
                        ResultAsduVQT datavqt = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = data.Timestamp.GetDateTime()
                        };
                        resultvqt.Add(datavqt);
                    }
                    break;
                case TypeID.M_BO_NA_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (Bitstring32)asdu.GetElement(i);
                        ResultAsduVQT datavq = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = DateTime.Now
                        };
                        resultvqt.Add(datavq);
                    }
                    break;
                case TypeID.M_BO_TB_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (Bitstring32WithCP56Time2a)asdu.GetElement(i);
                        ResultAsduVQT datavqt = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = data.Timestamp.GetDateTime()
                        };
                        resultvqt.Add(datavqt);
                    }
                    break;
                case TypeID.M_ME_NA_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (MeasuredValueNormalized)asdu.GetElement(i);
                        ResultAsduVQT datavq = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.NormalizedValue,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = DateTime.Now
                        };
                        resultvqt.Add(datavq);
                    }
                    break;
                case TypeID.M_ME_TB_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (MeasuredValueNormalizedWithCP56Time2a)asdu.GetElement(i);
                        ResultAsduVQT datavqt = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.NormalizedValue,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = data.Timestamp.GetDateTime()
                        };
                        resultvqt.Add(datavqt);
                    }
                    break;
                case TypeID.M_ME_NB_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (MeasuredValueScaled)asdu.GetElement(i);
                        ResultAsduVQT datavq = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.ScaledValue,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = DateTime.Now
                        };
                        resultvqt.Add(datavq);
                    }
                    break;
                case TypeID.M_ME_TE_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement(i);
                        ResultAsduVQT datavqt = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.ScaledValue,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = data.Timestamp.GetDateTime()
                        };
                        resultvqt.Add(datavqt);
                    }
                    break;
                case TypeID.M_ME_NC_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (MeasuredValueShort)asdu.GetElement(i);
                        ResultAsduVQT datavq = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = DateTime.Now
                        };
                        resultvqt.Add(datavq);
                    }
                    break;
                case TypeID.M_ME_TF_1:
                    for (int i = 0; i < asdu.NumberOfElements; i++)
                    {
                        var data = (MeasuredValueShortWithCP56Time2a)asdu.GetElement(i);
                        ResultAsduVQT datavqt = new ResultAsduVQT
                        {
                            IOA = data.ObjectAddress,
                            Value = data.Value,
                            Quality = data.Quality.EncodedValue,
                            Timestamp = data.Timestamp.GetDateTime()
                        };
                        resultvqt.Add(datavqt);
                    }
                    break;
            }
            return true;
        }

        public ResultAsduModel ReadAllParameters(string IpAddress)
        {
            Connection con = new Connection(IpAddress);
            con.DebugOutput = true;
            resultvqt = new List<dynamic>();
            
            con.SetASDUReceivedHandler(asduReceivedHandler, null);
            con.SetConnectionHandler(ConnectionHandler, null);
            bool running = true;

            con.Connect();
            while (running)
            {
                Thread.Sleep(100);
                running = false;
            }
            
            con.Close();

            ResultAsduModel result = new ResultAsduModel()
            {
                data = resultvqt,
                error = false,
                errormessage = null
            };

            return (result);
        }
    }
}

Can anyone help me with how to clear the ASDUReceivedhandler so that I can get only new records??

Thank you..

File service does not support large directories

Should be

  if (!directoryAsdu.AddInformationObject (io)) {
    masterConnection.SendASDU(directoryAsdu);

    directoryAsdu = new ASDU(masterConnection.GetApplicationLayerParameters(), cot, false, false, 0, currentCa, true);
    directoryAsdu.AddInformationObject(io);
  }

SendCommand

Hi, I need a help in order to send command via IEC 104.

I tried to use the standard function SendControlCommand but it turns out that it can not accept the type of command? I am at the beginning od IEC 104 so please if someone could provide me some example with working send command function?

Regards.

Send Invalid Message to Master

How can we send invalid quality message for all information objects from same ASDU to master device?

QualityDescriptor qualityDescriptor = new QualityDescriptor();
qualityDescriptor.Invalid = true;

I can send information object with invalid quality description but I want to send invalid message to without change any value of information objects. Is it possible?

104server Received sequence number out of range

When i start 104server and client connect, it's running normal.
But when S num about 8500, some error occured
this is the debug log:
`CS104 SLAVE CONNECTION 1: SEND I (size = 18) : 68-10-AC-42-3A-00-0B-01-01-00-01-00-6E-00-00-DF-00-00

CS104 SLAVE CONNECTION 1: ------k-buffer------

CS104 SLAVE CONNECTION 1: 0 : S 8528 : time 1689817203216 : 0

CS104 SLAVE CONNECTION 1: 1 : S 8529 : time 1689817203337 : 1

CS104 SLAVE CONNECTION 1: 2 : S 8530 : time 1689817203428 : 2

CS104 SLAVE CONNECTION 1: 3 : S 8531 : time 1689817203521 : 3
CS104 SLAVE CONNECTION 1: 4 : S 8532 : time 1689817203649 : 4
CS104 SLAVE CONNECTION 1: 5 : S 8533 : time 1689817203770 : 5
CS104 SLAVE CONNECTION 1: 6 : S 8534 : time 1689817203863 : 6
CS104 SLAVE CONNECTION 1: 7 : S 8535 : time 1689817203985 : 7
CS104 SLAVE CONNECTION 1: --------------------
CS104 SLAVE CONNECTION 1: RCVD: 68-0E-3A-00-2E-04-64-01-06-00-01-00-00-00-00-14
CS104 SLAVE CONNECTION 1: Received I frame: N(S) = 29 N(R) = 535
CS104 SLAVE CONNECTION 1: Received sequence number out of range
CS104 SLAVE CONNECTION 1: CS104 SLAVE: Queue contains 9 messages (oldest: 0 latest: 8)
Sequence number check failed
CS104 SLAVE CONNECTION 1: CLOSE CONNECTION!
CS104 SLAVE CONNECTION 1: CONNECTION CLOSED!
CS104 SLAVE CONNECTION 1: ProcessASDUs exit thread`

The code is examples cs104-server, just modify the send frequency
`while (running && server.IsRunning())

{
Thread.Sleep(100);
newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
var rnd = new Random().Next(0, 360);
newAsdu.AddInformationObject(new MeasuredValueScaled(110, rnd, new QualityDescriptor()));
server.EnqueueASDU(newAsdu);
}`

Extra code for operation "remove confirmed messages from list" (ClientConnection)

For operation "remove confirmed messages from list" used 2 cycles:
First cycle in class ClientConnection (upwards)

/* remove confirmed messages from list */

Second cycle in class ASDUQueue (downwards)

while (enqueuedASDUs[currentIndex].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)

if set Server.DebugOutput=true i see this lines in console (in your code without time)

08.10.2020 7:52:46.277 CS104 SLAVE: Remove from queue with index 0
08.10.2020 7:52:46.277 CS104 SLAVE: queue state: noASDUs: 5 oldest: 1 latest: 5
08.10.2020 7:52:46.290 CS104 SLAVE: Remove from queue with index 1
08.10.2020 7:52:46.291 CS104 SLAVE: queue state: noASDUs: 5 oldest: 2 latest: 6
08.10.2020 7:52:46.303 CS104 SLAVE: Remove from queue with index 2
08.10.2020 7:52:46.304 CS104 SLAVE: queue state: noASDUs: 4 oldest: 3 latest: 6
08.10.2020 7:52:46.305 CS104 SLAVE: Remove from queue with index 3
08.10.2020 7:52:46.305 CS104 SLAVE: queue state: noASDUs: 3 oldest: 4 latest: 6
08.10.2020 7:52:46.321 CS104 SLAVE: Remove from queue with index 4
08.10.2020 7:52:46.331 CS104 SLAVE: queue state: noASDUs: 2 oldest: 5 latest: 6

This means that First cycle (asduQueue.MarkASDUAsConfirmed) goes 5 times.

Maybe I'm wrong

IEC104 Client Hangs On Connection.Close()

Hi.
When I call Connection.Close(); method for IEC104 Client, the system runs into hang. in the server side shows that client disconnected. but my form freezes in client side. I have Traced the Close() method and freezing occurs after execute line workerThread.Join();.
How can I solve the problem.
Thanks.

Is it a must to INACTIVE previous client connections in a TCP server?

I've noticed that the Activated method in Server.cs file line 628 , deactivate all previous client connections when a new connection is OPENED.

This detail prevents asdu sending to all previous client connections , even if ServerMode is CONNECTION_IS_REDUNDANCY_GROUP.

I'm not sure if it is a bug or a specific related to the protocol, when the server is a multi client tcp server.

Thanks.

internal void Activated(ClientConnection activeConnection)
{
...
  // deactivate all other connections
  foreach (ClientConnection connection in allOpenConnections) {
    if (connection != activeConnection) {
...
      connection.IsActive = false;
...

[Ask] SBO command (bool select = true)

Hello,

As you know for command there is two type. First is direct and second is SBO (select then execute)

In the examples :
con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new DoubleCommand (5001, DoubleCommand.ON, false, 0));

The bool select state is set to "false".
Do you have additional example if the bool select is set to "true"?

If I just set to True, it doesnt work. It will need confirmation first before execute command.

Regards,

Reading data

Hello,,

I have problem to read several data on C#. In example i want to read "single point information", "measured scale" then show the result on "Label or textboxt" simultanously and continuesly. Is it possible?
I tried to copy paste the code from lib60870.NET/examples/cs104-client1/ then change the code from "console.writeln" to "Label.text or textboxt.text" but it doesn't work. =)

I am new comer on C# programming, so please feel free to explain.

best regards,
kemas

Enqueue wait

if (connection.IsActive)

Hi!
I am trying to integrate your libray in my code. I am queuing more than 1000 asdu and I noticed that this method is blocking.
I have commented this "for loop" and the communication is faster and the enqueuing procedure is not blocking anymore.

Am I missing something (I am new to this library)?

Best regards.

There is no method or property to check IEC104 Server is running or not

Hi,
first, thank you for sharing the library, it is a great work.

But I can not find any public method or property to check status of IEC 104 server in
namespace lib60870.CS104
public class Server : Slave

So there is no way to check it.

Can you update this source in new version?
Thank you

IEC60870_5_104_MAX_ASDU_LENGTH (Questions)

// send information objects
var signalAsdu = new ASDU(parameters, cot, isTest, isNegative, oa, ca, isSequence);

        foreach (var rtuSignal in rtuSignals)
        {
            signalAsdu.AddInformationObject(new MeasuredValueNormalized(rtuSignal.IOA, scaleValue(rtuSignal), new QualityDescriptor()));            
        }

        return signalAsdu;

Please see above code. We have created an ASDU unit and trying to add information objects to the unit. We have around 45 Signals to be embedded into the unit object. But the AddInformationObject will not go over 40 because there is no space left due to max length of IEC60870_5_104_MAX_ASDU_LENGTH =249. Is there anyway to go over this limit or we have to send with 2 ASDU unit?

Thanks a lot in advance.

How to handle select-before-execute in a server?

As the title says; I'm just wondering if there is some sort of default stub for this that handles timeout etc., or if this is something that needs to be created from scratch.

I'm using the C# lib. If anyone has some examples of how to do this, that would be helpful too!

I previously posted this here; mz-automation/libiec61850#278, but realized I had the wrong repo!

IEC 104 Client connecting to 100s of devices

I am trying to write a client which will read data from 100s of IEC 104 servers (devices in the field).
I create multiple connections and interrogate periodically (1 minute or so).
I observe that data from only some devices is received while data from other devices does not show up on the log.
Do you have any example of how to interrogate multiple devices?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.