diff --git a/cmake_targets/CMakeLists.txt b/cmake_targets/CMakeLists.txt
index 3dbb61b5aec615544e728523304a10c0b48474fb..c5ca88d5b84808c945142e9122dccb3a6f486775 100644
--- a/cmake_targets/CMakeLists.txt
+++ b/cmake_targets/CMakeLists.txt
@@ -241,6 +241,7 @@ add_boolean_option(UE_AUTOTEST_TRACE   False "Activate UE autotest specific logs
 add_boolean_option(UE_DEBUG_TRACE      False "Activate UE debug trace")
 add_boolean_option(UE_TIMING_TRACE     False "Activate UE timing trace")
 add_boolean_option(DISABLE_LOG_X       False "Deactivate all LOG_* macros")
+add_boolean_option(USRP_REC_PLAY       False "Enable USRP record playback mode")
 
 add_boolean_option(DEBUG_CONSOLE False "makes debugging easier, disables stdout/stderr buffering")
 
diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai
index d3a13c41c93cab31eda220e1e7c5d2240b709322..d625dfa159e3dbc768115f2eba37913fb2d08ff9 100755
--- a/cmake_targets/build_oai
+++ b/cmake_targets/build_oai
@@ -66,6 +66,7 @@ UE_AUTOTEST_TRACE="False"
 UE_DEBUG_TRACE="False"
 UE_TIMING_TRACE="False"
 DISABLE_LOG_X="False"
+USRP_REC_PLAY="False"
 BUILD_ECLIPSE=0
 trap handle_ctrl_c INT
 
@@ -158,6 +159,8 @@ Options
    Disable all LOG_* macros
 --build-eclipse
    Build eclipse project files. Paths are auto corrected by fixprj.sh
+--usrp-recplay
+   Build for I/Q record-playback modes
 Usage (first build):
  oaisim (eNB + UE): ./build_oai -I  --oaisim -x --install-system-files
  Eurecom EXMIMO + COTS UE : ./build_oai -I  --eNB -x --install-system-files
@@ -350,6 +353,10 @@ function main() {
             BUILD_TELNETSRV=1
             echo_info "Build embedded telnet server"
             shift ;;			
+        --usrp-recplay)
+            USRP_REC_PLAY="True"
+            echo_info "Enabling USRP record playback mode"
+            shift 1;;
         -h | --help)
             print_help
             exit 1;;
@@ -540,6 +547,7 @@ function main() {
     echo "set (UE_DEBUG_TRACE $UE_DEBUG_TRACE)"        >>  $cmake_file
     echo "set (UE_TIMING_TRACE $UE_TIMING_TRACE)"        >>  $cmake_file
     echo "set (DISABLE_LOG_X $DISABLE_LOG_X)"        >>  $cmake_file
+    echo "set (USRP_REC_PLAY $USRP_REC_PLAY)"        >>  $cmake_file
     if [ "$UE" = 1 -a "$NOS1" = "0" ] ; then
      echo_info "Compiling UE S1 build : enabling Linux and NETLINK"
      echo "set (LINUX True )"              >>  $cmake_file
diff --git a/openair1/PHY/LTE_TRANSPORT/pucch.c b/openair1/PHY/LTE_TRANSPORT/pucch.c
index 61d2cc0e9b3a841e904e45452fe511df20cab387..c8da27bcb8cf3d60caf22f54e6c38f68750a3ffd 100644
--- a/openair1/PHY/LTE_TRANSPORT/pucch.c
+++ b/openair1/PHY/LTE_TRANSPORT/pucch.c
@@ -1871,6 +1871,23 @@ uint32_t rx_pucch(PHY_VARS_eNB *eNB,
 	eNB->pucch1ab_stats_cnt[j][i]=0;
       }
     }
+#if defined(USRP_REC_PLAY)
+    // It's probably bad to do this statically only once.
+    // Looks like the above is incomplete.
+    // Such reset needs to be done once a UE PHY structure is being used/re-used
+    // Don't know if this is ever possible in current architecture
+    for (i=0;i<10240;i++) {
+      for (j=0;j<NUMBER_OF_UE_MAX;j++) {
+	eNB->pucch1_stats[j][i]=0;
+	eNB->pucch1_stats_thres[j][i]=0;
+      }
+    }
+    for (i=0;i<20480;i++) {
+      for (j=0;j<NUMBER_OF_UE_MAX;j++) {
+	eNB->pucch1ab_stats[j][i]=0;
+      }
+    }
+#endif    
     first_call=0;
   }
 
@@ -2152,8 +2169,9 @@ uint32_t rx_pucch(PHY_VARS_eNB *eNB,
     } //phase
 
 //    stat_max *= nsymb;  // normalize to energy per symbol
-//    stat_max /= (frame_parms->N_RB_UL*12); // 
+//    stat_max /= (frame_parms->N_RB_UL*12); //
     stat_max /= (nsymb*12);
+    
 #ifdef DEBUG_PUCCH_RX
     printf("[eNB] PUCCH: stat %d, stat_max %d, phase_max %d\n", stat,stat_max,phase_max);
 #endif
