Chapter 3 — Programming foundations
Lesson map
Lesson 03 published. Map, example, EX007.
flowchart TD
N0["Learning outcomes"]
N1["3.1 Why technologists script"]
N2["3.2 Dictionaries for lookups"]
N3["3.3 Missing data is a result"]
N4["3.4 Readable output"]
N5["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5
N5 --> EX["Worked example"]
EX --> Q["Exercise EX007"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["3.1 Why technologists script"]
N2["3.2 Dictionaries for lookups"]
N3["3.3 Missing data is a result"]
N4["3.4 Readable output"]
N5["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX007"]
[Learning outcomes]
|
v
[3.1 Why technologists script]
|
v
[3.2 Dictionaries for lookups]
|
v
[3.3 Missing data is a result]
|
v
[3.4 Readable output]
|
v
[Synthesis]
|
v
[Worked example] --> [Exercise EX007]
Learning outcomes
You will (1) write short Python programs using only the standard library; (2) use lists, dictionaries and loops to process records; (3) handle missing data explicitly; and (4) keep scripts readable for colleagues who are not programmers.
3.1 Why technologists script
Much OT security work is comparing lists: assets in documentation versus assets seen on the network, approved flows versus observed flows, certificate dates versus today. Doing this by eye across hundreds of rows is slow and error-prone. A twenty-line script does it the same way every time, and its output can be attached to a ticket.
Priya's first such script compared the contractor's documentation with a passive observation and produced the five-line Chapter 11 output: two matches, one camera only on the network, HMI-C only in the documents and PLC-R2's version conflict.
3.2 Dictionaries for lookups
A Python dictionary maps a key (device name) to a value (firmware version). Checking "is this device documented?" becomes name in docs, which is fast and readable. Sets give union and difference directly: set(a) - set(b) lists what is in a but not b.
Priya's inventory script keeps one dictionary from the contractor's documents and one from passive observation, each mapping a device name to its firmware string. The union of the two key sets is the full list of devices to report; the differences are the two interesting groups, CAM-DOCK only on the network and HMI-C only in the documents.
3.3 Missing data is a result
When a value is absent, say so in the output. dict.get(key) returns None instead of crashing, and the script can print "-" and a status such as "only-in-docs". Silently skipping missing rows is how gaps disappear from reports.
Priya's first draft did exactly that: it looped over matches only, so CAM-DOCK, which was missing from the documents, never appeared. A colleague asked where the camera was. Her fix was one line, iterating over sorted(set(docs) | set(seen)), so every device prints with a status and the missing one says so. The Chapter 11 output now shows docs=- for CAM-DOCK rather than leaving it out.
3.4 Readable output
Align columns with f-string widths (f"{name:9}") and put the decision at the end of each line. A reviewer can then scan the last column for anything that is not "match".
In the Chapter 11 output the last column reads only-on-network, match, only-in-docs, match and VERSION-CONFLICT, so a facilities manager can find the three lines that need action without reading the firmware strings. The device names are padded to nine characters, which keeps the documentation and network columns aligned even for CAM-DOCK. A reviewer who sees a status that is not one of the five documented values knows the script has met an input it does not understand.
Worked example (synthetic NCS)
Priya writes a loop over two dictionaries, one from the refrigeration contractor's documentation and one from a passive network observation. Her first draft printed only matches; a colleague asked where CAM-DOCK was. She rewrote it to iterate over the union of keys so every device appears with a status. The full version is the Chapter 11 lab.
Synthetic case — Northgate Cold Storage. The data are invented.
Errors and limits
Scripts that read live devices need a permit and careful rate limits; scripts in this course read files only. Name variables for what they hold, not x and y.
Exercises
- EX007 — Set difference. Given
docs = {"A", "B", "C"}andseen = {"B", "C", "D"}, what dodocs - seenandseen - docsreturn? Rubric: 3 = {"A"} and {"D"} with meaning; 2 = values only; 1 = one; 0 = none. - EX008 — Handling a missing key. What does
{"PLC-R1": "2.4"}.get("PLC-R9")return, and what should the script print? Rubric: 3 = None; print "-" plus a status; 2 = value only; 1 = says KeyError; 0 = none. - EX009 — Fixing the matches-only bug. Write the one-line change that makes Priya's loop show every device. Rubric: 3 = iterate over
sorted(set(docs) | set(seen)); 2 = idea right; 1 = vague; 0 = none.
Synthesis
Small, readable scripts that report every row — including missing ones — turn list comparison into repeatable evidence.
Continue studying — chapter 03 of 30
← Chapter 02 (draft) · Course chapter index · Chapter 04 (draft) →