parser

package
v0.0.0-...-b3b72e3 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package parser decodes CEE/CEPA XML payloads from Dell PowerStore into strongly-typed CEPAEvent slices.

Two payload shapes are supported:

  1. Single-event: <CEEEvent>…</CEEEvent>
  2. VCAPS bulk batch: <EventBatch><CEEEvent>…</CEEEvent>…</EventBatch>

Two handshakes are detected and handled separately — callers must check for both before calling Parse, because neither is an event payload:

<RegisterRequest />   PowerStore, via Dell CEE
<CheckFileRequest>…   PowerScale (OneFS), which speaks CEPA directly

Index

Constants

View Source
const OneFSEventAction = "11"

OneFSEventAction is the Args/@action value OneFS uses for a file event, as opposed to OneFSHeartbeatAction for its heartbeat. Both arrive in the same <CheckFileRequest> element and both need a CheckFileResponse back; only this attribute separates them.

View Source
const OneFSHeartbeatAction = "9"

OneFSHeartbeatAction is the Args/@action value OneFS uses for its heartbeat. Events use other values — action 11 was measured for NFS file events.

Variables

This section is empty.

Functions

func CheckFileAction

func CheckFileAction(body []byte) string

CheckFileAction returns the Args/@action attribute of a CheckFileRequest, or "" if the payload cannot be decoded or carries no action.

This distinction is load-bearing. A CheckFileRequest with action 11 holds a real audit event:

<CheckFileRequest><Args action="11" … name="<base64 UTF-16LE UNC path>"
  protocol="1"><Cluster …/><Zone …/></Args>
  <NFSEventArgs eventType="8" desiredAccess="0x100106" createDispo="0x3"
    userSid="S-1-22-1-1000" clientIP="10.26.1.222" userId="1000"
    timeStamp="1786563708" inode="4295432746" fsId="1"/></CheckFileRequest>

Answering that with a heartbeat response and nothing else makes OneFS advance its forwarding cursor — the event is acknowledged and gone. Treating every CheckFileRequest as a heartbeat therefore loses events *silently*, which is worse than rejecting them.

func CheckFileActionDecoded

func CheckFileActionDecoded(decoded []byte) string

CheckFileActionDecoded is CheckFileAction for already-decoded input.

func DecodeBody

func DecodeBody(body []byte) ([]byte, error)

DecodeBody converts a UTF-16 CEPA payload to UTF-8, returning the input unchanged if it is not UTF-16.

Dell CEE sends UTF-16LE without a BOM. Measured on the wire against CEE 9.2.0.0 (2026-08-11), the RegisterRequest handshake is 38 bytes for a 19-character document, with `Accept-Charset: utf-16` in the request headers:

<.R.e.g.i.s.t.e.r.R.e.q.u.e.s.t. ./.>.

