/*
 *
 *  Copyright (C) 1996-2018, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmnet
 *
 *  Author:  Andrew Hewett
 *
 *  Purpose: Storage Service Class User (C-STORE operation)
 *
 */

#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */

#define INCLUDE_CSTDLIB
#define INCLUDE_CSTDIO
#define INCLUDE_CSTRING
#define INCLUDE_CCTYPE
#include "dcmtk/ofstd/ofstdinc.h"

BEGIN_EXTERN_C
#ifdef HAVE_SYS_FILE_H
#include <sys/file.h>
#endif
END_EXTERN_C

#include "dcmtk/ofstd/ofstd.h"
#include "dcmtk/ofstd/ofconapp.h"
#include "dcmtk/ofstd/ofstring.h"
#include "dcmtk/ofstd/ofstream.h"
#include "dcmtk/dcmnet/dicom.h"      /* for DICOM_APPLICATION_REQUESTOR */
#include "dcmtk/dcmnet/dimse.h"
#include "dcmtk/dcmnet/diutil.h"
#include "dcmtk/dcmnet/dcmtrans.h"   /* for dcmSocketSend/ReceiveTimeout */
#include "dcmtk/dcmnet/dcasccfg.h"   /* for class DcmAssociationConfiguration */
#include "dcmtk/dcmnet/dcasccff.h"   /* for class DcmAssociationConfigurationFile */
#include "dcmtk/dcmdata/dcdatset.h"
#include "dcmtk/dcmdata/dcmetinf.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/dcuid.h"
#include "dcmtk/dcmdata/dcdict.h"
#include "dcmtk/dcmdata/dcdeftag.h"
#include "dcmtk/dcmdata/cmdlnarg.h"
#include "dcmtk/dcmdata/dcuid.h"     /* for dcmtk version name */
#include "dcmtk/dcmdata/dcostrmz.h"  /* for dcmZlibCompressionLevel */
#include "dcmtk/dcmtls/tlsopt.h"      /* for DcmTLSOptions */

#ifdef ON_THE_FLY_COMPRESSION
#include "dcmtk/dcmjpeg/djdecode.h"  /* for JPEG decoders */
#include "dcmtk/dcmjpeg/djencode.h"  /* for JPEG encoders */
#include "dcmtk/dcmjpls/djdecode.h"  /* for JPEG-LS decoders */
#include "dcmtk/dcmjpls/djencode.h"  /* for JPEG-LS encoders */
#include "dcmtk/dcmdata/dcrledrg.h"  /* for RLE decoder */
#include "dcmtk/dcmdata/dcrleerg.h"  /* for RLE encoder */
#include "dcmtk/dcmjpeg/dipijpeg.h"  /* for dcmimage JPEG plugin */
#endif

#ifdef WITH_ZLIB
#include <zlib.h>          /* for zlibVersion() */
#endif

#if defined (HAVE_WINDOWS_H) || defined(HAVE_FNMATCH_H)
#define PATTERN_MATCHING_AVAILABLE
#endif

#define OFFIS_CONSOLE_APPLICATION "storescu"

static OFLogger storescuLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);

static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
  OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";

/* default application titles */
#define APPLICATIONTITLE     "STORESCU"
#define PEERAPPLICATIONTITLE "ANY-SCP"

static OFBool opt_showPresentationContexts = OFFalse;
static OFBool opt_abortAssociation = OFFalse;
static OFCmdUnsignedInt opt_maxReceivePDULength = ASC_DEFAULTMAXPDU;
static OFCmdUnsignedInt opt_maxSendPDULength = 0;
static E_TransferSyntax opt_networkTransferSyntax = EXS_Unknown;
static E_FileReadMode opt_readMode = ERM_autoDetect;

static OFBool opt_scanDir = OFFalse;
static OFBool opt_recurse = OFFalse;
static const char *opt_scanPattern = "";

static OFBool opt_haltOnUnsuccessfulStore = OFTrue;
static OFBool unsuccessfulStoreEncountered = OFFalse;
static int lastStatusCode = STATUS_Success;

static OFBool opt_proposeOnlyRequiredPresentationContexts = OFFalse;
static OFBool opt_combineProposedTransferSyntaxes = OFFalse;

static OFCmdUnsignedInt opt_repeatCount = 1;
static OFCmdUnsignedInt opt_inventPatientCount = 25;
static OFCmdUnsignedInt opt_inventStudyCount = 50;
static OFCmdUnsignedInt opt_inventSeriesCount = 100;
static OFBool opt_inventSOPInstanceInformation = OFFalse;
static OFBool opt_correctUIDPadding = OFFalse;
static OFString patientNamePrefix("OFFIS^TEST_PN_");   // PatientName is PN (maximum 16 chars)
static OFString patientIDPrefix("PID_"); // PatientID is LO (maximum 64 chars)
static OFString studyIDPrefix("SID_");   // StudyID is SH (maximum 16 chars)
static OFString accessionNumberPrefix;   // AccessionNumber is SH (maximum 16 chars)
static const char *opt_configFile = NULL;
static const char *opt_profileName = NULL;
T_DIMSE_BlockingMode opt_blockMode = DIMSE_BLOCKING;
int opt_dimse_timeout = 0;
int opt_acse_timeout = 30;
OFCmdSignedInt opt_socket_timeout = 60;

#ifdef WITH_ZLIB
static OFCmdUnsignedInt opt_compressionLevel = 0;
#endif

// User Identity Negotiation
static T_ASC_UserIdentityNegotiationMode opt_identMode = ASC_USER_IDENTITY_NONE;
static OFString opt_user;
static OFString opt_password;
static OFString opt_identFile;
static OFBool opt_identResponse = OFFalse;

static OFCondition
addStoragePresentationContexts(T_ASC_Parameters *params, OFList<OFString> &sopClasses);

static OFCondition
cstore(T_ASC_Association *assoc, const OFString &fname);

static OFBool
findSOPClassAndInstanceInFile(
  const char *fname,
  char *sopClass,
  size_t sopClassSize,
  char *sopInstance,
  size_t sopInstanceSize);

static OFCondition
configureUserIdentityRequest(T_ASC_Parameters *params);

static OFCondition
checkUserIdentityResponse(T_ASC_Parameters *params);

/* helper macro for converting stream output to a string */
#define CONVERT_TO_STRING(output, string) \
    optStream.str(""); \
    optStream.clear(); \
    optStream << output << OFStringStream_ends; \
    OFSTRINGSTREAM_GETOFSTRING(optStream, string)

#define SHORTCOL 4
#define LONGCOL 19

int main(int argc, char *argv[])
{
  OFOStringStream optStream;

  const char *opt_peer = NULL;
  OFCmdUnsignedInt opt_port = 104;
  const char *opt_peerTitle = PEERAPPLICATIONTITLE;
  const char *opt_ourTitle = APPLICATIONTITLE;

  OFList<OFString> fileNameList;       // list of files to transfer to SCP
  OFList<OFString> sopClassUIDList;    // the list of SOP classes
  OFList<OFString> sopInstanceUIDList; // the list of SOP instances

  T_ASC_Network *net;
  T_ASC_Parameters *params;
  DIC_NODENAME peerHost;
  T_ASC_Association *assoc;
  DcmAssociationConfiguration asccfg;  // handler for association configuration profiles
  DcmTLSOptions tlsOptions(NET_REQUESTOR);

  OFStandard::initializeNetwork();
#ifdef WITH_OPENSSL
  DcmTLSTransportLayer::initializeOpenSSL();
#endif

  OFString temp_str;
  OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , "DICOM storage (C-STORE) SCU", rcsid);
  OFCommandLine cmd;

  cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
  cmd.addParam("peer", "hostname of DICOM peer");
  cmd.addParam("port", "tcp/ip port number of peer");
  cmd.addParam("dcmfile-in", "DICOM file or directory to be transmitted", OFCmdParam::PM_MultiMandatory);

  cmd.setOptionColumns(LONGCOL, SHORTCOL);
  cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
   cmd.addOption("--help",                    "-h",      "print this help text and exit", OFCommandLine::AF_Exclusive);
   cmd.addOption("--version",                            "print version information and exit", OFCommandLine::AF_Exclusive);
   OFLog::addOptions(cmd);
   cmd.addOption("--verbose-pc",              "+v",      "show presentation contexts in verbose mode");

  cmd.addGroup("input options:");
    cmd.addSubGroup("input file format:");
      cmd.addOption("--read-file",            "+f",      "read file format or data set (default)");
      cmd.addOption("--read-file-only",       "+fo",     "read file format only");
      cmd.addOption("--read-dataset",         "-f",      "read data set without file meta information");
    cmd.addSubGroup("input files:");
      cmd.addOption("--scan-directories",     "+sd",     "scan directories for input files (dcmfile-in)");
