# JSON+ Protocol Dictionary File Guide
# ====================================

## Overview

Dictionary files define how JSON fields are dissected and displayed in Wireshark.
Each dictionary file is an XML document that maps JSON paths to Wireshark fields.

## Where Dictionary Files Live

JSON+ loads dictionaries from two locations, in order:

  1. System directory   <datadir>/json/
                        (e.g. /usr/share/wireshark/json/, or
                         <build>/run/json/ for an in-tree build)
  2. Personal directory ~/.config/wireshark/json/  (Linux/macOS)
                        %APPDATA%\Wireshark\json\ (Windows)

Each directory has its own config.txt listing the XML files to load.
The personal directory is opt-in — JSON+ does not create it. Create it
yourself, drop in a config.txt and one or more .xml files, and restart
Wireshark.

Precedence: later-loaded wins. Order of loading is

  system config.txt (top → bottom), then
  personal config.txt (top → bottom)

so when two dictionaries define the same <field path="...">,
<protocol port="...">, or <protocol path="..."> regex, the later one
overrides the earlier. In practice this means:

  - A personal dictionary always wins over a system dictionary.
  - Within either config.txt, the last-listed dictionary wins on
    collisions with earlier ones.

External parser scripts referenced by parser="..." on a <field> use
the same precedence: the personal copy at
~/.config/wireshark/json/parsers/<name> is used when it exists,
otherwise the system copy at <datadir>/json/parsers/<name>. The
"JSON+: Run external parsers" preference still gates whether scripts
are executed at all.

Personal files survive Wireshark upgrades and rebuilds. The system
directory is overwritten by the installer; the personal one is not.

## File Format

Dictionary files must be valid XML with a root <json-dictionary> element.

Basic structure:
<?xml version="1.0" encoding="UTF-8"?>
<json-dictionary>
    <base name="Your Protocol Name" version="1.0">
        <!-- Type definitions -->
        <!-- Protocol info -->
        <!-- Field definitions -->
    </base>
</json-dictionary>

##### Type Definitions #######

Every dictionary must define the base types it uses.

Required type definitions:
<typedefn type-name="String" base-type="string"/>
<typedefn type-name="Integer" base-type="int64"/>
<typedefn type-name="Float" base-type="double"/>
<typedefn type-name="Boolean" base-type="boolean"/>
<typedefn type-name="Object" base-type="object"/>
<typedefn type-name="Array" base-type="array"/>

Available base-type values:
- string   → FT_STRING (text values)
- int64    → FT_INT64 (64-bit integers)
- uint64   → FT_UINT64 (unsigned 64-bit integers)
- int32    → FT_INT32 (32-bit integers)
- uint32   → FT_UINT32 (unsigned 32-bit integers)
- double   → FT_DOUBLE (floating point numbers)
- boolean  → FT_BOOLEAN (true/false values)
- object   → FT_NONE (JSON objects - container only)
- array    → FT_NONE (JSON arrays - container only)

### Special Display Types

The optional `display` attribute allows specialized formatting and validation for specific data types.
This is useful for IP addresses, MAC addresses, timestamps, etc.

**Syntax:**
<typedefn type-name="CustomType" base-type="base" display="format"/>

**Available display formats:**

| Display Value | Base Type | Description | Example |
|--------------|-----------|-------------|---------|
| `ipv4` | string or int32 | IPv4 address (dotted decimal) | "192.168.1.1" |
| `ipv6` | string | IPv6 address | "2001:db8::1" |
| `ether` | string | MAC/Ethernet address | "00:11:22:33:44:55" |
| `absolute_time` | int64 or uint64 | Unix timestamp (seconds) | Shows as date/time |
| `relative_time` | int64 or uint64 | Relative time (seconds) | Shows as duration |
| `hex2dec` | string | Hex string as decimal with compact hex | "2328" → 9000 (0x2328) |

**Examples:**
<!-- IP Addresses -->
<typedefn type-name="IPAddress" base-type="string" display="ipv4"/>
<typedefn type-name="IPv6Address" base-type="string" display="ipv6"/>

<!-- MAC Address -->
<typedefn type-name="MacAddress" base-type="string" display="ether"/>

<!-- Timestamps -->
<typedefn type-name="Timestamp" base-type="int64" display="absolute_time"/>
<typedefn type-name="Duration" base-type="int64" display="relative_time"/>

<!-- Hex Values -->
<typedefn type-name="Hex2Dec" base-type="string" display="hex2dec"/>

**Usage in fields:**
<field name="ClientIP" path="client.ip" type="IPAddress"/>
<field name="ServerMAC" path="server.mac" type="MacAddress"/>
<field name="CellId" path="cell.id" type="Hex2Dec"/>
<field name="EventTime" path="event.timestamp" type="Timestamp"/>