Before this existed, IsRegisterRequest and Parse compared raw ASCII bytes, so every handshake fell through to the parse-error path and every UTF-16 event payload was dropped after being ACKed with HTTP 200 (see issue #32).

Decoding happens once, at the head of both entry points, so the two cannot disagree about what a payload says.

It is exported so a caller asking several questions about the same body can decode once and pass the result on. That only matters on the VCAPS path: OneFS sends plain UTF-8, for which this is a no-op returning the input untouched, while CEE sends UTF-16LE, for which every call allocates a []uint16 of len(body)/2 and a []byte of up to 4 bytes per rune. A batch of thousands of events routed through IsRegisterRequest, IsCheckFileRequest and Parse would otherwise be transcoded and discarded three times per PUT.

The result is safe to hand back to any of them: decoded UTF-8 still starts with '<' followed by a non-zero byte, so a second call falls through untouched and no caller has to know whether it holds raw or decoded bytes.

func EncodeUTF16LE

func EncodeUTF16LE(b []byte) []byte

EncodeUTF16LE converts UTF-8 to UTF-16LE without a BOM, which is how Dell CEE puts XML on the wire — measured against CEE 9.2.0.0 and 9.3.0.0, whose own replies carry no BOM either.

func IsCheckEventRequest

func IsCheckEventRequest(body []byte) bool

IsCheckEventRequest reports whether the body is a Dell CEE event delivery.

This is CEE's own dialect, and it is a third thing — neither the <RegisterRequest /> handshake CEE opens with, nor the <CheckFileRequest> OneFS uses for both its heartbeat and its events. Shape recovered from CCheckEventRequest::GetXmlRequest() in libCEPPAPIWrapper.so:

<CheckEventRequest><EventList count="1">
  <Event event="0x…" path="…" flag="0x…" server="…" share="…"
    clientIP="…" serverIP="…" timeStamp="…" userSid="…" ownerSid="…"
    fileSize="0x…" newName="…" desiredAccess="0x…" createDispo="0x…"
    ntStatus="0x…" relativePath="…" encodingType="…" encodedPath="…"
    encodedRelativePath="…" encodedNewName="…"/>
</EventList></CheckEventRequest>

func IsCheckFileRequest

func IsCheckFileRequest(body []byte) bool

IsCheckFileRequest returns true if the body is the OneFS (PowerScale) heartbeat, which is a <CheckFileRequest> rather than PowerStore's <RegisterRequest>.

Measured on the wire from a 4-node OneFS 9.13.0.0 cluster (2026-08-12), 229 bytes of plain UTF-8 — not the 38-byte UTF-16LE that CEE sends:

<CheckFileRequest><Args action="9" sourceIP="10.26.1.150" sourceID="2"
  name="cABvAHcAZQByAHMAYwBhAGwAZQAxAA=="><Cluster id="00505692…"
  name="cABvAHcAZQByAHMAYwBhAGwAZQAxAA=="/></Args></CheckFileRequest>

`action="9"` is the heartbeat; `name` is the cluster name as base64 of UTF-16LE.

The element alone does not tell you what the payload is: OneFS carries its *events* in the same CheckFileRequest element, distinguished only by the action attribute. Use CheckFileAction to tell them apart — both need a CheckFileResponse back, but only action 9 is a heartbeat.

func IsHeartBeatRequest

func IsHeartBeatRequest(body []byte) bool

IsHeartBeatRequest reports whether the body is CEE's post-registration liveness probe.

This is the third thing CEE sends over HTTP, after <RegisterRequest /> and alongside <CheckEventRequest>. Its literal sits in libCEPPAPIWrapper.so immediately beside CHttpClient's other request bodies:

<RegisterRequest />
<HeartBeatRequest />
CHttpClient
hbStatus=
ntStatus=

It had never been seen on the wire because registration never succeeded, so CEE never got as far as sending one.

func IsRegisterRequest

func IsRegisterRequest(body []byte) bool

IsRegisterRequest returns true if the body is the CEPA handshake payload. Matches a <RegisterRequest> root element — guards against event payloads whose content (e.g. a file path) happens to contain the word.

This is the PowerStore/CEE dialect. OneFS opens with a different element entirely — see IsCheckFileRequest.

func IsUTF16

func IsUTF16(body []byte) bool

decodeUTF16 converts a UTF-16 CEPA payload to UTF-8, returning the input unchanged if it is not UTF-16.

Dell CEE sends UTF-16LE without a BOM. Measured on the wire against CEE 9.2.0.0 (2026-08-11), the RegisterRequest handshake is 38 bytes for a 19-character document, with `Accept-Charset: utf-16` in the request headers:

<.R.e.g.i.s.t.e.r.R.e.q.u.e.s.t. ./.>.

Before this existed, IsRegisterRequest and Parse compared raw ASCII bytes, so every handshake fell through to the parse-error path and every UTF-16 event payload was dropped after being ACKed with HTTP 200 (see issue #32).

Decoding happens once, at the head of both entry points, so the two cannot disagree about what a payload says. IsUTF16 reports whether a payload arrived as UTF-16, using the same detection decodeUTF16 uses so the two cannot disagree.

Callers need this to answer in the encoding they were addressed in. The two publishers differ: PowerStore sends UTF-16LE and CEE answers it in UTF-16LE, while OneFS sends plain UTF-8 and is answered in UTF-8 — measured on the wire from both. Replying in the wrong one produces a body the publisher cannot parse, which for OneFS is fatal (STATUS_DATA_ERROR) and for PowerStore means the CEPP session never establishes.

func IsUnmappedCEEEventType

func IsUnmappedCEEEventType(eventType string) bool

IsUnmappedCEEEventType reports whether an event carries a CEE event code this package has no established meaning for, so the caller can log the gap.

func IsUnmappedOneFSEventType

func IsUnmappedOneFSEventType(eventType string) bool

IsUnmappedOneFSEventType reports whether an event carries a OneFS eventType this package has no established meaning for. Such events are still parsed and still flow to the writers — losing them would be worse, because the CheckFileResponse has already advanced the cluster's forwarding cursor by the time anyone could decide to drop them — but the caller should log them so the gap stays visible.

Types

type CEPAEvent

type CEPAEvent struct {
	// Raw CEPA identifier, e.g. "CEPP_FILE_WRITE"
	EventType string

	// Filesystem path of the affected object
	FilePath string

	// User context
	Username string
	Domain   string
	UserSID  string
	LogonID  string

	// Network context
	ClientAddr string

	// Protocol the operation used, resolved from the wire's numeric code:
	// CIFS, NFS, FTP or Unknown. Never empty — an unmapped code renders
	// Unknown rather than blank, so it cannot merge with a missing value when
	// used as a metric label.
	Protocol string

	// Server is the NAS server the operation happened on, as the array reports
	// it. Bounded by the size of the estate, unlike ClientAddr, which is every
	// workstation that ever touched a share — which is why this is the one
	// safe to carry as a metric label and that one is not.
	Server string

	// Event timestamp (parsed from the XML or synthesised from receive time)
	Timestamp time.Time

	// I/O statistics — only meaningful for CEPP_CLOSE_MODIFIED
	BytesRead      int64
	BytesWritten   int64
	NumberOfReads  int64
	NumberOfWrites int64
}

CEPAEvent is the normalised representation of a single CEPA audit event.

func Parse

func Parse(body []byte, receiveTime time.Time) ([]CEPAEvent, error)

Parse decodes one or more CEPA events from a raw XML body. The receiveTime is used as a fallback when the XML payload contains no timestamp.

func ParseCheckEventRequest

func ParseCheckEventRequest(body []byte, receiveTime time.Time) ([]CEPAEvent, error)

ParseCheckEventRequest decodes a CEE <CheckEventRequest> into the same CEPAEvent values the OneFS and VCAPS paths produce, so all three feed one mapper and one set of writers.

receiveTime is the fallback when an event carries no usable timestamp.

func ParseCheckEventRequestDecoded

func ParseCheckEventRequestDecoded(decoded []byte, receiveTime time.Time) ([]CEPAEvent, error)

ParseCheckEventRequestDecoded is ParseCheckEventRequest for already-decoded input.

func ParseDecoded

func ParseDecoded(decoded []byte, receiveTime time.Time) ([]CEPAEvent, error)

ParseDecoded is Parse for input Classify has already decoded.

func ParseOneFSEvent

func ParseOneFSEvent(body []byte, receiveTime time.Time) ([]CEPAEvent, error)

ParseOneFSEvent decodes a OneFS <CheckFileRequest action="11"> into the same CEPAEvent the PowerStore path produces, so both feed one mapper and one set of writers rather than a parallel pipeline.

receiveTime is the fallback when the payload carries no usable timestamp.

func ParseOneFSEventDecoded

func ParseOneFSEventDecoded(decoded []byte, receiveTime time.Time) ([]CEPAEvent, error)

ParseOneFSEventDecoded is ParseOneFSEvent for already-decoded input.

type Dialect

type Dialect int

Dialect names one of the payload shapes that arrive on the CEPA URL.

All of them are POSTed or PUT to the same path by different publishers, and answering one with another's document is a silent, fatal failure — see pkg/server. Recognition lives here; the reply documents live in pkg/server.

const (
	// DialectUnknown is a payload whose root element matches none of the
	// others. It is not an error on its own: the VCAPS event shapes are
	// recognised by Parse rather than by root element.
	DialectUnknown Dialect = iota
	DialectRegisterRequest
	DialectHeartBeatRequest
	DialectCheckFileRequest  // OneFS: heartbeat and event share this element
	DialectCheckEventRequest // Dell CEE's event delivery
)

func Classify

func Classify(body []byte) (Dialect, []byte, error)

Classify transcodes the body once and reports which dialect it is, returning the decoded bytes for the caller to hand to the matching parser.

This exists because transcoding is the dominant cost of handling a request. Every Is* predicate calls DecodeBody on the whole body just to read its root element, so dispatching through four predicates and then parsing decoded the same payload five times. Measured on a 1000-event UTF-16LE batch (698 KB): 41.2 ms and 47.6 MB per request, of which 25.5 ms and 43.7 MB — 62% of the time and 92% of the allocations — was redundant transcoding. DecodeBody allocates roughly 12.5x the body size each time it runs, and pkg/server accepts bodies up to 64 MiB against a ~3 s CEPA deadline.

The Is* predicates remain for callers holding a raw body; they are unchanged and still decode. Classify is what the dispatcher should use.

Jump to

Keyboard shortcuts

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