#ifdef PATTERN_MATCHING_AVAILABLE
      cmd.addOption("--scan-pattern",         "+sp",  1, "[p]attern: string (only with --scan-directories)",
                                                         "pattern for filename matching (wildcards)");
#endif
      cmd.addOption("--no-recurse",           "-r",      "do not recurse within directories (default)");
      cmd.addOption("--recurse",              "+r",      "recurse within specified directories");
  cmd.addGroup("network options:");
    cmd.addSubGroup("application entity titles:");
      cmd.addOption("--aetitle",              "-aet", 1, "[a]etitle: string", "set my calling AE title (default: " APPLICATIONTITLE ")");
      cmd.addOption("--call",                 "-aec", 1, "[a]etitle: string", "set called AE title of peer (default: " PEERAPPLICATIONTITLE ")");
    cmd.addSubGroup("association negotiation profile from configuration file:");
      cmd.addOption("--config-file",          "-xf",  2, "[f]ilename, [p]rofile: string",
                                                         "use profile p from config file f");
    cmd.addSubGroup("proposed transmission transfer syntaxes (not with --config-file):");
      cmd.addOption("--propose-uncompr",      "-x=",     "propose all uncompressed TS, explicit VR\nwith local byte ordering first (default)");
      cmd.addOption("--propose-little",       "-xe",     "propose all uncompressed TS, explicit VR\nlittle endian first");
      cmd.addOption("--propose-big",          "-xb",     "propose all uncompressed TS, explicit VR\nbig endian first");
      cmd.addOption("--propose-implicit",     "-xi",     "propose implicit VR little endian TS only");
      cmd.addOption("--propose-lossless",     "-xs",     "propose default JPEG lossless TS\nand all uncompressed transfer syntaxes");
      cmd.addOption("--propose-jpeg8",        "-xy",     "propose default JPEG lossy TS for 8 bit data\nand all uncompressed transfer syntaxes");
      cmd.addOption("--propose-jpeg12",       "-xx",     "propose default JPEG lossy TS for 12 bit data\nand all uncompressed transfer syntaxes");
      cmd.addOption("--propose-j2k-lossless", "-xv",     "propose JPEG 2000 lossless TS\nand all uncompressed transfer syntaxes");
      cmd.addOption("--propose-j2k-lossy",    "-xw",     "propose JPEG 2000 lossy TS\nand all uncompressed transfer syntaxes");
      cmd.addOption("--propose-jls-lossless", "-xt",     "propose JPEG-LS lossless TS\nand all uncompressed transfer syntaxes");
      cmd.addOption("--propose-jls-lossy",    "-xu",     "propose JPEG-LS lossy TS\nand all uncompressed transfer syntaxes");
      cmd.addOption("--propose-mpeg2",        "-xm",     "propose MPEG2 Main Profile @ Main Level TS");
      cmd.addOption("--propose-mpeg2-high",   "-xh",     "propose MPEG2 Main Profile @ High Level TS");
      cmd.addOption("--propose-mpeg4",        "-xn",     "propose MPEG4 AVC/H.264 HP / Level 4.1 TS");
      cmd.addOption("--propose-mpeg4-bd",     "-xl",     "propose MPEG4 AVC/H.264 BD-compatible TS");
      cmd.addOption("--propose-mpeg4-2-2d",   "-x2",     "propose MPEG4 AVC/H.264 HP / Level 4.2 TS (2D)");
      cmd.addOption("--propose-mpeg4-2-3d",   "-x3",     "propose MPEG4 AVC/H.264 HP / Level 4.2 TS (3D)");
      cmd.addOption("--propose-mpeg4-2-st",   "-xo",     "propose MPEG4 AVC/H.264 Stereo / Level 4.2 TS");
      cmd.addOption("--propose-hevc",         "-x4",     "propose HEVC/H.265 Main Profile / Level 5.1 TS");
      cmd.addOption("--propose-hevc10",       "-x5",     "propose HEVC/H.265 Main 10 Profile / Level 5.1 TS");
      cmd.addOption("--propose-rle",          "-xr",     "propose RLE lossless TS\nand all uncompressed transfer syntaxes");
#ifdef WITH_ZLIB
      cmd.addOption("--propose-deflated",     "-xd",     "propose deflated expl. VR little endian TS\nand all uncompressed transfer syntaxes");
#endif
      cmd.addOption("--required",             "-R",      "propose only required presentation contexts\n(default: propose all supported)");
      cmd.addOption("--combine",              "+C",      "combine proposed transfer syntaxes\n(default: separate pres. context for each TS)");
    cmd.addSubGroup("post-1993 value representations:");
      cmd.addOption("--enable-new-vr",        "+u",      "enable support for new VRs (UN/UT) (default)");
      cmd.addOption("--disable-new-vr",       "-u",      "disable support for new VRs, convert to OB");
#ifdef WITH_ZLIB
    cmd.addSubGroup("deflate compression level (only with --propose-deflated or --config-file):");
      cmd.addOption("--compression-level",    "+cl",  1, "[l]evel: integer (default: 6)",
                                                         "0=uncompressed, 1=fastest, 9=best compression");
#endif
    cmd.addSubGroup("user identity negotiation:");
      cmd.addOption("--user",                 "-usr", 1, "[u]ser name: string",
                                                         "authenticate using user name u");
      cmd.addOption("--password",             "-pwd", 1, "[p]assword: string (only with --user)",
                                                         "authenticate using password p");
      cmd.addOption("--empty-password",       "-epw",    "send empty password (only with --user)");
      cmd.addOption("--kerberos",             "-kt",  1, "[f]ilename: string",
                                                         "read kerberos ticket from file f");
      cmd.addOption("--saml",                         1, "[f]ilename: string",
                                                         "read SAML request from file f");
      cmd.addOption("--jwt",                          1, "[f]ilename: string",
                                                         "read JWT data from file f");
      cmd.addOption("--pos-response",         "-rsp",    "expect positive response");
    cmd.addSubGroup("other network options:");
      cmd.addOption("--timeout",              "-to",  1, "[s]econds: integer (default: unlimited)", "timeout for connection requests");
      CONVERT_TO_STRING("[s]econds: integer (default: " << opt_socket_timeout << ")", optString1);
      cmd.addOption("--socket-timeout",       "-ts",  1, optString1.c_str(), "timeout for network socket (0 for none)");
      CONVERT_TO_STRING("[s]econds: integer (default: " << opt_acse_timeout << ")", optString2);
      cmd.addOption("--acse-timeout",         "-ta",  1, optString2.c_str(), "timeout for ACSE messages");
      cmd.addOption("--dimse-timeout",        "-td",  1, "[s]econds: integer (default: unlimited)", "timeout for DIMSE messages");
      CONVERT_TO_STRING("[n]umber of bytes: integer (" << ASC_MINIMUMPDUSIZE << ".." << ASC_MAXIMUMPDUSIZE << ")", optString3);
      CONVERT_TO_STRING("set max receive pdu to n bytes (default: " << opt_maxReceivePDULength << ")", optString4);
      cmd.addOption("--max-pdu",              "-pdu", 1, optString3.c_str(), optString4.c_str());
      cmd.addOption("--max-send-pdu",                 1, optString3.c_str(), "restrict max send pdu to n bytes");
      cmd.addOption("--repeat",                       1, "[n]umber: integer", "repeat n times");
      cmd.addOption("--abort",                           "abort association instead of releasing it");
      cmd.addOption("--no-halt",              "-nh",     "do not halt if unsuccessful store encountered\n(default: do halt)");
      cmd.addOption("--uid-padding",          "-up",     "silently correct space-padded UIDs");

      cmd.addOption("--invent-instance",      "+II",     "invent a new SOP instance UID for every image\nsent");
      CONVERT_TO_STRING("invent a new series UID after n images" << OFendl << "have been sent (default: " << opt_inventSeriesCount << ")", optString5);
      cmd.addOption("--invent-series",        "+IR",  1, "[n]umber: integer (implies --invent-instance)", optString5.c_str());
      CONVERT_TO_STRING("invent a new study UID after n series" << OFendl << "have been sent (default: " << opt_inventStudyCount << ")", optString6);
      cmd.addOption("--invent-study",         "+IS",  1, "[n]umber: integer (implies --invent-instance)", optString6.c_str());
      CONVERT_TO_STRING("invent a new patient ID and name after n studies" << OFendl << "have been sent (default: " << opt_inventPatientCount << ")", optString7);
      cmd.addOption("--invent-patient",       "+IP",  1, "[n]umber: integer (implies --invent-instance)", optString7.c_str());

    // add TLS specific command line options if (and only if) we are compiling with OpenSSL
    tlsOptions.addTLSCommandlineOptions(cmd);

    /* evaluate command line */
    prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
    if (app.parseCommandLine(cmd, argc, argv))
    {
      /* check exclusive options first */
      if (cmd.hasExclusiveOption())
      {
        if (cmd.findOption("--version"))
        {
          app.printHeader(OFTrue /*print host identifier*/);
          COUT << OFendl << "External libraries used:";
#if !defined(WITH_ZLIB) && !(ON_THE_FLY_COMPRESSION) && !defined(WITH_OPENSSL)
          COUT << " none" << OFendl;
#else
          COUT << OFendl;
#endif
#ifdef WITH_ZLIB
          COUT << "- ZLIB, Version " << zlibVersion() << OFendl;
#endif
#ifdef ON_THE_FLY_COMPRESSION
          COUT << "- " << DiJPEGPlugin::getLibraryVersionString() << OFendl;
          COUT << "- " << DJLSDecoderRegistration::getLibraryVersionString() << OFendl;
#endif
          // print OpenSSL version if (and only if) we are compiling with OpenSSL
          tlsOptions.printLibraryVersion();
          return 0;
        }

        // check if the command line contains the --list-ciphers option
        if (tlsOptions.listOfCiphersRequested(cmd))
        {
            tlsOptions.printSupportedCiphersuites(app, COUT);
            return 0;
        }
      }

      /* command line parameters */

      cmd.getParam(1, opt_peer);
      app.checkParam(cmd.getParamAndCheckMinMax(2, opt_port, 1, 65535));

      OFLog::configureFromCommandLine(cmd, app);
      if (cmd.findOption("--verbose-pc"))
      {
        app.checkDependence("--verbose-pc", "verbose mode", storescuLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL));
        opt_showPresentationContexts = OFTrue;
      }

      cmd.beginOptionBlock();
      if (cmd.findOption("--read-file")) opt_readMode = ERM_autoDetect;
      if (cmd.findOption("--read-file-only")) opt_readMode = ERM_fileOnly;
      if (cmd.findOption("--read-dataset")) opt_readMode = ERM_dataset;
      cmd.endOptionBlock();

      if (cmd.findOption("--scan-directories")) opt_scanDir = OFTrue;
