# The protocol contract

What the host promises to your plugin, and what your plugin must promise to the host.

## Lifecycle

```
+-------------------+    +-------------------+    +-----------------+
|  app launch       |    |  packet capture   |    |  detail view    |
|  (main thread)    |    |  (background)     |    |  (main thread)  |
+-------------------+    +-------------------+    +-----------------+
        |                          |                       |
        v                          v                       v
PrincipalClass.init       Analyzer.init(data:)      Analyzer.protocolNodes(_:)
   register(CPAPlugIn)    Analyzer.protocolInfo(_:) -> [CPANodeProtocol]
                          Analyzer.value(_:data:)
```

## Principal class — once, on the main actor

The host loads your bundle, reads `NSPrincipalClass`, instantiates it, and calls `init?(plugInsController:)`. This happens once during app launch on the main actor. You must:

1. Build a ``CPAPlugInStructure`` describing your plugin (title, description, version, author, classes, attributes, target ports, columns).
2. Call `CPAPlugInsController.sharedManager.register(CPAPlugIn:forBundle:)` exactly once, passing your bundle.

The host caches the registration, but **keeps your principal-class instance alive** as the structure's `plugInPreferencesInstance`: it is the object the settings pane asks for `analyzerSettingsConfig`, and the object it calls back into when the user edits a port row. Store the registration you built in `pluginStruct` before returning — see [Analyzer Settings](AnalyzerSettings.md).

## Analyzer — per packet, on a background queue

For every captured packet that matches your registered protocol/port, the host:

1. Resolves the protocol name to your analyzer class.
2. Instantiates it via `init?(data: Data, packet: Any?)`.
3. Calls `protocolSize()`, `nextProtocol()`, `protocolInfo(_:)`, `hasPayload()` to learn the basics.
4. Calls `protocolNodes(_:)` to get a tree of ``CPANodeProtocol`` for the detail view.
5. Calls `value(_:data:)` for each registered attribute key to fill the table columns.

`init?` returns `nil` to mean "this packet is not mine" — the host will move on without error.

The analyzer instance is discarded after the host is done with the packet.

## Node tree

Build the detail-view tree with ``CPANode``:

```swift
let root = CPANode.nodeWithName("MyProto-Header")
root.headerLocation = 0
root.headerLength = headerSize
root.addChild(CPANode.nodeWithName("Field A", value: "0x1234"))
let group = CPANode.nodeWithName("Flags")
group.addChild(CPANode.nodeWithName("ACK", value: "1"))
group.addChild(CPANode.nodeWithName("SYN", value: "0"))
root.addChild(group)
return [root]
```

`headerLocation` and `headerLength` drive the hex-view highlighting when a node is selected. Set them on every node that maps to specific bytes.

## Columns and find

The host's packets table can show your attributes as columns and use them in the find bar. To enable both:

1. Declare a ``CPAPlugInAnalyzerAttribute`` for each attribute (key + type + analyzer class).
2. Declare a matching ``CPAPlugInColumn`` (title + key path + find predicate format).
3. Expose the value from `value(_:data:)` *and* as an `@objc dynamic var` (so KVC bindings work for the table cells).

The column's `keyPath` and the attribute's `attributeKey` **must be byte-for-byte identical**.
