diff --git a/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp b/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp
index 3fb87c96ca0803a3db57c24e3de00683df8d0fa5..2fcaab56b7be5967633197e5920e20324eb25e4c 100644
--- a/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp
+++ b/tapeserver/castor/tape/tapeserver/SCSI/Constants.hpp
@@ -739,6 +739,11 @@ namespace SCSI {
    * Addition for the mode page Length for the Control Data Protection Mode Page
    */
   const unsigned char controlDataProtectionModePageLengthAddition = 4; 
+  
+  class modeRAO {
+  public:
+      static const uint16_t DEFAULT_RRAO_ALLOCATION = 64000;
+  };
 } // namespace SCSI
 } // namespace tape
 } // namespace castor
diff --git a/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp b/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp
index 03ec545c5442fde62c31224da62a7d824cc4b907..5a8e3f9d2ad0c21e3de02a04bb9caf22dda76634 100644
--- a/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp
+++ b/tapeserver/castor/tape/tapeserver/SCSI/Structures.hpp
@@ -172,6 +172,16 @@ namespace SCSI {
       *((uint16_t *) t) = htons(val);
     }
 
+    /**
+     * Helper function setting in place a 64 bits SCSI number from a value
+     * expressed in the local endianness.
+     * @param t pointer to the char array at the 64 bits value position.
+     * @param val the value.
+     */
+    inline void setU64(unsigned char(& t)[8], uint64_t val) {
+        *((uint64_t *) t) = htobe64(val);
+    }
+    
     /**
      * Inquiry CDB as described in SPC-4.
      */
@@ -1170,6 +1180,140 @@ namespace SCSI {
         unsigned char keyData[SCSI::encryption::ENC_KEY_LENGTH];
       };
     }
+    
+    namespace RAO {
+        
+       /**
+        * Receive RAO Command Descriptor Block (CDB)
+        */
+       class recieveRAO_t {
+       public:
+         recieveRAO_t() {
+           zeroStruct(this);
+           opcode = SCSI::Commands::MAINTENANCE_IN;
+         }
+         unsigned char opcode;
+
+         unsigned char serviceAction   :5;
+         unsigned char                 :2;
+         unsigned char udsLimits       :1;
+
+         unsigned char raoListOffset[4];
+
+         unsigned char allocationLength[4];
+
+         unsigned char udsType         :3;
+         unsigned char                 :5;
+
+         unsigned char control;
+
+       };
+
+       /**
+        * UDS (User Data Segments) limits page
+        */
+       class udsLimitsPage_t {
+       public:
+         udsLimitsPage_t() {
+           zeroStruct(this);
+         }
+         unsigned int maxSupported;
+         unsigned int maxSize;
+       };
+
+       /**
+        * Generate RAO CDB
+        */
+       class generateRAO_t {
+       public:
+         generateRAO_t() {
+           zeroStruct(this);
+           opcode = SCSI::Commands::MAINTENANCE_OUT;
+           raoProcess = 2;
+         }
+         unsigned char opcode;
+
+         unsigned char serviceAction   :5;
+         unsigned char                 :3;
+
+         unsigned char raoProcess      :3;
+         unsigned char                 :5;
+
+         unsigned char udsType         :3;
+         unsigned char                 :5;
+
+         unsigned char reserved[2];
+
+         unsigned char paramsListLength[4];
+
+         unsigned char reserved2;
+
+         unsigned char control;
+
+       };
+
+       class udsDescriptor {
+       public:
+         udsDescriptor() {
+           zeroStruct(this);
+           setU16(descriptorLength, 0x1e);
+         }
+         unsigned char descriptorLength[2];
+         unsigned char reserved[3];
+         unsigned char udsName[10];
+         unsigned char partitionNumber;
+         unsigned char beginLogicalObjID[8];
+         unsigned char endLogicalObjID[8];
+       };
+
+       /**
+        * RAO list struct
+        */
+       class raoList {
+       public:
+         raoList() {
+           zeroStruct(this);
+         }
+         unsigned char raoProcess      :3;
+         unsigned char                 :5;
+
+         unsigned char status          :3;
+         unsigned char                 :5;
+
+         unsigned char res[2];
+
+         unsigned char raoDescriptorListLength[4];
+
+         udsDescriptor udsDescriptors[2000];
+
+       };
+
+       /**
+        * Generate RAO parameters
+        */
+       class generateRAOParams_t {
+       public:
+         generateRAOParams_t() {
+           zeroStruct(this);
+         }
+         unsigned char res[4];
+         unsigned char additionalData[4];
+         udsDescriptor userDataSegmentDescriptors[2000];
+       };
+       
+       /**
+        * Block Limits
+        */
+       class blockLims {
+       public:
+         blockLims() {
+           zeroStruct(this);
+         }
+         unsigned char fseq[10];
+         uint64_t begin;
+         uint64_t end;
+       };
+    }
  
     template <size_t n>
     /**
diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp
index 67cdbb9047aab6a43bbe8b9042c654d312beee22..f563c110bb5c93bde18a7ff838207a630931bdba 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.cpp
@@ -41,6 +41,7 @@ castor::tape::tapeserver::daemon::DataTransferConfig::DataTransferConfig()
   maxFilesBeforeFlush(0),
   nbDiskThreads(0),
   useLbp(false),
+  useRAO(false),
   externalEncryptionKeyScript("") {}
 
 //------------------------------------------------------------------------------
@@ -85,6 +86,8 @@ castor::tape::tapeserver::daemon::DataTransferConfig
     "TapeServer", "XrootTimeout", 0, log);
   const std::string useLBP = castorConf.getConfEntString(
     "TapeServer", "UseLogicalBlockProtection", "no", log);
+  const std::string useRAO = castorConf.getConfEntString(
+    "TapeServer", "UseRecommendedAccessOrder", "no", log);
 
   if (!strcasecmp(useLBP.c_str(), "yes") || !strcmp(useLBP.c_str(), "1")) {
     config.useLbp = true;
@@ -92,6 +95,12 @@ castor::tape::tapeserver::daemon::DataTransferConfig
     config.useLbp = false;
   }
 
+  if (!strcasecmp(useRAO.c_str(), "yes") || !strcmp(useRAO.c_str(), "1")) {
+    config.useRAO = true;
+  } else {
+    config.useRAO = false;
+  }
+
   config.externalEncryptionKeyScript = castorConf.getConfEntString("TapeServer",
     "ExternalEncryptionKeyScript", "");
 
diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp
index 69b377a846622c34f72463ab3a3d730a30cad58c..02ecc4a68c253441d8f38c3e0ebe3574e7a27e3c 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferConfig.hpp
@@ -112,6 +112,12 @@ struct DataTransferConfig {
    */
   bool useLbp;
 
+  /**
+   * The boolean variable describing to use on not to use Recommended
+   * Access Order
+   */
+  bool useRAO;
+
   /**
    * The path to the operator provided encyption control script (or empty string)
    */
diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp
index a8237b58899c2c540dbc32714f82f10d1b2f5b89..745082232028e203d291806ef651cabfd9d283b2 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSession.cpp
@@ -203,7 +203,7 @@ castor::tape::tapeserver::daemon::Session::EndOfSessionAction
     
     TapeReadSingleThread trst(*drive, m_mc, tsr, m_volInfo, 
         m_castorConf.bulkRequestRecallMaxFiles,m_capUtils,rwd,lc,rrp,
-        m_castorConf.useLbp, m_castorConf.externalEncryptionKeyScript);
+        m_castorConf.useLbp, m_castorConf.useRAO, m_castorConf.externalEncryptionKeyScript);
     DiskWriteThreadPool dwtp(m_castorConf.nbDiskThreads,
         rrp,
         rwd,
@@ -219,10 +219,18 @@ castor::tape::tapeserver::daemon::Session::EndOfSessionAction
     trst.setTaskInjector(&rti);
     rrp.setWatchdog(rwd);
     
+    rti.setDriveInterface(trst.getDriveReference());
+
     // We are now ready to put everything in motion. First step is to check
     // we get any concrete job to be done from the client (via the task injector)
     cta::utils::Timer timer;
-    if (rti.synchronousInjection()) {  //adapt the recall task injector (starting from synchronousInjection)
+
+    // The RecallTaskInjector and the TapeReadSingleThread share the promise
+    if (m_castorConf.useRAO) {
+      rti.initRAO();
+    }
+
+    if (rti.synchronousFetch()) {  //adapt the recall task injector (starting from synchronousFetch)
       // We got something to recall. Time to start the machinery
       trst.setWaitForInstructionsTime(timer.secs());
       rwd.startThread();
diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp
index ccb658c7e7b9e64a99d6b5115e1568f4fbfbc7b7..cfbc3b271d76bbeabf0581ec5fdb89454319134f 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp
@@ -660,6 +660,218 @@ TEST_P(DataTransferSessionTest, DataTransferSessionWrongRecall) {
                                                "mountTotalReadRetries=\"25\" mountTotalWriteRetries=\"25\" mountWriteTransients=\"10\""));
 }
 
+TEST_P(DataTransferSessionTest, DataTransferSessionRAORecall) {
+  // 0) Prepare the logger for everyone
+  cta::log::StringLogger logger("tapeServerUnitTest",cta::log::DEBUG);
+  cta::log::LogContext logContext(logger);
+  
+  setupDefaultCatalogue();
+  // 1) prepare the fake scheduler
+  std::string vid = s_vid;
+  // cta::MountType::Enum mountType = cta::MountType::RETRIEVE;
+
+  // 3) Prepare the necessary environment (logger, plus system wrapper), 
+  castor::tape::System::mockWrapper mockSys;
+  mockSys.delegateToFake();
+  mockSys.disableGMockCallsCounting();
+  mockSys.fake.setupForVirtualDriveSLC6();
+  //delete is unnecessary
+  //pointer with ownership will be passed to the application,
+  //which will do the delete 
+  mockSys.fake.m_pathToDrive["/dev/nst0"] = new castor::tape::tapeserver::drive::FakeDrive;
+
+  // 4) Create the scheduler
+  auto & catalogue = getCatalogue();
+  auto & scheduler = getScheduler();
+  
+  // Always use the same requester
+  const cta::common::dataStructures::SecurityIdentity requester;
+
+  // List to remember the path of each remote file so that the existance of the
+  // files can be tested for at the end of the test
+  std::list<std::string> remoteFilePaths;
+  
+  // 5) Create the environment for the migration to happen (library + tape) 
+    const std::string libraryComment = "Library comment";
+  catalogue.createLogicalLibrary(s_adminOnAdminHost, s_libraryName,
+    libraryComment);
+  {
+    auto libraries = catalogue.getLogicalLibraries();
+    ASSERT_EQ(1, libraries.size());
+    ASSERT_EQ(s_libraryName, libraries.front().name);
+    ASSERT_EQ(libraryComment, libraries.front().comment);
+  }
+  const uint64_t capacityInBytes = 12345678;
+  const std::string tapeComment = "Tape comment";
+  bool notDisabled = false;
+  bool notFull = false;
+  catalogue.createTape(s_adminOnAdminHost, s_vid, s_libraryName, s_tapePoolName, capacityInBytes,
+    notDisabled, notFull, tapeComment);
+
+  int MAX_RECALLS = 50;
+  int MAX_BULK_RECALLS = 31;
+  std::vector<int> expectedOrder;
+  std::vector<std::string> expectedFseqOrderLog;
+
+  // 6) Prepare files for reading by writing them to the mock system
+  {
+    // Label the tape
+    castor::tape::tapeFile::LabelSession ls(*mockSys.fake.m_pathToDrive["/dev/nst0"], 
+        s_vid, false);
+    mockSys.fake.m_pathToDrive["/dev/nst0"]->rewind();
+    // And write to it
+    castor::tape::tapeserver::daemon::VolumeInfo volInfo;
+    volInfo.vid=s_vid;
+    castor::tape::tapeFile::WriteSession ws(*mockSys.fake.m_pathToDrive["/dev/nst0"],
+       volInfo , 0, true, false);
+
+    // Write a few files on the virtual tape and modify the archive name space
+    // so that it is in sync
+    uint8_t data[1000];
+    size_t archiveFileSize=sizeof(data);
+    castor::tape::SCSI::Structures::zeroStruct(&data);
+    int fseq;
+    bool isFirst = true;
+    for (fseq=1; fseq <= MAX_RECALLS ; fseq ++) {
+      // Create a path to a remote destination file
+      std::ostringstream remoteFilePath;
+      remoteFilePath << "file://" << m_tmpDir << "/test" << fseq;
+      remoteFilePaths.push_back(remoteFilePath.str());
+
+      // Create an archive file entry in the archive namespace
+      cta::catalogue::TapeFileWritten tapeFileWritten;
+      
+      // Write the file to tape
+      cta::MockArchiveMount mam(catalogue);
+      std::unique_ptr<cta::ArchiveJob> aj(new cta::MockArchiveJob(mam, catalogue));
+      aj->tapeFile.fSeq = fseq;
+      aj->archiveFile.archiveFileID = fseq;
+      castor::tape::tapeFile::WriteFile wf(&ws, *aj, archiveFileSize);
+      tapeFileWritten.blockId = wf.getBlockId();
+      // Write the data (one block)
+      wf.write(data, archiveFileSize);
+      // Close the file
+      wf.close();
+
+      // Create file entry in the archive namespace
+      tapeFileWritten.archiveFileId=fseq;
+      tapeFileWritten.checksumType="ADLER32";
+      tapeFileWritten.checksumValue=cta::utils::getAdler32String(data, archiveFileSize);
+      tapeFileWritten.vid=volInfo.vid;
+      tapeFileWritten.size=archiveFileSize;
+      tapeFileWritten.fSeq=fseq;
+      tapeFileWritten.copyNb=1;
+      tapeFileWritten.compressedSize=archiveFileSize; // No compression
+      tapeFileWritten.diskInstance = s_diskInstance;
+      tapeFileWritten.diskFileId = fseq;
+      tapeFileWritten.diskFilePath = remoteFilePath.str();
+      tapeFileWritten.diskFileUser = s_userName;
+      tapeFileWritten.diskFileGroup = "someGroup";
+      tapeFileWritten.diskFileRecoveryBlob = "B106";
+      tapeFileWritten.storageClassName = s_storageClassName;
+      tapeFileWritten.tapeDrive = "drive0";
+      catalogue.filesWrittenToTape(std::set<cta::catalogue::TapeFileWritten>{tapeFileWritten});
+
+      // Schedule the retrieval of the file
+      std::string diskInstance="disk_instance";
+      cta::common::dataStructures::RetrieveRequest rReq;
+      rReq.archiveFileID=fseq;
+      rReq.requester.name = s_userName;
+      rReq.requester.group = "someGroup";
+      rReq.dstURL = remoteFilePaths.back();
+      std::list<std::string> archiveFilePaths;
+      scheduler.queueRetrieve(diskInstance, rReq, logContext);
+
+      expectedOrder.push_back(fseq);
+
+      bool apply_rao = false;
+      bool add_expected = false;
+      if (MAX_BULK_RECALLS < 2) {
+        if (expectedOrder.size() % MAX_BULK_RECALLS == 0 ||
+            fseq % MAX_RECALLS == 0) {
+          add_expected = true;
+        }
+      }
+      else if (MAX_BULK_RECALLS >= 30) {
+        if ((expectedOrder.size() % 30 == 0) ||
+            (fseq % MAX_RECALLS == 0) || (fseq % MAX_BULK_RECALLS == 0)) {
+          apply_rao = true & isFirst;
+          add_expected = true;
+        }
+      }
+      else if ((fseq % MAX_BULK_RECALLS == 0) || (fseq % MAX_RECALLS == 0)) {
+        apply_rao = true & isFirst;
+        add_expected = true;
+      }
+      if (apply_rao) {
+        std::reverse(expectedOrder.begin(), expectedOrder.end());
+        isFirst = false;
+      }
+      if (add_expected) {
+        std::stringstream expectedLogLine;
+        std::copy(expectedOrder.begin(), expectedOrder.end(),
+                std::ostream_iterator<int>(expectedLogLine, " "));
+        expectedFseqOrderLog.push_back(expectedLogLine.str());
+        expectedOrder.clear();
+      }
+    }
+  }
+    
+  // 6) Report the drive's existence and put it up in the drive register.
+  cta::tape::daemon::TpconfigLine driveConfig("T10D6116", "TestLogicalLibrary", "/dev/tape_T10D6116", "manual");
+  cta::common::dataStructures::DriveInfo driveInfo;
+  driveInfo.driveName=driveConfig.unitName;
+  driveInfo.logicalLibrary=driveConfig.rawLibrarySlot;
+  // We need to create the drive in the registry before being able to put it up.
+  scheduler.reportDriveStatus(driveInfo, cta::common::dataStructures::MountType::NoMount, cta::common::dataStructures::DriveStatus::Down);
+  scheduler.setDesiredDriveState(s_adminOnAdminHost, driveConfig.unitName, true, false);
+
+  // 7) Create the data transfer session
+  DataTransferConfig castorConf;
+  castorConf.bufsz = 1024*1024; // 1 MB memory buffers
+  castorConf.nbBufs = 10;
+  castorConf.bulkRequestRecallMaxBytes = UINT64_C(100)*1000*1000*1000;
+  castorConf.bulkRequestRecallMaxFiles = MAX_BULK_RECALLS - 1;
+  castorConf.nbDiskThreads = 1;
+  castorConf.useRAO = true;
+  cta::log::DummyLogger dummyLog("dummy");
+  cta::mediachanger::MediaChangerFacade mc(dummyLog);
+  cta::server::ProcessCap capUtils;
+  castor::messages::TapeserverProxyDummy initialProcess;
+  castor::tape::tapeserver::daemon::DataTransferSession sess("tapeHost", logger, mockSys,
+    driveConfig, mc, initialProcess, capUtils, castorConf, scheduler);
+
+  // 8) Run the data transfer session
+  sess.execute();
+
+  // 9) Check the session git the correct VID
+  ASSERT_EQ(s_vid, sess.getVid());
+
+  // 10) Check the remote files exist and have the correct size
+  for(auto & path: remoteFilePaths) {
+    struct stat statBuf;
+    bzero(&statBuf, sizeof(statBuf));
+    const int statRc = stat(path.substr(7).c_str(), &statBuf); //remove the "file://" for stat-ing
+    ASSERT_EQ(0, statRc);
+    ASSERT_EQ(1000, statBuf.st_size); //same size of data
+  }
+
+  // 10) Check logs
+  std::string logToCheck = logger.getLog();
+  logToCheck += "";
+  ASSERT_NE(std::string::npos, logToCheck.find("firmwareVersion=\"123A\" serialNumber=\"123456\" "
+                                               "mountTotalCorrectedReadErrors=\"5\" mountTotalReadBytesProcessed=\"4096\" "
+                                               "mountTotalUncorrectedReadErrors=\"1\" mountTotalNonMediumErrorCounts=\"2\""));
+  ASSERT_NE(std::string::npos, logToCheck.find("firmwareVersion=\"123A\" serialNumber=\"123456\" lifetimeMediumEfficiencyPrct=\"100\" "
+                                               "mountReadEfficiencyPrct=\"100\" mountWriteEfficiencyPrct=\"100\" "
+                                               "mountReadTransients=\"10\" "
+                                               "mountServoTemps=\"10\" mountServoTransients=\"5\" mountTemps=\"100\" "
+                                               "mountTotalReadRetries=\"25\" mountTotalWriteRetries=\"25\" mountWriteTransients=\"10\""));
+  for (std::string s : expectedFseqOrderLog) {
+    ASSERT_NE(std::string::npos, logToCheck.find(s));
+  }
+}
+
 TEST_P(DataTransferSessionTest, DataTransferSessionNoSuchDrive) {
   
   // 0) Prepare the logger for everyone
diff --git a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp
index a83cf9b72c7410da393731c352925912c348e5cd..e80b66eb71f2ab90140ab9eb679880c9db8f6f43 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.cpp
@@ -27,6 +27,8 @@
 #include "castor/tape/tapeserver/daemon/TapeReadTask.hpp"
 #include "castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp"
 #include "castor/tape/tapeserver/daemon/VolumeInfo.hpp"
+#include "castor/tape/tapeserver/SCSI/Structures.hpp"
+#include "castor/tape/tapeserver/drive/DriveInterface.hpp"
 #include "scheduler/RetrieveJob.hpp"
 
 #include <stdint.h>
@@ -46,8 +48,8 @@ RecallTaskInjector::RecallTaskInjector(RecallMemoryManager & mm,
         uint64_t maxFiles, uint64_t byteSizeThreshold,cta::log::LogContext lc) : 
         m_thread(*this),m_memManager(mm),
         m_tapeReader(tapeReader),m_diskWriter(diskWriter),
-        m_retrieveMount(retrieveMount),m_lc(lc),m_maxFiles(maxFiles),m_maxBytes(byteSizeThreshold)
-{}
+        m_retrieveMount(retrieveMount),m_lc(lc),m_maxFiles(maxFiles),m_maxBytes(byteSizeThreshold),
+        m_useRAO(false) {}
 //------------------------------------------------------------------------------
 //destructor
 //------------------------------------------------------------------------------
@@ -82,41 +84,126 @@ void RecallTaskInjector::startThreads() {
   m_thread.start();
 }
 //------------------------------------------------------------------------------
+//setDriveInterface
+//------------------------------------------------------------------------------
+void RecallTaskInjector::setDriveInterface(castor::tape::tapeserver::drive::DriveInterface *di) {
+  m_drive = di;
+}
+//------------------------------------------------------------------------------
+//initRAO
+//------------------------------------------------------------------------------
+void RecallTaskInjector::initRAO() {
+  m_useRAO = true;
+  m_raoFuture = m_raoPromise.get_future();
+  m_raoLimits = m_drive->getLimitUDS();
+}
+//------------------------------------------------------------------------------
+//waitForPromise
+//------------------------------------------------------------------------------
+bool RecallTaskInjector::waitForPromise() {
+  std::chrono::milliseconds duration (1000);
+   std::future_status status = m_raoFuture.wait_for(duration);
+   if (status == std::future_status::ready)
+     return true;
+   return false;
+}
+//------------------------------------------------------------------------------
+//setPromise
+//------------------------------------------------------------------------------
+void RecallTaskInjector::setPromise() {
+  try {
+    m_raoPromise.set_value();
+  } catch (const std::exception &exc) {
+    throw cta::exception::Exception(std::string("In RecallTaskInjector::setPromise() got std::exception: ") + exc.what());
+  }
+}
+//------------------------------------------------------------------------------
 //injectBulkRecalls
 //------------------------------------------------------------------------------
-void RecallTaskInjector::injectBulkRecalls(std::vector<std::unique_ptr<cta::RetrieveJob>>& jobs) {
-  for (auto it = jobs.begin(); it != jobs.end(); ++it) {
+void RecallTaskInjector::injectBulkRecalls() {
+
+  uint32_t block_size = 262144;
+  uint32_t njobs = m_jobs.size();
+  std::vector<uint32_t> raoOrder;
+
+  if (m_useRAO) {
+    std::list<castor::tape::SCSI::Structures::RAO::blockLims> files;
+
+    for (uint32_t i = 0; i < njobs; i++) {
+      cta::RetrieveJob *job = m_jobs.at(i).get();
+
+      castor::tape::SCSI::Structures::RAO::blockLims lims;
+      strncpy((char*)lims.fseq, std::to_string(i).c_str(), sizeof(i));
+      lims.begin = job->selectedTapeFile().blockId;
+      lims.end = job->selectedTapeFile().blockId + 8 +
+                 /* ceiling the number of blocks */
+                 ((job->archiveFile.fileSize + block_size - 1) / block_size);
+
+      files.push_back(lims);
+
+      if (files.size() == m_raoLimits.maxSupported ||
+              ((i == njobs - 1) && (files.size() > 1))) {
+        /* We do a RAO query if:
+         *  1. the maximum number of files supported by the drive
+         *     for RAO query has been reached
+         *  2. the end of the jobs list has been reached and there are at least
+         *     2 unordered files
+         */
+        m_drive->queryRAO(files, m_raoLimits.maxSupported);
+
+        /* Add the RAO sorted files to the new list*/
+        for (auto fit = files.begin(); fit != files.end(); fit++) {
+          uint64_t id = atoi((char*)fit->fseq);
+          raoOrder.push_back(id);
+        }
+        files.clear();
+      }
+    }
     
-    (*it)->positioningMethod=cta::PositioningMethod::ByBlock;
+    /* Copy the rest of the files in the new ordered list */
+    for (auto fit = files.begin(); fit != files.end(); fit++) {
+      uint64_t id = atoi((char*)fit->fseq);
+      raoOrder.push_back(id);
+    }
+    files.clear();
+  }
+
+  std::string queryOrderLog = "Query fseq order:";
+  for (uint32_t i = 0; i < njobs; i++) {
+    uint32_t index = m_useRAO ? raoOrder.at(i) : i;
+
+    cta::RetrieveJob *job = m_jobs.at(index).release();
+    queryOrderLog += std::to_string(job->selectedTapeFile().fSeq) + " ";
+
+    job->positioningMethod=cta::PositioningMethod::ByBlock;
 
     LogContext::ScopedParam sp[]={
-      LogContext::ScopedParam(m_lc, Param("fileId", (*it)->retrieveRequest.archiveFileID)),
-      LogContext::ScopedParam(m_lc, Param("fSeq", (*it)->selectedTapeFile().fSeq)),
-      LogContext::ScopedParam(m_lc, Param("blockID", (*it)->selectedTapeFile().blockId)),
-      LogContext::ScopedParam(m_lc, Param("dstURL", (*it)->retrieveRequest.dstURL))
+      LogContext::ScopedParam(m_lc, Param("fileId", job->retrieveRequest.archiveFileID)),
+      LogContext::ScopedParam(m_lc, Param("fSeq", job->selectedTapeFile().fSeq)),
+      LogContext::ScopedParam(m_lc, Param("blockID", job->selectedTapeFile().blockId)),
+      LogContext::ScopedParam(m_lc, Param("dstURL", job->retrieveRequest.dstURL))
     };
     tape::utils::suppresUnusedVariable(sp);
     
     m_lc.log(cta::log::INFO, "Recall task created");
     
-    cta::RetrieveJob *job = it->get();
-    DiskWriteTask * dwt = new DiskWriteTask(it->release(), m_memManager);
+    DiskWriteTask * dwt = new DiskWriteTask(job, m_memManager);
     TapeReadTask * trt = new TapeReadTask(job, *dwt, m_memManager);
-    
+
     m_diskWriter.push(dwt);
     m_tapeReader.push(trt);
     m_lc.log(cta::log::INFO, "Created tasks for recalling a file");
   }
-  LogContext::ScopedParam sp03(m_lc, Param("nbFile", jobs.size()));
+  m_lc.log(cta::log::INFO, queryOrderLog);
+  m_jobs.clear();
+  LogContext::ScopedParam sp03(m_lc, Param("nbFile", m_jobs.size()));
   m_lc.log(cta::log::INFO, "Finished processing batch of recall tasks from client");
 }
 //------------------------------------------------------------------------------
 //synchronousInjection
 //------------------------------------------------------------------------------
-bool RecallTaskInjector::synchronousInjection()
+bool RecallTaskInjector::synchronousFetch()
 {
-  std::vector<std::unique_ptr<cta::RetrieveJob>> jobs;
-  
   try {
     uint64_t files=0;
     uint64_t bytes=0;
@@ -125,7 +212,7 @@ bool RecallTaskInjector::synchronousInjection()
       if(!job.get()) break;
       files++;
       bytes+=job->archiveFile.fileSize;
-      jobs.emplace_back(job.release());
+      m_jobs.emplace_back(job.release());
     }
   } catch (cta::exception::Exception & ex) {
     cta::log::ScopedParamContainer scoped(m_lc);
@@ -139,12 +226,13 @@ bool RecallTaskInjector::synchronousInjection()
   cta::log::ScopedParamContainer scoped(m_lc); 
   scoped.add("byteSizeThreshold",m_maxBytes)
         .add("maxFiles", m_maxFiles);
-  if(jobs.empty()) { 
+  if(m_jobs.empty()) {
     m_lc.log(cta::log::ERR, "No files to recall: empty mount");
     return false;
   }
   else {
-    injectBulkRecalls(jobs);
+    if (! m_useRAO)
+      injectBulkRecalls();
     return true;
   }
 }
@@ -174,18 +262,33 @@ void RecallTaskInjector::WorkerThread::run()
   using cta::log::LogContext;
   m_parent.m_lc.pushOrReplace(Param("thread", "RecallTaskInjector"));
   m_parent.m_lc.log(cta::log::DEBUG, "Starting RecallTaskInjector thread");
-  
+  if (m_parent.m_useRAO) {
+    bool moreJobs = true;
+    /* RecallTaskInjector is waiting to have access to the drive in order
+     * to perform the RAO query; while waiting, it is fetching more jobs
+     */
+    while (true) {
+      if (m_parent.waitForPromise()) break;
+      if (moreJobs) {
+        /* Fetching while there are still jobs to fetch
+         * Otherwise, we are just waiting for the promise
+         */
+        moreJobs =  m_parent.synchronousFetch();
+      }
+    }
+    m_parent.injectBulkRecalls();
+    m_parent.m_useRAO = false;
+  }
   try{
     while (1) {
       Request req = m_parent.m_queue.pop();
       if (req.end) {
         m_parent.m_lc.log(cta::log::INFO,"Received a end notification from tape thread: triggering the end of session.");
+        
         m_parent.signalEndDataMovement();
         break;
       }
       m_parent.m_lc.log(cta::log::DEBUG,"RecallJobInjector:run: about to call client interface");
-      std::vector<std::unique_ptr<cta::RetrieveJob>> jobs;
-      
       uint64_t files=0;
       uint64_t bytes=0;
       while(files<=req.filesRequested && bytes<=req.bytesRequested) {
@@ -193,12 +296,12 @@ void RecallTaskInjector::WorkerThread::run()
         if(!job.get()) break;
         files++;
         bytes+=job->archiveFile.fileSize;
-        jobs.emplace_back(job.release());
+        m_parent.m_jobs.emplace_back(job.release());
       }
       
       LogContext::ScopedParam sp01(m_parent.m_lc, Param("transactionId", m_parent.m_retrieveMount.getMountTransactionId()));
       
-      if (jobs.empty()) {
+      if (m_parent.m_jobs.empty()) {
         if (req.lastCall) {
           m_parent.m_lc.log(cta::log::INFO,"No more file to recall: triggering the end of session.");
           m_parent.signalEndDataMovement();
@@ -207,7 +310,7 @@ void RecallTaskInjector::WorkerThread::run()
           m_parent.m_lc.log(cta::log::DEBUG,"In RecallJobInjector::WorkerThread::run(): got empty list, but not last call. NoOp.");
         }
       } else {
-        m_parent.injectBulkRecalls(jobs);
+        m_parent.injectBulkRecalls();
       }
     } // end of while(1)
   } //end of try
@@ -218,7 +321,6 @@ void RecallTaskInjector::WorkerThread::run()
       m_parent.m_lc.logBacktrace(cta::log::ERR,ex.backtrace());
       m_parent.m_lc.log(cta::log::ERR,"In RecallJobInjector::WorkerThread::run(): "
       "could not retrieve a list of file to recall. End of session");
-      
       m_parent.signalEndDataMovement();
       m_parent.deleteAllTasks();
     }
diff --git a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp
index ce8e4df9562bb23f4257243ef6960e472d6aafc4..8d1443da76016f97fe6d7bbfe890f65c72b3855a 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjector.hpp
@@ -29,6 +29,9 @@
 #include "common/threading/Thread.hpp"
 #include "scheduler/RetrieveJob.hpp"
 #include "scheduler/RetrieveMount.hpp"
+#include "castor/tape/tapeserver/drive/DriveInterface.hpp"
+
+#include <future>
 
 namespace castor{
 namespace tape{
@@ -99,7 +102,7 @@ public:
      * @param byteSizeThreshold total bytes count  at least requested
      * @return true if there are jobs to be done, false otherwise 
      */
-  bool synchronousInjection();
+  bool synchronousFetch();
 
   /**
    * Wait for the inner thread to finish
@@ -110,6 +113,22 @@ public:
    * Start the inner thread 
    */
   void startThreads();
+
+  /**
+   * Set the drive interface in use
+   * @param di - Drive interface
+   */
+  void setDriveInterface(castor::tape::tapeserver::drive::DriveInterface *di);
+
+  /**
+   * Initialize Recommended Access Order parameters
+   */
+  void initRAO();
+
+  bool waitForPromise();
+
+  void setPromise();
+
 private:
   /**
    * It will signal to the disk read thread  pool, tape write single thread
@@ -124,9 +143,8 @@ private:
   
   /**
    * Create all the tape-read and write-disk tasks for set of files to retrieve
-   * @param jobs
    */
-  void injectBulkRecalls(std::vector<std::unique_ptr<cta::RetrieveJob>>& jobs);
+  void injectBulkRecalls();
 
   /**
    * A request of files to recall. We request EITHER
@@ -179,6 +197,11 @@ private:
   /// the client who is sending us jobs
   cta::RetrieveMount &m_retrieveMount;
   
+  /// Drive interface needed for performing Recommended Access Order query
+  castor::tape::tapeserver::drive::DriveInterface * m_drive;
+
+  std::vector<std::unique_ptr<cta::RetrieveJob>> m_jobs;
+
   /**
    * utility member to log some pieces of information
    */
@@ -193,6 +216,21 @@ private:
   
   //maximal number of cumulated byte requested. at once
   const uint64_t m_maxBytes;
+
+  /** Flag indicating if the file recalls are performed using
+   * the Recommended Access Order (RAO)
+   */
+  bool m_useRAO;
+
+  /** Drive-specific RAO parameters */
+  SCSI::Structures::RAO::udsLimitsPage_t m_raoLimits;
+
+  /**
+   * The promise for reordering the read tasks according to RAO by the
+   * RecallTaskInjector. The tasks to be run are placed in the m_tasks queue
+   */
+  std::promise<void> m_raoPromise;
+  std::future<void> m_raoFuture;
 };
 
 } //end namespace daemon
diff --git a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp
index b4e4826cf1cfe775e2d1c3596933b9f501acdff8..d6c809716d0fa3e1e9d8a841a81bfffe0466fdd3 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp
@@ -163,7 +163,7 @@ namespace unitTests
     FakeSingleTapeReadThread tapeRead(drive, mc, gsr, volume, cap, lc);
     tapeserver::daemon::RecallTaskInjector rti(mm, tapeRead, diskWrite, trm, maxNbJobsInjectedAtOnce, blockSize, lc);
 
-    ASSERT_EQ(true, rti.synchronousInjection());
+    ASSERT_EQ(true, rti.synchronousFetch());
     ASSERT_EQ(maxNbJobsInjectedAtOnce+1, diskWrite.m_tasks.size());
     ASSERT_EQ(maxNbJobsInjectedAtOnce+1, tapeRead.m_tasks.size());
 
@@ -224,7 +224,7 @@ namespace unitTests
 
     tapeserver::daemon::RecallTaskInjector rti(mm, tapeRead, diskWrite, trm, 6, blockSize, lc);
 
-    ASSERT_FALSE(rti.synchronousInjection());
+    ASSERT_FALSE(rti.synchronousFetch());
     ASSERT_EQ(0U, diskWrite.m_tasks.size());
     ASSERT_EQ(0U, tapeRead.m_tasks.size());
     ASSERT_EQ(1, trm.getJobs);
diff --git a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp
index de61a016394f04a503258beca7a541268a668778..f17a567ef1099dbb071087dfb53f3e77223f2c12 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.cpp
@@ -36,13 +36,15 @@ castor::tape::tapeserver::daemon::TapeReadSingleThread::TapeReadSingleThread(
   cta::log::LogContext&  lc,
   RecallReportPacker &rrp,
   const bool useLbp,
+  const bool useRAO,
   const std::string & externalEncryptionKeyScript) :
   TapeSingleThreadInterface<TapeReadTask>(drive, mc, initialProcess, volInfo,
     capUtils, lc, externalEncryptionKeyScript),
   m_maxFilesRequest(maxFilesRequest),
   m_watchdog(watchdog),
   m_rrp(rrp),
-  m_useLbp(useLbp) {}
+  m_useLbp(useLbp),
+  m_useRAO(useRAO) {}
 
 //------------------------------------------------------------------------------
 //TapeCleaning::~TapeCleaning()
@@ -154,13 +156,13 @@ castor::tape::tapeserver::daemon::TapeReadSingleThread::popAndRequestMoreJobs(){
     vrp = m_tasks.popGetSize();
   // If we just passed (down) the half full limit, ask for more
   // (the remaining value is after pop)
-  if(vrp.remaining + 1 == m_maxFilesRequest/2) {
-    // This is not a last call
-    m_taskInjector->requestInjection(false);
-  } else if (0 == vrp.remaining) {
+  if(0 == vrp.remaining) {
     // This is a last call: if the task injector comes up empty on this
     // one, he'll call it the end.
     m_taskInjector->requestInjection(true);
+  } else if (vrp.remaining + 1 == m_maxFilesRequest/2) {
+    // This is not a last call
+    m_taskInjector->requestInjection(false);
   }
   return vrp.value;
 }
@@ -265,6 +267,10 @@ void castor::tape::tapeserver::daemon::TapeReadSingleThread::run() {
         m_logContext.log(cta::log::ERR, "Drive encryption could not be enabled for this mount.");
         throw;
       }
+      if (m_useRAO) {
+        /* Give the RecallTaskInjector access to the drive to perform RAO query */
+        m_taskInjector->setPromise();
+      }
       // Then we have to initialise the tape read session
       currentErrorToCount = "Error_tapesCheckLabelBeforeReading";
       std::unique_ptr<castor::tape::tapeFile::ReadSession> rs(openReadSession());
diff --git a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp
index 4e0bf007add867320e08a5277f177e62310cbdcc..f686faa39c849380e6c3a2c97f937255717d94f3 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/TapeReadSingleThread.hpp
@@ -67,16 +67,17 @@ public:
           cta::log::LogContext & lc,
           RecallReportPacker &rrp,
           const bool useLbp,
+          const bool useRAO,
           const std::string & externalEncryptionKeyScript);
    
-   /**
-    * Set the task injector. Has to be done that way (and not in the constructor)
-    *  because there is a dependency 
-    * @param ti the task injector 
-    */
-   void setTaskInjector(RecallTaskInjector * ti) { 
-     m_taskInjector = ti; 
-   }
+  /**
+   * Set the task injector. Has to be done that way (and not in the constructor)
+   *  because there is a dependency
+   * @param ti the task injector
+   */
+  void setTaskInjector(RecallTaskInjector * ti) {
+    m_taskInjector = ti;
+  }
 
 private:  
   
@@ -146,6 +147,11 @@ private:
    */
   const bool m_useLbp;
 
+  /**
+   * The boolean variable describing to use on not to use Recommended
+   * Access Order
+   */
+  bool m_useRAO;
   /// Helper virtual function to access the watchdog from parent class
   virtual void countTapeLogError(const std::string & error) { 
     m_watchdog.addToErrorCount(error);
diff --git a/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp b/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp
index d63297d8cf3e2728741f557f60545bcce1f4ee54..b966eae4af742c0c7286be4848d51a89ba12a23a 100644
--- a/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp
+++ b/tapeserver/castor/tape/tapeserver/daemon/TapeSingleThreadInterface.hpp
@@ -287,6 +287,10 @@ public:
     m_stats.waitInstructionsTime = secs; 
   }
 
+  virtual castor::tape::tapeserver::drive::DriveInterface* getDriveReference() {
+    return &m_drive;
+  }
+
   /**
    * Constructor
    * @param drive An interface to manipulate the drive to manipulate the tape
diff --git a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp
index 73950fbc4354ecf76c8c52cd6256465d3b113703..4937477d940d778fc4fb4c6cbb65107abb934a06 100644
--- a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp
+++ b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.cpp
@@ -31,6 +31,10 @@
 
 #include <errno.h>
 
+#include <string>
+#include <map>
+#include <list>
+
 namespace castor {
 namespace tape {
 namespace tapeserver {
@@ -750,6 +754,117 @@ bool drive::DriveMHVTL::isEncryptionCapEnabled() {
   return false;
 }
 
+SCSI::Structures::RAO::udsLimitsPage_t drive::DriveGeneric::getLimitUDS() {
+    SCSI::Structures::LinuxSGIO_t sgh;
+    SCSI::Structures::RAO::recieveRAO_t cdb;
+    SCSI::Structures::senseData_t<127> senseBuff;
+    unsigned char dataBuff[sizeof(SCSI::Structures::RAO::udsLimitsPage_t)];
+
+    cdb.serviceAction = 0x1d;
+    cdb.udsLimits = 1;
+    SCSI::Structures::setU32(cdb.allocationLength, SCSI::modeRAO::DEFAULT_RRAO_ALLOCATION);
+    
+    sgh.setCDB(&cdb);
+    sgh.setSenseBuffer(&senseBuff);
+    sgh.setDataBuffer(&dataBuff);
+    sgh.dxfer_direction = SG_DXFER_FROM_DEV;
+
+    /* Manage both system error and SCSI errors. */
+    cta::exception::Errnum::throwOnMinusOne(
+    m_sysWrapper.ioctl(this->m_tapeFD, SG_IO, &sgh),
+            "Failed SG_IO ioctl in DriveGeneric::getLimitUDS");
+    SCSI::ExceptionLauncher(sgh, "SCSI error in DriveGeneric::getLimitUDS");
+
+    SCSI::Structures::RAO::udsLimitsPage_t & limits =
+            *(SCSI::Structures::RAO::udsLimitsPage_t *) dataBuff;        
+   
+    return limits;
+}
+
+void drive::DriveGeneric::generateRAO(std::list<SCSI::Structures::RAO::blockLims> &files,
+                                      int maxSupported) {
+    SCSI::Structures::LinuxSGIO_t sgh;
+    SCSI::Structures::RAO::generateRAO_t cdb;
+    SCSI::Structures::senseData_t<127> senseBuff;
+
+    int udSize = std::min((int) files.size(), maxSupported);
+    
+    std::unique_ptr<SCSI::Structures::RAO::udsDescriptor[]>  ud (new SCSI::Structures::RAO::udsDescriptor[udSize]());
+    
+    auto it = files.begin();
+    for (int i = 0; i < udSize; ++i) {
+      strncpy((char*)ud.get()[i].udsName, (char*)it->fseq, 10);
+      SCSI::Structures::setU64(ud.get()[i].beginLogicalObjID, it->begin);
+      SCSI::Structures::setU64(ud.get()[i].endLogicalObjID, it->end);
+      ++it;
+    }
+
+    SCSI::Structures::RAO::generateRAOParams_t params;
+    int real_params_len = sizeof(params) - (2000 - udSize) *
+                          sizeof(SCSI::Structures::RAO::udsDescriptor);
+
+    cdb.serviceAction = 0x1d;
+
+    SCSI::Structures::setU32(cdb.paramsListLength, real_params_len);
+    SCSI::Structures::setU32(params.additionalData, udSize * sizeof(*(ud.get())));
+    memcpy(&params.userDataSegmentDescriptors, ud.get(), udSize * sizeof(*(ud.get())));
+    
+    sgh.setCDB(&cdb);
+    sgh.setSenseBuffer(&senseBuff);
+    sgh.setDataBuffer(&params);
+    sgh.dxfer_direction = SG_DXFER_TO_DEV;
+
+    /* Manage both system error and SCSI errors. */
+    cta::exception::Errnum::throwOnMinusOne(
+    m_sysWrapper.ioctl(this->m_tapeFD, SG_IO, &sgh),
+            "Failed SG_IO ioctl in DriveGeneric::requestRAO");
+    SCSI::ExceptionLauncher(sgh, "SCSI error in DriveGeneric::requestRAO");
+}
+
+void drive::DriveGeneric::receiveRAO(std::list<SCSI::Structures::RAO::blockLims> &files,
+                                     int offset, int allocationLength) {
+    SCSI::Structures::LinuxSGIO_t sgh;
+    SCSI::Structures::RAO::recieveRAO_t cdb;
+    SCSI::Structures::senseData_t<255> senseBuff;
+    unsigned char dataBuff[sizeof(SCSI::Structures::RAO::raoList)];
+
+    cdb.udsLimits = 0;
+    cdb.serviceAction = 0x1d;
+
+    SCSI::Structures::setU32(cdb.allocationLength, allocationLength);
+    SCSI::Structures::setU32(cdb.raoListOffset, offset);
+
+    sgh.setCDB(&cdb);
+    sgh.setSenseBuffer(&senseBuff);
+    sgh.setDataBuffer(&dataBuff);
+    sgh.dxfer_direction = SG_DXFER_FROM_DEV;
+
+    /* Manage both system error and SCSI errors. */
+    cta::exception::Errnum::throwOnMinusOne(
+    m_sysWrapper.ioctl(this->m_tapeFD, SG_IO, &sgh),
+            "Failed SG_IO ioctl in DriveGeneric::getRAO");
+    SCSI::ExceptionLauncher(sgh, "SCSI error in DriveGeneric::getRAO");
+
+    SCSI::Structures::RAO::raoList & params =
+            *(SCSI::Structures::RAO::raoList *) dataBuff;
+
+    uint32_t desc_list_len = SCSI::Structures::toU32(params.raoDescriptorListLength);
+    for (uint32_t i = 0;i < desc_list_len / sizeof(SCSI::Structures::RAO::udsDescriptor);++i) {
+        SCSI::Structures::RAO::blockLims bl;
+        strncpy((char*)bl.fseq, (char*)params.udsDescriptors[i].udsName, 10);
+        bl.begin = SCSI::Structures::toU64(params.udsDescriptors[i].beginLogicalObjID);
+        bl.end = SCSI::Structures::toU64(params.udsDescriptors[i].endLogicalObjID);
+        files.emplace_back(bl);
+    }
+}
+
+void drive::DriveGeneric::queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files,
+                                   int maxSupported) {
+    generateRAO(files, maxSupported);
+    files.clear();
+    receiveRAO(files, 0, SCSI::modeRAO::DEFAULT_RRAO_ALLOCATION);
+}
+
 /**
  * Function that checks if a tape is blank (contains no records) 
  * @return true if tape is blank, false otherwise
diff --git a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp
index 7031589feafaac9555a072631cee0f59c71d962e..333f74e918c617f430e485927db4575e536be85d 100644
--- a/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp
+++ b/tapeserver/castor/tape/tapeserver/drive/DriveGeneric.hpp
@@ -452,6 +452,20 @@ namespace drive {
      */
     virtual bool isEncryptionCapEnabled();
     
+    /**
+     * Query the drive for the maximum number and size of User Data Segments (UDS)
+     * @return udsLimitsPage_t class. A pair of the above mentioned parameters
+     */
+    virtual SCSI::Structures::RAO::udsLimitsPage_t getLimitUDS();
+
+    /**
+     * Query the drive for the Recommended Access Order (RAO)
+     * for a series of files
+     * @param filename The name of the file containing the sequential order of
+     * a list of files [line format: ID:BLOCK_START:BLOCK_END]
+     */
+    virtual void queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported);
+
   protected:
     SCSI::DeviceInfo m_SCSIInfo;
     int m_tapeFD; 
@@ -485,6 +499,23 @@ namespace drive {
     virtual void setLogicalBlockProtection(const unsigned char method,
       unsigned char methodLength, const bool enableLPBforRead, 
       const bool enableLBBforWrite);
+
+    /**
+     * Send to the drive the command to generate the Recommended Access Order for
+     * a series of files
+     * @param blocks A mapping between a string identifier referring the file ID
+     * and a pair of block limits
+     * @param maxSupported The maximum number of UDS supported - obtained by getLimitUDS()
+     */
+    virtual void generateRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported);
+    
+    /**
+     * Receive the Recommended Access Order
+     * @param offset 
+     * @param allocationLength
+     */
+    virtual void receiveRAO(std::list<SCSI::Structures::RAO::blockLims> &files,
+                            int offset, int allocationLength);
   };
 
   class DriveT10000 : public DriveGeneric {
diff --git a/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp b/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp
index bd14ff0699150cdd83ac3c2015a7e0f178d1b259..eebc9984cf14c2595536ece31dfd1154e2c50a25 100644
--- a/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp
+++ b/tapeserver/castor/tape/tapeserver/drive/DriveInterface.hpp
@@ -33,6 +33,8 @@
 #include "common/exception/Exception.hpp"
 #include "common/exception/TimeOut.hpp"
 
+#include <list>
+
 /**
  * Class wrapping the tape server. Has to be templated (and hence fully in .hh)
  * to allow unit testing against system wrapper.
@@ -230,6 +232,9 @@ namespace drive {
     virtual lbpToUse getLbpToUse() = 0;
     virtual bool hasTapeInPlace() = 0;
     
+    virtual SCSI::Structures::RAO::udsLimitsPage_t getLimitUDS() = 0;
+    virtual void queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported) = 0;
+
     /**
      * The configuration of the tape drive as parsed from the TPCONFIG file.
      */
diff --git a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp
index 86faa33e297bfec67641ac696d5747a3a468bd88..0990bacb6b3a9264de9471619612408e32348e54 100644
--- a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp
+++ b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.cpp
@@ -22,6 +22,7 @@
  *****************************************************************************/
 
 #include "castor/tape/tapeserver/drive/FakeDrive.hpp"
+#include "castor/tape/tapeserver/SCSI/Structures.hpp"
 #include <iostream>
 
 namespace {
@@ -297,6 +298,19 @@ bool castor::tape::tapeserver::drive::FakeDrive::hasTapeInPlace() {
   return true;
 }
 
+castor::tape::SCSI::Structures::RAO::udsLimitsPage_t
+    castor::tape::tapeserver::drive::FakeDrive::getLimitUDS() {
+  castor::tape::SCSI::Structures::RAO::udsLimitsPage_t lims;
+  lims.maxSize = 30000;
+  lims.maxSupported = 30;
+  return lims;
+}
+
+void castor::tape::tapeserver::drive::FakeDrive::queryRAO(
+            std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported)  {
+  files.reverse();
+}
+
 std::map<std::string,uint64_t> castor::tape::tapeserver::drive::FakeDrive::getTapeWriteErrors() {
   std::map<std::string,uint64_t> writeErrorsStats;
   writeErrorsStats["mountTotalCorrectedWriteErrors"] = 5;
diff --git a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp
index 12575f5a24e3046deee5471c5d81ffa4760724ad..62e79b68395f202802486036341b3ee4625a1011 100644
--- a/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp
+++ b/tapeserver/castor/tape/tapeserver/drive/FakeDrive.hpp
@@ -109,6 +109,8 @@ namespace drive {
     virtual bool isTapeBlank();
     virtual lbpToUse getLbpToUse();
     virtual bool hasTapeInPlace();
+    virtual castor::tape::SCSI::Structures::RAO::udsLimitsPage_t getLimitUDS();
+    virtual void queryRAO(std::list<SCSI::Structures::RAO::blockLims> &files, int maxSupported);
   };
   
 }}}}
