Skip to content

v0.16.0

Latest

Choose a tag to compare

@xbmlz xbmlz released this 25 Aug 10:10
71c43e2

A robustness release. Most of it is the audit of the wire decoders, the SCP
association lifecycle and the DICOMweb handlers
(#39); the two
negotiation and C-MOVE fixes that head Fixed came out of following that work up
(#42,
#41).

No exported symbol was removed. Three behaviour changes are visible to callers
and are listed under Changed, each with the way to restore the old behaviour.

Security

  • pdu.Read no longer sizes its buffer from the declared length, where six bytes
    from an unauthenticated peer bought a 4 GiB allocation: a 16 MiB
    MaxPDUReadLength backstop with ErrPDUTooLarge, ReadLimit for a tighter
    bound, and a chunked read that grows with bytes actually received
  • SCP resource limits, none of which existed: ServerConfig.HandshakeTimeout
    (30s by default), IdleTimeout (refreshed per read and per write, so it
    measures silence rather than duration) and MaxConcurrentAssociations (over
    the cap the peer gets an A-ASSOCIATE-RJ, not a bare close). Serve also closes
    in-flight connections when its context is cancelled
  • SCP validates the Called AE Title and the Protocol Version during negotiation
    instead of accepting whatever arrives, and rejects an empty Calling AE Title —
    which used to make the SCP fail while encoding its own A-ASSOCIATE-AC and drop
    the connection with no explanation
  • WADO-RS multipart boundaries are per-response and random. The boundary was
    hard-coded, so an instance containing that delimiter line ended the body early:
    HTTP 200 with silently truncated data
  • DICOMweb path segments are validated (ErrInvalidPath), request and response
    bodies are bounded (DefaultMaxRequestBytes, DefaultMaxResponseBytes,
    WithMaxRequestBytes, WithMaxResponseBytes, ErrTooLarge), and handler
    errors no longer echo store internals to the client
  • 32-bit wire lengths are compared in uint64 instead of being converted to
    int. Where int is 32 bits wide a DIMSE Value Length of 0xFFFFFFF8 came
    out negative, passed its bounds check and panicked the decoder from 8 bytes; a
    PDV item length of 0x7FFFFFFF did the same from 20. Not reachable in a
    released build, since godicom does not compile for a 32-bit target either

Fixed

  • UID padding is trimmed when decoding an A-ASSOCIATE
    (#42). PS3.5 9.1 pads an
    odd-length UID with a NUL and most UIDs traded during negotiation are odd
    (1.2.840.10008.1.1 is 17 bytes), so a conforming peer sends that byte — and
    it stayed in the string, losing every comparison built on it: the SCP answered
    "abstract syntax not supported" for a SOP class it supports, a proposed Role
    Selection was dropped, and an accepted Transfer Syntax reached godicom naming
    nothing. Covers the Application Context, Abstract Syntax, Transfer Syntax,
    Implementation Class UID and Role Selection SOP Class items. Encoding is
    unchanged and still writes UIDs unpadded, which is what pynetdicom sends
  • C-MOVE sub-operation counts saturate at 65535 instead of wrapping
    (#41). The four counts
    are US on the wire, but the SCP also counted in uint16, so a MovePlan of
    65536 stores reported Remaining: 0 in its first Pending response — an SCU
    reading that as "nothing outstanding" stopped there — and the next response
    computed total-done as 0-1 and said 65535 were outstanding, so the count
    moved backwards. On the failure path 65536 failures were reported as 0: total
    success under a failure status. Counting is now in int and narrowed once, at
    the wire, and the final status is decided on the real counts. C-MOVE itself has
    no 65535 limit, so an oversized plan is served and logged rather than rejected
  • One reader per SCP association. Cancel detection read the connection directly
    under a 2 ms deadline, so a C-CANCEL-RQ split across TCP segments was consumed
    and discarded and the next read landed mid-PDU, after which the SCP could block
    forever on an open connection. Removing the per-response deadline also made
    600 C-FIND matches stream in ~20 ms instead of ~1.5 s
  • The SCP dispatches on the Command Field rather than trying each Decode*RQ in
    turn: a command set carrying no (0000,0100) satisfied all of them and was
    handled as a C-ECHO. A C-CANCEL-RQ arriving after its operation finished is
    logged and ignored instead of aborting the association
  • Presentation-context-IDs are assigned without collisions and validated before
    anything is sent (ErrPresentationContexts, MaxPresentationContexts). The
    old id = 2*i+1 overflowed a byte past the 128th context and ignored IDs the
    caller had set, proposing one ID twice
  • CStore no longer writes the SOP Instance UID it generates into the caller's
    Dataset, which made a reused Dataset send every instance under one identity
  • Message IDs are atomic, and Abort no longer clears the connection out from
    under a blocked reader — aborting a blocked C-FIND panicked on a nil net.Conn
  • pdu refuses to encode a value its length field cannot describe (ErrTooLong)
    rather than writing a truncated length

Changed

  • A requested Priority of MEDIUM is sent as MEDIUM. MEDIUM encodes as
    0x0000, and every SCU verb read that zero as "unset" and substituted LOW, so
    MEDIUM was the one priority the API could not express. An unset Priority now
    means MEDIUM; pass dimse.PriorityLow to deprioritise a request
  • An SCP answers only to its own AE title. Set
    ServerConfig.AllowAnyCalledAETitle, or list the titles it should answer to in
    AlternativeAETitles, for the previous behaviour
  • pdu.Read rejects a PDU declaring more than MaxPDUReadLength (16 MiB).
  • ae.AllStorageSOPClasses is built from godicom/uid constants instead of UID
    strings with names in trailing comments, five of which disagreed with the UID
    beside them. The exported list is byte-identical: same 170 entries, same order
  • Named constants for the A-ASSOCIATE-RJ result / source / reason bytes and the
    protocol version bit (pdu.RejectResultPermanent,
    pdu.RejectSourceServiceUser, pdu.RejectReasonCalledAENotRecognized,
    pdu.ProtocolVersion1, …)

Added

  • ae.UIDStrings converts uid.UID values to the []string the abstract-syntax
    and transfer-syntax fields hold, so callers can name a SOP class instead of
    pasting its UID
  • Fuzz targets over the unauthenticated wire path — pdu.FuzzRead,
    pdu.FuzzDecodePDataTF, dimse.FuzzDecodeElements,
    dimse.FuzzCommandDecoders — asserting encode/decode round-trip byte identity
    and full byte accounting, not just the absence of panics
  • CI runs ubuntu/windows/macos against Go 1.26 and 1.27, tests ./pdu ./dimse ./status on linux/386, and fuzzes each target for 60s per run