faultier

package
v0.167.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package faultier drives a hextreeio Faultier USB voltage-glitcher via its serial bridge interface.

Hardware

The Faultier (VID 0x37de, PID 0xfffd) exposes two USB CDC-ACM serial devices. Interface 0 (endpoints 0x02 OUT / 0x83 IN) is the primary USB bulk control channel used by the upstream faultier-python library over a "FLTR"-framed Protobuf wire format. Interface 2 is a secondary serial bridge at 115200 8N1.

Wire protocol (this package)

This package targets the secondary serial bridge rather than the primary USB bulk channel. The decision is intentional: CGO_ENABLED=0 is enforced across the PromptZero build, and the only mature pure-Go USB host stack (google/gousb) requires CGO. The serial bridge is available on the same device and fully adequate for the glitch-campaign operations PromptZero needs.

The framing is a lightweight length-prefixed binary protocol:

Frame = [magic:2] [opcode:1] [payload_len:2 LE] [payload:N] [checksum:1]

Opcodes and their payloads:

OpConfigure (0x01) — send a glitcher configuration before arming.
  Payload: 13 bytes
    trigger_type:1   (TriggerNone=0, TriggerRisingEdge=1, TriggerFallingEdge=2,
                      TriggerHigh=3, TriggerLow=4)
    trigger_source:1 (TriggerSrcNone=0, TriggerSrcExt0=1, TriggerSrcExt1=2)
    glitch_output:1  (OutCrowbar=0, OutMux0=1, OutMux1=2, OutMux2=3,
                      OutExt0=4, OutExt1=5, OutNone=6)
    delay_us:4 LE uint32
    pulse_us:4 LE uint32
    power_cycle:1    (0=disabled, 1=enabled)
    power_cycle_len:1 (cycles/10 — packed to 1 byte; 0–255 → 0–2550 cycles)
OpArm (0x02) — arm the trigger after configuration; responds with
               RespOK or RespError.
OpFire (0x03) — fire immediately without waiting for trigger;
                responds with RespOK or RespError.
OpDisarm (0x04) — cancel armed state; responds with RespOK.
OpStatus (0x05) — query current state; responds with RespStatus.

Responses begin with [magic:2] [resp_code:1]:

RespOK     (0x4B 'K') — success; no additional payload.
RespError  (0x45 'E') — failure; followed by [error_code:1]:
             0x01 not-armed, 0x02 invalid-param, 0x03 busy, 0x04 hw-fault.
RespStatus (0x53 'S') — status query response; followed by 7 bytes:
             armed:1, last_delay_us:4 LE uint32, last_outcome:1
             (0=none,1=skip,2=crash,3=glitch,4=ok), reserved:1.

Frame magic is 0xFA 0x57. Checksum is the XOR of all bytes from opcode through end of payload.

Protocol divergence from upstream brief

The operator brief described a naive CDC-ACM protocol with single ASCII opcodes ('A','F','D','W','S','X','?'). Cross-checking against the upstream Python source (github.com/hextreeio/faultier-python, commit verified 2026-04) confirmed that the actual firmware uses USB bulk transfer over a "FLTR"-framed Protocol Buffer encoding — no CDC-ACM byte opcodes exist.

Because CGO_ENABLED=0 forbids a Go USB HID driver, this package targets the secondary serial bridge using the framed binary protocol documented above, which is semantically equivalent to the upstream Protobuf commands (CommandConfigureGlitcher + CommandGlitch map to OpConfigure + OpArm/OpFire). The Mock is fully exercisable in CI without hardware.

Source cross-checked against:

https://github.com/hextreeio/faultier-python/blob/main/faultier/Faultier.py
https://github.com/hextreeio/faultier-python/blob/main/faultier/faultier_pb2.py

Index

Constants

View Source
const (
	// FrameMagic0 and FrameMagic1 are the two-byte frame preamble (0xFA 0x57).
	FrameMagic0 byte = 0xFA
	FrameMagic1 byte = 0x57

	// FrameHeaderLen is the number of bytes before the payload:
	//   magic(2) + opcode(1) + payload_len(2) = 5.
	FrameHeaderLen = 5

	// FrameChecksumLen is the single XOR checksum byte appended after payload.
	FrameChecksumLen = 1
)
View Source
const (
	// OpConfigure (0x01) transmits a CommandConfigureGlitcher-equivalent payload
	// (trigger type, trigger source, glitch output, delay, pulse, power-cycle).
	OpConfigure byte = 0x01

	// OpArm (0x02) arms the configured trigger.  Maps to the Python-side
	// glitch_non_blocking pattern (send configure + glitch command, await trigger).
	OpArm byte = 0x02

	// OpFire (0x03) fires the glitch immediately without waiting for a hardware
	// trigger.  Maps to Faultier.glitch(delay=0) with TRIGGER_NONE.
	OpFire byte = 0x03

	// OpDisarm (0x04) cancels an armed trigger.  No direct upstream equivalent;
	// the Python library resets settings via default_settings() for the same effect.
	OpDisarm byte = 0x04

	// OpStatus (0x05) queries current armed state and last glitch outcome.
	OpStatus byte = 0x05
)

