◇ OpenCyber Academy
Bachelor's · Chapter 8 · English

Chapter 8 — Programming II

Open, free learning for anyone who wants to protect people and technology. No account is required to read this text or its worked answers.

Chapter 8 — Programming II

Lesson map

Lesson 08 published. Map, example, EX022.

flowchart TD
  N0["Learning outcomes"]
  N1["8.1 Pure functions are reviewable"]
  N2["8.2 Fail closed"]
  N3["8.3 Tests as documentation"]
  N4["8.4 Logging decisions for review"]
  N5["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5
  N5 --> EX["Worked example"]
  EX --> Q["Exercise EX022"]

Logical drawing

flowchart LR
  N0["Learning outcomes"]
  N1["8.1 Pure functions are reviewable"]
  N2["8.2 Fail closed"]
  N3["8.3 Tests as documentation"]
  N4["8.4 Logging decisions for review"]
  N5["Synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX022"]
[Learning outcomes]
    |
    v
[8.1 Pure functions are reviewable]
    |
    v
[8.2 Fail closed]
    |
    v
[8.3 Tests as documentation]
    |
    v
[8.4 Logging decisions for review]
    |
    v
[Synthesis]
    |
    v
[Worked example] --> [Exercise EX022]

Learning outcomes

You will (1) structure an analysis tool as pure functions with tests; (2) use unittest from the standard library; (3) make functions fail closed; and (4) version a script so a result can be reproduced months later.

8.1 Pure functions are reviewable

A function that takes inputs and returns a decision, without reading the network or the clock, can be tested exhaustively. For OT tooling this matters because the reviewer is often a control engineer who will accept a tool only if its behaviour on edge cases is demonstrated. Keep input and output (files, printing) at the edges and the decision logic in the middle.

Maya's classifier is written this way: classify(code) takes a function code and returns a word, so a control engineer can read the test list and see every decision without running a capture.

8.2 Fail closed

When an allow-list function receives an unknown source, an unknown function code or a malformed address, it should return "block" rather than raise an exception that a caller might catch and ignore. Failing closed in a monitoring tool means "flag for review"; in an enforcement device it means "deny". Neither means "assume fine".

Maya's first classifier failed open: anything outside the write set returned "read". A reviewer asked about code 8, diagnostics, whose sub-functions can restart communications, and the answer exposed the bug. Her fix returns "unknown", which in an offline monitor means flag for review.

8.3 Tests as documentation

A small unittest case list reads like a specification:

import unittest
def classify(fc):
    return "write" if fc in (5, 6, 15, 16) else ("read" if fc in (1, 2, 3, 4) else "unknown")
class T(unittest.TestCase):
    def test_codes(self):
        self.assertEqual(classify(3), "read"); self.assertEqual(classify(16), "write")
        self.assertEqual(classify(8), "unknown"); self.assertEqual(classify(43), "unknown")
unittest.main(argv=["x"], exit=False)

Running it prints Ran 1 test and OK. Code 43 (encapsulated interface transport, used for device identification) is deliberately "unknown" rather than "read", because the tool's author has not reasoned about it.

The test list is also the specification a reviewer signs: classify(3) is read, classify(16) is a write, classify(8) and classify(43) are unknown. Maya's original bug would have been caught by the single assertion that code 8 returns "unknown"; that assertion is now in the list. When a new function code appears in an LDHP capture, the first change is a new assertion with the expected answer, written before the classifier is edited.

Worked example (synthetic LDHP)

Maya's first classifier returned "read" for anything not in the write set. A reviewer asked what code 8 (diagnostics) would return. The answer — "read" — was wrong: some diagnostic sub-functions restart communications. She changed the default to "unknown" and added the test above.

Synthetic case — Lakeside District Heating Plant. The classifier is a teaching tool for offline captures, not an enforcement device.

8.4 Logging decisions for review

Each decision the tool makes should be written with the input that produced it, the rule that fired and the script version, for example fc=8 -> unknown (rule: default) v=3f2a91c. A control engineer reviewing a week of captures can then filter on "unknown" and decide, case by case, whether the classifier needs a new rule. This record is also what lets you show later that a surprising result came from a known version of the code and not from an edit made afterwards.

Errors and limits

Tests prove the cases you wrote, not the ones you forgot. Record the script's version (a git commit hash) next to every result you publish.

Exercises

Synthesis

Reviewable OT tools are pure, tested, fail closed and versioned — properties that let a sceptical engineer trust their output.

Continue studying — chapter 08 of 40

← Chapter 07 (draft) · Course chapter index · Chapter 09 (draft) →