netdicom

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2025 License: Apache-2.0 Imports: 22 Imported by: 0

README

Golang implementation of DICOM network protocol.

See doc.go for (incomplete) documentation. See storeclient and storeserver for examples.

Inspired by https://github.com/pydicom/pynetdicom3.

Status as of 2017-10-02:

  • C-STORE, C-FIND, C-GET work, both for the client and the server. Look at sampleclient, sampleserver, or e2e_test.go for examples. In general, the server (provider)-side code is better tested than the client-side code.

  • Compatibility has been tested against pynetdicom and Osirix MD.

TODO:

  • Documentation.

  • Better SSL support.

  • Implement the rest of DIMSE protocols, in particular C-MOVE on the client side, and N-* commands.

  • Better message validation.

  • Remove the "limit" param from the Decoder, and rely on io.EOF detection instead.

go-netdicom

Fuzzing

Native Go Fuzzing (Go 1.18+)

This project now supports native Go fuzzing that replaces the deprecated go-fuzz tools:

PDU Fuzzing (✅ Working)
# Run PDU fuzzing - tests Protocol Data Unit parsing
go test -fuzz=FuzzPDU ./fuzzpdu

# Run with specific time limit
go test -fuzz=FuzzPDU -fuzztime=30s ./fuzzpdu
End-to-End Fuzzing (⚠️ In Progress)
# E2E fuzzing currently has issues but is being improved
# Use PDU fuzzing for now, or contribute to fix E2E fuzzing
# go test -fuzz=FuzzE2E ./fuzze2e
Migration from go-fuzz ✅ Complete

✅ MIGRATION COMPLETED:

  • ✅ Removed deprecated go-fuzz dependencies
  • ✅ Created native Go 1.18+ fuzz tests
  • ✅ Updated all documentation to English
  • ✅ PDU fuzzing working perfectly
  • ⚠️ E2E fuzzing needs additional work (complex network operations)
Legacy go-fuzz (DEPRECATED)

⚠️ WARNING: go-fuzz is deprecated since Go 1.18 and no longer receives maintenance.

Use native Go fuzzing above instead.

Documentation

Overview

Package netdicom implements the DICOM network protocol.

This package exports two main classes: ServiceUser for implementing DICOM clients, and ServiceProvider for implementing DICOM servers.

Index

Constants

View Source
const DefaultMaxPDUSize = 4 << 20

DefaultMaxPDUSize is the the PDU size advertized by go-netdicom.

Variables

This section is empty.

Functions

func RunProviderForConn

func RunProviderForConn(ctx context.Context, conn net.Conn, params ServiceProviderParams)

RunProviderForConn starts threads for running a DICOM server on "conn". This function returns immediately; "conn" will be cleaned up in the background.

func SetProviderFaultInjector

func SetProviderFaultInjector(f FaultInjector)

SetProviderFaultInjector sets the fault injector to be used by all provider (server) side statemachines.

func SetUserFaultInjector

func SetUserFaultInjector(f FaultInjector)

SetUserFaultInjector sets the fault injector to be used by all user (client) side statemachines.

Types

type CEchoCallback

type CEchoCallback func(conn ConnectionState) dimse.Status

CEchoCallback implements C-ECHO callback. It typically just returns dimse.Success.

type CFindCallback

type CFindCallback func(
	conn ConnectionState,
	transferSyntaxUID string,
	sopClassUID string,
	filters []*dicom.Element,
	ch chan CFindResult)

CFindCallback implements a C-FIND handler. sopClassUID is the data type requested (e.g.,"1.2.840.10008.5.1.4.1.1.1.2"), and transferSyntaxUID is the data encoding requested (e.g., "1.2.840.10008.1.2.1"). These args are extracted from the request packet.

This function should stream CFindResult objects through "ch". The function may block. To report a matched DICOM dataset, the function should send one CFindResult with a nonempty Element field. To report multiple DICOM-dataset matches, the callback should send multiple CFindResult objects, one for each dataset. The callback must close the channel after it produces all the responses.

type CFindResult

type CFindResult struct {
	// Exactly one of Err or Elements is set.
	Err      error
	Elements []*dicom.Element // Elements belonging to one dataset.
}

CFindResult is an object streamed by CFind method.

type CMoveCallback

type CMoveCallback func(
	conn ConnectionState,
	transferSyntaxUID string,
	sopClassUID string,
	filters []*dicom.Element,
	ch chan CMoveResult)

CMoveCallback implements C-MOVE or C-GET handler. sopClassUID is the data type requested (e.g.,"1.2.840.10008.5.1.4.1.1.1.2"), and transferSyntaxUID is the data encoding requested (e.g., "1.2.840.10008.1.2.1"). These args are extracted from the request packet.

The callback must stream datasets or error to "ch". The callback may block. The callback must close the channel after it produces all the datasets.

type CMoveResult

type CMoveResult struct {
	Remaining int // Number of files remaining to be sent. Set -1 if unknown.
	Err       error
	Path      string         // Path name of the DICOM file being copied. Used only for reporting errors.
	DataSet   *dicom.DataSet // Contents of the file.
}

