Lab
Memento
// The memento: an empty token. Nothing is saved in it yet.
type PortCheckpoint = Readonly<object>;
// A switch port. vlan and allowed are its configuration, set by operators;
// link and rx are live, changed by the cable and the traffic.
class Port {
name: string;
vlan: number;
allowed: number[];
link = 'up'; // live: the cable changes it
rx = 0; // live: packets received
constructor(name: string, vlan: number, allowed: number[]) {
this.name = name;
this.vlan = vlan;
this.allowed = [...allowed];
}
setVlan(vlan: number): void {
this.vlan = vlan;
}
allow(vlan: number): void {
if (!this.allowed.includes(vlan)) this.allowed.push(vlan);
}
disallow(vlan: number): void {
const at = this.allowed.indexOf(vlan);
if (at >= 0) this.allowed.splice(at, 1);
}
receive(packets: number): void {
this.rx += packets;
}
setLink(state: string): void {
this.link = state;
}
// Not written yet: the history copies the port's fields itself (below).
capture(): PortCheckpoint {
return Object.freeze({});
}
restore(token: PortCheckpoint): void {
// Not written yet: nothing to put back.
}
}
// The console's rollback history. It was written when a port had only a
// VLAN: it saves the port as it was and writes it back.
type Entry = { label: string; vlan: number; link: string; rx: number };
class RollbackHistory {
private entries: Entry[] = [];
checkpoint(port: Port, label: string): void {
this.entries.push({ label, vlan: port.vlan, link: port.link, rx: port.rx });
}
rollback(port: Port, label: string): void {
const entry = this.entries.filter((e) => e.label === label).pop();
if (!entry) throw new Error('no checkpoint');
port.vlan = entry.vlan;
port.link = entry.link;
port.rx = entry.rx;
}
}