#ifdef PATTERN_MATCHING_AVAILABLE
      if (cmd.findOption("--scan-pattern"))
      {
        app.checkDependence("--scan-pattern", "--scan-directories", opt_scanDir);
        app.checkValue(cmd.getValue(opt_scanPattern));
      }
#endif
      cmd.beginOptionBlock();
      if (cmd.findOption("--no-recurse")) opt_recurse = OFFalse;
      if (cmd.findOption("--recurse"))
      {
        app.checkDependence("--recurse", "--scan-directories", opt_scanDir);
        opt_recurse = OFTrue;
      }
      cmd.endOptionBlock();

      if (cmd.findOption("--aetitle")) app.checkValue(cmd.getValue(opt_ourTitle));
      if (cmd.findOption("--call")) app.checkValue(cmd.getValue(opt_peerTitle));

      cmd.beginOptionBlock();
      if (cmd.findOption("--propose-uncompr")) opt_networkTransferSyntax = EXS_Unknown;
      if (cmd.findOption("--propose-little")) opt_networkTransferSyntax = EXS_LittleEndianExplicit;
      if (cmd.findOption("--propose-big")) opt_networkTransferSyntax = EXS_BigEndianExplicit;
      if (cmd.findOption("--propose-implicit")) opt_networkTransferSyntax = EXS_LittleEndianImplicit;
      if (cmd.findOption("--propose-lossless")) opt_networkTransferSyntax = EXS_JPEGProcess14SV1;
      if (cmd.findOption("--propose-jpeg8")) opt_networkTransferSyntax = EXS_JPEGProcess1;
      if (cmd.findOption("--propose-jpeg12")) opt_networkTransferSyntax = EXS_JPEGProcess2_4;
      if (cmd.findOption("--propose-j2k-lossless")) opt_networkTransferSyntax = EXS_JPEG2000LosslessOnly;
      if (cmd.findOption("--propose-j2k-lossy")) opt_networkTransferSyntax = EXS_JPEG2000;
      if (cmd.findOption("--propose-jls-lossless")) opt_networkTransferSyntax = EXS_JPEGLSLossless;
      if (cmd.findOption("--propose-jls-lossy")) opt_networkTransferSyntax = EXS_JPEGLSLossy;
      if (cmd.findOption("--propose-mpeg2")) opt_networkTransferSyntax = EXS_MPEG2MainProfileAtMainLevel;
      if (cmd.findOption("--propose-mpeg2-high")) opt_networkTransferSyntax = EXS_MPEG2MainProfileAtHighLevel;
      if (cmd.findOption("--propose-mpeg4")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_1;
      if (cmd.findOption("--propose-mpeg4-bd")) opt_networkTransferSyntax = EXS_MPEG4BDcompatibleHighProfileLevel4_1;
      if (cmd.findOption("--propose-mpeg4-2-2d")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_2_For2DVideo;
      if (cmd.findOption("--propose-mpeg4-2-3d")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_2_For3DVideo;
      if (cmd.findOption("--propose-mpeg4-2-st")) opt_networkTransferSyntax = EXS_MPEG4StereoHighProfileLevel4_2;
      if (cmd.findOption("--propose-hevc")) opt_networkTransferSyntax = EXS_HEVCMainProfileLevel5_1;
      if (cmd.findOption("--propose-hevc10")) opt_networkTransferSyntax = EXS_HEVCMain10ProfileLevel5_1;
      if (cmd.findOption("--propose-rle")) opt_networkTransferSyntax = EXS_RLELossless;
#ifdef WITH_ZLIB
      if (cmd.findOption("--propose-deflated")) opt_networkTransferSyntax = EXS_DeflatedLittleEndianExplicit;
#endif
      cmd.endOptionBlock();

      if (cmd.findOption("--required")) opt_proposeOnlyRequiredPresentationContexts = OFTrue;
      if (cmd.findOption("--combine")) opt_combineProposedTransferSyntaxes = OFTrue;

      if (cmd.findOption("--config-file"))
      {
        // check conflicts with other command line options
        app.checkConflict("--config-file", "--propose-little", opt_networkTransferSyntax == EXS_LittleEndianExplicit);
        app.checkConflict("--config-file", "--propose-big", opt_networkTransferSyntax == EXS_BigEndianExplicit);
        app.checkConflict("--config-file", "--propose-implicit", opt_networkTransferSyntax == EXS_LittleEndianImplicit);
        app.checkConflict("--config-file", "--propose-lossless", opt_networkTransferSyntax == EXS_JPEGProcess14SV1);
        app.checkConflict("--config-file", "--propose-jpeg8", opt_networkTransferSyntax == EXS_JPEGProcess1);
        app.checkConflict("--config-file", "--propose-jpeg12", opt_networkTransferSyntax == EXS_JPEGProcess2_4);
        app.checkConflict("--config-file", "--propose-j2k-lossless", opt_networkTransferSyntax == EXS_JPEG2000LosslessOnly);
        app.checkConflict("--config-file", "--propose-j2k-lossy", opt_networkTransferSyntax == EXS_JPEG2000);
        app.checkConflict("--config-file", "--propose-jls-lossless", opt_networkTransferSyntax == EXS_JPEGLSLossless);
        app.checkConflict("--config-file", "--propose-jls-lossy", opt_networkTransferSyntax == EXS_JPEGLSLossy);
        app.checkConflict("--config-file", "--propose-mpeg2", opt_networkTransferSyntax == EXS_MPEG2MainProfileAtMainLevel);
        app.checkConflict("--config-file", "--propose-mpeg2-high", opt_networkTransferSyntax == EXS_MPEG2MainProfileAtHighLevel);
        app.checkConflict("--config-file", "--propose-mpeg4", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_1);
        app.checkConflict("--config-file", "--propose-mpeg4-bd", opt_networkTransferSyntax == EXS_MPEG4BDcompatibleHighProfileLevel4_1);
        app.checkConflict("--config-file", "--propose-mpeg4-2-2d", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_2_For2DVideo);
        app.checkConflict("--config-file", "--propose-mpeg4-2-3d", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_2_For3DVideo);
        app.checkConflict("--config-file", "--propose-mpeg4-2-st", opt_networkTransferSyntax == EXS_MPEG4StereoHighProfileLevel4_2);
        app.checkConflict("--config-file", "--propose-hevc", opt_networkTransferSyntax == EXS_HEVCMainProfileLevel5_1);
        app.checkConflict("--config-file", "--propose-hevc10", opt_networkTransferSyntax == EXS_HEVCMain10ProfileLevel5_1);
        app.checkConflict("--config-file", "--propose-rle", opt_networkTransferSyntax == EXS_RLELossless);
#ifdef WITH_ZLIB
        app.checkConflict("--config-file", "--propose-deflated", opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit);
#endif
        app.checkConflict("--config-file", "--required", opt_proposeOnlyRequiredPresentationContexts);
        app.checkConflict("--config-file", "--combine", opt_combineProposedTransferSyntaxes);

        app.checkValue(cmd.getValue(opt_configFile));
        app.checkValue(cmd.getValue(opt_profileName));

        // read configuration file. The profile name is checked later.
        OFCondition cond = DcmAssociationConfigurationFile::initialize(asccfg, opt_configFile);
        if (cond.bad())
        {
          OFLOG_ERROR(storescuLogger, "reading config file: " << cond.text());
          return 1;
        }
      }

#ifdef WITH_ZLIB
      if (cmd.findOption("--compression-level"))
      {
        app.checkDependence("--compression-level", "--propose-deflated or --config-file",
          (opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit) || (opt_configFile != NULL));
        app.checkValue(cmd.getValueAndCheckMinMax(opt_compressionLevel, 0, 9));
        dcmZlibCompressionLevel.set(OFstatic_cast(int, opt_compressionLevel));
      }
#endif

      cmd.beginOptionBlock();
      if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs();
      if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs();
      cmd.endOptionBlock();

      if (cmd.findOption("--timeout"))
      {
        OFCmdSignedInt opt_timeout = 0;
        app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
        dcmConnectionTimeout.set(OFstatic_cast(Sint32, opt_timeout));
      }

      if (cmd.findOption("--socket-timeout"))
        app.checkValue(cmd.getValueAndCheckMin(opt_socket_timeout, -1));
      // always set the timeout values since the global default might be different
      dcmSocketSendTimeout.set(OFstatic_cast(Sint32, opt_socket_timeout));
      dcmSocketReceiveTimeout.set(OFstatic_cast(Sint32, opt_socket_timeout));

      if (cmd.findOption("--acse-timeout"))
      {
        OFCmdSignedInt opt_timeout = 0;
        app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
        opt_acse_timeout = OFstatic_cast(int, opt_timeout);
      }

      if (cmd.findOption("--dimse-timeout"))
      {
        OFCmdSignedInt opt_timeout = 0;
        app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
        opt_dimse_timeout = OFstatic_cast(int, opt_timeout);
        opt_blockMode = DIMSE_NONBLOCKING;
      }

      if (cmd.findOption("--max-pdu"))
        app.checkValue(cmd.getValueAndCheckMinMax(opt_maxReceivePDULength, ASC_MINIMUMPDUSIZE, ASC_MAXIMUMPDUSIZE));

      if (cmd.findOption("--max-send-pdu"))
      {
        app.checkValue(cmd.getValueAndCheckMinMax(opt_maxSendPDULength, ASC_MINIMUMPDUSIZE, ASC_MAXIMUMPDUSIZE));
        dcmMaxOutgoingPDUSize.set(OFstatic_cast(Uint32, opt_maxSendPDULength));
      }

      if (cmd.findOption("--repeat"))  app.checkValue(cmd.getValueAndCheckMin(opt_repeatCount, 1));
      if (cmd.findOption("--abort"))   opt_abortAssociation = OFTrue;
      if (cmd.findOption("--no-halt")) opt_haltOnUnsuccessfulStore = OFFalse;
      if (cmd.findOption("--uid-padding")) opt_correctUIDPadding = OFTrue;

      if (cmd.findOption("--invent-instance")) opt_inventSOPInstanceInformation = OFTrue;
      if (cmd.findOption("--invent-series"))
      {
        opt_inventSOPInstanceInformation = OFTrue;
        app.checkValue(cmd.getValueAndCheckMin(opt_inventSeriesCount, 1));
      }
      if (cmd.findOption("--invent-study"))
      {
        opt_inventSOPInstanceInformation = OFTrue;
        app.checkValue(cmd.getValueAndCheckMin(opt_inventStudyCount, 1));
      }
      if (cmd.findOption("--invent-patient"))
      {
        opt_inventSOPInstanceInformation = OFTrue;
        app.checkValue(cmd.getValueAndCheckMin(opt_inventPatientCount, 1));
      }

      // evaluate (most of) the TLS command line options (if we are compiling with OpenSSL)
      tlsOptions.parseArguments(app, cmd);

      // User Identity Negotiation
      cmd.beginOptionBlock();
      if (cmd.findOption("--user"))
      {
        app.checkValue(cmd.getValue(opt_user));
        opt_identMode = ASC_USER_IDENTITY_USER;
      }
      if (cmd.findOption("--kerberos"))
      {
        app.checkValue(cmd.getValue(opt_identFile));
        opt_identMode = ASC_USER_IDENTITY_KERBEROS;
      }
      if (cmd.findOption("--saml"))
      {
        app.checkValue(cmd.getValue(opt_identFile));
        opt_identMode = ASC_USER_IDENTITY_SAML;
      }
      if (cmd.findOption("--jwt"))
      {
        app.checkValue(cmd.getValue(opt_identFile));
        opt_identMode = ASC_USER_IDENTITY_JWT;
      }
      cmd.endOptionBlock();
      cmd.beginOptionBlock();
      if (cmd.findOption("--password"))
      {
        app.checkDependence("--password", "--user", opt_identMode == ASC_USER_IDENTITY_USER);
        app.checkValue(cmd.getValue(opt_password));
        opt_identMode = ASC_USER_IDENTITY_USER_PASSWORD;
      }
      if (cmd.findOption("--empty-password"))
      {
        app.checkDependence("--empty-password", "--user", opt_identMode == ASC_USER_IDENTITY_USER);
        opt_password= "";
        opt_identMode = ASC_USER_IDENTITY_USER_PASSWORD;
      }
      cmd.endOptionBlock();
      if (cmd.findOption("--pos-response"))
      {
         app.checkDependence("--pos-response", "--user, --kerberos or --saml", opt_identMode != ASC_USER_IDENTITY_NONE);
         opt_identResponse = OFTrue;
      }
   }

    /* print resource identifier */
    OFLOG_DEBUG(storescuLogger, rcsid << OFendl);

    /* make sure data dictionary is loaded */
    if (!dcmDataDict.isDictionaryLoaded())
    {
      OFLOG_WARN(storescuLogger, "no data dictionary loaded, check environment variable: "
          << DCM_DICT_ENVIRONMENT_VARIABLE);
    }

    /* finally, create list of input files */
    const char *paramString = NULL;
    const int paramCount = cmd.getParamCount();
    OFList<OFString> inputFiles;
    if (opt_scanDir)
      OFLOG_INFO(storescuLogger, "determining input files ...");
    /* iterate over all input filenames/directories */
    for (int i = 3; i <= paramCount; i++)
    {
      cmd.getParam(i, paramString);
      /* search directory recursively (if required) */
      if (OFStandard::dirExists(paramString))
      {
        if (opt_scanDir)
          OFStandard::searchDirectoryRecursively(paramString, inputFiles, opt_scanPattern, "" /*dirPrefix*/, opt_recurse);
        else
          OFLOG_WARN(storescuLogger, "ignoring directory because option --scan-directories is not set: " << paramString);
      } else
        inputFiles.push_back(paramString);
    }
    /* check whether there are any input files at all */
    if (inputFiles.empty())
    {
      OFLOG_FATAL(storescuLogger, "no input files to be sent");
      exit(1);
    }

    /* check input files */
    OFString errormsg;
    DcmFileFormat dfile;
    char sopClassUID[128];
    char sopInstanceUID[128];
    OFBool ignoreName;
    const char *currentFilename = NULL;
    OFListIterator(OFString) if_iter = inputFiles.begin();
    OFListIterator(OFString) if_last = inputFiles.end();
    OFLOG_INFO(storescuLogger, "checking input files ...");
    /* iterate over all input filenames */
    while (if_iter != if_last)
    {
      ignoreName = OFFalse;
      currentFilename = (*if_iter).c_str();
      if (OFStandard::fileExists(currentFilename))
      {
        if (opt_proposeOnlyRequiredPresentationContexts)
        {
          if (!findSOPClassAndInstanceInFile(currentFilename, sopClassUID, sizeof(sopClassUID), sopInstanceUID, sizeof(sopInstanceUID)))
          {
            ignoreName = OFTrue;
            errormsg = "missing SOP class (or instance) in file: ";
            errormsg += currentFilename;
            if (opt_haltOnUnsuccessfulStore)
            {
              OFLOG_FATAL(storescuLogger, errormsg);
              exit(1);
            }
            else
              OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
          }
          else if (!dcmIsaStorageSOPClassUID(sopClassUID, ESSC_All))
          {
            ignoreName = OFTrue;
            errormsg = "unknown storage SOP class in file: ";
            errormsg += currentFilename;
            errormsg += ": ";
            errormsg += sopClassUID;
            if (opt_haltOnUnsuccessfulStore)
            {
              OFLOG_FATAL(storescuLogger, errormsg);
              exit(1);
            }
            else
              OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
          }
          else
          {
            sopClassUIDList.push_back(sopClassUID);
            sopInstanceUIDList.push_back(sopInstanceUID);
          }
        }
        if (!ignoreName) fileNameList.push_back(currentFilename);
      }
      else
      {
        errormsg = "cannot access file: ";
        errormsg += currentFilename;
        if (opt_haltOnUnsuccessfulStore)
        {
          OFLOG_FATAL(storescuLogger, errormsg);
          exit(1);
        }
        else
          OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
      }
      ++if_iter;
    }

#ifdef ON_THE_FLY_COMPRESSION
    // register global JPEG decompression codecs
    DJDecoderRegistration::registerCodecs();

    // register global JPEG compression codecs
    DJEncoderRegistration::registerCodecs();

    // register JPEG-LS decompression codecs
    DJLSDecoderRegistration::registerCodecs();

    // register JPEG-LS compression codecs
    DJLSEncoderRegistration::registerCodecs();

    // register RLE compression codec
    DcmRLEEncoderRegistration::registerCodecs();

    // register RLE decompression codec
    DcmRLEDecoderRegistration::registerCodecs();
#endif

    /* initialize network, i.e. create an instance of T_ASC_Network*. */
    OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, opt_acse_timeout, &net);
    if (cond.bad()) {
      OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
      return 1;
    }

    /* initialize asscociation parameters, i.e. create an instance of T_ASC_Parameters*. */
    cond = ASC_createAssociationParameters(&params, opt_maxReceivePDULength);
    if (cond.bad()) {
      OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
      return 1;
    }

    /* create a secure transport layer if requested and OpenSSL is available */
    cond = tlsOptions.createTransportLayer(net, params, app, cmd);
    if (cond.bad()) {
        OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
        return 1;
    }

    /* sets this application's title and the called application's title in the params */
    /* structure. The default values to be set here are "STORESCU" and "ANY-SCP". */
    ASC_setAPTitles(params, opt_ourTitle, opt_peerTitle, NULL);

    /* Figure out the presentation addresses and copy the */
    /* corresponding values into the association parameters.*/
    sprintf(peerHost, "%s:%d", opt_peer, OFstatic_cast(int, opt_port));
    ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), peerHost);

    /* Configure User Identity Negotiation*/
    if (opt_identMode != ASC_USER_IDENTITY_NONE)
    {
      cond = configureUserIdentityRequest(params);
      if (cond.bad())
        return 1;
    }

    if (opt_profileName)
    {
      /* perform name mangling for config file key */
      OFString sprofile;
      const unsigned char *c = OFreinterpret_cast(const unsigned char *, opt_profileName);
      while (*c)
      {
        if (!isspace(*c)) sprofile += OFstatic_cast(char, toupper(*c));
        ++c;
      }

      /* set presentation contexts as defined in config file */
      cond = asccfg.setAssociationParameters(sprofile.c_str(), *params);
    }
    else
    {
      /* Set the presentation contexts which will be negotiated */
      /* when the network connection will be established */
      cond = addStoragePresentationContexts(params, sopClassUIDList);
    }

    if (cond.bad()) {
      OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
      return 1;
    }

    /* dump presentation contexts if required */
    if (opt_showPresentationContexts)
      OFLOG_INFO(storescuLogger, "Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ));
    else
      OFLOG_DEBUG(storescuLogger, "Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ));

    /* create association, i.e. try to establish a network connection to another */
    /* DICOM application. This call creates an instance of T_ASC_Association*. */
    OFLOG_INFO(storescuLogger, "Requesting Association");
    cond = ASC_requestAssociation(net, params, &assoc);
    if (cond.bad()) {
      if (cond == DUL_ASSOCIATIONREJECTED) {
        T_ASC_RejectParameters rej;

        ASC_getRejectParameters(params, &rej);
        OFLOG_FATAL(storescuLogger, "Association Rejected:" << OFendl << ASC_printRejectParameters(temp_str, &rej));
        return 1;
      } else {
        OFLOG_FATAL(storescuLogger, "Association Request Failed: " << DimseCondition::dump(temp_str, cond));
        return 1;
      }
    }

    /* dump the connection parameters if in debug mode*/
    OFLOG_DEBUG(storescuLogger, ASC_dumpConnectionParameters(temp_str, assoc));

    /* dump the presentation contexts which have been accepted/refused */
    if (opt_showPresentationContexts)
      OFLOG_INFO(storescuLogger, "Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC));
    else
      OFLOG_DEBUG(storescuLogger, "Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC));

    /* count the presentation contexts which have been accepted by the SCP */
    /* If there are none, finish the execution */
    if (ASC_countAcceptedPresentationContexts(params) == 0) {
      OFLOG_FATAL(storescuLogger, "No Acceptable Presentation Contexts");
      return 1;
    }

    /* check user authentication response (if applicable) */
    cond = checkUserIdentityResponse(params);
    if (cond.bad())
    {
      OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
      return 1;
    }
    /* dump general information concerning the establishment of the network connection if required */
    OFLOG_INFO(storescuLogger, "Association Accepted (Max Send PDV: " << assoc->sendPDVLength << ")");

    /* do the real work, i.e. for all files which were specified in the */
    /* command line, transmit the encapsulated DICOM objects to the SCP. */
    cond = EC_Normal;
    OFListIterator(OFString) iter = fileNameList.begin();
    OFListIterator(OFString) enditer = fileNameList.end();

    while ((iter != enditer) && cond.good())
    {
      cond = cstore(assoc, *iter);
      ++iter;
    }

    /* tear down association, i.e. terminate network connection to SCP */
    if (cond == EC_Normal)
    {
      if (opt_abortAssociation) {
        OFLOG_INFO(storescuLogger, "Aborting Association");
        cond = ASC_abortAssociation(assoc);
        if (cond.bad()) {
          OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
          return 1;
        }
      } else {
        /* release association */
        OFLOG_INFO(storescuLogger, "Releasing Association");
        cond = ASC_releaseAssociation(assoc);
        if (cond.bad())
        {
          OFLOG_ERROR(storescuLogger, "Association Release Failed: " << DimseCondition::dump(temp_str, cond));
          return 1;
        }
      }
    }
    else if (cond == DUL_PEERREQUESTEDRELEASE)
    {
      OFLOG_ERROR(storescuLogger, "Protocol Error: Peer requested release (Aborting)");
      OFLOG_INFO(storescuLogger, "Aborting Association");
      cond = ASC_abortAssociation(assoc);
      if (cond.bad()) {
        OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
        return 1;
      }
    }
    else if (cond == DUL_PEERABORTEDASSOCIATION)
    {
      OFLOG_INFO(storescuLogger, "Peer Aborted Association");
    }
    else
    {
      OFLOG_ERROR(storescuLogger, "Store SCU Failed: " << DimseCondition::dump(temp_str, cond));
      OFLOG_INFO(storescuLogger, "Aborting Association");
      cond = ASC_abortAssociation(assoc);
      if (cond.bad()) {
        OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
        return 1;
      }
    }

    /* destroy the association, i.e. free memory of T_ASC_Association* structure. This */
    /* call is the counterpart of ASC_requestAssociation(...) which was called above. */
    cond = ASC_destroyAssociation(&assoc);
    if (cond.bad()) {
      OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
      return 1;
    }
    /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
    /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
    cond = ASC_dropNetwork(&net);
    if (cond.bad()) {
      OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
      return 1;
    }

    OFStandard::shutdownNetwork();

    cond = tlsOptions.writeRandomSeed();
    if (cond.bad()) {
        // failure to write back the random seed is a warning, not an error
        OFLOG_WARN(storescuLogger, DimseCondition::dump(temp_str, cond));
    }

    int exitCode = 0;
    if (opt_haltOnUnsuccessfulStore && unsuccessfulStoreEncountered) {
      if (lastStatusCode == STATUS_Success) {
        // there must have been some kind of general network error
        exitCode = 0xff;
      } else {
        exitCode = (lastStatusCode >> 8); // only the least significant byte is relevant as exit code
      }
    }

#ifdef ON_THE_FLY_COMPRESSION
    // deregister JPEG codecs
    DJDecoderRegistration::cleanup();
    DJEncoderRegistration::cleanup();

    // deregister JPEG-LS codecs
    DJLSDecoderRegistration::cleanup();
    DJLSEncoderRegistration::cleanup();

    // deregister RLE codecs
    DcmRLEDecoderRegistration::cleanup();
    DcmRLEEncoderRegistration::cleanup();
#endif

#ifdef DEBUG
    dcmDataDict.clear();  /* useful for debugging with dmalloc */
#endif
    return exitCode;
}


static OFBool
isaListMember(OFList<OFString> &lst, OFString &s)
{
  OFListIterator(OFString) cur = lst.begin();
  OFListIterator(OFString) end = lst.end();

  OFBool found = OFFalse;
  while (cur != end && !found) {
    found = (s == *cur);
    ++cur;
  }

  return found;
}

static OFCondition
addPresentationContext(T_ASC_Parameters *params,
  int presentationContextId,
  const OFString &abstractSyntax,
  const OFString &transferSyntax,
  T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT)
{
  const char *c_p = transferSyntax.c_str();
  OFCondition cond = ASC_addPresentationContext(params, presentationContextId,
    abstractSyntax.c_str(), &c_p, 1, proposedRole);
  return cond;
}

static OFCondition
addPresentationContext(T_ASC_Parameters *params,
  int presentationContextId,
  const OFString &abstractSyntax,
  const OFList<OFString> &transferSyntaxList,
  T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT)
{
  // create an array of supported/possible transfer syntaxes
  const char **transferSyntaxes = new const char*[transferSyntaxList.size()];
  int transferSyntaxCount = 0;
  OFListConstIterator(OFString) s_cur = transferSyntaxList.begin();
  OFListConstIterator(OFString) s_end = transferSyntaxList.end();
  while (s_cur != s_end) {
    transferSyntaxes[transferSyntaxCount++] = (*s_cur).c_str();
    ++s_cur;
  }

  OFCondition cond = ASC_addPresentationContext(params, presentationContextId,
    abstractSyntax.c_str(), transferSyntaxes, transferSyntaxCount, proposedRole);

  delete[] transferSyntaxes;
  return cond;
}

static OFCondition
addStoragePresentationContexts(T_ASC_Parameters *params,
  OFList<OFString> &sopClasses)
{
  /*
   * Each SOP Class will be proposed in two presentation contexts (unless
   * the opt_combineProposedTransferSyntaxes global variable is true).
   * The command line specified a preferred transfer syntax to use.
   * This prefered transfer syntax will be proposed in one
   * presentation context and a set of alternative (fallback) transfer
   * syntaxes will be proposed in a different presentation context.
   *
   * Generally, we prefer to use Explicitly encoded transfer syntaxes
   * and if running on a Little Endian machine we prefer
   * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
   * Some SCP implementations will just select the first transfer
   * syntax they support (this is not part of the standard) so
   * organise the proposed transfer syntaxes to take advantage
   * of such behaviour.
   */

  // Which transfer syntax was preferred on the command line
  OFString preferredTransferSyntax;
  if (opt_networkTransferSyntax == EXS_Unknown) {
    /* gLocalByteOrder is defined in dcxfer.h */
    if (gLocalByteOrder == EBO_LittleEndian) {
      /* we are on a little endian machine */
      preferredTransferSyntax = UID_LittleEndianExplicitTransferSyntax;
    } else {
      /* we are on a big endian machine */
      preferredTransferSyntax = UID_BigEndianExplicitTransferSyntax;
    }
  } else {
    DcmXfer xfer(opt_networkTransferSyntax);
    preferredTransferSyntax = xfer.getXferID();
  }

  OFListIterator(OFString) s_cur;
  OFListIterator(OFString) s_end;

  OFList<OFString> fallbackSyntaxes;
  // - If little endian implicit is preferred, we don't need any fallback syntaxes
  //   because it is the default transfer syntax and all applications must support it.
  // - If MPEG2, MPEG4 or HEVC is preferred, we don't want to propose any fallback solution
  //   because this is not required and we cannot decompress the movie anyway.
  if ((opt_networkTransferSyntax != EXS_LittleEndianImplicit) &&
      (opt_networkTransferSyntax != EXS_MPEG2MainProfileAtMainLevel) &&
      (opt_networkTransferSyntax != EXS_MPEG2MainProfileAtHighLevel) &&
      (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_1) &&
      (opt_networkTransferSyntax != EXS_MPEG4BDcompatibleHighProfileLevel4_1) &&
      (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For2DVideo) &&
      (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For3DVideo) &&
      (opt_networkTransferSyntax != EXS_MPEG4StereoHighProfileLevel4_2) &&
      (opt_networkTransferSyntax != EXS_HEVCMainProfileLevel5_1) &&
      (opt_networkTransferSyntax != EXS_HEVCMain10ProfileLevel5_1))
  {
    fallbackSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
    fallbackSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
    fallbackSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
    // Remove the preferred syntax from the fallback list
    fallbackSyntaxes.remove(preferredTransferSyntax);
  }

  // create a list of transfer syntaxes combined from the preferred and fallback syntaxes
  OFList<OFString> combinedSyntaxes;
  s_cur = fallbackSyntaxes.begin();
  s_end = fallbackSyntaxes.end();
  combinedSyntaxes.push_back(preferredTransferSyntax);
  while (s_cur != s_end)
  {
    if (!isaListMember(combinedSyntaxes, *s_cur)) combinedSyntaxes.push_back(*s_cur);
    ++s_cur;
  }

  if (!opt_proposeOnlyRequiredPresentationContexts) {
    // add the (short list of) known storage SOP classes to the list
    // the array of Storage SOP Class UIDs comes from dcuid.h
    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
      sopClasses.push_back(dcmShortSCUStorageSOPClassUIDs[i]);
  }

  // thin out the SOP classes to remove any duplicates
  OFList<OFString> sops;
  s_cur = sopClasses.begin();
  s_end = sopClasses.end();
  while (s_cur != s_end) {
    if (!isaListMember(sops, *s_cur)) {
      sops.push_back(*s_cur);
    }
    ++s_cur;
  }

  // add a presentations context for each SOP class / transfer syntax pair
  OFCondition cond = EC_Normal;
  int pid = 1; // presentation context id
  s_cur = sops.begin();
  s_end = sops.end();
  while (s_cur != s_end && cond.good()) {

    if (pid > 255) {
      OFLOG_ERROR(storescuLogger, "Too many presentation contexts");
      return ASC_BADPRESENTATIONCONTEXTID;
    }

    if (opt_combineProposedTransferSyntaxes) {
      cond = addPresentationContext(params, pid, *s_cur, combinedSyntaxes);
      pid += 2;   /* only odd presentation context id's */
    } else {

      // SOP class with preferred transfer syntax
      cond = addPresentationContext(params, pid, *s_cur, preferredTransferSyntax);
      pid += 2;   /* only odd presentation context id's */

      if (fallbackSyntaxes.size() > 0) {
        if (pid > 255) {
          OFLOG_ERROR(storescuLogger, "Too many presentation contexts");
          return ASC_BADPRESENTATIONCONTEXTID;
        }

        // SOP class with fallback transfer syntax
        cond = addPresentationContext(params, pid, *s_cur, fallbackSyntaxes);
        pid += 2; /* only odd presentation context id's */
      }
    }
    ++s_cur;
  }

  return cond;
}

static int
secondsSince1970()
{
  time_t t = time(NULL);
  return OFstatic_cast(int, t);
}

static OFString
intToString(int i)
{
  char numbuf[32];
  sprintf(numbuf, "%d", i);
  return numbuf;
}

static OFString
makeUID(OFString basePrefix, int counter)
{
  OFString prefix = basePrefix + "." + intToString(counter);
  char uidbuf[65];
  OFString uid = dcmGenerateUniqueIdentifier(uidbuf, prefix.c_str());
  return uid;
}

static OFBool
updateStringAttributeValue(DcmItem *dataset, const DcmTagKey &key, OFString &value)
{
  DcmStack stack;
  DcmTag tag(key);

  OFCondition cond = EC_Normal;
  cond = dataset->search(key, stack, ESM_fromHere, OFFalse);
  if (cond != EC_Normal) {
    OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: cannot find: " << tag.getTagName()
         << " " << key << ": " << cond.text());
    return OFFalse;
  }

  DcmElement *elem = OFstatic_cast(DcmElement *, stack.top());

  DcmVR vr(elem->ident());
  if (elem->getLength() > vr.getMaxValueLength()) {
    OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: INTERNAL ERROR: " << tag.getTagName()
         << " " << key << ": value too large (max " << vr.getMaxValueLength()
         << ") for " << vr.getVRName() << " value: " << value);
    return OFFalse;
  }

  cond = elem->putOFStringArray(value);
  if (cond != EC_Normal) {
    OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: cannot put string in attribute: " << tag.getTagName()
         << " " << key << ": " << cond.text());
    return OFFalse;
  }

  return OFTrue;
}