**Benefits:**
- Wireshark validates and formats values correctly
- IP addresses shown in standard notation
- MAC addresses properly formatted
- Better filtering capabilities (e.g., IP subnet matching)

###### Protocol Information (Optional) #########

Define protocol metadata and port-based protocol identification:
<protocol name="My API Protocol" port="8080" transport="tcp" displayName="MyAPI"/>
<!-- Or with multiple ports -->
<protocol name="My API Protocol" port="8080,9000,9001" transport="tcp" displayName="MyAPI"/>

Attributes:
- name: Descriptive protocol name (internal use)
- port: Port number(s) for protocol matching (optional)
  - Single port: port="8000"
  - Multiple ports (comma-separated): port="8000,8080,9000"
  - When a packet's source or destination port matches any specified value,
    the protocol will be identified and the displayName will be used
- transport: "tcp" or "udp" (optional, default: "tcp")
- displayName: Custom name shown in Protocol column (optional)
  - If specified, this name appears in the Protocol column for matching packets
  - If not specified, "JSON+" is used as default
  - Protocol matching occurs by checking destination port first, then source port
- path: Regex (PCRE2) matched against the HTTP request path/URI (optional)
  - Matched against EXACTLY ONE of these display-filter fields, picked
    automatically based on what's carrying the JSON:
      * http2.headers.path   - when the frame is HTTP/2 (the :path
                                pseudo-header value for the current stream)
      * http.request.uri     - when the frame is HTTP/1.1 (the request URI
                                from the original request line)
  - You can preview the value Wireshark will feed the regex by selecting
    the frame in the GUI and reading the appropriate display-filter field.
  - For raw TCP/UDP JSON (no HTTP layer in front), neither field exists
    and path= has no effect; fall back to port= for those captures.
  - Examples: path=".*nchf-convergedcharging.*", path="/api/v[0-9]+/orders"
  - Escape regex meta-characters (e.g., \. for a literal dot)
  - At dissection time, path matching is tried before port matching, so
    path-based entries can carry JSON over any HTTP-mux'd port
- case: "sensitive" (default) or "insensitive" - controls path regex
  case sensitivity. Matches the <field> case= default.
- condition: "or" (default) or "and" - when BOTH port= and path= are set,
  this controls how they combine:
    - "or" (default): either port or path matching is sufficient
    - "and": both port and path must match to select this protocol
  When only one of port=/path= is set, condition= is a no-op.

When at least one <protocol> entry exists in the loaded dictionary, the
dictionary's <field> definitions only apply to packets that match one of
the <protocol> entries (gated dispatch). Packets that don't match any
<protocol> are rendered as generic JSON. When no <protocol> entries exist,
all JSON traffic is parsed against the dictionary's fields (legacy
behavior, no gating).

Example: Port-based protocol identification:
<!-- 5G AMF API on port 8000 -->
<protocol name="5G Access Management" port="8000" transport="tcp" displayName="5G-JSON"/>

<!-- Test API on port 9999 -->
<protocol name="Test JSON API" port="9999" transport="tcp" displayName="TestAPI"/>

<!-- CHF API on multiple ports -->
<protocol name="CHF Charging API" port="8080,1090,3000" transport="tcp" displayName="AMF-JSON"/>

Example: Path-based protocol identification (HTTP/2 or HTTP/1.1):
<!-- 5G SBI: select on the request URI regardless of TCP port -->
<protocol name="SMF Convergent Charging"
          path=".*nchf-convergedcharging.*"
          displayName="SMF_CHF"/>

<!-- OR (default): either URI OR port match selects this protocol -->
<protocol name="SMF (Loose)"
          path=".*nchf-convergedcharging.*" port="1090"
          displayName="SMF_CHF_LOOSE"/>

<!-- AND: require both URI and port match -->
<protocol name="SMF on Port 1090 (Strict)"
          path=".*nchf-convergedcharging.*" port="1090"
          condition="and" displayName="SMF_CHF_1090"/>

Per-Protocol Filterable Boolean Field:
Each <protocol> element automatically registers a hidden FT_BOOLEAN field
with abbrev json.<sanitized-name>, where the name is lowercased and
non-alphanumeric characters are replaced with underscores:
  <protocol name="NUDM_SDM" ...>  →  json.nudm_sdm
  <protocol name="GitHub API" ...>  →  json.github_api
  <protocol name="5GC Core" ...>  →  json._5gc_core