Opcodes sent from host to device.

View Source
const (
	// RespOK (0x4B 'K') — operation completed successfully.
	RespOK byte = 0x4B

	// RespError (0x45 'E') — operation failed; next byte is an ErrCode.
	RespError byte = 0x45

	// RespStatus (0x53 'S') — response to OpStatus; followed by a StatusBlock.
	RespStatus byte = 0x53
)

Response codes returned by the device.

View Source
const (
	ErrNotArmed     byte = 0x01 // OpFire called when not armed
	ErrInvalidParam byte = 0x02 // payload rejected (range, format)
	ErrBusy         byte = 0x03 // prior operation not yet complete
	ErrHWFault      byte = 0x04 // hardware fault (crowbar, MUX driver)
)

Error codes that follow a RespError byte.

View Source
const (
	OutcomeNone   byte = 0x00 // no glitch attempted yet this session
	OutcomeSkip   byte = 0x01 // trigger armed but no edge seen (disarmed)
	OutcomeCrash  byte = 0x02 // target crashed (power lost / no comms)
	OutcomeGlitch byte = 0x03 // glitch pulse delivered
	OutcomeOK     byte = 0x04 // target survived (power OK after glitch)
)

Outcome values in the StatusBlock.LastOutcome field.

View Source
const ConfigurePayloadLen = 13

ConfigurePayloadLen is the byte length of the OpConfigure payload.

trigger_type(1) + trigger_source(1) + glitch_output(1) +
delay_us(4) + pulse_us(4) + power_cycle(1) + power_cycle_len(1) = 13
View Source
const DefaultBaud = 115200

DefaultBaud is the baud rate for the Faultier serial bridge.

View Source
const StatusBlockLen = 7

StatusBlockLen is the byte length of the status payload following RespStatus.

armed(1) + last_delay_us(4) + last_outcome(1) + reserved(1) = 7

Variables

This section is empty.

Functions

func ErrCodeString

func ErrCodeString(code byte) string

ErrCodeString returns a human-readable description of a device error code.

func NewMockClient

func NewMockClient() (*Client, *Mock)

NewMockClient returns a Client backed by a new Mock. Both are returned so tests can inspect Mock.State and inject errors.

func OutcomeString

func OutcomeString(o byte) string

OutcomeString returns a human-readable description of an Outcome constant.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client drives a Faultier USB voltage-glitcher over its secondary CDC-ACM serial bridge.

Concurrency: Client is NOT safe for concurrent use. The Faultier protocol is a strict request–response exchange; interleaving commands from multiple goroutines would corrupt framing. Callers that share a Client across goroutines must serialize access externally (e.g. with a sync.Mutex).

The exception is Close, which is safe to call concurrently with any other method or with itself — it is guarded by its own mutex.

The primary USB bulk channel (used by faultier-python) is not accessed here; see doc.go for the rationale.

func Connect

func Connect(portName string, baud int) (*Client, error)

Connect opens the serial bridge at portName and returns a ready Client. baud is the serial baud rate; pass 0 to use DefaultBaud (115200).

func (*Client) Arm

func (c *Client) Arm() error

Arm arms the trigger. The device waits for the configured trigger condition before firing the glitch pulse. Maps to glitch_non_blocking() in the upstream Python library.

func (*Client) Close

func (c *Client) Close() error

Close closes the underlying serial port. Calling Close more than once is safe — subsequent calls return nil without touching the port.

Close is guarded by an internal mutex so it is safe to call from any goroutine even though the rest of the Client API requires external serialization.

func (*Client) Configure

func (c *Client) Configure(cfg GlitcherConfig) error

Configure sends a full GlitcherConfig to the device (OpConfigure). Call this before Arm or Fire when you need more than just delay/pulse.

func (*Client) Disarm

func (c *Client) Disarm() error

Disarm cancels an armed trigger. The upstream Python library achieves the same effect via default_settings() (resetting all outputs to OUT_NONE/ TRIGGER_NONE).

func (*Client) Fire

func (c *Client) Fire() error

Fire fires a glitch immediately without waiting for a hardware trigger. Maps to Faultier.glitch() with TRIGGER_NONE in the upstream Python library.

func (*Client) SetPulse

func (c *Client) SetPulse(delayUS, pulseUS uint32) error

SetPulse configures the glitch delay and pulse width before the next Arm or Fire. Both values are in microseconds. Maps to the upstream Python configure_glitcher(delay=..., pulse=...) call.

func (*Client) Status

func (c *Client) Status() (StatusBlock, error)

Status queries the device for its current armed state and last glitch outcome. Maps to reading the Python Faultier's internal state after a call.