static void
replaceSOPInstanceInformation(DcmDataset *dataset)
{
  static OFCmdUnsignedInt patientCounter = 0;
  static OFCmdUnsignedInt studyCounter = 0;
  static OFCmdUnsignedInt seriesCounter = 0;
  static OFCmdUnsignedInt imageCounter = 0;
  static OFString seriesInstanceUID;
  static OFString seriesNumber;
  static OFString studyInstanceUID;
  static OFString studyID;
  static OFString accessionNumber;
  static OFString patientID;
  static OFString patientName;

  if (seriesInstanceUID.empty()) seriesInstanceUID=makeUID(SITE_SERIES_UID_ROOT, OFstatic_cast(int, seriesCounter));
  if (seriesNumber.empty()) seriesNumber = intToString(OFstatic_cast(int, seriesCounter));
  if (studyInstanceUID.empty()) studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, OFstatic_cast(int, studyCounter));
  if (studyID.empty()) studyID = studyIDPrefix + intToString(OFstatic_cast(int, secondsSince1970())) + intToString(OFstatic_cast(int, studyCounter));
  if (accessionNumber.empty()) accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
  if (patientID.empty()) patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
  if (patientName.empty()) patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));

  if (imageCounter >= opt_inventSeriesCount) {
    imageCounter = 0;
    seriesCounter++;
    seriesInstanceUID = makeUID(SITE_SERIES_UID_ROOT, OFstatic_cast(int, seriesCounter));
    seriesNumber = intToString(OFstatic_cast(int, seriesCounter));
  }
  if (seriesCounter >= opt_inventStudyCount) {
    seriesCounter = 0;
    studyCounter++;
    studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, OFstatic_cast(int, studyCounter));
    studyID = studyIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
    accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
  }
  if (studyCounter >= opt_inventPatientCount) {
    // we create as many patients as necessary */
    studyCounter = 0;
    patientCounter++;
    patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
    patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
  }

  OFString sopInstanceUID = makeUID(SITE_INSTANCE_UID_ROOT, OFstatic_cast(int, imageCounter));
  OFString imageNumber = intToString(OFstatic_cast(int, imageCounter));

  OFLOG_INFO(storescuLogger, "Inventing Identifying Information ("
         << "pa" << patientCounter << ", st" << studyCounter
         << ", se" << seriesCounter << ", im" << imageCounter << "):");
  OFLOG_INFO(storescuLogger, "  PatientName=" << patientName);
  OFLOG_INFO(storescuLogger, "  PatientID=" << patientID);
  OFLOG_INFO(storescuLogger, "  StudyInstanceUID=" << studyInstanceUID);
  OFLOG_INFO(storescuLogger, "  StudyID=" << studyID);
  OFLOG_INFO(storescuLogger, "  SeriesInstanceUID=" << seriesInstanceUID);
  OFLOG_INFO(storescuLogger, "  SeriesNumber=" << seriesNumber);
  OFLOG_INFO(storescuLogger, "  SOPInstanceUID=" << sopInstanceUID);
  OFLOG_INFO(storescuLogger, "  ImageNumber=" << imageNumber);

  updateStringAttributeValue(dataset, DCM_PatientName, patientName);
  updateStringAttributeValue(dataset, DCM_PatientID, patientID);
  updateStringAttributeValue(dataset, DCM_StudyInstanceUID, studyInstanceUID);
  updateStringAttributeValue(dataset, DCM_StudyID, studyID);
  updateStringAttributeValue(dataset, DCM_SeriesInstanceUID, seriesInstanceUID);
  updateStringAttributeValue(dataset, DCM_SeriesNumber, seriesNumber);
  updateStringAttributeValue(dataset, DCM_SOPInstanceUID, sopInstanceUID);
  updateStringAttributeValue(dataset, DCM_InstanceNumber, imageNumber);

  imageCounter++;
}

