Utilities and SCADA-Adjacent Legacy Code

Utilities and SCADA-Adjacent Legacy Code: Special Considerations for Modernization Teams

The IT/OT boundary in a utility organization is not a clean line on a network diagram. It is a permeable membrane through which data flows in both directions, COBOL batch programs generating setpoint configuration files consumed by PLCs, RPG programs reading SCADA historian data for billing and regulatory reporting, legacy C bridges translating mainframe outputs into formats that distributed control systems understand, JCL job streams that schedule and sequence data exchanges across the boundary on timing that operational processes depend on. The software that manages this flow is neither purely IT nor purely OT. It is the connective tissue that makes utilities function, and it is the category of code that modernization teams are least prepared to analyze when a transformation program begins.

The energy sector is undergoing one of the most significant digital transformations in its history. As utilities modernize their infrastructure with smart grids, connected substations, ICS, and advanced automation, OT networks have become more interconnected than ever before. That interconnection does not eliminate the legacy code layer, it makes it more critical, because every new smart grid endpoint and cloud analytics platform depends on data feeds that originate in applications written decades ago. Modernizing those applications without understanding their role in the operational data chain is not a modernization, it is a disruption with consequences that extend beyond the data center to physical infrastructure.

Find Every Hardcoded Operational Parameter

SMART TS XL locates every EU constant, alarm limit, and protocol address embedded in your legacy code portfolio.

FIND OUT MORE…

What “SCADA-Adjacent” Actually Means

The term SCADA-adjacent describes IT-side software that interfaces with operational technology systems, not the SCADA software itself, not the PLC firmware, not the RTU embedded code, but the business application layer that feeds data into and receives data from those systems. This category is large, underanalyzed, and genuinely different from the rest of the enterprise application portfolio.

In a typical utility environment, SCADA-adjacent code includes:

Rate and setpoint calculation programs. COBOL and PL/I programs that calculate load setpoints, voltage targets, pressure thresholds, and operational limits delivered to SCADA systems as configuration files or direct data feeds. These programs encode regulatory compliance requirements, engineering specifications, and physical safety limits. An incorrect calculation does not produce a wrong number in a report, it produces an incorrect operational setpoint that a control system acts on.

Historian data consumers. RPG and COBOL programs that read operational data from SCADA historian databases for billing, regulatory reporting, and performance analysis. These programs depend on specific data formats, timestamp conventions, and engineering unit definitions that the historian produces. A format change in the historian output, or a change in the IT-side consumer program, can silently corrupt billing calculations or regulatory submissions.

Protocol bridge programs. Custom C programs that translate between mainframe output formats and the file-based or network-based interfaces that DCS (Distributed Control System) and SCADA systems consume. These bridges implement specific protocols, Modbus, DNP3, IEC 61850, proprietary vendor formats, and have hard-coded assumptions about message structure, byte ordering, and timing that exist nowhere in documentation.

Batch-to-realtime data pathways. JCL job streams that schedule and sequence data exchanges across the IT/OT boundary on specific timing windows. A utility’s overnight batch run may produce configuration data that must be available to the SCADA system before morning operations begin. The timing dependency is implicit in the scheduler configuration and the operational expectations of the control room, not documented anywhere in the application code.

Alarm and event processing programs. Programs that receive alarm records from SCADA systems, apply classification and routing logic, generate work orders, and produce regulatory compliance records. The alarm classification logic, which events require which regulatory reports, within which timeframes, is frequently embedded in program code that has accumulated decades of regulatory changes.

This is the code that controls engineers and IT developers both partially own and neither fully understands. When a modernization program asks “what can we change?” the answer for SCADA-adjacent code is almost always “less than you think, and only after more analysis than you planned.”

Why Standard Modernization Analysis Fails Here

Most enterprise modernization analysis frameworks assume that the code being analyzed controls only data and business logic, that a change to a program produces a different data outcome with no physical world consequence. SCADA-adjacent code breaks this assumption in four specific ways.

Physical Consequence of Data Errors