This field is set on every packet where the protocol matched, making it
possible to filter: json.nudm_sdm (shows only NUDM_SDM-matched packets).
The field exists in the registry at startup even if no packets matched.

Per-Protocol Field Scoping:
When <protocol> entries are defined, each protocol's <field> definitions
are scoped to packets that matched that specific protocol. Fields from
one XML file do not apply to packets matched by a protocol in a different
XML file. Files with no <protocol> element contribute fields only in
legacy mode (no protocols defined anywhere).

Protocol Column Behavior:
- Packet on port 8000 → Shows "5G-JSON" in Protocol column
- Packet on port 9999 → Shows "TestAPI" in Protocol column
- Packet on port 8080, 1090, or 3000 → Shows "AMF-JSON" in Protocol column
- HTTP/2 packet with :path matching ".*nchf-convergedcharging.*" → "SMF_CHF"
- Packet on other ports / non-matching paths → Shows "JSON+" in Protocol
  column AND the JSON tree is rendered as generic JSON (no dictionary
  field names), provided at least one <protocol> exists in the dictionary.

Note: When two <protocol> entries declare the same port=, the later one
overrides the earlier in the port lookup table (per-port slot). Path-based
dispatch is unaffected (every path= entry is iterated). Avoid configuring
two protocols on the exact same port with no distinguishing path=.

###### Regex Patterns for the path= Attribute #########

The path= attribute is a PCRE2 regular expression matched against the
HTTP request path/URI from the current frame:

  - HTTP/2 traffic:  the :path pseudo-header
                     (display filter: http2.headers.path)
  - HTTP/1.1 traffic: the request URI
                      (display filter: http.request.uri)

The match is "contains" semantics - no implicit anchoring at start or
end - so use .* or ^ / $ as needed.

Tip: select the frame in Wireshark and look in the packet detail pane
for http2.headers.path or http.request.uri to see the exact string the
regex will be tested against.

### Alternation (OR)

PCRE2 uses a single | character. Two equivalent forms:

<!-- Verbose: full alternative on each side -->
<protocol name="5G-NF" path=".*nchf.*|.*smf.*" displayName="5G-NF"/>

<!-- Compact: non-capturing group (?:...) - preferred -->
<protocol name="5G-NF" path=".*(?:nchf|smf).*" displayName="5G-NF"/>

The (?:...) form groups the alternation without creating a capture group
(we don't extract submatches here, so non-capturing is slightly faster).

Common mistake: || is NOT alternation. It matches an empty string OR
an empty string, which matches everything. Always use single |.

### Anchoring

By default the regex is unanchored (acts like "contains"). To require
the path to start with a specific prefix, anchor at the beginning:

<protocol name="CHF" path="^/nchf-convergedcharging/" displayName="CHF"/>

To require an exact match, anchor at both ends:

<protocol name="Exact" path="^/api/v1/orders$" displayName="Exact"/>

### Escaping the literal dot

In regex, . matches any single character. To match a literal period,
escape with backslash:

<!-- Match version "v1.2" - the . must be escaped -->
<protocol name="V12" path="/api/v1\.2/.*" displayName="V12"/>

XML attribute values accept \. as-is - no XML entity escaping needed.

### Character classes

[0-9]    any digit
[a-z]    any lowercase letter
[a-fA-F0-9]  any hex digit
[^x]     anything EXCEPT x (negation)
\d       shorthand for [0-9]
\w       shorthand for [a-zA-Z0-9_]

### Quantifiers

x*       zero or more of x
x+       one or more of x
x?       zero or one of x  (makes x optional)
x{3}     exactly 3 of x
x{2,5}   between 2 and 5 of x
x{2,}    2 or more of x

### Case sensitivity

Use the case= attribute on the <protocol>, NOT inline (?i) flags:

<!-- Match either case of NCHF -->
<protocol name="CHF" path=".*nchf-convergedcharging.*"
          case="insensitive" displayName="CHF"/>

The compiled regex picks up WS_REGEX_CASELESS automatically when
case="insensitive". Default is case-sensitive (matches the <field>
default).

### Worked examples for 5G SBI

<!-- Any 5G NF service prefix -->
<protocol name="5G-SBI" path="^/(?:nchf|nudm|nsmf|npcf|namf|nausf|nudr)-"
          displayName="5G-SBI"/>

<!-- Versioned CHF endpoint -->
<protocol name="CHF-v2+" path="^/nchf-convergedcharging/v[0-9]+/"
          displayName="CHF"/>

<!-- Any path containing a UUID -->
<protocol name="UUID-Path"
          path=".*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}.*"
          displayName="UUID-Path"/>

<!-- Versioned REST endpoint -->
<protocol name="OrdersAPI" path="^/api/v[0-9]+/orders" displayName="Orders"/>

<!-- Default: match if either path OR port matches (condition="or" is default) -->
<protocol name="SBI-Loose"
          path="^/(?:nchf|nudm|nsmf)-" port="1090,8443"
          displayName="SBI"/>

<!-- Stricter: require both URI prefix AND port=1090 (condition="and") -->
<protocol name="CHF-1090" path="^/nchf-convergedcharging/" port="1090"
          condition="and" displayName="CHF-1090"/>

### Common mistakes to avoid

- || does not mean OR.  Use single |.
- Unescaped . matches any character. Use \. for literal dot.
- Forgetting to anchor when intended. ".*nchf.*" matches anywhere;
  "^/nchf-" requires the prefix.
- Setting case="insensitive" but writing the regex with explicit
  upper-case characters - works fine but the case= flag does the work,
  the regex shouldn't carry [Nn][Cc][Hh][Ff] noise.

### Debugging a bad regex

If the regex fails to compile, the JSON+ loader logs a warning via
ws_warning() including the protocol name and pattern. Run Wireshark/
tshark from the terminal to see the warning, e.g.:

  WARN: JSON dictionary: failed to compile path regex for protocol "X"
        (pattern="[unterminated"): missing ]