static void
progressCallback(void * /*callbackData*/,
  T_DIMSE_StoreProgress *progress,
  T_DIMSE_C_StoreRQ * req)
{
  if (progress->state == DIMSE_StoreBegin)
  {
    OFString str;
    OFLOG_DEBUG(storescuLogger, DIMSE_dumpMessage(str, *req, DIMSE_OUTGOING));
  }

  // We can't use oflog for the pdu output, but we use a special logger for
  // generating this output. If it is set to level "INFO" we generate the
  // output, if it's set to "DEBUG" then we'll assume that there is debug output
  // generated for each PDU elsewhere.
  OFLogger progressLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION ".progress");
  if (progressLogger.getChainedLogLevel() == OFLogger::INFO_LOG_LEVEL) {
    switch (progress->state) {
      case DIMSE_StoreBegin:
        COUT << "XMIT: "; break;
      case DIMSE_StoreEnd:
        COUT << OFendl; break;
      default:
        COUT << "."; break;
    }
    COUT.flush();
  }
}

static OFCondition
storeSCU(T_ASC_Association *assoc, const char *fname)
  /*
   * This function will read all the information from the given file,
   * figure out a corresponding presentation context which will be used
   * to transmit the information over the network to the SCP, and it
   * will finally initiate the transmission of all data to the SCP.
   *
   * Parameters:
   *   assoc - [in] The association (network connection to another DICOM application).
   *   fname - [in] Name of the file which shall be processed.
   */
{
  DIC_US msgId = assoc->nextMsgID++;
  T_ASC_PresentationContextID presID;
  T_DIMSE_C_StoreRQ req;
  T_DIMSE_C_StoreRSP rsp;
  DIC_UI sopClass;
  DIC_UI sopInstance;
  DcmDataset *statusDetail = NULL;

  unsuccessfulStoreEncountered = OFTrue; // assumption

  OFLOG_INFO(storescuLogger, "Sending file: " << fname);

  /* read information from file. After the call to DcmFileFormat::loadFile(...) the information */
  /* which is encapsulated in the file will be available through the DcmFileFormat object. */
  /* In detail, it will be available through calls to DcmFileFormat::getMetaInfo() (for */
  /* meta header information) and DcmFileFormat::getDataset() (for data set information). */
  DcmFileFormat dcmff;
  OFCondition cond = dcmff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, opt_readMode);

  /* figure out if an error occured while the file was read*/
  if (cond.bad()) {
    OFLOG_ERROR(storescuLogger, "Bad DICOM file: " << fname << ": " << cond.text());
    return cond;
  }

  /* if required, invent new SOP instance information for the current data set (user option) */
  if (opt_inventSOPInstanceInformation) {
    replaceSOPInstanceInformation(dcmff.getDataset());
  }

  /* figure out which SOP class and SOP instance is encapsulated in the file */
  if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(),
    sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), opt_correctUIDPadding)) {
      OFLOG_ERROR(storescuLogger, "No SOP Class or Instance UID in file: " << fname);
      return DIMSE_BADDATA;
  }

  /* figure out which of the accepted presentation contexts should be used */
  DcmXfer filexfer(dcmff.getDataset()->getOriginalXfer());

  /* special case: if the file uses an unencapsulated transfer syntax (uncompressed
   * or deflated explicit VR) and we prefer deflated explicit VR, then try
   * to find a presentation context for deflated explicit VR first.
   */
  if (filexfer.isNotEncapsulated() &&
    opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit)
  {
    filexfer = EXS_DeflatedLittleEndianExplicit;
  }

  if (filexfer.getXfer() != EXS_Unknown)
    presID = ASC_findAcceptedPresentationContextID(assoc, sopClass, filexfer.getXferID());
  else
    presID = ASC_findAcceptedPresentationContextID(assoc, sopClass);
  if (presID == 0) {
    const char *modalityName = dcmSOPClassUIDToModality(sopClass);
    if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
    if (!modalityName) modalityName = "unknown SOP class";
    OFLOG_ERROR(storescuLogger, "No presentation context for: (" << modalityName << ") " << sopClass);
    return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  }

  T_ASC_PresentationContext pc;
  ASC_findAcceptedPresentationContext(assoc->params, presID, &pc);
  DcmXfer netTransfer(pc.acceptedTransferSyntax);

  /* if required, dump general information concerning transfer syntaxes */
  if (storescuLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) {
    DcmXfer fileTransfer(dcmff.getDataset()->getOriginalXfer());
    OFLOG_INFO(storescuLogger, "Converting transfer syntax: " << fileTransfer.getXferName()
      << " -> " << netTransfer.getXferName());
  }