In a standard billing application, an incorrect calculation produces a wrong invoice. The error is discoverable, reversible, and limited in scope. In SCADA-adjacent code, an incorrect calculation may produce an incorrect setpoint, a target value that a control system acts on by adjusting physical parameters: pressure, voltage, flow rate, temperature. The consequence is not a wrong number in a database. It is a physical process running outside its intended parameters, with consequences ranging from inefficiency to equipment damage to safety incidents.

This asymmetry between data error and physical consequence is the fundamental reason SCADA-adjacent code cannot be analyzed with the same risk tolerance as standard business code. A change that “works correctly” from the perspective of producing valid output may still produce operationally incorrect output, within the legal range of the data type, syntactically valid, but physically wrong for the operational context it represents.

Timing Dependencies That Static Analysis Cannot Model

IT-side programs adjacent to SCADA systems frequently have timing constraints that are operationally significant but invisible to static analysis tools. A program that generates configuration data must complete before the SCADA system’s polling cycle reads it. A batch job that aggregates historian data must finish before the end-of-interval timestamp that regulatory reporting requires. A bridge program that relays alarms must process events within the response time that the control room’s operational procedures specify.

These timing constraints exist in the operational procedures of the utility, in the scheduler configuration, and in the implicit understanding of the developers who wrote the programs, not in the source code. Static analysis tools that focus on code structure and data flow have no visibility into timing requirements that exist outside the code itself.

The practical implication: modernization analysis for SCADA-adjacent code must explicitly document the timing context for every program in scope. This requires operational knowledge, interviews with control room operators, review of regulatory compliance schedules, analysis of scheduler job dependencies, not just code analysis.

Safety Function Identification

IEC 61511 (functional safety for process industry sectors) and IEC 61508 (functional safety for electrical/electronic/programmable electronic safety-related systems) define certification requirements for software that performs safety functions. Code that has been certified under these standards is not simply legacy code that can be refactored for maintainability. The certification is to specific code artifacts, the specific version of the specific binary that the certification body evaluated. Changing the code, even to fix a quality issue that would be unremarkable in a business application, invalidates the certification and requires recertification before the changed code can be deployed in a safety function.

Many utilities have SCADA-adjacent programs that perform safety-related calculations, overpressure detection, transformer protection setpoint calculation, emergency shutdown logic, that may be under safety certification requirements without the IT modernization team knowing it. The first analytical question for SCADA-adjacent code is: does any of this code perform a safety function? If yes, which functions, under which certification, and what does changing it require?

Hardware and Protocol Coupling

Protocol bridge programs and embedded interface code have direct dependencies on the hardware and protocol versions they implement. A program that implements Modbus RTU with specific function codes, register maps, and timeout values for a specific model of RTU from a specific vendor is not generically implementing Modbus, it is implementing that specific combination, with assumptions that may not hold for any other configuration.

When this code is analyzed for modernization, the dependency is not just on the COBOL or C source, it is on the RTU device model, the firmware version, the physical wiring topology, and the network configuration. Changes to any of these can break the interface even if the program source code is unchanged. And changes to the program source code can break interfaces that appear unrelated because the bridge was written to compensate for vendor-specific protocol quirks that are not documented anywhere.

The IT/OT Boundary: Where the Code Lives

The Purdue Model (ISA-99 / IEC 62443) defines the conceptual architecture of industrial control system networks in five levels, from physical processes at Level 0 to enterprise business systems at Level 4. SCADA-adjacent legacy code in utility environments typically lives at Levels 3 and 4, the manufacturing operations and enterprise network zones, but its data flows cross into Level 2 (the SCADA supervisory layer) in both directions.

The IT/OT boundary between Levels 3 and 2 is where the security and operational risk is highest. Nation-state actors pre-position inside OT networks months before activation, while ransomware groups now deploy ICS-aware payloads engineered to lock HMIs and halt production. The most frequent entry point is not the embedded SCADA software, it is the IT/OT boundary layer, where IT-side code and OT-side systems exchange data through interfaces that were designed for operational reliability rather than adversarial security.

