Mapa da aula
Aula 04 publicada. Mapa, exemplo, EX010.
flowchart TD
N0["4.1 Decisões como funções"]
N1["4.2 Tipos exatos"]
N2["4.3 Testes em quadros decodificados sintéticos"]
N3["4.3b Registrando o que a função decidiu"]
N4["Síntese"]
N0 --> N1 --> N2 --> N3 --> N4
N4 --> EX["Exemplo resolvido"]
EX --> Q["Exercício EX010"]
Desenho lógico
flowchart LR
N0["4.1 Decisões como funções"]
N1["4.2 Tipos exatos"]
N2["4.3 Testes em quadros decodificados sintéticos"]
N3["4.3b Registrando o que a função decidiu"]
N4["Síntese"]
N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX010"]
[4.1 Decisões como funções]
|
v
[4.2 Tipos exatos]
|
v
[4.3 Testes em quadros decodificados sintéticos]
|
v
[4.3b Registrando o que a função decidiu]
|
v
[Síntese]
|
v
[Exemplo resolvido] --> [Exercício EX010]
Objetivos de aprendizagem
Você vai (1) escrever funções pequenas e puras que decidem se uma mensagem de TO é permitida; (2) representar endereços industriais com tipos exatos; (3) testar com tráfego decodificado sintético, nunca com tráfego real da planta.
4.1 Decisões como funções
Uma lista de permissões (allow-list) para Modbus é uma função permit(source, function_code, register) -> (bool, reason). Torná-la pura — entradas entram, decisão sai, sem configuração oculta — permite testá-la num laptop com registros sintéticos. A string de motivo importa: "register-out-of-range" diz a um revisor que a IHM tentou escrever fora do bloco de setpoints, o que é uma história diferente de uma estação desconhecida usando uma função de diagnóstico.
Lena mantém as strings de motivo curtas e fixas, para que os logs possam ser contados por motivo.
4.2 Tipos exatos
Armazene os códigos de função como inteiros, as faixas de registradores como objetos range e as identidades das estações como strings fixas vindas de um inventário. Um bug frequente trata a notação 4xxxx de "holding register" como string e casa por prefixo, de modo que 400500 parece 40050. Converta os endereços uma única vez, no momento da análise, em inteiros com deslocamentos documentados.
Lena escreve uma única função de conversão para os endereços de registradores e a testa nas duas pontas da faixa gravável da IHM, 40001 e 40010, e um passo fora de cada uma. Os nomes das estações vêm do inventário, então um erro de digitação na lista de permissões falha numa consulta, em vez de criar em silêncio uma nova estação que não casa com nada.
4.3 Testes em quadros decodificados sintéticos
O laboratório abaixo nunca abre um socket; ele verifica tuplas já decodificadas. Isso é deliberado. O código do curso não pode enviar tráfego Modbus a nenhum dispositivo. Escreva um teste por regra, mais os registradores de fronteira 40001 e 40010.
"""Lab: Modbus function-code allow-list on synthetic, already-decoded requests (no network)."""
ALLOW = {("HMI-1", 3), ("HMI-1", 6), ("HIST-1", 3), ("HIST-1", 4)} # (source, function code)
WRITE_REGS = {("HMI-1", 6): range(40001, 40011)} # HMI may write setpoints only
reqs = [("HMI-1", 3, 40001), ("HMI-1", 6, 40003), ("HMI-1", 6, 40050),
("HIST-1", 3, 30001), ("HIST-1", 16, 40001), ("ENG-WS", 8, 0), ("HIST-1", 4, 30010)]
for src, fc, reg in reqs:
if (src, fc) not in ALLOW:
d = "BLOCK fc-not-allowed"
elif (src, fc) in WRITE_REGS and reg not in WRITE_REGS[(src, fc)]:
d = "BLOCK register-out-of-range"
else:
d = "PASS"
print(f"{src:7} fc={fc:2} reg={reg:5} -> {d}")
Saída esperada:
HMI-1 fc= 3 reg=40001 -> PASS
HMI-1 fc= 6 reg=40003 -> PASS
HMI-1 fc= 6 reg=40050 -> BLOCK register-out-of-range
HIST-1 fc= 3 reg=30001 -> PASS
HIST-1 fc=16 reg=40001 -> BLOCK fc-not-allowed
ENG-WS fc= 8 reg= 0 -> BLOCK fc-not-allowed
HIST-1 fc= 4 reg=30010 -> PASS
Os testes são agrupados por regra, para que uma falha aponte direto para a cláusula que quebrou. O teste de escrita do historiador, que pegou a função 16, é mantido como caso permanente. A tabela de testes de Lena lista cada tupla, a decisão esperada e a string de motivo, e um revisor pode lê-la sem executar nada. Novas estações ou registradores acrescentados à lista de permissões chegam com as suas próprias linhas no mesmo pull request.
4.3b Registrando o que a função decidiu
Toda decisão deve ser registrada com a origem, o código de função, o registrador, a decisão, o motivo e a versão do arquivo da lista de permissões. Quando um operador pergunta por que uma escrita da IHM foi bloqueada às 03:12, o log responde numa linha. Sem o campo de versão, ninguém consegue dizer se o bloqueio veio da lista de ontem ou da de hoje.
Caso sintético — Ashford Batch Plant. A primeira lista de permissões de Lena deixava o HIST-1 usar a função 16 (escrever múltiplos registradores) porque ele "só lê". A tabela de testes pegou isso: o historiador não tem nada que escrever, então a função 16 foi removida e o teste agora espera BLOCK.
4.4 Erros e limites
Uma lista de permissões num monitor só é tão boa quanto o decodificador. Quadros malformados precisam ser contados e alarmados, não descartados em silêncio. Os dispositivos reais de aplicação precisam de validação do fornecedor antes da implantação.
O monitor de Lena conta os quadros malformados por minuto e gera um alarme se a contagem subir, porque uma rajada repentina pode significar um dispositivo falhando ou um bug no decodificador. Tratar esses quadros como permitidos transformaria cada lacuna do decodificador numa porta aberta. O monitor deste curso apenas observa; qualquer dispositivo que de fato bloqueie tráfego na ABP é um produto de fornecedor validado antes na bancada.
Exercícios
- EX010 — Regra. Escreva a regra de permissão (em palavras ou em código) para "o HIST-1 pode ler registradores de entrada e de retenção, nada mais". Rubrica: 3 = só fc 3 e 4, todo o resto BLOCK com motivo; 2 = falta um motivo; 1 = vago; 0 = inclui escritas.
- EX011 — Fronteira. Quais valores de registrador testam as bordas da faixa de escrita da IHM no laboratório, e o que se espera para 40011? Rubrica: 3 = 40001 e 40010 PASS, 40011 BLOCK; 2 = uma borda; 1 = vago; 0 = errado.
- EX012 — Laboratório. Por que ENG-WS fc=8 é bloqueado, e por que esse é o padrão correto? Rubrica: 3 = não há entrada (ENG-WS, 8); diagnóstico/negar por padrão; 2 = primeira metade; 1 = vago; 0 = errado.
Síntese
Funções de permissão puras, tipadas e testadas são a menor unidade confiável de software de segurança de TO.