CMoveResult is an object streamed by CMove implementation.

type CStoreCallback

type CStoreCallback func(
	ctx context.Context,
	conn ConnectionState,
	transferSyntaxUID string,
	sopClassUID string,
	sopInstanceUID string,
	dataReader io.Reader,
	dataSize int64) dimse.Status

CStoreCallback is called C-STORE request. sopInstanceUID is the UID of the data. sopClassUID is the data type requested (e.g.,"1.2.840.10008.5.1.4.1.1.1.2"), and transferSyntaxUID is the encoding of the data (e.g., "1.2.840.10008.1.2.1"). These args are extracted from the request packet.

"dataReader" provides access to the payload as a stream, i.e., a sequence of serialized dicom.DataElement objects in transferSyntaxUID. "dataSize" indicates the total size of the data stream in bytes. The data does not contain metadata elements (elements whose Tag.Group=2 -- e.g., TransferSyntaxUID and MediaStorageSOPClassUID), since they are stripped by the requester (two key metadata are passed as sop{Class,Instance)UID).

The function should store encode the sop{Class,InstanceUID} as the DICOM header, followed by data. It should return either dimse.Success0 on success, or one of CStoreStatus* error codes on errors. CStoreCallback is called on C-STORE request. All data is provided as a stream via io.Reader for memory efficiency. For small files, the reader will be backed by a bytes.Reader. For large files, it streams directly from network PDUs.

type ConnectionState

type ConnectionState struct {
	// TLS connection state. It is nonempty only when the connection is set up
	// over TLS.
	TLS tls.ConnectionState
}

ConnectionState informs session state to callbacks.

type FaultInjector

type FaultInjector interface {
	fmt.Stringer
	// contains filtered or unexported methods
}

FaultInjector is a unittest helper. It's used by the statemachine to inject faults.

func NewFuzzFaultInjector

func NewFuzzFaultInjector(fuzz []byte) FaultInjector

NewFuzzFaultInjector creates a new fuzzing fault injector

type QRLevel

type QRLevel int

QRLevel is used to specify the element hierarchy assumed during C-FIND, C-GET, and C-MOVE. P3.4, C.3.

http://dicom.nema.org/Dicom/2013/output/chtml/part04/sect_C.3.html

const (
	// QRLevelPatient chooses Patient-Root QR model.  P3.4, C.3.1
	QRLevelPatient QRLevel = iota

	// QRLevelStudy chooses Study-Root QR model.  P3.4, C.3.2
	QRLevelStudy

	// QRLevelSeries chooses Study-Root QR model, but using "SERIES" QueryRetrieveLevel.  P3.4, C.3.2
	QRLevelSeries
)

func (QRLevel) String

func (i QRLevel) String() string

type ServiceProvider

type ServiceProvider struct {
	// contains filtered or unexported fields
}

ServiceProvider encapsulates the state for DICOM server (provider).

func NewServiceProvider

func NewServiceProvider(params ServiceProviderParams, port string) (*ServiceProvider, error)

NewServiceProvider creates a new DICOM server object. "listenAddr" is the TCP address to listen to. E.g., ":1234" will listen to port 1234 at all the IP address that this machine can bind to. Run() will actually start running the service.

func (*ServiceProvider) Close

func (sp *ServiceProvider) Close() error

Close closes the underlying listener, causing the server to stop accepting new connections. Existing connections will continue to be served.

func (*ServiceProvider) ListenAddr

func (sp *ServiceProvider) ListenAddr() net.Addr

ListenAddr returns the TCP address that the server is listening on. It is the address passed to the NewServiceProvider(), except that if value was of form <name>:0, the ":0" part is replaced by the actual port numwber.

func (*ServiceProvider) Run

func (sp *ServiceProvider) Run(ctx context.Context)

Run listens to incoming connections, accepts them, and runs the DICOM protocol. This function blocks until the context is cancelled.

func (*ServiceProvider) RunForever

func (sp *ServiceProvider) RunForever()

RunForever listens to incoming connections, accepts them, and runs the DICOM protocol. This function never returns and is provided for backward compatibility. For new code, prefer using Run() with a context.Context for graceful shutdown.

type ServiceProviderParams

type ServiceProviderParams struct {
	// The application-entity title of the server. Must be nonempty
	AETitle string

	// Names of remote AEs and their host:ports. Used only by C-MOVE. This
	// map should be nonempty iff the server supports CMove.
	RemoteAEs map[string]string

	// Called on C_ECHO request. If nil, a C-ECHO call will produce an error response.
	//
	// TODO(saito) Support a default C-ECHO callback?
	CEcho CEchoCallback

	// Called on C_FIND request.
	// If CFindCallback=nil, a C-FIND call will produce an error response.
	CFind CFindCallback

	// CMove is called on C_MOVE request.
	CMove CMoveCallback

	// CGet is called on C_GET request. The only difference between cmove
	// and cget is that cget uses the same connection to send images back to
	// the requester. Generally you shuold set the same function to CMove
	// and CGet.
	CGet CMoveCallback

	// If CStoreCallback=nil, a C-STORE call will produce an error response.
	// The callback always receives data as io.Reader for memory efficiency.
	CStore CStoreCallback

	// StreamingThreshold specifies the size (in bytes) above which true streaming mode
	// is enabled. Files smaller than this threshold will be buffered in memory for
	// better performance. Files larger will stream directly from network. Default: 100MB.
	StreamingThreshold int64

	// TLSConfig, if non-nil, enables TLS on the connection. See
	// https://gist.github.com/michaljemala/d6f4e01c4834bf47a9c4 for an
	// example for creating a TLS config from x509 cert files.
	TLSConfig *tls.Config

	Verbose bool
}