Understanding the exact set of programs that cross this boundary, and what they do there, is the prerequisite for both modernization planning and security posture improvement. A program that reads from a SCADA historian and writes results to a billing database crosses the boundary in one direction. A program that calculates setpoints and writes them to a configuration directory that a PLC reads crosses it in the other. Both are SCADA-adjacent. Neither appears in a SCADA network scan or an IT application inventory, which is why they are systematically underanalyzed.

Code Patterns Specific to SCADA-Adjacent Programs

Engineering Unit Calculation Code

Engineering unit (EU) calculations convert raw sensor values, typically integer counts from analog-to-digital converters, into physical measurements with specific units, ranges, and precision. A 4-20mA current loop from a pressure transmitter produces a raw count; the EU calculation converts it to PSI or bar with the correct zero and span calibration.

This calculation code has characteristics that distinguish it from standard business logic:

cobol

       CALCULATE-PRESSURE-EU.
      *  RAW-COUNT ranges 0-4095 (12-bit ADC)
      *  SENSOR-ZERO-OFFSET = 819  (4mA = 20% of 4095)
      *  SENSOR-SPAN       = 3276  (16mA span = 80% of 4095)  
      *  RANGE-LOW-PSI     = 0
      *  RANGE-HIGH-PSI    = 500
           COMPUTE EU-PRESSURE-PSI =
               (RAW-COUNT - SENSOR-ZERO-OFFSET) /
               SENSOR-SPAN *
               (RANGE-HIGH-PSI - RANGE-LOW-PSI)
               + RANGE-LOW-PSI
           IF EU-PRESSURE-PSI < RANGE-LOW-PSI OR
              EU-PRESSURE-PSI > RANGE-HIGH-PSI
               MOVE 'RANGE-VIOLATION' TO ALARM-STATUS
               PERFORM GENERATE-ALARM
           END-IF.

The constants in this calculation, SENSOR-ZERO-OFFSET, SENSOR-SPAN, RANGE-LOW-PSI, RANGE-HIGH-PSI, correspond to physical instrument specifications. If they are hardcoded (as they frequently are in legacy code), a change to the physical instrument requires a code change. If they are wrong (due to instrument recalibration, replacement, or original misconfiguration), the EU value is systematically incorrect for every record the program has ever produced. Static analysis can find where these constants are defined; only operational validation can confirm whether they are correct for the current instrument configuration.

Alarm Generation and Classification Logic

Alarm generation code is among the most regulatory-sensitive SCADA-adjacent code in utility environments. NERC CIP (Critical Infrastructure Protection) standards for electric utilities, NRC requirements for nuclear facilities, and EPA reporting requirements for water and wastewater utilities all specify which events must generate alarms, what information those alarms must contain, and within what timeframes they must be reported.

cobol

       CLASSIFY-ALARM.
           EVALUATE TRUE
               WHEN EU-PRESSURE-PSI > HIGH-HIGH-LIMIT
                   MOVE 'HH'   TO ALARM-PRIORITY
                   MOVE 'NERC' TO REPORTING-FLAG
                   PERFORM GENERATE-NERC-EVENT
               WHEN EU-PRESSURE-PSI > HIGH-LIMIT
                   MOVE 'HI'   TO ALARM-PRIORITY
                   MOVE 'LOG'  TO REPORTING-FLAG
               WHEN EU-PRESSURE-PSI < LOW-LIMIT
                   MOVE 'LO'   TO ALARM-PRIORITY
                   MOVE 'LOG'  TO REPORTING-FLAG
               WHEN EU-PRESSURE-PSI < LOW-LOW-LIMIT
                   MOVE 'LL'   TO ALARM-PRIORITY
                   MOVE 'NERC' TO REPORTING-FLAG
                   PERFORM GENERATE-NERC-EVENT
           END-EVALUATE.

The alarm limits in this code, HIGH-HIGH-LIMIT, HIGH-LIMIT, LOW-LIMIT, LOW-LOW-LIMIT, are operational parameters with regulatory significance. Changes to these limits affect both the operational behavior of the control system and the regulatory reporting obligations of the utility. Any modernization program that touches alarm classification code must have regulatory affairs review in its change control process, not just engineering sign-off.

Protocol Bridge Implementations