#ifdef ON_THE_FLY_COMPRESSION
  cond = dcmff.getDataset()->chooseRepresentation(netTransfer.getXfer(), NULL);
  if (cond.bad()) {
    OFLOG_ERROR(storescuLogger, "No conversion to transfer syntax " << netTransfer.getXferName() << " possible!");
    return cond;
  }
#endif

  /* prepare the transmission of data */
  bzero(OFreinterpret_cast(char *, &req), sizeof(req));
  req.MessageID = msgId;
  OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID));
  OFStandard::strlcpy(req.AffectedSOPInstanceUID, sopInstance, sizeof(req.AffectedSOPInstanceUID));
  req.DataSetType = DIMSE_DATASET_PRESENT;
  req.Priority = DIMSE_PRIORITY_MEDIUM;

  /* if required, dump some more general information */
  OFLOG_INFO(storescuLogger, "Sending Store Request (MsgID " << msgId << ", "
    << dcmSOPClassUIDToModality(sopClass, "OT") << ")");

  /* finally conduct transmission of data */
  cond = DIMSE_storeUser(assoc, presID, &req,
    NULL, dcmff.getDataset(), progressCallback, NULL,
    opt_blockMode, opt_dimse_timeout,
    &rsp, &statusDetail, NULL, OFstatic_cast(long, OFStandard::getFileSize(fname)));

  /*
   * If store command completed normally, with a status
   * of success or some warning then the image was accepted.
   */
  if (cond == EC_Normal && (rsp.DimseStatus == STATUS_Success || DICOM_WARNING_STATUS(rsp.DimseStatus))) {
    unsuccessfulStoreEncountered = OFFalse;
  }

  /* remember the response's status for later transmissions of data */
  lastStatusCode = rsp.DimseStatus;

  /* dump some more general information */
  if (cond == EC_Normal)
  {
    OFString temp_str;
    if (storescuLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
      OFLOG_INFO(storescuLogger, "Received Store Response");
      OFLOG_DEBUG(storescuLogger, DIMSE_dumpMessage(temp_str, rsp, DIMSE_INCOMING, NULL, presID));
    } else {
      OFLOG_INFO(storescuLogger, "Received Store Response (" << DU_cstoreStatusString(rsp.DimseStatus) << ")");
    }
  }
  else
  {
    OFString temp_str;
    OFLOG_ERROR(storescuLogger, "Store Failed, file: " << fname << ":" << OFendl << DimseCondition::dump(temp_str, cond));
  }

  /* dump status detail information if there is some */
  if (statusDetail != NULL) {
    OFLOG_DEBUG(storescuLogger, "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
    delete statusDetail;
  }
  /* return */
  return cond;
}