The protocol's port= setting (if any) still applies; only the path=
matching is disabled.

############## Field Definitions ##################

### Simple Fields

Define top-level JSON fields:
<field name="request_id" path="request_id" type="String"/>
<field name="timestamp" path="timestamp" type="Integer"/>
<field name="price" path="price" type="Float"/>
<field name="active" path="active" type="Boolean"/>

Attributes:
- name: Display name in Wireshark packet tree 
- path: JSON path to the field
- type: One of your defined type-name values
- description: (Optional) Tooltip text shown on hover
- info: (Optional) Label for Info column display
  - When specified, the field value appears in the Info column
  - Format: "label: value" (e.g., "User Name: John Doe")
  - Useful for highlighting important fields in packet list
- df: (Optional) Custom display filter name
  - When specified, this short name is used for filtering instead of the full path
  - Very useful for long, nested paths to improve filter usability
  - Example: df="nfci.ipv4" instead of nfConsumerIdentification.nFIPv4Address

Generated filter field:
- Default: json.<path> (Example: json.request_id)
- With df: json.<df> (Example: json.nfci.ipv4)

Custom Display Filter Example:
<!-- Without df: filter would be json.nfConsumerIdentification.nFIPv4Address -->
<field name="Nfipv4Address" path="nfConsumerIdentification.nFIPv4Address" type="String" df="nfci.ipv4"/>

<!-- Now you can use the shorter filter: -->
<!-- json.nfci.ipv4 == "10.0.1.1" -->

### Case-Sensitive vs Case-Insensitive Field Matching

By default, JSON field paths are matched case-sensitively against the dictionary definitions.
For protocols with inconsistent casing, you can enable case-insensitive matching per field using
the optional `case` attribute.

Syntax:
<field name="..." path="..." type="..." case="insensitive"/>

Attribute values:
- `case="sensitive"` - Exact case match required (default behavior)
- `case="insensitive"` - Match field names regardless of case
- If case attribute is not specified, field matching is case-sensitive

Important Notes:
- Case sensitivity is set **per field** - there is no inheritance
- Each field's case attribute is independent of its parent
- Default is case-sensitive - no need to specify case="sensitive" 
- Only use case="insensitive" when necessary for protocols with inconsistent casing

How It Works:
When case="insensitive" is set, the dissector will match the JSON field name regardless
of case. For example, a field defined as "userName" will match:
- "userName" (exact match)
- "UserName" (different case)
- "USERNAME" (all caps)
- "username" (all lowercase)
- Any other case variation

- Use case-insensitive matching sparingly for best performance

Examples:

Basic case-insensitive field:
<field name="User Name" path="userName" type="String" case="insensitive"
       description="User name (case-insensitive matching)"/>

This matches JSON: {"userName": "alice"}
Also matches:     {"USERNAME": "alice"}
Also matches:     {"username": "alice"}

Case-sensitive field (default):
<field name="User ID" path="userId" type="Integer"
       description="User ID (case-sensitive by default)"/>

This matches JSON: {"userId": 123}
Does NOT match:    {"UserID": 123}
Does NOT match:    {"USERID": 123}

Mixed case sensitivity in nested objects:
<field name="User Profile" path="user.profile" type="Object" case="insensitive">
    <!-- Parent is case-insensitive, but children are independent -->
    <field name="Email" path="user.profile.email" type="String" case="sensitive"/> <-- case="sensitive" is not necessary (default), but can be used for readability.
    <field name="Phone" path="user.profile.phone" type="String"/> <-- Case sensitive as well ( sensitive by default )
    <field name="Display Name" path="user.profile.displayName" type="String" case="insensitive"/>
</field>

This matches:
{"user": {"profile": {"email": "alice@example.com", "displayName": "Alice"}}}
{"USER": {"PROFILE": {"email": "alice@example.com", "DISPLAYNAME": "Alice"}}}

But this does NOT fully match (email is case-sensitive):
{"user": {"profile": {"EMAIL": "alice@example.com", "displayName": "Alice"}}}
^-- user.profile matches (insensitive), EMAIL does NOT match email (sensitive)

Case-insensitive arrays:
<field name="Tags" path="tags" type="Array" case="insensitive">
    <array-element type="String"/>
</field>

Matches: {"tags": ["admin", "user"]}
Matches: {"TAGS": ["admin", "user"]}
Matches: {"Tags": ["admin", "user"]}

Info Column Example:
<field name="user_id" path="user.id" type="Integer" info="User ID"/>
<field name="status" path="status" type="String" info="Status"/>
This will show in the Info column: "User ID: 42 Status: active"

### Nested Objects

Use nested <field> elements for objects:
<field name="user" path="user" type="Object">
    <field name="id" path="user.id" type="Integer"/>
    <field name="name" path="user.name" type="String"/>
    <field name="email" path="user.email" type="String"/>
</field>

Path notation uses dots: user.id, user.name, etc.
Filter fields: json.user.id, json.user.name

Deeply nested objects:
<field name="response" path="response" type="Object">
    <field name="data" path="response.data" type="Object">
        <field name="items" path="response.data.items" type="Array">
            <!-- array definition here -->
        </field>
    </field>
</field>

### Arrays

Define array element types using <array-element>:

Array of Objects:
<field name="items" path="items" type="Array">
    <array-element type="Object">
        <field name="id" path="items[].id" type="Integer"/>
        <field name="name" path="items[].name" type="String"/>
        <field name="price" path="items[].price" type="Float"/>
    </array-element>
</field>

IMPORTANT: Use brackets [] in the path: items[].id
Filter field removes brackets: json.items.id

Array of Strings:
<field name="tags" path="tags" type="Array">
    <array-element type="String"/>
</field>

Array of Numbers:
<field name="scores" path="scores" type="Array">
    <array-element type="Integer"/>
</field>

Nested Arrays:
<field name="matrix" path="matrix" type="Array">
    <array-element type="Array">
        <array-element type="Integer"/>
    </array-element>
</field>

### Wildcard Object Keys (<wildcardfield>)

Some JSON APIs use object keys that are runtime values — DNN names,
subscription IDs, UUIDs — rather than fixed schema names. A
`<wildcardfield>` element matches those keys by regex and dissects their
children using a fixed alias path, so child fields are filterable even
though the parent key is unknown at schema time.

Syntax:
<field name="DnnConfigurations" path="dnnConfigurations" type="Object">
    <wildcardfield name="Apn" path="dnnConfigurations.apn"
                   displayvalue="name" match=".*\.com">
        <field name="DefaultSessionType"
               path="dnnConfigurations.apn.pduSessionTypes.defaultSessionType"
               type="String"/>
        <field name="5qi" path="dnnConfigurations.apn.5gQosProfile.5qi"
               type="Integer"/>
        <!-- Nested wildcardfield is allowed -->
        <wildcardfield name="Slice" path="dnnConfigurations.apn.slice"
                       displayvalue="id" match="[0-9]+">
            <field name="Sst" path="dnnConfigurations.apn.slice.sst"
                   type="Integer"/>
        </wildcardfield>
    </wildcardfield>
    <!-- Catch-all after more specific patterns -->
    <wildcardfield name="AnyKey" path="dnnConfigurations.key"
                   displayvalue="key" match=".*">
    </wildcardfield>
</field>

Attributes:
- name:         Display name for the wildcard subtree
- path:         Full path string. The last segment (after the final dot)
                is the alias token — it replaces the runtime key in all
                child path lookups. Example: path="dnnConfigurations.apn"
                → alias "apn", child paths use "dnnConfigurations.apn.*"
- displayvalue: Label for the auto-generated string field that shows the
                actual runtime key value. Default: "key".
                Filter: json.<path>.<displayvalue> == "web.operator.com"
- match:        PCRE2 regex matched against the runtime JSON key. Required.
                First matching wildcardfield wins (document order).

Matching priority for each key encountered under the parent object:
  1. Exact literal <field> lookup by full path — wins if found
  2. <wildcardfield> entries tried in document order — first match wins
  3. No match — key displayed as generic JSON

Child elements inside <wildcardfield>:
  - <field>         — regular child field (path uses alias, not runtime key)
  - <array-element> — if the wildcard value is an array
  - <wildcardfield> — nested wildcard (recursive)

Auto-generated key-value field:
Each <wildcardfield> registers a FT_STRING field at
json.<path>.<displayvalue> (e.g. json.dnnConfigurations.apn.name).
This field holds the actual runtime key and is always filterable.

Examples:
  Filter the key:       json.dnnConfigurations.apn.name == "web.operator.com"
  Filter a child:       json.dnnConfigurations.apn.5gQosProfile.5qi == 9
  Filter array child:   json.dnnConfigurations.apn.sscModes.allowedSscModes == "SSC_MODE_1"

## Integer Enum Mapping

For integer fields, you can define human-readable enum names that map to numeric codes.
This makes both the display and filtering more intuitive.

### Basic Enum Definition

<field name="Status" path="response.status" type="Integer" info="Status">
    <enum name="Success" code="0"/>
    <enum name="Failed" code="1"/>
    <enum name="Pending" code="2"/>
</field>

Enum attributes:
- name: Human-readable enum name (used in display and filtering)
- code: Numeric value that maps to this enum name

### How Enums Work

**Display in Packet Tree:**
Shows both the enum name and numeric value in parentheses.

Status: Success (0)

**Display in Info Column:**
If the field has an `info` attribute, the enum name is used:

Status: Success

**Filtering by Enum Name:**
You can filter using the human-readable enum name.

json.response.status == Success

**Filtering by Numeric Code:**
You can also filter using the numeric code directly.

json.response.status == 0

### Enum Examples

**Network Slice Type (5G):**
<field name="Sst" path="amData.nssai.defaultSingleNssais[].sst" type="Integer" info="nssai sst">
    <enum name="priSlice" code="1"/>
    <enum name="backSlice" code="2"/>
    <enum name="iotSlice" code="3"/>
</field>

Usage:
- Filter: `json.amData.nssai.defaultSingleNssais.sst == priSlice`
- Filter: `json.amData.nssai.defaultSingleNssais.sst == 1`
- Display: "nssai sst: priSlice" (in Info column)
- Display: "Sst: priSlice (1)" (in packet tree)

**HTTP Status Codes:**
<field name="StatusCode" path="http.status" type="Integer" info="HTTP Status">
    <enum name="OK" code="200"/>
    <enum name="Created" code="201"/>
    <enum name="BadRequest" code="400"/>
    <enum name="Unauthorized" code="401"/>
    <enum name="NotFound" code="404"/>
    <enum name="ServerError" code="500"/>
</field>

**Priority Levels:**
<field name="Priority" path="message.priority" type="Integer" info="Priority">
    <enum name="Low" code="0"/>
    <enum name="Normal" code="1"/>
    <enum name="High" code="2"/>
    <enum name="Critical" code="3"/>
</field>

### Technical Notes

- Enums only work with Integer and UnsignedInteger types (not Float, String, etc.)
- When enums are defined, the field type is automatically converted to 32-bit internally
  (this is a Wireshark limitation - value_string only supports 32-bit integers)
- Enum codes must be unique within a field
- Enum names are case-sensitive in filters
- If a numeric value doesn't match any enum code, the raw number is displayed

## Path Notation Rules

1. Top-level fields: Use field name directly
   JSON: {"status": "ok"}
   Path: status

2. Nested objects: Use dot notation
   JSON: {"user": {"id": 42}}
   Path: user.id

3. Array elements: Use brackets []
   JSON: {"items": [{"id": 1}, {"id": 2}]}
   Path: items[].id
   Filter: json.items.id (brackets removed)

4. Nested array elements:
   JSON: {"data": {"users": [{"name": "John"}]}}
   Path: data.users[].name
   Filter: json.data.users.name

## Using Custom Display Filters (df) for Better Usability

For complex protocols with long field names (like 5G/telco APIs), use the df attribute
to create shorter, more memorable filter names.

### Example: 5G NF Consumer Identification

**Without df attribute (hard to use):**
<field name="Nfconsumeridentification" path="nfConsumerIdentification" type="Object">
    <field name="Nfipv4Address" path="nfConsumerIdentification.nFIPv4Address" type="String"/>
    <field name="Nfname" path="nfConsumerIdentification.nFName" type="String"/>
    <field name="Nodefunctionality" path="nfConsumerIdentification.nodeFunctionality" type="String"/>
</field>

<!-- Filtering requires very long names: -->
<!-- json.nfConsumerIdentification.nFIPv4Address == "10.0.1.1" -->

**With df attribute (easy to use):**
<field name="Nfconsumeridentification" path="nfConsumerIdentification" type="Object" df="nfci">
    <field name="Nfipv4Address" path="nfConsumerIdentification.nFIPv4Address" type="String" df="nfci.ipv4"/>
    <field name="Nfname" path="nfConsumerIdentification.nFName" type="String" df="nfci.name"/>
    <field name="Nodefunctionality" path="nfConsumerIdentification.nodeFunctionality" type="String" df="nfci.func"/>
</field>

<!-- Now filtering is much easier: -->
<!-- json.nfci.ipv4 == "10.0.1.1" -->
<!-- json.nfci.name contains "amf" -->
<!-- json.nfci.func == "SMF" -->

## Complete Example

<?xml version="1.0" encoding="UTF-8"?>
<json-dictionary>
    <base name="E-Commerce API" version="1.0">
        <!-- Type definitions -->
        <typedefn type-name="String" base-type="string"/>
        <typedefn type-name="Integer" base-type="int64"/>
        <typedefn type-name="Float" base-type="double"/>
        <typedefn type-name="Boolean" base-type="boolean"/>
        <typedefn type-name="Object" base-type="object"/>
        <typedefn type-name="Array" base-type="array"/>
        
        <!-- Protocol info -->
        <protocol name="E-Commerce API" port="9999" transport="tcp"/>
        
        <!-- Top-level fields -->
        <field name="request_id" path="request_id" type="String" 
               description="Unique request identifier"/>
        <field name="timestamp" path="timestamp" type="Integer"
               description="Unix timestamp"/>
        <field name="success" path="success" type="Boolean"/>
        
        <!-- Nested object: Customer -->
        <field name="customer" path="customer" type="Object">
            <field name="id" path="customer.id" type="Integer"/>
            <field name="name" path="customer.name" type="String"/>
            <field name="email" path="customer.email" type="String"/>
            <field name="premium" path="customer.premium" type="Boolean"/>
        </field>
        
        <!-- Array of objects: Products -->
        <field name="products" path="products" type="Array">
            <array-element type="Object">
                <field name="id" path="products[].id" type="Integer"/>
                <field name="name" path="products[].name" type="String"/>
                <field name="price" path="products[].price" type="Float"/>
                <field name="quantity" path="products[].quantity" type="Integer"/>
                <field name="in_stock" path="products[].in_stock" type="Boolean"/>
            </array-element>
        </field>
        
        <!-- Array of strings: Tags -->
        <field name="tags" path="tags" type="Array">
            <array-element type="String"/>
        </field>
        
        <!-- Nested object with nested array -->
        <field name="order" path="order" type="Object">
            <field name="order_id" path="order.order_id" type="String"/>
            <field name="total" path="order.total" type="Float"/>
            <field name="items" path="order.items" type="Array">
                <array-element type="Object">
                    <field name="sku" path="order.items[].sku" type="String"/>
                    <field name="quantity" path="order.items[].quantity" type="Integer"/>
                </array-element>
            </field>
        </field>
    </base>
</json-dictionary>

This would dissect JSON like:
{
  "request_id": "req-12345",
  "timestamp": 1735410000,
  "success": true,
  "customer": {
    "id": 42,
    "name": "John Doe",
    "email": "john@example.com",
    "premium": true
  },
  "products": [
    {"id": 1, "name": "Widget", "price": 19.99, "quantity": 2, "in_stock": true},
    {"id": 2, "name": "Gadget", "price": 29.99, "quantity": 1, "in_stock": false}
  ],
  "tags": ["sale", "featured", "new"],
  "order": {
    "order_id": "ORD-9999",
    "total": 69.97,
    "items": [
      {"sku": "WID-001", "quantity": 2},
      {"sku": "GAD-002", "quantity": 1}
    ]
  }
}

With filter fields like:
- json.request_id
- json.customer.id
- json.customer.name
- json.products.id
- json.products.price
- json.order.total
- json.order.items.sku

###### Usage in Wireshark #######

### Filtering Examples:
json.customer.id == 42
json.products.price > 20.0
json.customer.premium == 1
json.request_id == "req-12345"
json.order.items.quantity > 1

### Filtering with Enums:
json.response.status == Success        (by enum name)
json.response.status == 0              (by numeric code)
json.message.priority == High          (by enum name)
json.amData.nssai.defaultSingleNssais.sst == priSlice

### Display Hierarchy:
The packet details pane will show:
JSON Protocol
  request_id: req-12345
  timestamp: 1735410000
  success: true
  customer
    id: 42
    name: John Doe
    email: john@example.com
    premium: true
  products [2 elements]
    products 0
      id: 1
      name: Widget
      price: 19.99
      quantity: 2
      in_stock: true
    products 1
      id: 2
      name: Gadget
      price: 29.99
      quantity: 1
      in_stock: false
  tags [3 elements]
    tags 0: sale
    tags 1: featured
    tags 2: new
  order
    order_id: ORD-9999
    total: 69.97
    items [2 elements]
      items 0
        sku: WID-001
        quantity: 2
      items 1
        sku: GAD-002
        quantity: 1

## Info Column Behavior

The Info column in Wireshark's packet list shows a summary of each packet.

**Default Behavior (No Dictionary Match):**
JSON Data (1105 bytes)
When no dictionary fields have `info` attributes, or when the JSON doesn't match
any loaded dictionary, the default message shows the data size.

**Custom Info Labels:**
When fields have the `info` attribute, their values appear in the Info column:

<field name="request_id" path="request_id" type="String" info="Request"/>
<field name="user_name" path="user.name" type="String" info="User Name"/>
<field name="status" path="status" type="Integer" info="Status">
    <enum name="Success" code="0"/>
    <enum name="Failed" code="1"/>
</field>

Example Info column output:
Request: req-12345 User Name: John Doe Status: Success

**Info Column Rules:**
- Only fields with `info` attribute appear in the Info column
- The first info label replaces the default "JSON Data" message
- Subsequent labels are appended with space separators
- Enum fields show the enum name instead of numeric code
- Format is always "label: value"
- Empty or null values are skipped

## Loading Your Dictionary

1. Save your dictionary as an XML file (e.g., my-api.xml)
2. Edit config.txt to include it:
   jsonmain.xml
   my-api.xml
3. Restart Wireshark/tshark
4. Fields will be registered at startup

## Dictionary Loading Order and Collisions

When multiple dictionary files are loaded, they are processed in the order listed in config.txt.

### Loading Order
The order in config.txt determines which dictionary takes precedence:
5g.xml          # Loaded first
amf.xml         # Loaded second
my-api.xml      # Loaded third

### Port Collisions
When multiple dictionaries define the same port, **the last dictionary loaded wins** for that port's displayName:

**Example:**
config.txt:
  5g.xml
  amf.xml

5g.xml:   <protocol port="1090" displayName="5G-JSON"/>
amf.xml:  <protocol port="1090" displayName="AMF-JSON"/>

Result: Port 1090 → Shows "AMF-JSON" (amf.xml loaded last)

**Current Behavior Table:**
| Port | Dictionary Files | DisplayName Shown | Winner |
|------|-----------------|-------------------|--------|
| 8000 | 5g.xml only | "5G-JSON" | 5g.xml |
| 1090 | 5g.xml + amf.xml | "AMF-JSON" | amf.xml (last) |
| 9999 | jsonmain.xml only | "TestAPI" | jsonmain.xml |
| Other | None | "JSON+" | Default |

### Field Collisions
When multiple dictionaries define the same field path:
- **Different paths**: All fields from all dictionaries are available (merged)
- **Same path**: Last dictionary loaded wins (overwrites the field definition)

**Example:**
5g.xml:   <field name="userId" path="user.id" type="Integer"/>
amf.xml:  <field name="userId" path="user.id" type="String"/>

Result: user.id will be treated as String (amf.xml loaded last)


## Troubleshooting

**Fields not appearing:**
- Check config.txt includes your dictionary file
- Verify XML syntax is valid
- Check JSON+ is enabled: `-o "json.plus_form:TRUE"`
- Check tshark output for error messages:
  ./tshark -r capture.pcap -o "json.plus_form:TRUE" 2>&1 | grep Dictionary

**Fields not filtering:**
- Use: json.<path> (with dots, no brackets)
- List all fields: ./tshark -o "json.plus_form:TRUE" -G fields | grep json

**Type mismatches:**
- Check JSON data type matches dictionary type
- Use String for mixed-type fields
- Watch for numbers stored as strings in JSON

## Getting Help

List all registered fields:
./tshark -o "json.plus_form:TRUE" -G fields 2>/dev/null | grep json

Check dictionary loading:
./tshark -r capture.pcap -o "json.plus_form:TRUE" 2>&1 | grep Dictionary

Test dissection:
./tshark -r capture.pcap -o "json.plus_form:TRUE" -V

Filter by field:
./tshark -r capture.pcap -o "json.plus_form:TRUE" -Y "json.field.name == value"

## Authors

See AUTHORS file

Mark Stout <mark.stout@markstout.com>