Legacy protocol bridge programs are among the most difficult SCADA-adjacent code to modernize because their dependencies are the hardest to enumerate. The program implements a specific protocol version for a specific device, with undocumented vendor-specific behaviors compensated for in the code.

c

/* Legacy Modbus RTU bridge -- written for Modicon 984 PLCs, circa 1998 */
/* NOTE: 984 series uses 1-based register addressing, not 0-based */
/* Function code 03 only -- 984 does not support FC 04 */
/* Max 60 registers per request -- 984 firmware limitation */

#define MODBUS_FC03_READ_HOLDING  0x03
#define MAX_REGS_PER_REQUEST      60    /* 984 firmware limit, not Modbus spec */
#define REGISTER_OFFSET           1     /* 984 uses 1-based addressing */

int read_holding_registers(int start_reg, int count, uint16_t *buffer) {
    /* Compensate for 984 1-based addressing */
    uint16_t adjusted_start = (uint16_t)(start_reg + REGISTER_OFFSET);
    
    if (count > MAX_REGS_PER_REQUEST) {
        /* 984 will return error if count exceeds 60 */
        /* Split into multiple requests silently */
        return read_registers_chunked(adjusted_start, count, buffer);
    }
    /* ... */
}

This code has four embedded assumptions about a specific PLC model that are not part of the Modbus specification: 1-based register addressing, function code 03 only, 60-register maximum, and the chunking behavior for larger requests. None of these appear in Modbus documentation. They are vendor-specific behaviors of the Modicon 984, documented in a 1998 hardware manual that may no longer be available. If this bridge is modernized without understanding these assumptions, or if the PLC is replaced with a newer model that uses standard 0-based addressing, every register read returns the wrong value by exactly one register address offset.

The Pre-Modernization Analysis: What Must Be Produced

Before any SCADA-adjacent code is modified, refactored, or replaced, the analysis must produce a set of deliverables that go beyond what standard enterprise modernization analysis provides.

Operational function inventory. Every SCADA-adjacent program must be classified by its operational function: EU calculation, setpoint delivery, historian data consumer, alarm generation, protocol bridge, batch-to-realtime pathway. This classification determines who must be involved in the change process, IT engineers alone, or a cross-functional team that includes control engineers, operations staff, and regulatory compliance.

IT/OT boundary crossing map. Every data flow that crosses the IT/OT boundary must be documented: which program produces the data, in what format, on what schedule; which OT system consumes it; and what the consequence is if the data is incorrect, delayed, or absent. This map is the operational risk profile for the SCADA-adjacent layer.

Safety function identification. Every program must be evaluated for whether it performs a safety function under IEC 61511, IEC 61508, NERC CIP, or other applicable standards. Programs identified as safety-function code require separate change control, regulatory notification, and potentially recertification, the modernization timeline for these programs is fundamentally different from standard business code.

Hardcoded operational parameter registry. Every hardcoded constant that represents an operational parameter, sensor calibration values, alarm limits, protocol constraints, timing thresholds, must be identified, documented with its operational meaning, and validated against current instrument specifications. This registry becomes the input to the configuration management process that replaces hardcoded constants with externally managed configuration.

Timing dependency documentation. Every program’s timing context, the operational windows within which it must complete, the scheduler dependencies that enforce those windows, the operational procedures that depend on its completion, must be documented explicitly. This documentation is the specification against which the modernized implementation must be validated.

Protocol and interface specification. Every protocol bridge program must be analyzed for vendor-specific behaviors, protocol version assumptions, and device-specific compensations. The output is a specification document that can be used to validate a replacement implementation, confirming that every compensating behavior is preserved, even the ones that were not in the original specification.

The Modernization Approaches That Work and the Ones That Do Not

Strangler Fig, applied carefully. The Strangler Fig pattern, building new functionality alongside the old, routing incrementally, decommissioning gradually, is appropriate for SCADA-adjacent code that performs IT-side data processing (historian data consumers, billing calculations). The old program continues running during the transition; the new implementation produces parallel outputs that are validated for equivalence before the old program is retired.