static OFCondition
cstore(T_ASC_Association *assoc, const OFString &fname)
  /*
   * This function will process the given file as often as is specified by opt_repeatCount.
   * "Process" in this case means "read file, send C-STORE-RQ, receive C-STORE-RSP".
   *
   * Parameters:
   *   assoc - [in] The association (network connection to another DICOM application).
   *   fname - [in] Name of the file which shall be processed.
   */
{
  OFCondition cond = EC_Normal;

  /* opt_repeatCount specifies how many times a certain file shall be processed */
  int n = OFstatic_cast(int, opt_repeatCount);

  /* as long as no error occured and the counter does not equal 0 */
  while ((cond.good()) && n-- && !(opt_haltOnUnsuccessfulStore && unsuccessfulStoreEncountered))
  {
    /* process file (read file, send C-STORE-RQ, receive C-STORE-RSP) */
    cond = storeSCU(assoc, fname.c_str());
  }

  // we don't want to return an error code if --no-halt was specified.
  if (!opt_haltOnUnsuccessfulStore)
  {
    cond = EC_Normal;
  }

  /* return result value */
  return cond;
}


static OFBool
findSOPClassAndInstanceInFile(
  const char *fname,
  char *sopClass,
  size_t sopClassSize,
  char *sopInstance,
  size_t sopInstanceSize)
{
    DcmFileFormat ff;
    if (!ff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, opt_readMode).good())
        return OFFalse;

    /* look in the meta-header first */
    OFBool found = DU_findSOPClassAndInstanceInDataSet(ff.getMetaInfo(), sopClass, sopClassSize, sopInstance, sopInstanceSize, opt_correctUIDPadding);

    if (!found)
      found = DU_findSOPClassAndInstanceInDataSet(ff.getDataset(), sopClass, sopClassSize, sopInstance, sopInstanceSize, opt_correctUIDPadding);

    return found;
}