@@ -2289,8 +2307,13 @@ uint32_t rx_pucch(PHY_VARS_eNB *eNB,
     stat_im=0;
 
     // Do detection now
+#if defined(USRP_REC_PLAY)
+    // It looks like the value is a bit messy when RF is replayed.
+    // For instance i assume to skip pucch1_thres from the test below.
+    if (sigma2_dB<(dB_fixed(stat_max)))  {//
+#else
     if (sigma2_dB<(dB_fixed(stat_max)-pucch1_thres))  {//
-
+#endif
       chL = (nsymb>>1)-4;
       chest_mag=0;
       cfo =  (frame_parms->Ncp==0) ? &cfo_pucch_np[14*phase_max] : &cfo_pucch_ep[12*phase_max];
@@ -2431,7 +2454,11 @@ uint32_t rx_pucch(PHY_VARS_eNB *eNB,
       if (fmt==pucch_format1b)
         *(1+payload) = (stat_im<0) ? 1 : 2;
     } else { // insufficient energy on PUCCH so NAK
+#if defined(USRP_REC_PLAY)
+      LOG_I(PHY,"PUCCH 1a/b: NAK subframe %d : sigma2_dB %d, stat_max %d, pucch1_thres %d\n",subframe,sigma2_dB,dB_fixed(stat_max),pucch1_thres);
+#else
       LOG_I(PHY,"PUCCH 1a/b: subframe %d : sigma2_dB %d, stat_max %d, pucch1_thres %d\n",subframe,sigma2_dB,dB_fixed(stat_max),pucch1_thres);
+#endif      
       *payload = 4;  // DTX
       ((int16_t*)&eNB->pucch1ab_stats[UE_id][(subframe<<10) + (eNB->pucch1ab_stats_cnt[UE_id][subframe])])[0] = (int16_t)(stat_re);
       ((int16_t*)&eNB->pucch1ab_stats[UE_id][(subframe<<10) + (eNB->pucch1ab_stats_cnt[UE_id][subframe])])[1] = (int16_t)(stat_im);
diff --git a/openair2/LAYER2/MAC/defs.h b/openair2/LAYER2/MAC/defs.h
index 65e090e370ec5660cec4449ddff0c531a6c2249c..43d8167fe604315290e416dbd8190fbfcffae392 100644
--- a/openair2/LAYER2/MAC/defs.h
+++ b/openair2/LAYER2/MAC/defs.h
@@ -128,7 +128,11 @@
 /*!\brief Maximum number od control elemenets */
 #define MAX_NUM_CE 5
 /*!\brief Maximum number of random access process */
+#if defined(USRP_REC_PLAY)
+#define NB_RA_PROC_MAX 1
+#else
 #define NB_RA_PROC_MAX 4
+#endif
 /*!\brief size of buffer status report table */
 #define BSR_TABLE_SIZE 64
 /*!\brief The power headroom reporting range is from -23 ...+40 dB and beyond, with step 1 */
diff --git a/openair2/LAYER2/MAC/eNB_scheduler_RA.c b/openair2/LAYER2/MAC/eNB_scheduler_RA.c
index 939f35f2ebd405d0624729efbe87012a774b12cd..327f5faafe16182cceb24ed5736661b47b0f186b 100644
--- a/openair2/LAYER2/MAC/eNB_scheduler_RA.c
+++ b/openair2/LAYER2/MAC/eNB_scheduler_RA.c
@@ -1579,7 +1579,25 @@ initiate_ra_proc(module_id_t module_idP,
 	    ra[i].Msg2_subframe = (subframeP + 4) % 10;
 	    /* TODO: find better procedure to allocate RNTI */
 	    do {
+#if defined(USRP_REC_PLAY) // deterministic rnti in usrp record/playback mode
+	        static int drnti[NUMBER_OF_UE_MAX] = { 0xbda7, 0x71da, 0x9c40, 0xc350, 0x2710, 0x4e20, 0x7530, 0x1388, 0x3a98, 0x61a8, 0x88b8, 0xafc8, 0xd6d8, 0x1b58, 0x4268, 0x6978 };
+	        int j = 0;
+		int nb_ue = 0;
+		for (j = 0; j < NUMBER_OF_UE_MAX; j++) {
+		    if (UE_RNTI(module_idP, j) > 0) {
+		        nb_ue++;
+		    } else {
+		        break;
+		    }
+		}
+		if (nb_ue >= NUMBER_OF_UE_MAX) {
+		    printf("No more free RNTI available, increase NUMBER_OF_UE_MAX\n");
+		    abort();
+		}
+		ra[i].rnti = drnti[nb_ue];
+#else	
 		ra[i].rnti = taus();
+#endif
 		loop++;
 	    }
 	    while (loop != 100 &&
diff --git a/openair2/LAYER2/MAC/eNB_scheduler_primitives.c b/openair2/LAYER2/MAC/eNB_scheduler_primitives.c
index 42c50d8063a5450574b0ccc9610e6b234cb22621..8b88bef244078eba236f2d2d4224e39f8e0548ea 100644
--- a/openair2/LAYER2/MAC/eNB_scheduler_primitives.c
+++ b/openair2/LAYER2/MAC/eNB_scheduler_primitives.c
@@ -2057,6 +2057,9 @@ int add_new_ue(module_id_t mod_idP, int cc_idP, rnti_t rntiP, int harq_pidP
 	UE_list->ordered_ULCCids[0][UE_id] = cc_idP;
 	UE_list->num_UEs++;
 	UE_list->active[UE_id] = TRUE;
+#if defined(USRP_REC_PLAY) // not specific to record/playback ?
+	UE_list->UE_template[cc_idP][UE_id].pre_assigned_mcs_ul = 0;
+#endif    
 
 #ifdef Rel14
 	UE_list->UE_template[cc_idP][UE_id].rach_resource_type =
diff --git a/openair2/LAYER2/PDCP_v10.1.0/pdcp_security.c b/openair2/LAYER2/PDCP_v10.1.0/pdcp_security.c
index d395f959819098e9ff7e4db191afeddd1d823b6e..2554eb145ab9de975100795e207efe6276e39978 100644
--- a/openair2/LAYER2/PDCP_v10.1.0/pdcp_security.c
+++ b/openair2/LAYER2/PDCP_v10.1.0/pdcp_security.c
@@ -220,7 +220,7 @@ pdcp_validate_security(
   stream_decrypt(pdcp_pP->cipheringAlgorithm,
                  &decrypt_params,
                  &buffer_decrypted);
-
+#if !defined(USRP_REC_PLAY)
   if (srb_flagP) {
     /* Now check the integrity of the complete PDU */
     decrypt_params.message    = pdcp_pdu_buffer;
@@ -241,7 +241,7 @@ pdcp_validate_security(
       return -1;
     }
   }
-
+#endif
   VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PDCP_VALIDATE_SECURITY, VCD_FUNCTION_OUT);
 
   return 0;
diff --git a/openair2/RRC/LITE/defs.h b/openair2/RRC/LITE/defs.h
index 0811818c722717b6bda8236cc20879cc3140a1d8..9d7f7fdf2d169d4104de8c5ff5662d51aa2bfc85 100644
--- a/openair2/RRC/LITE/defs.h
+++ b/openair2/RRC/LITE/defs.h
@@ -242,7 +242,11 @@ typedef enum HO_STATE_e {
 // #define NUM_MAX_CBA_GROUP 4 // in the platform_constants
 
 /* TS 36.331: RRC-TransactionIdentifier ::= INTEGER (0..3) */
+#if defined(USRP_REC_PLAY)
+#define RRC_TRANSACTION_IDENTIFIER_NUMBER  1
+#else
 #define RRC_TRANSACTION_IDENTIFIER_NUMBER  3
+#endif
 
 typedef struct {
   unsigned short transport_block_size;                  /*!< \brief Minimum PDU size in bytes provided by RLC to MAC layer interface */
diff --git a/openair2/RRC/LITE/rrc_eNB_S1AP.c b/openair2/RRC/LITE/rrc_eNB_S1AP.c
index 55adb217facd8ecaacde5e83e08fccd6bb5091ec..24ee3c06dafa7a46b8920697db8969b87a806f55 100644
--- a/openair2/RRC/LITE/rrc_eNB_S1AP.c
+++ b/openair2/RRC/LITE/rrc_eNB_S1AP.c
@@ -390,8 +390,10 @@ rrc_pdcp_config_security(
                      ue_context_pP->ue_context.kenb,
                      &kRRCint);
 
+#if !defined(USRP_REC_PLAY)
 #define DEBUG_SECURITY 1
-
+#endif
+  
 #if defined (DEBUG_SECURITY)
 #undef msg
 #define msg printf
diff --git a/targets/ARCH/COMMON/common_lib.h b/targets/ARCH/COMMON/common_lib.h
index 6214c81cc693b86c3de7235d4681c11bb22f2be7..2dc1650bfd719208ef5e182b0b79f76e5f12ff55 100644
--- a/targets/ARCH/COMMON/common_lib.h
+++ b/targets/ARCH/COMMON/common_lib.h
@@ -207,6 +207,15 @@ typedef struct {
   int iq_rxrescale;
   //! Configuration file for LMS7002M
   char *configFilename;
+#if defined(USRP_REC_PLAY)
+  unsigned short sf_mode;           // 1=record, 2=replay
+  char           sf_filename[1024]; // subframes file path
+  unsigned int   sf_max;            // max number of recorded subframes
+  unsigned int   sf_loops;          // number of loops in replay mode
+  unsigned int   sf_read_delay;     // read delay in replay mode
+  unsigned int   sf_write_delay;    // write delay in replay mode
+  unsigned int   eth_mtu;           // ethernet MTU
+#endif  
 } openair0_config_t;
 
 /*! \brief RF mapping */ 
diff --git a/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp b/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp
index 30faf8d2b7aefc890973ee59da70274bd203dc0a..8b62a8e69855a36410e191c6832aa91009170906 100644
--- a/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp
+++ b/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.cpp
@@ -270,11 +270,53 @@ static int sync_to_gps(openair0_device *device)
     return EXIT_SUCCESS;
 }
 
+#if defined(USRP_REC_PLAY)
+#include "usrp_lib.h"
+static FILE    *pFile = NULL;
+int             mmapfd = 0;
+struct stat     sb;
+iqrec_t        *ms_sample = NULL;                      // memory for all subframes
+unsigned int    nb_samples = 0;
+unsigned int    cur_samples = 0;
+int64_t         wrap_count = 0;
+int64_t         wrap_ts = 0;
+unsigned int    u_sf_mode = 0;                         // 1=record, 2=replay
+unsigned int    u_sf_record = 0;                       // record mode
+unsigned int    u_sf_replay = 0;                       // replay mode
+char            u_sf_filename[1024];                   // subframes file path
+unsigned int    u_sf_max = DEF_NB_SF;                  // max number of recorded subframes
+unsigned int    u_sf_loops = DEF_SF_NB_LOOP;           // number of loops in replay mode
+unsigned int    u_sf_read_delay = DEF_SF_DELAY_READ;   // read delay in replay mode
+unsigned int    u_sf_write_delay = DEF_SF_DELAY_WRITE; // write delay in replay mode
+char           *tmp_filename[1];                       // use an array of pointer (libconfig does not seems to work with char array yet)
+
+char config_opt_sf_file[] = CONFIG_OPT_SF_FILE;
+char config_def_sf_file[] = DEF_SF_FILE;
+char config_hlp_sf_file[] = CONFIG_HLP_SF_FILE;
+char config_opt_sf_rec[] = CONFIG_OPT_SF_REC;
+char config_hlp_sf_rec[] = CONFIG_HLP_SF_REC;
+char config_opt_sf_rep[] = CONFIG_OPT_SF_REP;
+char config_hlp_sf_rep[] = CONFIG_HLP_SF_REP;
+char config_opt_sf_max[] = CONFIG_OPT_SF_MAX;
+char config_hlp_sf_max[] = CONFIG_HLP_SF_MAX;
+char config_opt_sf_loops[] = CONFIG_OPT_SF_LOOPS;
+char config_hlp_sf_loops[] = CONFIG_HLP_SF_LOOPS;
+char config_opt_sf_rdelay[] = CONFIG_OPT_SF_RDELAY;
+char config_hlp_sf_rdelay[] = CONFIG_HLP_SF_RDELAY;
+char config_opt_sf_wdelay[] = CONFIG_OPT_SF_WDELAY;
+char config_hlp_sf_wdelay[] = CONFIG_HLP_SF_WDELAY;
+
+#endif
+
 /*! \brief Called to start the USRP transceiver. Return 0 if OK, < 0 if error
     @param device pointer to the device structure specific to the RF hardware target
 */
 static int trx_usrp_start(openair0_device *device) {
 
+#if defined(USRP_REC_PLAY)
+    if (u_sf_mode != 2) { // not replay mode
+#endif      
+
     usrp_state_t *s = (usrp_state_t*)device->priv;
 
 
@@ -313,12 +355,21 @@ static int trx_usrp_start(openair0_device *device) {
     s->rx_count = 0;
     s->tx_count = 0;
     s->rx_timestamp = 0;
+#if defined(USRP_REC_PLAY)
+    }
+#endif      
     return 0;
 }
 /*! \brief Terminate operation of the USRP transceiver -- free all associated resources
  * \param device the hardware to use
  */
 static void trx_usrp_end(openair0_device *device) {
+#if defined(USRP_REC_PLAY) // For some ugly reason, this can be called several times...
+  static int done = 0;
+  if (done == 1) return;
+  done = 1;
+  if (u_sf_mode != 2) { // not subframes replay
+#endif  
     usrp_state_t *s = (usrp_state_t*)device->priv;
 
     s->rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
@@ -326,7 +377,45 @@ static void trx_usrp_end(openair0_device *device) {
     s->tx_md.end_of_burst = true;
     s->tx_stream->send("", 0, s->tx_md);
     s->tx_md.end_of_burst = false;
-
+#if defined(USRP_REC_PLAY)
+    }
+#endif
+#if defined(USRP_REC_PLAY)
+    if (u_sf_mode == 1) { // subframes store
+      pFile = fopen (u_sf_filename,"wb+");
+      if (pFile == NULL) {
+	std::cerr << "Cannot open " << u_sf_filename << std::endl;
+      } else {
+	unsigned int i = 0;
+	unsigned int modu = 0;
+	if ((modu = nb_samples % 10) != 0) {
+	  nb_samples -= modu; // store entire number of frames
+	}
+	std::cerr << "Writing " << nb_samples << " subframes to " << u_sf_filename << " ..." << std::endl;
+	for (i = 0; i < nb_samples; i++) {
+	  fwrite(ms_sample+i, sizeof(unsigned char), sizeof(iqrec_t), pFile);
+	}
+	fclose (pFile);
+	std::cerr << "File " << u_sf_filename << " closed." << std::endl;
+      }
+    }
+    if (u_sf_mode == 1) { // record
+      if (ms_sample != NULL) {
+	free((void*)ms_sample);
+	ms_sample = NULL;
+      }
+    }
+    if (u_sf_mode == 2) { // replay
+      if (ms_sample != MAP_FAILED) {
+	munmap(ms_sample, sb.st_size);
+	ms_sample = NULL;
+      }
+      if (mmapfd != 0) {
+	close(mmapfd);
+	mmapfd = 0;
+      }
+    }
+#endif    
 }
 
 /*! \brief Called to send samples to the USRP RF target
@@ -339,6 +428,9 @@ static void trx_usrp_end(openair0_device *device) {
 */
 static int trx_usrp_write(openair0_device *device, openair0_timestamp timestamp, void **buff, int nsamps, int cc, int flags) {
   int ret=0;
+#if defined(USRP_REC_PLAY)
+  if (u_sf_mode != 2) { // not replay mode
+#endif    
   usrp_state_t *s = (usrp_state_t*)device->priv;
   
   int nsamps2;  // aligned to upper 32 or 16 byte boundary
@@ -405,6 +497,15 @@ static int trx_usrp_write(openair0_device *device, openair0_timestamp timestamp,
   
   if (ret != nsamps)
     LOG_E(PHY,"[xmit] tx samples %d != %d\n",ret,nsamps);
+#if defined(USRP_REC_PLAY)
+  } else {
+    struct timespec req;
+    req.tv_sec = 0;
+    req.tv_nsec = u_sf_write_delay * 1000;
+    nanosleep(&req, NULL);
+    ret = nsamps;
+  }
+#endif    
 
   return ret;
 }
@@ -421,9 +522,12 @@ static int trx_usrp_write(openair0_device *device, openair0_timestamp timestamp,
  * \returns the number of sample read
 */
 static int trx_usrp_read(openair0_device *device, openair0_timestamp *ptimestamp, void **buff, int nsamps, int cc) {
-    usrp_state_t *s = (usrp_state_t*)device->priv;
-    int samples_received=0,i,j;
-    int nsamps2;  // aligned to upper 32 or 16 byte boundary
+  usrp_state_t *s = (usrp_state_t*)device->priv;
+  int samples_received=0,i,j;
+  int nsamps2;  // aligned to upper 32 or 16 byte boundary
+#if defined(USRP_REC_PLAY)
+  if (u_sf_mode != 2) { // not replay mode
+#endif    
 #if defined(__x86_64) || defined(__i386__)
 #ifdef __AVX2__
     nsamps2 = (nsamps+7)>>3;
@@ -490,7 +594,45 @@ static int trx_usrp_read(openair0_device *device, openair0_timestamp *ptimestamp
     s->rx_count += nsamps;
     s->rx_timestamp = s->rx_md.time_spec.to_ticks(s->sample_rate);
     *ptimestamp = s->rx_timestamp;
-    return samples_received;
+#if defined (USRP_REC_PLAY)
+  }
+#endif    
+#if defined(USRP_REC_PLAY)
+  if (u_sf_mode == 1) { // record mode
+    // Copy subframes to memory (later dump on a file)
+    if (nb_samples < u_sf_max) {
+      (ms_sample+nb_samples)->header = BELL_LABS_IQ_HEADER; 
+      (ms_sample+nb_samples)->ts = *ptimestamp;
+      memcpy((ms_sample+nb_samples)->samples, buff[0], nsamps*4);
+      nb_samples++;
+    }
+  } else if (u_sf_mode == 2) { // replay mode
+    if (cur_samples == nb_samples) {
+      cur_samples = 0;
+      wrap_count++;
+      if (wrap_count == u_sf_loops) {
+	std::cerr << "USRP device terminating subframes replay mode after " << u_sf_loops << " loops." << std::endl;
+	return 0; // should make calling process exit
+      }
+      wrap_ts = wrap_count * (nb_samples * (((int)(device->openair0_cfg[0].sample_rate)) / 1000));
+    }
+    if (cur_samples < nb_samples) {
+      *ptimestamp = (ms_sample[0].ts + (cur_samples * (((int)(device->openair0_cfg[0].sample_rate)) / 1000))) + wrap_ts;
+      if (cur_samples == 0) {
+	std::cerr << "starting subframes file with wrap_count=" << wrap_count << " wrap_ts=" << wrap_ts
+		  << " ts=" << *ptimestamp << std::endl;
+      }
+      memcpy(buff[0], &ms_sample[cur_samples].samples[0], nsamps*4);
+      cur_samples++;
+    }
+    struct timespec req;
+    req.tv_sec = 0;
+    req.tv_nsec = u_sf_read_delay * 1000;
+    nanosleep(&req, NULL);
+    return nsamps;
+  }
+#endif
+  return samples_received;
 }
 
 /*! \brief Compares two variables within precision
@@ -684,12 +826,135 @@ int trx_usrp_reset_stats(openair0_device* device) {
     return(0);
 }
 
+#if defined(USRP_REC_PLAY)
+extern "C" {
+/*! \brief Initializer for USRP record/playback config
+ * \param parameter array description
+ * \returns  0 on success
+ */
+int trx_usrp_recplay_config_init(paramdef_t *usrp_recplay_params) {
+    // --subframes-file
+    memcpy(usrp_recplay_params[0].optname, config_opt_sf_file, strlen(config_opt_sf_file));
+    usrp_recplay_params[0].helpstr = config_hlp_sf_file;
+    usrp_recplay_params[0].paramflags=PARAMFLAG_NOFREE;
+    usrp_recplay_params[0].strptr=(char **)&tmp_filename[0];
+    usrp_recplay_params[0].defstrval = NULL;
+    usrp_recplay_params[0].type=TYPE_STRING;
+    usrp_recplay_params[0].numelt=sizeof(u_sf_filename);
+    // --subframes-record
+    memcpy(usrp_recplay_params[1].optname, config_opt_sf_rec, strlen(config_opt_sf_rec));
+    usrp_recplay_params[1].helpstr = config_hlp_sf_rec;
+    usrp_recplay_params[1].paramflags=PARAMFLAG_BOOL;
+    usrp_recplay_params[1].uptr=&u_sf_record;
+    usrp_recplay_params[1].defuintval=0;
+    usrp_recplay_params[1].type=TYPE_UINT;
+    usrp_recplay_params[1].numelt=0;
+    // --subframes-replay
+    memcpy(usrp_recplay_params[2].optname, config_opt_sf_rep, strlen(config_opt_sf_rep));
+    usrp_recplay_params[2].helpstr = config_hlp_sf_rep;
+    usrp_recplay_params[2].paramflags=PARAMFLAG_BOOL;
+    usrp_recplay_params[2].uptr=&u_sf_replay;
+    usrp_recplay_params[2].defuintval=0;
+    usrp_recplay_params[2].type=TYPE_UINT;
+    usrp_recplay_params[2].numelt=0;
+    // --subframes-max
+    memcpy(usrp_recplay_params[3].optname, config_opt_sf_max, strlen(config_opt_sf_max));
+    usrp_recplay_params[3].helpstr = config_hlp_sf_max;
+    usrp_recplay_params[3].paramflags=0;
+    usrp_recplay_params[3].uptr=&u_sf_max;
+    usrp_recplay_params[3].defuintval=DEF_NB_SF;
+    usrp_recplay_params[3].type=TYPE_UINT;
+    usrp_recplay_params[3].numelt=0;
+    // --subframes-loops
+    memcpy(usrp_recplay_params[4].optname, config_opt_sf_loops, strlen(config_opt_sf_loops));
+    usrp_recplay_params[4].helpstr = config_hlp_sf_loops;
+    usrp_recplay_params[4].paramflags=0;
+    usrp_recplay_params[4].uptr=&u_sf_loops;
+    usrp_recplay_params[4].defuintval=DEF_SF_NB_LOOP;
+    usrp_recplay_params[4].type=TYPE_UINT;
+    usrp_recplay_params[4].numelt=0;
+    // --subframes-read-delay
+    memcpy(usrp_recplay_params[5].optname, config_opt_sf_rdelay, strlen(config_opt_sf_rdelay));
+    usrp_recplay_params[5].helpstr = config_hlp_sf_rdelay;
+    usrp_recplay_params[5].paramflags=0;
+    usrp_recplay_params[5].uptr=&u_sf_read_delay;
+    usrp_recplay_params[5].defuintval=DEF_SF_DELAY_READ;
+    usrp_recplay_params[5].type=TYPE_UINT;
+    usrp_recplay_params[5].numelt=0;
+    // --subframes-write-delay
+    memcpy(usrp_recplay_params[6].optname, config_opt_sf_wdelay, strlen(config_opt_sf_wdelay));
+    usrp_recplay_params[6].helpstr = config_hlp_sf_wdelay;
+    usrp_recplay_params[6].paramflags=0;
+    usrp_recplay_params[6].uptr=&u_sf_write_delay;
+    usrp_recplay_params[6].defuintval=DEF_SF_DELAY_WRITE;
+    usrp_recplay_params[6].type=TYPE_UINT;
+    usrp_recplay_params[6].numelt=0;
+
+    return 0; // always ok
+}
+}
+#endif
+
 extern "C" {
     /*! \brief Initialize Openair USRP target. It returns 0 if OK
     * \param device the hardware to use
          * \param openair0_cfg RF frontend parameters set by application
          */
     int device_init(openair0_device* device, openair0_config_t *openair0_cfg) {
+#if defined(USRP_REC_PLAY)
+      paramdef_t usrp_recplay_params[7];
+      // to check
+      static int done = 0;
+      if (done == 1) {
+	return 0;
+      } // prevent from multiple init
+      done = 1;
+      // end to check
+      memset(usrp_recplay_params, 0, 7*sizeof(paramdef_t));
+      memset(&u_sf_filename[0], 0, 1024);
+      tmp_filename[0] = u_sf_filename;
+      if (trx_usrp_recplay_config_init(usrp_recplay_params) != 0) {
+	std::cerr << "USRP device record/replay mode configuration error exiting" << std::endl;
+	return -1;
+      }
+      config_process_cmdline(usrp_recplay_params,sizeof(usrp_recplay_params)/sizeof(paramdef_t),NULL);
+
+      if (strlen(tmp_filename[0]) != 0) {
+	(void) strcpy(u_sf_filename, tmp_filename[0]);
+      } else {
+	(void) strcpy(u_sf_filename, DEF_SF_FILE);
+      }
+
+      if (u_sf_replay == 1) u_sf_mode = 2;
+      if (u_sf_record == 1) u_sf_mode = 1;
+      
+      if (u_sf_mode == 2) {
+	// Replay subframes from from file
+        int bw_gain_adjust=0;
+        device->openair0_cfg = openair0_cfg;
+	device->type = USRP_B200_DEV;
+	openair0_cfg[0].rx_gain_calib_table = calib_table_b210_38;
+	bw_gain_adjust=1;
+	openair0_cfg[0].tx_sample_advance     = 80;
+	openair0_cfg[0].tx_bw                 = 20e6;
+	openair0_cfg[0].rx_bw                 = 20e6;
+        openair0_cfg[0].iq_txshift = 4;//shift
+        openair0_cfg[0].iq_rxrescale = 15;//rescale iqs
+	set_rx_gain_offset(&openair0_cfg[0],0,bw_gain_adjust);
+        device->priv = NULL;
+        device->trx_start_func = trx_usrp_start;
+        device->trx_write_func = trx_usrp_write;
+        device->trx_read_func  = trx_usrp_read;
+        device->trx_get_stats_func = trx_usrp_get_stats;
+        device->trx_reset_stats_func = trx_usrp_reset_stats;
+        device->trx_end_func   = trx_usrp_end;
+        device->trx_stop_func  = trx_usrp_stop;
+        device->trx_set_freq_func = trx_usrp_set_freq;
+        device->trx_set_gains_func   = trx_usrp_set_gains;
+        device->openair0_cfg = openair0_cfg;
+	std::cerr << "USRP device initialized in subframes replay mode for " << u_sf_loops << " loops." << std::endl;
+      } else {
+#endif
         uhd::set_thread_priority_safe(1.0);
         usrp_state_t *s = (usrp_state_t*)calloc(sizeof(usrp_state_t),1);
         
@@ -705,6 +970,11 @@ extern "C" {
         int vers=0,subvers=0,subsubvers=0;
         int bw_gain_adjust=0;
 
+#if defined(USRP_REC_PLAY)
+	if (u_sf_mode == 1) {
+	  std::cerr << "USRP device initialized in subframes record mode" << std::endl;
+	}
+#endif	
         sscanf(uhd::get_version_string().c_str(),"%d.%d.%d",&vers,&subvers,&subsubvers);
         LOG_I(PHY,"Checking for USRPs : UHD %s (%d.%d.%d)\n",
               uhd::get_version_string().c_str(),vers,subvers,subsubvers);
@@ -742,7 +1012,9 @@ extern "C" {
             //s->usrp->set_master_clock_rate(usrp_master_clock);
 
             openair0_cfg[0].rx_gain_calib_table = calib_table_x310;
-
+#if defined(USRP_REC_PLAY)
+	    std::cerr << "-- Using calibration table: calib_table_x310" << std::endl; // Bell Labs info
+#endif	    
             switch ((int)openair0_cfg[0].sample_rate) {
             case 30720000:
                 // from usrp_time_offset
@@ -801,9 +1073,15 @@ extern "C" {
             if ((vers == 3) && (subvers == 9) && (subsubvers>=2)) {
                 openair0_cfg[0].rx_gain_calib_table = calib_table_b210;
                 bw_gain_adjust=0;
+#if defined(USRP_REC_PLAY)
+		std::cerr << "-- Using calibration table: calib_table_b210" << std::endl; // Bell Labs info
+#endif		
             } else {
                 openair0_cfg[0].rx_gain_calib_table = calib_table_b210_38;
                 bw_gain_adjust=1;
+#if defined(USRP_REC_PLAY)
+		std::cerr << "-- Using calibration table: calib_table_b210_38" << std::endl; // Bell Labs info
+#endif		
             }
 
             switch ((int)openair0_cfg[0].sample_rate) {
@@ -965,6 +1243,45 @@ extern "C" {
            } 
         }
  
+#if defined(USRP_REC_PLAY)
+      }
+#endif
+#if defined(USRP_REC_PLAY)
+      if (u_sf_mode == 1) { // record mode	
+	ms_sample = (iqrec_t*) malloc(u_sf_max * sizeof(iqrec_t));
+	if (ms_sample == NULL) {
+	  std::cerr<< "Memory allocation failed for subframe record or replay mode." << std::endl;
+	  exit(-1);
+	}
+	memset(ms_sample, 0, u_sf_max * BELL_LABS_IQ_BYTES_PER_SF);
+      }
+      if (u_sf_mode == 2) {
+	// use mmap
+	mmapfd = open(u_sf_filename, O_RDONLY | O_LARGEFILE);
+	if (mmapfd != 0) {
+	  fstat(mmapfd, &sb);
+	  std::cerr << "Loading subframes using mmap() from " << u_sf_filename << " size=" << (uint64_t)sb.st_size << " bytes ..." << std::endl;
+	  ms_sample = (iqrec_t*) mmap(NULL, sb.st_size, PROT_WRITE, MAP_PRIVATE, mmapfd, 0);
+	  if (ms_sample != MAP_FAILED) {
+	    nb_samples = (sb.st_size / sizeof(iqrec_t));
+	    int aligned = (((unsigned long)ms_sample & 31) == 0)? 1:0;
+	    std::cerr<< "Loaded "<< nb_samples << " subframes." << std::endl;
+	    if (aligned == 0) {
+	      std::cerr<< "mmap address is not 32 bytes aligned, exiting." << std::endl;
+	      close(mmapfd);
+	      exit(-1);
+	    }
+	  } else {
+	    std::cerr << "Cannot mmap file, exiting." << std::endl;
+	    close(mmapfd);
+	    exit(-1);
+	  }
+	} else {
+	    std::cerr << "Cannot open " << u_sf_filename << " , exiting." << std::endl;
+	    exit(-1);
+	}
+      }
+#endif	
         return 0;
     }
 }
diff --git a/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.h b/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.h
new file mode 100644
index 0000000000000000000000000000000000000000..47ba171f1b9805973d2076629037bb9e920d22c9
--- /dev/null
+++ b/targets/ARCH/USRP/USERSPACE/LIB/usrp_lib.h
@@ -0,0 +1,89 @@
+#ifndef __USRP_LIB_H
+#define __USRP_LIB_H
+/*
+ * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The OpenAirInterface Software Alliance licenses this file to You under
+ * the OAI Public License, Version 1.1  (the "License"); you may not use this file
+ * except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.openairinterface.org/?page_id=698
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------------
+ * For more information about the OpenAirInterface (OAI) Software Alliance:
+ *      contact@openairinterface.org
+ */
+
+/** usrp_lib.h
+ *
+ * \author: bruno.mongazon-cazavet@nokia-bell-labs.com
+ */
+
+#if defined (USRP_REC_PLAY)
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "common/config/config_paramdesc.h"
+#include "common/config/config_userapi.h"
+
+#define BELL_LABS_IQ_HEADER       0xabababababababab
+#define BELL_LABS_IQ_PER_SF       7680 // Up to 5MHz bw for now
+#define BELL_LABS_IQ_BYTES_PER_SF (BELL_LABS_IQ_PER_SF * 4)
+typedef struct {
+  int64_t       header;
+  int64_t       ts;
+  int64_t       rfu1;
+  int64_t       rfu2; // pad for 256 bits alignement required by AVX2
+  unsigned char samples[BELL_LABS_IQ_BYTES_PER_SF]; // iq's for one subframe
+} iqrec_t;
+#define DEF_NB_SF           120000               // default nb of sf or ms to capture (2 minutes at 5MHz)
+#define DEF_SF_FILE         "/home/nokia/iqfile" // default subframes file name
+#define DEF_SF_DELAY_READ   400                  // default read delay  µs (860=real)
+#define DEF_SF_DELAY_WRITE  15                   // default write delay µs (15=real)
+#define DEF_SF_NB_LOOP      5                    // default nb loops
+
+
+/* help strings definition for command line options, used in CMDLINE_XXX_DESC macros and printed when -h option is used */
+#define CONFIG_HLP_SF_FILE      "Path of the file used for subframes record or replay"
+#define CONFIG_HLP_SF_REC       "Record subframes from USRP driver into a file for later replay"
+#define CONFIG_HLP_SF_REP       "Replay subframes into USRP driver from a file"
+#define CONFIG_HLP_SF_MAX       "Maximum count of subframes to be recorded in subframe file"
+#define CONFIG_HLP_SF_LOOPS     "Number of loops to replay of the entire subframes file"
+#define CONFIG_HLP_SF_RDELAY    "Delay in microseconds to read a subframe in replay mode"
+#define CONFIG_HLP_SF_WDELAY    "Delay in microseconds to write a subframe in replay mode"
+
+/* keyword strings for command line options, used in CMDLINE_XXX_DESC macros and printed when -h option is used */
+#define CONFIG_OPT_SF_FILE      "subframes-file"
+#define CONFIG_OPT_SF_REC       "subframes-record"
+#define CONFIG_OPT_SF_REP       "subframes-replay"
+#define CONFIG_OPT_SF_MAX       "subframes-max"
+#define CONFIG_OPT_SF_LOOPS     "subframes-loops"
+#define CONFIG_OPT_SF_RDELAY    "subframes-read-delay"
+#define CONFIG_OPT_SF_WDELAY    "subframes-write-delay"
+
+/* For information only - the macro is not usable in C++ */
+/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
+/*                                            command line parameters for USRP record/playback                                                                               */
+/*   optname                     helpstr                paramflags                      XXXptr                  defXXXval                            type           numelt   */
+/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
+#define USRP_RECPLAY_PARAMS_DESC {  \
+{"subframes-file",        	 CONFIG_HLP_SF_FILE,	0,		  strptr:(char **)&u_sf_filename,       defstrval:DEF_SF_FILE, 		   TYPE_STRING,   sizeof(u_sf_filename)}, \
+{"subframes-record",      	 CONFIG_HLP_SF_REC,	PARAMFLAG_BOOL,	  uptr:&u_sf_record,	                defuintval:0,			   TYPE_UINT,	  0}, \
+{"subframes-replay",      	 CONFIG_HLP_SF_REP,	PARAMFLAG_BOOL,	  uptr:&u_sf_replay,	                defuintval:0,			   TYPE_UINT,	  0}, \
+{"subframes-max",              	 CONFIG_HLP_SF_MAX,    	0,                uptr:&u_sf_max,			defintval:DEF_NB_SF,	       	   TYPE_UINT,	  0}, \
+{"subframes-loops",           	 CONFIG_HLP_SF_LOOPS,   0,                uptr:&u_sf_loops,			defintval:DEF_SF_NB_LOOP,	   TYPE_UINT,	  0}, \
+{"subframes-read-delay",         CONFIG_HLP_SF_RDELAY,  0,                uptr:&u_sf_read_delay,	      	defintval:DEF_SF_DELAY_READ,	   TYPE_UINT,	  0}, \
+{"subframes-write-delay",        CONFIG_HLP_SF_WDELAY,  0,                uptr:&u_sf_write_delay,		defintval:DEF_SF_DELAY_WRITE,	   TYPE_UINT,	  0}, \
+}
+#endif // BELL_LABS_MUST
+#endif // __USRP_LIB_H
+