Portable IEC 60870-5-104 protocol core for SCADA, gateways, and deterministic simulation.
moon update
moon fmt
moon check --deny-warn --target all
moon build --target wasm-gc
moon test --deny-warn --target wasm-gcmoon run cmd/main
moon run cmd/main -- --help
moon run cmd/main -- --benchmark///|
let payload = @hhxhhx78/moonbit-iec104.normalized_value_asdu(3, 1, 2300, 0)
///|
let apdu = @hhxhhx78/moonbit-iec104.encode_frame(
@hhxhhx78/moonbit-iec104.information_frame(0, 0, payload),
)| 层次 | 主要文件 | 职责 |
|---|---|---|
| 链路与帧 | frame_types.mbt, codec.mbt, validation.mbt, state_machine.mbt | APCI、ASDU 基础模型、校验和链路状态 |
| 应用数据 | protocol_domain.mbt, quality.mbt, time_tags.mbt, application_objects.mbt, extended_asdu.mbt | Type ID、地址、质量、时标和信息对象 |
| 服务与状态 | transport_layer.mbt, application_services.mbt, point_store.mbt | 序号窗口、定时器、召唤事务、点表和历史 |
| 工具与可靠性 | wire_tools.mbt, security_limits.mbt, diagnostics_metrics.mbt, health_report.mbt | 字节工具、资源保护、指标和诊断 |
| 仿真与契约 | simulation.mbt, conformance_catalog.mbt, protocol_profiles.mbt, benchmark_api.mbt | 确定性仿真、类型目录、能力协商和基准接口 |
| 示例 | cmd/main | 可运行的最小 CLI |
1..5 | ForEach-Object { Measure-Command { moon run cmd/main -- --benchmark } }moon check --deny-warn --target all
moon test --deny-warn --target wasm-gc
moon test --deny-warn --target nativepub struct ApplicationObject {
address : InformationAddress
type_id : ApplicationType
value : ApplicationValue
time_tag : TimeTag?
} derive(Eq, Debug)fn ApplicationObject::new(address : InformationAddress, value : ApplicationValue, time_tag? : TimeTag) -> Result[ApplicationObject, String]pub enum ApplicationType {
MSpNa
MSpTa
MDpNa
MDpTa
MStNa
MStTa
MBoNa
MBoTa
MMeNa
MMeTa
MMeNb
MMeTb
MMeNc
MMeTc
MItNa
MItTa
MEpTa
MEpTb
MEpTc
MPsNa
MMeNd
MSpTb
MDpTb
MStTb
MBoTb
MMeTd
MMeTe
MMeTf
MItTb
MEpTd
MEpTe
MEpTf
MEiNa
CScNa
CDcNa
CRcNa
CSeNa
CSeNb
CSeNc
CBoNa
CIcNa
CCiNa
CRdNa
CCsNa
CTsNa
CRpNa
UnknownType(Int)
} derive(Eq, Debug)pub enum ApplicationValue {
SinglePointValue(SinglePointValue)
DoublePointValue(DoublePointValue)
StepPositionValue(StepPositionValue)
BitStringValue(UInt)
NormalizedMeasurement(NormalizedValue)
ScaledMeasurement(ScaledValue)
ShortFloatMeasurement(ShortFloatValue)
BinaryCounterMeasurement(BinaryCounterValue)
SingleCommand(Bool, Int)
DoubleCommand(Int, Int)
RegulatingStepCommand(Int, Int)
NormalizedSetPoint(Int, Int)
ScaledSetPoint(Int, Int)
ShortFloatSetPoint(Float, Int)
BitStringCommand(UInt)
InterrogationCommand(Int)
CounterInterrogationCommand(Int)
ReadCommand
ClockSyncCommand(Cp56Time)
TestCommand(Int)
ResetCommand(Int)
DelayCommand(Int)
EndOfInitialization(Int)
RawValue(Bytes)
} derive(Eq, Debug)pub struct AsduEnvelope {
type_id : ApplicationType
sequence : Bool
cause : CauseOfTransmission
common_address : CommonAddress
objects : Array[ApplicationObject]
} derive(Eq, Debug)fn AsduEnvelope::new(type_id : ApplicationType, sequence : Bool, cause : CauseOfTransmission, common_address : CommonAddress, objects : Array[ApplicationObject]) -> Result[AsduEnvelope, Diagnostic]fn BenchmarkCase::new(name : String, rounds : Int, payload_size : Int, target : String) -> Result[BenchmarkCase, String]pub struct BenchmarkResult {
case : BenchmarkCase
encoded_frames : Int
encoded_bytes : Int
checksum : UInt
elapsed_micros : Int
} derive(Eq, Debug)fn BenchmarkResult::new(case : BenchmarkCase, workload : BenchmarkWorkload, elapsed_micros : Int) -> Result[BenchmarkResult, String]fn BenchmarkSuite::run(self : BenchmarkSuite, elapsed_micros : Array[Int]) -> Result[Array[BenchmarkResult], String]fn BinaryCounterValue::new(value : UInt, sequence? : Int, carry? : Bool, adjusted? : Bool, invalid? : Bool) -> Result[BinaryCounterValue, String]pub struct CauseOfTransmission {
category : CauseCategory
number : Int
positive : Bool
test_flag : Bool
originator : Int
qualifier : Int
} derive(Eq, Debug)fn CauseOfTransmission::from_number(number : Int, positive? : Bool, test_flag? : Bool, originator? : Int, qualifier? : Int) -> Result[CauseOfTransmission, String]fn CauseOfTransmission::new(category : CauseCategory, positive? : Bool, test_flag? : Bool, originator? : Int, qualifier? : Int) -> Result[CauseOfTransmission, String]pub struct ClockSyncTransaction {
request : ServiceRequest
status : ServiceStatus
received : Cp56Time?
applied_at : Int?
} derive(Debug)fn ClockSyncTransaction::apply(self : ClockSyncTransaction, timestamp : Cp56Time, applied_at : Int) -> Result[Int, String]pub struct CommandOutcome {
accepted : Bool
status : ServiceStatus
object : ApplicationObject?
message : String
} derive(Debug)pub struct CommandQueue {
pending : Array[ApplicationObject]
completed : Array[CommandOutcome]
limit : Int
} derive(Debug)pub struct ConformanceReport {
valid : Bool
type_id : ApplicationType
object_count : Int
expected_width : Int
actual_width : Int
issues : Array[String]
} derive(Eq, Debug)fn ConnectionParameters::new(k : Int, w : Int, t0_seconds : Int, t1_seconds : Int, t2_seconds : Int, t3_seconds : Int) -> Result[ConnectionParameters, String]pub struct CounterInterrogationRequest {
common_address : CommonAddress
qualifier : Int
created_at : Int
} derive(Eq, Debug)fn CounterInterrogationRequest::new(common_address : CommonAddress, qualifier : Int, created_at : Int) -> Result[CounterInterrogationRequest, String]pub struct GatewayAdmissionPolicy {
max_objects : Int
allow_monitoring : Bool
allow_control : Bool
allowed_types : Array[ApplicationType]
denied_types : Array[ApplicationType]
} derive(Eq, Debug)fn GatewayAdmissionPolicy::allow_type(self : GatewayAdmissionPolicy, type_id : ApplicationType) -> Unitfn GatewayAdmissionPolicy::deny_type(self : GatewayAdmissionPolicy, type_id : ApplicationType) -> Unitfn GatewayAdmissionPolicy::evaluate(self : GatewayAdmissionPolicy, envelope : AsduEnvelope) -> Result[Unit, String]fn GatewayAdmissionPolicy::new(max_objects : Int, allow_monitoring? : Bool, allow_control? : Bool) -> Result[GatewayAdmissionPolicy, String]pub enum GatewayDispatch {
Forwarded(GatewayEnvelope, GatewayRoute)
Dropped(GatewayEnvelope, String)
} derive(Debug)pub struct GatewayEnvelope {
ingress : Int
egress : Int
common_address : CommonAddress
asdu : AsduEnvelope
received_at : Int
trace_id : String
} derive(Debug)fn GatewayEnvelope::new(ingress : Int, egress : Int, asdu : AsduEnvelope, received_at : Int, trace_id? : String) -> Result[GatewayEnvelope, String]pub struct GatewayRoute {
id : Int
source : Int
destination : Int
common_address : CommonAddress?
type_ids : Array[ApplicationType]
enabled : Bool
priority : Int
} derive(Eq, Debug)fn GatewayRoute::new(id : Int, source : Int, destination : Int, common_address? : CommonAddress, type_ids? : Array[ApplicationType], priority? : Int) -> Result[GatewayRoute, String]fn GatewayRouteTable::select(self : GatewayRouteTable, source : Int, common_address : CommonAddress, type_id : ApplicationType) -> GatewayRoute?pub struct GatewayRuntime {
mode : GatewayMode
routes : GatewayRouteTable
policy : GatewayAdmissionPolicy
inbound : Array[GatewayEnvelope]
outbound : Array[GatewayEnvelope]
dead_letters : Array[GatewayEnvelope]
stores : Array[GatewayStoreBinding]
counters : GatewayCounters
max_queue : Int
last_error : String?
} derive(Debug)fn GatewayRuntime::attach_store(self : GatewayRuntime, binding : GatewayStoreBinding) -> Result[Unit, String]fn GatewayRuntime::enqueue(self : GatewayRuntime, envelope : GatewayEnvelope) -> Result[Unit, String]fn GatewayRuntime::ingest_to_store(self : GatewayRuntime, envelope : GatewayEnvelope) -> Result[Int, String]fn GatewayRuntime::new(max_queue : Int, route_capacity? : Int, policy? : GatewayAdmissionPolicy) -> Result[GatewayRuntime, String]pub struct GatewaySnapshot {
mode : GatewayMode
routes : Int
queue : Int
outbound : Int
dead_letters : Int
counters : GatewayCounters
last_error : String?
} derive(Eq, Debug)pub struct GatewayStoreBinding {
common_address : CommonAddress
store : PointStore
accepted : Int
rejected : Int
} derive(Debug)fn GatewayStoreBinding::ingest(self : GatewayStoreBinding, envelope : GatewayEnvelope) -> Result[Int, String]fn GatewayStoreBinding::new(common_address : CommonAddress, history_limit? : Int) -> Result[GatewayStoreBinding, String]pub struct HealthReport {
state : HealthState
score : Int
checks : Array[String]
warnings : Array[String]
} derive(Eq, Debug)fn HealthReport::new(state : HealthState, score : Int, checks : Array[String], warnings : Array[String]) -> HealthReportfn InformationAddress::from_octets(low : Int, middle : Int, high : Int) -> Result[InformationAddress, String]pub struct Interrogation {
request : InterrogationRequest
phase : InterrogationPhase
objects_sent : Int
}pub struct InterrogationPlan {
request : InterrogationRequest
objects : Array[ApplicationObject]
batch_size : Int
cursor : Int
status : ServiceStatus
} derive(Debug)fn InterrogationPlan::new(request : InterrogationRequest, objects : Array[ApplicationObject], batch_size? : Int) -> Result[InterrogationPlan, String]fn InterrogationPlan::next_batch(self : InterrogationPlan) -> Result[Array[ApplicationObject], String]fn NormalizedValue::new(value : Int, quality? : QualityDescriptor) -> Result[NormalizedValue, String]pub struct PointChange {
kind : PointChangeKind
address : InformationAddress
revision : Int
timestamp : Int
message : String
} derive(Eq, Debug)fn PointChange::new(kind : PointChangeKind, address : InformationAddress, revision : Int, timestamp : Int, message : String) -> PointChangepub struct PointFilter {
direction : PointDirection?
type_id : ApplicationType?
first_address : InformationAddress?
last_address : InformationAddress?
only_valid : Bool
source : String?
} derive(Eq, Debug)fn PointFilter::with_range(self : PointFilter, first : InformationAddress, last : InformationAddress) -> PointFilterpub struct PointRecord {
address : InformationAddress
type_id : ApplicationType
value : ApplicationValue
time_tag : TimeTag?
revision : Int
updated_at : Int
source : String
} derive(Eq, Debug)fn PointRecord::new(object : ApplicationObject, updated_at : Int, revision? : Int, source? : String) -> PointRecordpub struct PointStore {
common_address : CommonAddress
points : Map[Int, PointRecord]
history : Array[PointChange]
history_limit : Int
} derive(Debug)fn PointStore::new(common_address : CommonAddress, history_limit? : Int) -> Result[PointStore, String]fn PointStore::remove(self : PointStore, address : InformationAddress, timestamp : Int) -> Result[PointChange, Diagnostic]fn PointStore::upsert(self : PointStore, object : ApplicationObject, timestamp : Int, source? : String) -> Result[PointChange, Diagnostic]pub struct ProtocolTimers {
t0 : TimerState
t1 : TimerState
t2 : TimerState
t3 : TimerState
} derive(Eq, Debug)fn ReadTransaction::complete(self : ReadTransaction, object : ApplicationObject) -> Result[ServiceResponse, String]fn ResourceLimits::new(max_apdu : Int, max_asdu : Int, max_objects : Int, max_history : Int, max_commands : Int, max_trace_events : Int) -> Result[ResourceLimits, String]pub struct ServiceRequest {
service : ServiceKind
address : InformationAddress
common_address : CommonAddress
qualifier : Int
created_at : Int
originator : Int
} derive(Eq, Debug)fn ServiceRequest::new(service : ServiceKind, address : InformationAddress, common_address : CommonAddress, qualifier : Int, created_at : Int, originator? : Int) -> Result[ServiceRequest, Diagnostic]pub struct ServiceResponse {
request : ServiceRequest
status : ServiceStatus
cause : CauseOfTransmission
objects : Array[ApplicationObject]
message : String
} derive(Debug)fn ServiceResponse::new(request : ServiceRequest, status : ServiceStatus, cause : CauseOfTransmission, objects : Array[ApplicationObject], message? : String) -> ServiceResponsepub enum SessionAction {
SendStart
SendStop
SendTest
SendSupervisory(Int)
SendInformation(Int)
DeliverInformation(Bytes)
Acknowledge(Int)
Report(Diagnostic)
NoAction
} derive(Debug)pub struct Simulation {
clock : VirtualClock
actions : Array[ScheduledAction]
events : Array[SimulationEvent]
next_ordinal : Int
max_events : Int
} derive(Debug)fn Simulation::schedule(self : Simulation, at : Int, action : SimulationAction) -> Result[Unit, String]pub enum SimulationAction {
Receive(Frame)
Publish(ApplicationObject)
AdvanceTimer(String)
Record(String)
} derive(Debug)pub enum SimulationEvent {
FrameProduced(Int, Frame)
ObjectPublished(Int, ApplicationObject)
TimerAdvanced(Int, String)
Note(Int, String)
SimulationFault(Int, Diagnostic)
} derive(Debug)pub struct StationProfile {
name : String
common_address : CommonAddress
parameters : ConnectionParameters
supported_types : Array[ApplicationType]
max_objects : Int
tls_required : Bool
} derive(Debug)fn StationProfile::new(name : String, common_address : CommonAddress, parameters : ConnectionParameters, supported_types : Array[ApplicationType], max_objects? : Int, tls_required? : Bool) -> Result[StationProfile, String]pub struct StationSimulation {
store : PointStore
session : Session
simulation : Simulation
metrics : FrameMetrics
trace : TraceLog
} derive(Debug)fn StationSimulation::ingest(self : StationSimulation, object : ApplicationObject, timestamp : Int) -> Result[PointChange, Diagnostic]fn StationSimulation::new(common_address : CommonAddress, window_size? : Int) -> Result[StationSimulation, String]fn StationSimulation::schedule(self : StationSimulation, at : Int, action : SimulationAction) -> Result[Unit, String]pub struct StepPositionValue {
position : Int
transient : Bool
quality : StatusQuality
} derive(Eq, Debug)fn StepPositionValue::new(position : Int, transient? : Bool, quality? : StatusQuality) -> Result[StepPositionValue, String]pub struct StoreTransaction {
pending : Array[(ApplicationObject, Int, String)]
removed : Array[(InformationAddress, Int)]
committed : Bool
} derive(Debug)fn StoreTransaction::commit(self : StoreTransaction, store : PointStore) -> Result[Array[PointChange], Diagnostic]fn StoreTransaction::stage(self : StoreTransaction, object : ApplicationObject, timestamp : Int, source? : String) -> Result[Unit, Diagnostic]fn StoreTransaction::stage_remove(self : StoreTransaction, address : InformationAddress, timestamp : Int) -> Result[Unit, Diagnostic]fn TraceEvent::new(timestamp : Int, kind : TraceKind, detail : String, correlation? : Int) -> TraceEventpub struct TypeDescriptor {
type_id : ApplicationType
name : String
direction : PointDirection
time_tag : TimeTagKind
value_width : Int
supported : Bool
command : Bool
} derive(Eq, Debug)fn admit_inbound(data : Bytes, limits : ResourceLimits, replay : ReplayGuard, now : Int) -> Result[Frame, Diagnostic]fn binary_counter_object(address : InformationAddress, value : UInt, sequence? : Int, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn bit_string_object(address : InformationAddress, value : UInt, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn calendar_weekday(year : Int, month : Int, day : Int) -> Result[Int, String]fn clock_sync_object(address : InformationAddress, value : Cp56Time) -> Result[ApplicationObject, String]fn counter_interrogation_object(address : InformationAddress, qualifier : Int) -> Result[ApplicationObject, String]fn crc16_ibm(data : Bytes) -> UIntfn crc32_ieee(data : Bytes) -> UIntfn create_interrogation_plan(store : PointStore, request : InterrogationRequest, filter : PointFilter, batch_size? : Int) -> Result[InterrogationPlan, String]fn days_in_month(year : Int, month : Int) -> Int?fn decode_signed16(low : Int, high : Int) -> Result[Int, String]fn double_command_object(address : InformationAddress, state : Int, qualifier : Int) -> Result[ApplicationObject, String]fn double_point_object(address : InformationAddress, state : Int, quality? : StatusQuality, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn end_of_initialization_object(address : InformationAddress, value : Int) -> Result[ApplicationObject, String]fn hex_digit(value : Int) -> Charfn interrogation_object(address : InformationAddress, qualifier : Int) -> Result[ApplicationObject, String]fn milliseconds_since_midnight(hour : Int, minute : Int, second : Int, millisecond : Int) -> Result[Int, String]fn negotiate_profiles(local_profile : StationProfile, remote : StationProfile) -> NegotiationDecisionfn normalize_quality(value : Int) -> Intfn normalized_object(address : InformationAddress, value : Int, quality? : QualityDescriptor, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn normalized_set_point_object(address : InformationAddress, value : Int, qualifier : Int) -> Result[ApplicationObject, String]fn normalized_value_asdu(cause : Int, common_address : Int, value : Int, quality : Int) -> Bytesfn ordinal_day(year : Int, month : Int, day : Int) -> Result[Int, String]fn parse_vsq(value : Int) -> Result[(Int, Bool), String]fn regulating_step_command_object(address : InformationAddress, step : Int, qualifier : Int) -> Result[ApplicationObject, String]fn reset_command_object(address : InformationAddress, value : Int) -> Result[ApplicationObject, String]fn scaled_object(address : InformationAddress, value : Int, quality? : QualityDescriptor, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn scaled_set_point_object(address : InformationAddress, value : Int, qualifier : Int) -> Result[ApplicationObject, String]fn sequence_acknowledges(send_cursor : Int, acknowledgement : Int) -> Boolfn sequence_before(start : Int, candidate : Int, limit : Int) -> Boolfn sequence_distance(start : Int, end : Int) -> Intfn sequence_next(value : Int) -> Intfn sequence_normalize(value : Int) -> Intfn sequence_previous(value : Int) -> Intfn short_float_object(address : InformationAddress, value : Float, quality? : QualityDescriptor, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn short_float_set_point_object(address : InformationAddress, value : Float, qualifier : Int) -> Result[ApplicationObject, String]fn single_command_object(address : InformationAddress, state : Bool, qualifier : Int) -> Result[ApplicationObject, String]fn single_object_asdu(object : ApplicationObject, cause : CauseOfTransmission, common_address : CommonAddress) -> Result[AsduEnvelope, Diagnostic]fn single_point_asdu(cause : Int, common_address : Int, ioa : Int, status : Bool, quality : Int) -> Bytesfn single_point_object(address : InformationAddress, state : Bool, quality? : StatusQuality, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn split_milliseconds_since_midnight(value : Int) -> Result[(Int, Int, Int, Int), String]fn step_position_object(address : InformationAddress, position : Int, transient? : Bool, quality? : StatusQuality, time_tag? : TimeTag) -> Result[ApplicationObject, String]fn test_command_object(address : InformationAddress, value : Int) -> Result[ApplicationObject, String]Portable IEC 60870-5-104 protocol core for SCADA, gateways, and deterministic simulation.