static OFCondition
configureUserIdentityRequest(T_ASC_Parameters *params)
{
  OFCondition cond = EC_Normal;
  switch (opt_identMode)
  {
    case ASC_USER_IDENTITY_USER:
    {
      cond = ASC_setIdentRQUserOnly(params, opt_user, opt_identResponse);
      return cond;
    }
    case ASC_USER_IDENTITY_USER_PASSWORD:
    {
      cond = ASC_setIdentRQUserPassword(params, opt_user, opt_password, opt_identResponse);
      return cond;
    }
    case ASC_USER_IDENTITY_KERBEROS:
    case ASC_USER_IDENTITY_SAML:
    case ASC_USER_IDENTITY_JWT:
    {
      OFFile identFile;
      if (!identFile.fopen(opt_identFile.c_str(), "rb"))
      {
        OFString openerror;
        identFile.getLastErrorString(openerror);
        OFLOG_ERROR(storescuLogger, "Unable to open Kerberos, SAML or JWT file: " << openerror);
        return EC_IllegalCall;
      }
      // determine file size
      offile_off_t result = identFile.fseek(0, SEEK_END);
      if (result != 0)
        return EC_IllegalParameter;
      offile_off_t filesize = identFile.ftell();
      identFile.rewind();
      if (filesize > 65535)
      {
        OFLOG_INFO(storescuLogger, "Kerberos, SAML or JWT file is larger than 65535 bytes, bytes after that position are ignored");
        filesize = 65535;
      }

      char *buf = new char[OFstatic_cast(unsigned int, filesize)];
      size_t bytesRead = identFile.fread(buf, 1, OFstatic_cast(size_t, filesize));
      identFile.fclose();
      if (bytesRead == 0)
      {
        OFLOG_ERROR(storescuLogger, "Unable to read Kerberos, SAML or JWT info from file: File empty?");
        delete[] buf;
        return EC_IllegalCall;
      }
      // Casting to Uint16 should be safe since it is checked above that file
      // size does not exceed 65535 bytes.
      if (opt_identMode == ASC_USER_IDENTITY_KERBEROS)
        cond = ASC_setIdentRQKerberos(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
      else if (opt_identMode == ASC_USER_IDENTITY_SAML)
        cond = ASC_setIdentRQSaml(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
      else // JWT
        cond = ASC_setIdentRQJwt(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
      delete[] buf;
      break;
    }
    default:
    {
      cond = EC_IllegalCall;
    }
  }
  if (cond.bad())
  {
    OFString temp_str;
    OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
  }
  return cond;
}

static OFCondition
checkUserIdentityResponse(T_ASC_Parameters *params)
{
  if (params == NULL)
    return ASC_NULLKEY;

  /* So far it is only checked whether a requested, positive response was
     actually received */

  // In case we sent no user identity request, there are no checks at all
  if ((opt_identMode == ASC_USER_IDENTITY_NONE) || (!opt_identResponse))
    return EC_Normal;

  // If positive response was requested, we expect a corresponding response
  if ((opt_identMode == ASC_USER_IDENTITY_USER) || (opt_identMode == ASC_USER_IDENTITY_USER_PASSWORD))
  {
    UserIdentityNegotiationSubItemAC *rsp = params->DULparams.ackUserIdentNeg;
    if (rsp == NULL)
    {
      OFLOG_ERROR(storescuLogger, "User Identity Negotiation failed: Positive response requested but none received");
      return ASC_USERIDENTIFICATIONFAILED;
    }
  }
  return EC_Normal;
}