diff --git a/tapeserver/castor/tape/tapeserver/file/BasicReadWriteTest.cpp b/tapeserver/castor/tape/tapeserver/file/BasicReadWriteTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..97a1c3fc8a4574821acef2afdf5655f17c199218
--- /dev/null
+++ b/tapeserver/castor/tape/tapeserver/file/BasicReadWriteTest.cpp
@@ -0,0 +1,284 @@
+/******************************************************************************
+ *
+ * This file is part of the Castor project.
+ * See http://castor.web.cern.ch/castor
+ *
+ * Copyright (C) 2017  CERN
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * 
+ *
+ * @author Castor Dev team, castor-dev@cern.ch
+ *****************************************************************************/
+
+/**
+ * Test main program. For development use.
+ */
+
+#include "castor/tape/tapeserver/SCSI/Device.hpp"
+#include "castor/tape/tapeserver/system/Wrapper.hpp"
+#include "castor/tape/tapeserver/drive/DriveInterface.hpp"
+#include "File.hpp"
+#include "../daemon/VolumeInfo.hpp"
+#include "scheduler/ArchiveJob.hpp"
+#include <iostream>
+#include <assert.h>
+#include <memory>
+#include <string>
+#include <list>
+
+char gen_random() {
+    static const char alphanum[] =
+        "123456789"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklmnopqrstuvwxyz";
+
+    return alphanum[rand() % (sizeof(alphanum) - 1)];
+}
+
+enum {
+    BLOCK_TEST,
+    FILE_TEST,
+    RAO_TEST
+};
+
+int test = RAO_TEST;
+
+class BasicRetrieveJob: public cta::RetrieveJob {
+  public:
+    BasicRetrieveJob() : cta::RetrieveJob(*((cta::RetrieveMount *)NULL),
+    cta::common::dataStructures::RetrieveRequest(), 
+    cta::common::dataStructures::ArchiveFile(), 1,
+    cta::PositioningMethod::ByBlock) {}
+  };
+
+class BasicArchiveJob: public cta::ArchiveJob {
+public:
+  BasicArchiveJob(): cta::ArchiveJob(*((cta::ArchiveMount *)NULL), 
+      *((cta::catalogue::Catalogue *)NULL), cta::common::dataStructures::ArchiveFile(), 
+      "", cta::common::dataStructures::TapeFile()) {
+  }
+};
+
+std::vector<std::string> split(std::string to_split, std::string delimiter) {
+  std::vector<std::string> toBeReturned;
+  int pos = 0;
+  while ((pos = to_split.find(delimiter)) != -1) {
+    std::string token = to_split.substr(0, pos);
+    toBeReturned.push_back(token);
+    to_split.erase(0, pos + delimiter.length());
+  }
+  toBeReturned.push_back(to_split);
+  return toBeReturned;
+}
+
+int main (int argc, char *argv[])
+{
+  int fail = 0;
+  castor::tape::System::realWrapper sWrapper;
+  castor::tape::SCSI::DeviceVector dl(sWrapper);
+  for(castor::tape::SCSI::DeviceVector::iterator i = dl.begin();
+          i != dl.end(); i++) {
+    castor::tape::SCSI::DeviceInfo & dev = (*i);
+    std::cout << std::endl << "-- SCSI device: " 
+              << dev.sg_dev << " (" << dev.nst_dev << ")" << std::endl;
+    if (dev.type == castor::tape::SCSI::Types::tape) {
+      try {
+        // Create drive object and open tape device
+        std::unique_ptr<castor::tape::tapeserver::drive::DriveInterface> drive(
+          castor::tape::tapeserver::drive::createDrive(dev, sWrapper));
+     
+        /**
+         * From now we could use generic SCSI request for the drive object.
+         * We should be aware that there might be a problem with tape in the 
+         * drive for example incompatible media installed.
+         */
+        
+        try {
+          /**
+           * Gets generic device info for the drive object.
+           */
+          castor::tape::tapeserver::drive::deviceInfo devInfo;  
+          devInfo = drive->getDeviceInfo();
+          std::cout << "-- INFO --------------------------------------" << std::endl             
+                    << "  devInfo.vendor               : '"  << devInfo.vendor << "'" << std::endl
+                    << "  devInfo.product              : '" << devInfo.product << "'" << std::endl
+                    << "  devInfo.productRevisionLevel : '" << devInfo.productRevisionLevel << "'" << std::endl
+                    << "  devInfo.serialNumber         : '" << devInfo.serialNumber << "'" << std::endl
+                    << "----------------------------------------------" << std::endl;
+        } catch (std::exception & e) {
+          fail = 1;
+          std::string temp = e.what();
+          std::cout << "----------------------------------------------" << std::endl
+                    << temp 
+                    << "-- INFO --------------------------------------" << std::endl;
+          continue;
+        }  
+        
+        try {
+          /**
+           * Checks if the drive ready to use the tape installed loaded into it.
+           */
+          drive->waitUntilReady(5); 
+        } catch(cta::exception::Exception &ne) {
+          std::string temp=ne.getMessage().str();
+          fail = 1;
+          std::cout << "----------------------------------------------" << std::endl
+                    << temp  << std::endl
+                    << "----------------------------------------------" << std::endl;
+          continue;  
+        }
+
+        drive->enableCRC32CLogicalBlockProtectionReadWrite();
+
+        try {
+
+            if (test == BLOCK_TEST) {
+                const size_t count = 10;
+                unsigned char data[count];          
+                memset(data, 0, count);
+
+                std::cout << "Rewinding..." << std::endl;
+                drive->rewind(); // go back to the beginning of tape after Victor's positioning
+
+                memset(data, 'a', count-1);
+                std::cout << "Writing 1st block (9 a's)..." << std::endl;
+                drive->writeBlock((void *)data, count); // write 9 a's + string term
+
+                std::cout << "Writing EOD (2 filemarks)..." << std::endl;
+                drive->writeSyncFileMarks(2); // EOD and flush  
+
+                std::cout << "Rewinding..." << std::endl;
+                drive->rewind(); // go back to the beginning of tape
+
+                std::cout << "Reading back 1st block 9 a's)..." << std::endl;                    
+                memset(data, 0, count);
+                drive->readBlock((void *)data, count); // read 9 a's + string term
+
+                std::cout << "Rewinding..." << std::endl;
+                drive->rewind(); // go back to the beginning of tape
+
+            }
+            else if (test == FILE_TEST) {
+                drive->rewind();
+
+                castor::tape::tapeFile::LabelSession *ls;
+                std::string label = "TW8510";
+                ls = new castor::tape::tapeFile::LabelSession(*drive, label, true);
+                delete ls;
+
+                castor::tape::tapeserver::daemon::VolumeInfo m_volInfo;
+                m_volInfo.vid = label;
+                m_volInfo.nbFiles = 0;
+                m_volInfo.mountType = cta::common::dataStructures::MountType::Archive;
+
+                castor::tape::tapeFile::WriteSession *ws;
+                ws = new castor::tape::tapeFile::WriteSession(*drive, m_volInfo, 0, true, true);
+
+                uint32_t block_size = 262144;
+                uint32_t no_blocks = 100;
+
+                // Write test files ( ! add stop condition)
+                int j = 1;
+                while (true) {
+                  BasicArchiveJob fileToMigrate;
+                  fileToMigrate.archiveFile.fileSize = block_size * 100;
+                  fileToMigrate.archiveFile.archiveFileID = j;
+                  fileToMigrate.tapeFile.fSeq = j;
+                  std::unique_ptr<castor::tape::tapeFile::WriteFile> wf;
+                  wf.reset(new castor::tape::tapeFile::WriteFile(ws, fileToMigrate, block_size));
+
+                  std::string testString = "";
+                  for (uint32_t i = 0; i < block_size - 5; i++)
+                      testString += gen_random();
+                  for (uint32_t k = 0; k < no_blocks; k++) {
+                      wf->write(testString.c_str(),testString.size());  
+                  }
+
+                  wf->close();
+                  j++;
+                }
+
+                drive->rewind();
+
+                // Now read a random file
+                castor::tape::tapeFile::ReadSession *rs;
+                rs = new castor::tape::tapeFile::ReadSession(*drive, m_volInfo, true);
+
+                BasicRetrieveJob fileToRecall;
+                fileToRecall.selectedCopyNb=1;
+                fileToRecall.archiveFile.tapeFiles[1];
+                fileToRecall.selectedTapeFile().blockId = 110; // here should be the block ID of HDR1
+                fileToRecall.selectedTapeFile().fSeq = 2;
+                fileToRecall.retrieveRequest.archiveFileID = 2;
+                fileToRecall.positioningMethod = cta::PositioningMethod::ByBlock;
+
+                castor::tape::tapeFile::ReadFile rf(rs, fileToRecall);
+                size_t bs = rf.getBlockSize();
+                char *data = new char[bs+1];
+                j = 0;
+                while(j < 100) {
+                    rf.read(data, bs);
+                    j++;
+                    std::cout << data << std::endl;
+                }
+            }
+            else if (test == RAO_TEST) {
+                if (argc != 2) {
+                    std::cout << "For RAO testing the first parameter should be "
+                            "the file containing the sequential order" << std::endl;
+                }
+                else {
+                    drive->rewind();
+                    
+                    std::list<castor::tape::SCSI::Structures::RAO::blockLims> files;
+                    std::ifstream ns_file_pick(argv[1]);
+                    if (ns_file_pick.is_open()) {
+                      std::string line;
+                      while (getline(ns_file_pick, line)) {
+                        std::vector<std::string> tokens = split(line, ":");
+                        castor::tape::SCSI::Structures::RAO::blockLims lims;
+                        std::cout << tokens[0].c_str() << std::endl;
+                        tokens[0] += '\0';
+                        strcpy((char*)lims.fseq, tokens[0].c_str());
+                        lims.begin = std::stoi(tokens[1]);
+                        lims.end = std::stoi(tokens[2]);                        
+                        files.push_back(lims);
+                      }
+                    }
+                    else {
+                      throw -1;
+                    }
+                    castor::tape::SCSI::Structures::RAO::udsLimitsPage_t limits = drive->getLimitUDS();
+                    drive->queryRAO(files, limits.maxSupported);
+                }
+            }
+
+        } catch (std::exception & e) {
+          fail = 1;
+          std::cout << "-- EXCEPTION ---------------------------------" << std::endl
+                    << e.what() << std::endl
+                    << "----------------------------------------------" << std::endl;
+        }
+      } catch(cta::exception::Exception &ne) {
+        std::string temp=ne.getMessage().str();
+        fail = 1;
+        std::cout << "----------------------------------------------" << std::endl
+                  << temp  << std::endl
+                  << "-- object ------------------------------------" << std::endl;
+        break;  
+      } 
+    }  
+  }
+  return fail;
+}
diff --git a/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt b/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt
index 381826cc29ff1361860980997691276b9b43bec8..3142c622b0d34429429cf4b0dab1577795214002 100644
--- a/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt
+++ b/tapeserver/castor/tape/tapeserver/file/CMakeLists.txt
@@ -40,6 +40,26 @@ if(CMAKE_COMPILER_IS_GNUCC)
   endif(GCC_VERSION_GE_4_8_0)
 endif(CMAKE_COMPILER_IS_GNUCC)
 
+if(CMAKE_COMPILER_IS_GNUCC)
+  if(GCC_VERSION_GE_4_8_0)
+    set_property(SOURCE BasicReadWriteTest.cpp
+      PROPERTY COMPILE_FLAGS " -Wno-unused-local-typedefs")
+  endif(GCC_VERSION_GE_4_8_0)
+endif(CMAKE_COMPILER_IS_GNUCC)
+
+add_executable(BasicReadWriteTest
+  BasicReadWriteTest.cpp)
+
+target_link_libraries(BasicReadWriteTest
+  TapeDrive
+  ctamediachanger
+  SCSI
+  System
+  ctacommon
+  ctaTapeServerDaemon
+  gtest
+  pthread)
+
 add_library(File
   ${TAPESERVER_FILE_LIBRARY_SRCS})
 target_link_libraries (File XrdCl cryptopp radosstriper)