Strangler Fig does not apply to real-time pathways. For programs that sit in a real-time data path, where there is no safe way to run old and new in parallel because they would produce conflicting operational effects, the transition must be instantaneous and validated offline before any production cutover. Running a parallel setpoint calculation program that produces different values than the current program would send conflicting setpoints to the control system.

Configuration externalization before code changes. For programs with hardcoded operational parameters, the safest first modernization step is externalizing those parameters to a configuration file or database without changing the calculation logic. This makes the parameters visible, manageable, and auditable without touching the calculation code that has operational significance. The risk of externalizing parameters is substantially lower than refactoring the calculation logic.

Safety-function code: analysis and documentation, not refactoring. Safety-certified code should be analyzed and documented during the modernization planning phase, but changes to it should be deferred to a deliberate recertification program with regulatory coordination, not addressed as part of a general modernization initiative. The risk of invalidating a safety certification during a broad modernization sweep is not justified by any typical modernization benefit.

How SMART TS XL Supports SCADA-Adjacent Legacy Code Analysis

SMART TS XL’s static code analysis applies to the IT side of the SCADA-adjacent boundary, the COBOL, JCL, RPG, PL/I, and C programs that run on mainframes and midrange systems and generate, transform, or consume data that crosses into OT environments. For this code, structural analysis produces the operational function inventory and hardcoded parameter registry that the pre-modernization analysis requires.

The application dependency mapping builds the IT/OT boundary crossing map: every program that writes to a file interface consumed by a SCADA system, every JCL job step that produces data with operational timing significance, every program in the historian data pathway from OT source to IT consumer. When a utility’s billing COBOL program reads historian data through an intermediate C bridge, the dependency map represents both the COBOL-to-C dependency and the C-to-historian dependency as a connected chain, making the full IT/OT crossing visible rather than discoverable only through operational incident.

The impact analysis capability is particularly critical for SCADA-adjacent code because it enumerates the blast radius of any proposed change before the change is made. A modification to an EU calculation program that is shared (via copybook) with alarm generation code, setpoint delivery code, and historian write code requires understanding all three secondary impacts before touching the calculation. In standard business code, an incorrect change produces wrong data. In SCADA-adjacent code, it produces wrong operational parameters.

The JCL expansion capability reveals the timing and sequencing structure of the batch layer: which jobs run in what order, which dataset outputs feed which subsequent steps, and which job streams are time-bounded by operational requirements. This is the structural evidence base for the timing dependency documentation that SCADA-adjacent modernization requires.

The enterprise search capability makes the hardcoded parameter registry achievable at scale: find every occurrence of a specific engineering unit constant, every alarm limit value, every hardcoded protocol address across every COBOL, C, RPG, and JCL artifact in the environment, in seconds, across millions of lines of code. For utilities managing hundreds of thousands of lines of SCADA-adjacent legacy code, this search capability is the difference between a manual audit that takes months and an automated inventory that takes hours.

For teams planning legacy modernization of utility systems, SMART TS XL provides the IT-side structural analysis that positions the modernization team to work effectively alongside the OT-side control engineers who understand the operational context. The IT/OT boundary is not crossed safely by IT teams alone or by OT teams alone, it is crossed safely when both sides have accurate structural knowledge of what their systems contain.

Why This Code Demands a Different Kind of Attention

Utilities modernizing their SCADA-adjacent legacy code are not simply updating old software. They are changing the software layer that mediates between business systems and physical infrastructure. The consequences of getting it wrong are not limited to data errors, service disruptions, or financial losses, they extend to physical systems that serve the people who depend on utilities to function.

The characteristics that make this code special, the physical consequence of data errors, the timing dependencies invisible to static analysis, the safety certification constraints, the hardware and protocol coupling, are not arguments against modernizing it. They are arguments for understanding it completely before changing anything. The analysis framework in this guide produces that understanding. The modernization program that follows from it is safer because the scope of change is defined by evidence rather than assumption, the timing dependencies are documented rather than implicit, the safety-function code is identified rather than accidentally modified, and the IT/OT boundary crossings are mapped rather than discovered through operational incidents after deployment.