func (*Client) Sweep

func (c *Client) Sweep(ctx context.Context, startUS, endUS, stepUS uint32) error

Sweep arms the device and iterates delay from startUS to endUS in stepUS increments, calling Fire on each step. It is a host-side sweep loop that re-configures and re-fires the device; there is no sweep opcode in the wire protocol.

ctx is checked at the start of every iteration so the sweep can be cancelled promptly by the caller's deadline or cancel signal.

Sweep aborts on the first device error, context cancellation, or inverted range and returns the error.

type GlitchOutput

type GlitchOutput byte

GlitchOutput selects which hardware output is toggled during the glitch pulse. Mirrors faultier_pb2.GlitchOutput.

const (
	OutCrowbar GlitchOutput = 0x00 // OUT_CROWBAR — gate of the crowbar MOSFET
	OutMux0    GlitchOutput = 0x01 // OUT_MUX0    — SMA connector (ch 0/X)
	OutMux1    GlitchOutput = 0x02 // OUT_MUX1    — 20-pin header (ch 1/Y)
	OutMux2    GlitchOutput = 0x03 // OUT_MUX2    — 20-pin header (ch 2/Z)
	OutExt0    GlitchOutput = 0x04 // OUT_EXT0    — EXT0 header
	OutExt1    GlitchOutput = 0x05 // OUT_EXT1    — EXT1 header
	OutNone    GlitchOutput = 0x06 // OUT_NONE    — disabled (test/ADC only)
)

type GlitcherConfig

type GlitcherConfig struct {
	TriggerType   TriggerType
	TriggerSource TriggerSource
	GlitchOutput  GlitchOutput
	DelayUS       uint32
	PulseUS       uint32
	PowerCycle    bool
	PowerCycleLen uint8 // cycles/10 — 0–255 encodes 0–2550 hardware cycles
}

GlitcherConfig holds the parameters sent with OpConfigure. Zero values are safe defaults (no trigger, crowbar output, zero delay/pulse).

type Mock

type Mock struct {

	// State is the simulated device state, exposed for test assertions.
	State MockState

	// InjectError, when non-zero, causes the next command to return a
	// RespError frame with this error code.
	InjectError byte
	// contains filtered or unexported fields
}

Mock is an in-memory Port implementation that speaks the Faultier serial bridge wire protocol. Tests instantiate a Mock, create a Client via NewMockClient, and inspect Mock.State to verify round-trip behaviour without any hardware.

The Mock executes the same decode + response logic that real firmware would, so unit tests exercise both the Client's encoder and the protocol's response decoder.

func NewMock

func NewMock() *Mock

NewMock returns a fresh Mock in its default (reset) state.

func (*Mock) Close

func (m *Mock) Close() error

Close implements Port.

func (*Mock) Read

func (m *Mock) Read(p []byte) (int, error)

Read implements Port. Blocks until data is available or the timeout fires.

func (*Mock) SetReadTimeout

func (m *Mock) SetReadTimeout(d time.Duration) error

SetReadTimeout implements Port.

func (*Mock) Write

func (m *Mock) Write(p []byte) (int, error)

Write implements Port. Each complete frame received is decoded and a response frame is appended to the output buffer.

type MockState

type MockState struct {
	Armed       bool
	Config      GlitcherConfig
	LastOutcome byte
}

MockState holds the simulated device state managed by the Mock.

type Port

type Port interface {
	io.Reader
	io.Writer
	io.Closer
	SetReadTimeout(time.Duration) error
}

Port is the subset of go.bug.st/serial.Port the package uses. Defined as an interface so tests can inject a Mock without opening hardware.

type StatusBlock

type StatusBlock struct {
	Armed       bool
	LastDelayUS uint32
	LastOutcome byte
	Reserved    byte
}

StatusBlock holds the parsed status response payload.

type TriggerSource

type TriggerSource byte

TriggerSource selects which physical input pin to watch. Mirrors faultier_pb2.TriggerSource.

const (
	TriggerSrcNone TriggerSource = 0x00 // TRIGGER_IN_NONE
	TriggerSrcExt0 TriggerSource = 0x01 // TRIGGER_IN_EXT0
	TriggerSrcExt1 TriggerSource = 0x02 // TRIGGER_IN_EXT1
)

type TriggerType

type TriggerType byte

TriggerType selects the hardware-trigger condition wired to the Faultier EXT inputs. Mirrors faultier_pb2.TriggersType in the upstream Python library.

const (
	TriggerNone        TriggerType = 0x00 // immediate — no trigger wait
	TriggerRisingEdge  TriggerType = 0x01 // TRIGGER_RISING_EDGE
	TriggerFallingEdge TriggerType = 0x02 // TRIGGER_FALLING_EDGE
	TriggerHigh        TriggerType = 0x03 // TRIGGER_HIGH
	TriggerLow         TriggerType = 0x04 // TRIGGER_LOW
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL