Liking cljdoc? Tell your friends :D

Migration from v1.3.0 to v1.4.0

v1.4.0 was published on 2026-10-17 (Asia/Tokyo) and is available from Clojars. These notes describe its released behavior, including P01–P08 and merged fixes. The maintainer approved retaining capture-preserving Writer precedence/strict validation (D1) and exact bounded DNS aggregation/explicit failures (D2). These decisions have observable migration effects, including failures on some formerly accepted inputs; review the checklist below before upgrading. The API contract specifies the exact rules, and the compatibility assessment retains the released v1.3.0 comparison. The publication record records final-main validation, tag-triggered deployment and independent JDK 17/21 consumers of the actual published JAR.

Upgrade checklist

  • Audit Writer maps for stale capture timestamps, lengths and DLT metadata.
  • Rebaseline DNS RTT values/pair counts and QR-oriented client/server groups.
  • Configure finite analysis/report limits and handle every nonzero CLI exit.
  • Require complete reports when loss or cancellation is unacceptable.
  • Validate the published v1.4.0 JAR on your runtime before upgrading. See the release notes and installation coordinates.

Classic PCAP inputs

Valid byte arrays and plain maps with complete integer :sec/:usec continue to work. Capture maps now preserve :ts-sec/:ts-usec, original wire length and captured length. A complete capture pair wins over legacy keys. Previously, copying a capture map to the writer could replace its time with the clock and lose truncation. If intentionally retiming captured data, remove the old capture pair explicitly; changing only :sec does not change the selected time.

This example writes three records and reads their times/lengths back:

(require '[paclo.core :as core])
(let [file (java.io.File/createTempFile "paclo-migration-" ".pcap")
      legacy {:bytes (byte-array 60) :sec 1700000000 :usec 123}
      captured (assoc legacy :ts-sec 1700000001 :ts-usec 456
                      :caplen 60 :len 120)
      ;; Keep bytes/length/framing; intentionally select a new legacy time.
      retimed (-> captured (dissoc :ts-sec :ts-usec)
                  (assoc :sec 1700000002 :usec 789))]
  (try
    (core/write-pcap! [legacy captured retimed] (.getPath file))
    (core/reduce-packets {:path (.getPath file) :max 3}
                         (fn [rows packet]
                           (conj rows (select-keys packet
                                                   [:ts-sec :ts-usec :caplen :len])))
                         [])
    (finally (.delete file))))

A partial capture pair now fails even alongside a complete legacy pair. Complete selected pairs containing nil, fractions or invalid ranges fail. Missing both pairs and partial legacy pairs still select the clock; do not use that fallback for measured event time. Require a complete pair at your input boundary. This example checks writer rejection and then supplies a verified complete pair:

(require '[paclo.core :as core])
(let [file (java.io.File/createTempFile "paclo-invalid-time-" ".pcap")
      old-input {:bytes (byte-array 60) :sec 1700000000 :usec 123
                 :ts-sec 1700000001}]
  (try
    {:rejected (try (core/write-pcap! [old-input] (.getPath file)) false
                    (catch clojure.lang.ExceptionInfo e
                      (= "partial :ts-sec/:ts-usec timestamp" (.getMessage e))))
     ;; Only do this when upstream evidence supplies the missing component.
     :verified-time
     (do (core/write-pcap! [(assoc old-input :ts-usec 456)] (.getPath file))
         (core/reduce-packets {:path (.getPath file) :max 1}
                              (fn [_ packet]
                                (reduced (select-keys packet [:ts-sec :ts-usec])))
                              nil))}
    (finally (.delete file))))

If the complete legacy pair is intentionally authoritative, explicitly remove both capture keys before writing, as in the retiming example. Do not remove metadata indiscriminately or catch every writer failure and retry with “now”. That can hide corrupt evidence and produce convincing but false RTT/window data. Explicit :caplen must match array length and :len must be at least caplen; selected timestamps/lengths must satisfy the writer rules. Correct the producer or quarantine the record; do not silently clamp lengths or truncate frames. caplen must fit the selected snaplen, while original len may exceed it.

The two-argument writer still selects Ethernet DLT 1/snaplen 65536. For a cooked source, use the three-argument form with its actual native DLT, for example {:linktype 113 :snaplen 65536}. Use 12 for Linux/macOS Raw IP packet DLT, not savefile LINKTYPE 101. Mixed DLTs need separate Classic files or PCAPNG. Do not erase :linktype to bypass mismatch checks. Classic output is incremental and may be partial after failure; it has no atomic rollback.

Read errors and data consumers

A malformed native read now raises an error instead of appearing to finish at EOF. Default to failing the operation. If a valid prefix is explicitly useful, use reduce-packets-report with :error-mode :pass and inspect :stats/:error, or an error callback. A prefix is not a complete successful capture. Decode issues are separate: retain string :decode-error handling and tolerate new :diagnostics and :linktype keys. Prefer selecting known keys over rejecting all extra fields. Unsupported framing can be read raw; it is not decoded as Ethernet.

CLI wrappers and analytical results

  • Flow: row fields/formats and captured-byte totals remain. Equal-score ranking/bidirectional orientation may change; don't snapshot arbitrary order. Default unique-flow limit is 100000. Set a suitable positive --max-flows; exhaustion/source failure propagates as a nonzero JVM exit, not DNS code 5.
  • DNS RTT: native time now yields real millisecond RTT/mean rather than nil or truncated means. Strict question-aware matching, retries, expiry and quarantine can change pair counts. Rebaseline alerts using known transactions; use --reorder-us, timeout and reuse horizon for the actual traffic order.
  • DNS QPS: counts are packets per bucket, including both query and response, and bytes are captured bytes. Divide by bucket duration in seconds for QPS. Client/server are QR-oriented roles, so response destinations join clients. Applications needing source/destination groups should project those addresses explicitly with the library. Full offline EOF replaces accidental early caps.
  • Bounds/errors: old max-buckets could silently truncate; now exact reports and empty fills either fit configured finite bounds or fail before stdout. DNS usage/input/enum/flag exits remain 1/2/3/4; source/analysis/capacity failures use 5. Handle all nonzero exits, not only the old list. Exit 0 with dropped or cancelled delivery is possible: require stderr :complete? true when completeness matters. That flag still does not prove every DNS query has a response.

Formats/positional arguments are retained. Output volume, group keys, matches, metadata and valid-file success can change. Review the bounded CLI options before increasing memory budgets. The approved v1.4.0 policy retains these correctness and resource bounds; it does not restore silent truncation or guessed pairs.

Choose the API and output format deliberately

Use packet-xf for pure raw/full/flow transformation; reduce-packets for owned synchronous early termination, or reduce-packets-report for execution stats. Use managed with-open capture for externally stopped streams and bounded stream/fan-out for consumers. Abandoning a prefix of legacy lazy packets does not close a producer blocked on its queue. Bytes remain mutable and shared; treat published arrays as read-only.

Use explicit ordered :extensions [dns-ext/annotate-dns] for independent full decode pipelines; [] disables hooks. Omission retains dynamic global registration. Flow keys/aggregates/Top-N use paclo.flow; top-n takes an options map such as {:n 10}. paclo.window and paclo.dns provide pure bounded event-time analysis with explicit advance/finish, not CLI serialization or implicit clocks. See usage for runnable compositions.

InformationClassic core/write-pcap!paclo.pcapng event reader/writer
Packet bytes/captured and original lengthsYes, validated; one DLT/snaplenYes, within supported block contracts
TimeSelected integer microseconds; absent time uses clock fallbackExact ticks/resolution/offset and explicit timestamp absence
Multiple sections/interfaces, comments/optionsNot retainedOrdered supported metadata/owned octets retained
Precision/container identityNo nanosecond/section/byte-identical guaranteeKnown semantics, not byte-identical reproduction
PublicationIncremental overwrite, may leave partial fileExclusive atomic publication on supported Linux/macOS filesystems

Native core PCAPNG reads normalize to microseconds and lose rich metadata. For that metadata use pcapng/open-source with with-open and reduction, then pcapng/write-pcapng! preserving events. Authoring and explicitly lossy modes have different obligations; see PCAPNG guide. A .pcapng filename does not switch Classic Writer format. SPB time can be absent: choose a measured analysis policy instead of converting absence to zero or current time.

JDK 17 and 21 remain supported with Clojure 1.12.x and system libpcap. The v1.4.0 release restores JDK17-compatible distributed classes (major 61); the published v1.3.0 JAR contains major 65 and needs JDK21, or a Java17 source build. Validate the exact JAR you deploy; a JDK17 source build alone does not validate a JDK21-built artifact.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close