ServiceProviderParams defines parameters for ServiceProvider.

type ServiceUser

type ServiceUser struct {
	// contains filtered or unexported fields
}

ServiceUser encapsulates implements the client side of DICOM network protocol.

user, err := netdicom.NewServiceUser(netdicom.ServiceUserParams{SOPClasses: sopclass.QRFindClasses})
// Connect to server 1.2.3.4, port 8888
user.Connect("1.2.3.4:8888")
// Send test.dcm to the server
ds, err := dicom.ReadDataSetFromFile("test.dcm", dicom.ReadOptions{})
err := user.CStore(ds)
// Disconnect
user.Release()

The ServiceUser class is thread compatible. That is, you cannot call C* methods - say CStore and CFind requests - concurrently from two goroutines. You must wait for CStore to finish before issuing CFind.

func NewServiceUser

func NewServiceUser(params ServiceUserParams) (*ServiceUser, error)

NewServiceUser creates a new ServiceUser. The caller must call either Connect() or SetConn() before calling any other method, such as Cstore.

func (*ServiceUser) CEcho

func (su *ServiceUser) CEcho() error

CEcho send a C-ECHO request to the remote AE and waits for a response. Returns nil iff the remote AE responds ok.

func (*ServiceUser) CFind

func (su *ServiceUser) CFind(qrLevel QRLevel, filter []*dicom.Element) chan CFindResult

CFind issues a C-FIND request. Returns a channel that streams sequence of either an error or a dataset found. The caller MUST read all responses from the channel before issuing any other DIMSE command (C-FIND, C-STORE, etc).

The param sopClassUID is one of the UIDs defined in sopclass.QRFindClasses. filter is the list of elements to match and retrieve.

REQUIRES: Connect() or SetConn has been called.

func (*ServiceUser) CGet

func (su *ServiceUser) CGet(qrLevel QRLevel, filter []*dicom.Element,
	cb func(transferSyntaxUID, sopClassUID, sopInstanceUID string, data []byte) dimse.Status) error

CGet runs a C-GET command. It calls "cb" sequentially for every dataset received. "cb" should return dimse.Success iff the data was successfully and stably written. This function blocks until it receives all datasets from the server.

The "data" arg to "cb" is the serialized dataset, encoded according to transferSyntaxUID.

TODO(saito) We should parse the data into DataSet before passing to "cb".

func (*ServiceUser) CStore

func (su *ServiceUser) CStore(ds *dicom.DataSet) error

CStore issues a C-STORE request to transfer "ds" in remove peer. It blocks until the operation finishes.

REQUIRES: Connect() or SetConn has been called.

func (*ServiceUser) Connect

func (su *ServiceUser) Connect(serverAddr string)

Connect connects to the server at the given "host:port". Either Connect or SetConn must be before calling CStore, etc.

func (*ServiceUser) Release

func (su *ServiceUser) Release()

Release shuts down the connection. It must be called exactly once. After Release(), no other operation can be performed on the ServiceUser object.

func (*ServiceUser) SetConn

func (su *ServiceUser) SetConn(conn net.Conn)

SetConn instructs ServiceUser to use the given network connection to talk to the server. Either Connect or SetConn must be before calling CStore, etc.

type ServiceUserParams

type ServiceUserParams struct {
	// Application-entity title of the peer. If empty, set to "unknown-called-ae"
	CalledAETitle string
	// Application-entity title of the client. If empty, set to
	// "unknown-calling-ae"
	CallingAETitle string

	// List of SOPUIDs wanted by the client. The value is typically one of
	// the constants listed in sopclass package.
	SOPClasses []string

	// List of Transfer syntaxes supported by the user.  If you know the
	// transer syntax of the file you are going to copy, set that here.
	// Otherwise, you'll need to re-encode the data w/ the given transfer
	// syntax yourself.
	//
	// TODO(saito) Support reencoding internally on C_STORE, etc. The DICOM
	// spec is particularly moronic here, since we could just have specified
	// the transfer syntax per data sent.
	TransferSyntaxes []string
}

ServiceUserParams defines parameters for a ServiceUser.

Directories

Path Synopsis
pdu
A sample program for issuing C-STORE or C-FIND to a remote server.
A sample program for issuing C-STORE or C-FIND to a remote server.

Jump to

Keyboard shortcuts

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