Merge pull request #131 from romankh/wideband-scanner

Addition of the wideband scanner
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index d677e7f..0a91a0d 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -46,5 +46,6 @@
     airprobe_rtlsdr.py
     airprobe_decode.py
     airprobe_rtlsdr_capture.py
+    airprobe_rtlsdr_scanner.py
     DESTINATION bin
 )
diff --git a/apps/airprobe_rtlsdr_scanner.py b/apps/airprobe_rtlsdr_scanner.py
new file mode 100755
index 0000000..3ead0c9
--- /dev/null
+++ b/apps/airprobe_rtlsdr_scanner.py
@@ -0,0 +1,396 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @file
+# @author Piotr Krysik <ptrkrysik@gmail.com>
+# @author Roman Khassraf <rkhassraf@gmail.com>
+# @section LICENSE
+#
+# Gr-gsm 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 3, or (at your option)
+# any later version.
+#
+# Gr-gsm 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 gr-gsm; see the file COPYING.  If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+#
+from gnuradio import blocks
+from gnuradio import gr
+from gnuradio import eng_notation
+from gnuradio.eng_option import eng_option
+from gnuradio.filter import firdes
+from gnuradio.filter import pfb
+from math import pi
+from optparse import OptionParser
+
+import grgsm
+import numpy
+import os
+import osmosdr
+import pmt
+import time
+
+
+#from wideband_receiver import *
+
+class receiver_with_decoder(gr.hier_block2):
+
+    def __init__(self, OSR=4, chan_num=0, fc=939.4e6, ppm=0, samp_rate=0.2e6):
+        gr.hier_block2.__init__(
+            self, "Receiver With Decoder",
+            gr.io_signature(1, 1, gr.sizeof_gr_complex*1),
+            gr.io_signature(0, 0, 0),
+        )
+        self.message_port_register_hier_in("bursts")
+        self.message_port_register_hier_in("msgs")
+
+        ##################################################
+        # Parameters
+        ##################################################
+        self.OSR = OSR
+        self.chan_num = chan_num
+        self.fc = fc
+        self.ppm = ppm
+        self.samp_rate = samp_rate
+
+        ##################################################
+        # Variables
+        ##################################################
+        self.samp_rate_out = samp_rate_out = 1625000.0/6.0*OSR
+
+        ##################################################
+        # Blocks
+        ##################################################
+        self.gsm_receiver_0 = grgsm.receiver(OSR, ([chan_num]), ([]))
+        self.gsm_input_0 = grgsm.gsm_input(
+            ppm=ppm,
+            osr=OSR,
+            fc=fc,
+            samp_rate_in=samp_rate,
+        )
+        self.gsm_control_channels_decoder_0 = grgsm.control_channels_decoder()
+        self.gsm_clock_offset_control_0 = grgsm.clock_offset_control(fc)
+        self.gsm_bcch_ccch_demapper_0 = grgsm.universal_ctrl_chans_demapper(0, ([2,6,12,16,22,26,32,36,42,46]), ([1,2,2,2,2,2,2,2,2,2]))
+
+        ##################################################
+        # Connections
+        ##################################################
+        self.msg_connect(self.gsm_bcch_ccch_demapper_0, 'bursts', self, 'bursts')
+        self.msg_connect(self.gsm_bcch_ccch_demapper_0, 'bursts', self.gsm_control_channels_decoder_0, 'bursts')
+        self.msg_connect(self.gsm_clock_offset_control_0, 'ppm', self.gsm_input_0, 'ppm_in')
+        self.msg_connect(self.gsm_control_channels_decoder_0, 'msgs', self, 'msgs')
+        self.msg_connect(self.gsm_receiver_0, 'C0', self.gsm_bcch_ccch_demapper_0, 'bursts')
+        self.msg_connect(self.gsm_receiver_0, 'measurements', self.gsm_clock_offset_control_0, 'measurements')
+        self.connect((self.gsm_input_0, 0), (self.gsm_receiver_0, 0))    
+        self.connect((self, 0), (self.gsm_input_0, 0))    
+
+
+    def get_OSR(self):
+        return self.OSR
+
+    def set_OSR(self, OSR):
+        self.OSR = OSR
+        self.set_samp_rate_out(1625000.0/6.0*self.OSR)
+        self.gsm_input_0.set_osr(self.OSR)
+
+    def get_chan_num(self):
+        return self.chan_num
+
+    def set_chan_num(self, chan_num):
+        self.chan_num = chan_num
+
+    def get_fc(self):
+        return self.fc
+
+    def set_fc(self, fc):
+        self.fc = fc
+        self.gsm_input_0.set_fc(self.fc)
+
+    def get_ppm(self):
+        return self.ppm
+
+    def set_ppm(self, ppm):
+        self.ppm = ppm
+        self.gsm_input_0.set_ppm(self.ppm)
+
+    def get_samp_rate(self):
+        return self.samp_rate
+
+    def set_samp_rate(self, samp_rate):
+        self.samp_rate = samp_rate
+        self.gsm_input_0.set_samp_rate_in(self.samp_rate)
+
+    def get_samp_rate_out(self):
+        return self.samp_rate_out
+
+    def set_samp_rate_out(self, samp_rate_out):
+        self.samp_rate_out = samp_rate_out
+
+
+class wideband_receiver(gr.hier_block2):
+
+    def __init__(self, OSR=4, fc=939.4e6, samp_rate=0.4e6):
+        gr.hier_block2.__init__(
+            self, "Wideband receiver",
+            gr.io_signature(1, 1, gr.sizeof_gr_complex*1),
+            gr.io_signature(0, 0, 0),
+        )
+        self.message_port_register_hier_in("bursts")
+        self.message_port_register_hier_in("msgs")
+        self.__init(OSR, fc, samp_rate)
+    
+    def __init(self, OSR=4, fc=939.4e6, samp_rate=0.4e6):
+        ##################################################
+        # Parameters
+        ##################################################
+        self.OSR = OSR
+        self.fc = fc
+        self.samp_rate = samp_rate
+        self.channels_num = int(samp_rate/0.2e6)
+        self.OSR_PFB = 2
+        
+        ##################################################
+        # Blocks
+        ##################################################
+        self.pfb_channelizer_ccf_0 = pfb.channelizer_ccf(
+            self.channels_num,
+            (),
+            self.OSR_PFB,
+            100)
+        self.pfb_channelizer_ccf_0.set_channel_map(([]))
+        self.create_receivers()
+
+        ##################################################
+        # Connections
+        ##################################################
+        self.connect((self, 0), (self.pfb_channelizer_ccf_0, 0))
+        for chan in xrange(0,self.channels_num):
+            self.connect((self.pfb_channelizer_ccf_0, chan), (self.receivers_with_decoders[chan], 0))
+            self.msg_connect(self.receivers_with_decoders[chan], 'bursts', self, 'bursts')
+            self.msg_connect(self.receivers_with_decoders[chan], 'msgs', self, 'msgs')
+
+    def create_receivers(self):
+        self.receivers_with_decoders = {}
+        for chan in xrange(0,self.channels_num):
+            self.receivers_with_decoders[chan] = receiver_with_decoder(fc=self.fc, OSR=self.OSR, chan_num=chan, samp_rate=self.OSR_PFB*0.2e6)
+
+    def get_OSR(self):
+        return self.OSR
+
+    def set_OSR(self, OSR):
+        self.OSR = OSR
+        self.create_receivers()
+
+    def get_fc(self):
+        return self.fc
+
+    def set_fc(self, fc):
+        self.fc = fc
+        self.create_receivers()
+
+    def get_samp_rate(self):
+        return self.samp_rate
+
+
+class wideband_scanner(gr.top_block):
+    
+    def __init__(self, rec_len=3, sample_rate=2e6, carrier_frequency=939e6, ppm=0):
+        
+        gr.top_block.__init__(self, "Wideband Scanner")
+        
+        self.rec_len = rec_len
+        self.sample_rate = sample_rate
+        self.carrier_frequency = carrier_frequency
+        self.ppm = ppm
+        
+        # if no file name is given process data from rtl_sdr source
+        self.rtlsdr_source = osmosdr.source( args="numchan=" + str(1) + " " + "" )
+        
+        self.rtlsdr_source.set_sample_rate(sample_rate)
+        
+        # capture half of GSM channel lower than channel center (-0.1MHz)
+        # this is needed when even number of channels is captured in order to process full captured bandwidth
+        
+        self.rtlsdr_source.set_center_freq(carrier_frequency - 0.1e6, 0)
+        
+        # correction of central frequency
+        # if the receiver has large frequency offset 
+        # the value of this variable should be set close to that offset in ppm
+        self.rtlsdr_source.set_freq_corr(options.ppm, 0)
+        
+        self.rtlsdr_source.set_dc_offset_mode(2, 0)
+        self.rtlsdr_source.set_iq_balance_mode(0, 0)
+        self.rtlsdr_source.set_gain_mode(True, 0)
+        self.rtlsdr_source.set_bandwidth(sample_rate, 0)
+    
+        self.head = blocks.head(gr.sizeof_gr_complex * 1, int(rec_len * sample_rate))
+        
+        # shift again by -0.1MHz in order to align channel center in 0Hz
+        self.blocks_rotator_cc = blocks.rotator_cc(-2 * pi * 0.1e6 / options.samp_rate) 
+               
+        self.wideband_receiver = wideband_receiver(OSR=4, fc=carrier_frequency, samp_rate=sample_rate)
+        self.gsm_extract_system_info = grgsm.extract_system_info()
+        
+        
+        self.connect((self.rtlsdr_source, 0), (self.head, 0))
+        self.connect((self.head, 0), (self.blocks_rotator_cc, 0))
+        self.connect((self.blocks_rotator_cc, 0), (self.wideband_receiver,0))
+        self.msg_connect(self.wideband_receiver, 'msgs', self.gsm_extract_system_info, 'msgs')
+        
+    def set_carrier_frequency(self, carrier_frequency):
+        self.carrier_frequency = carrier_frequency
+        self.rtlsdr_source.set_center_freq(carrier_frequency - 0.1e6, 0)
+
+       
+        
+class channel_info(object):
+    
+    def __init__(self, arfcn, freq, cid, lac, mcc, mnc, ccch_conf, power, neighbours, cell_arfcns):
+        self.arfcn = arfcn
+        self.freq = freq
+        self.cid = cid
+        self.lac = lac
+        self.mcc = mcc
+        self.mnc = mnc
+        self.ccch_conf = ccch_conf
+        self.power = power
+        self.neighbours = neighbours
+        self.cell_arfcns = cell_arfcns
+                
+    def get_verbose_info(self):
+        i = "  |---- Configuration: %s\n" % self.get_ccch_conf()
+        i += "  |---- Cell ARFCNs: " + ", ".join(map(str, self.cell_arfcns)) + "\n"
+        i += "  |---- Neighbour Cells: " + ", ".join(map(str, self.neighbours)) + "\n"
+        return i
+        
+    def get_ccch_conf(self):
+        if self.ccch_conf == 0:
+            return "1 CCCH, not combined"
+        elif self.ccch_conf == 1:
+            return "1 CCCH, combined"
+        elif self.ccch_conf == 2:
+            return "2 CCCH, not combined"
+        elif self.ccch_conf == 4:
+            return "3 CCCH, not combined"
+        elif self.ccch_conf == 6:
+            return "4 CCCH, not combined"
+        else:
+            return "Unknown"
+        
+    def getKey(self):
+        return self.arfcn
+    
+    def __cmp__(self, other):
+        if hasattr(other, 'getKey'):
+            return self.getKey().__cmp__(other.getKey())
+        
+    def __repr__(self):
+        return "ARFCN: %4u, Freq: %6.1fM, CID: %5u, LAC: %5u, MCC: %3u, MNC: %3u, Pwr: %3i" % (self.arfcn, self.freq/1e6, self.cid, self.lac, self.mcc, self.mnc, self.power)
+            
+
+if __name__ == '__main__':
+    parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
+    bands_list = ", ".join(grgsm.arfcn.get_bands())
+    parser.add_option("-b", "--band", dest="band", default="P-GSM",
+                      help="Specify the GSM band for the frequency.\nAvailable bands are: " + bands_list)
+    parser.add_option("-s", "--samp-rate", dest="samp_rate", type="float", default=2e6,
+        help="Set sample rate [default=%default] - allowed values even_number*0.2e6")
+    parser.add_option("-p", "--ppm", dest="ppm", type="intx", default=-45,
+        help="Set frequency correction in ppm [default=%default]")
+    parser.add_option("-g", "--gain", dest="gain", type="eng_float", default=24.0,
+        help="Set gain [default=%default]")
+    parser.add_option("--speed", dest="speed", type="intx", default=4,
+        help="Scan speed [default=%default]. Value range 0-5.")
+    parser.add_option("-v", "--verbose", action="store_true", 
+                      help="If set, verbose information output is printed: ccch configuration, cell ARFCN's, neighbour ARFCN's")
+    
+    """
+        Dont forget: sudo sysctl kernel.shmmni=32000
+    """
+        
+    (options, args) = parser.parse_args()
+    
+    if options.band not in grgsm.arfcn.get_bands():
+        parser.error("Invalid GSM band\n")
+        
+    if options.speed < 0 or options.speed > 5:
+        parser.error("Invalid scan speed.\n")
+        
+    if (options.samp_rate / 0.2e6) % 2 != 0:
+        parser.error("Invalid sample rate. Sample rate must be an even numer * 0.2e6")
+        
+    channels_num = int(options.samp_rate/0.2e6)
+    
+    first_arfcn = grgsm.arfcn.get_first_arfcn(options.band)
+    last_arfcn = grgsm.arfcn.get_last_arfcn(options.band)
+    last_center_arfcn = last_arfcn - int((channels_num / 2) - 1)
+        
+    current_freq = grgsm.arfcn.arfcn2downlink(first_arfcn + int(channels_num / 2) - 1, options.band)
+    last_freq = grgsm.arfcn.arfcn2downlink(last_center_arfcn, options.band)
+    stop_freq = last_freq + 0.2e6 * channels_num
+    
+    while current_freq < stop_freq:
+        
+        # silence rtl_sdr output:
+        # open 2 fds
+        null_fds = [os.open(os.devnull, os.O_RDWR) for x in xrange(2)]
+        # save the current file descriptors to a tuple
+        save = os.dup(1), os.dup(2)
+        # put /dev/null fds on 1 and 2
+        os.dup2(null_fds[0], 1)
+        os.dup2(null_fds[1], 2)
+        
+        # instantiate scanner and processor
+        scanner = wideband_scanner(rec_len=6-options.speed, 
+                            sample_rate=options.samp_rate, 
+                            carrier_frequency=current_freq, 
+                            ppm=options.ppm)
+
+        # start recording
+        scanner.start()
+        scanner.wait()
+        scanner.stop()
+        
+        # restore file descriptors so we can print the results
+        os.dup2(save[0], 1)
+        os.dup2(save[1], 2)
+        # close the temporary fds
+        os.close(null_fds[0])
+        os.close(null_fds[1])
+        
+        freq_offsets = numpy.fft.ifftshift(numpy.array(range(int(-numpy.floor(channels_num/2)),int(numpy.floor((channels_num+1)/2))))*2e5)
+        detected_c0_channels = scanner.gsm_extract_system_info.get_chans()
+                
+        if detected_c0_channels:
+            chans = numpy.array(scanner.gsm_extract_system_info.get_chans())
+            found_freqs = current_freq + freq_offsets[(chans)]
+            
+            cell_ids = numpy.array(scanner.gsm_extract_system_info.get_cell_id())
+            lacs = numpy.array(scanner.gsm_extract_system_info.get_lac())
+            mccs = numpy.array(scanner.gsm_extract_system_info.get_mcc())
+            mncs = numpy.array(scanner.gsm_extract_system_info.get_mnc())
+            ccch_confs = numpy.array(scanner.gsm_extract_system_info.get_ccch_conf())
+            powers = numpy.array(scanner.gsm_extract_system_info.get_pwrs())
+                        
+            found_list = []
+            for i in range(0, len(chans)):
+                cell_arfcn_list = scanner.gsm_extract_system_info.get_cell_arfcns(chans[i])
+                neighbour_list = scanner.gsm_extract_system_info.get_neighbours(chans[i])
+
+                info = channel_info(grgsm.arfcn.downlink2arfcn(found_freqs[i], options.band), found_freqs[i], cell_ids[i], lacs[i], mccs[i], mncs[i], ccch_confs[i], powers[i], neighbour_list, cell_arfcn_list)
+                found_list.append(info)
+                            
+            for info in sorted(found_list):
+                print info
+                if options.verbose:
+                    print info.get_verbose_info()
+            
+        scanner = None
+        current_freq += channels_num * 0.2e6
diff --git a/include/grgsm/misc_utils/extract_system_info.h b/include/grgsm/misc_utils/extract_system_info.h
index 8be10a1..a05be18 100644
--- a/include/grgsm/misc_utils/extract_system_info.h
+++ b/include/grgsm/misc_utils/extract_system_info.h
@@ -54,7 +54,10 @@
       virtual std::vector<int> get_pwrs() = 0;
       virtual std::vector<int> get_lac() = 0;
       virtual std::vector<int> get_cell_id() = 0;
+      virtual std::vector<int> get_mcc() = 0;
       virtual std::vector<int> get_mnc() = 0;
+      virtual std::vector<int> get_ccch_conf() = 0;
+      virtual std::vector<int> get_cell_arfcns(int chan_id) = 0;
       virtual std::vector<int> get_neighbours(int chan_id) = 0;
       virtual void reset() = 0;
     };
diff --git a/lib/misc_utils/extract_system_info_impl.cc b/lib/misc_utils/extract_system_info_impl.cc
index 994c92b..22e09aa 100644
--- a/lib/misc_utils/extract_system_info_impl.cc
+++ b/lib/misc_utils/extract_system_info_impl.cc
@@ -74,7 +74,10 @@
             info.pwr_db = header->signal_dbm;
             info.cell_id = (msg_elements[3]<<8)+msg_elements[4];         //take cell id
             info.lac = (msg_elements[8]<<8)+msg_elements[9];             //take lac
-            info.mnc = (msg_elements[7]>>4);                             //take mnc
+            info.mcc =  ((msg_elements[5] & 0xF)  * 100) + (((msg_elements[5] & 0xF0) >> 4) * 10) + ((msg_elements[6] & 0xF)); // take mcc
+            info.mnc = (msg_elements[7] & 0xF) * 10 + (msg_elements[7]>>4); //take mnc
+            info.ccch_conf = (msg_elements[10] & 0x7); // ccch_conf
+            
             boost::mutex::scoped_lock lock(extract_mutex);
             if(d_c0_channels.find(info.id) != d_c0_channels.end()){
                 d_c0_channels[info.id].copy_nonzero_elements(info);
@@ -87,8 +90,9 @@
             info.id = be16toh(header->arfcn);                            //take arfcn
             info.pwr_db = header->signal_dbm;
             info.lac = (msg_elements[6]<<8)+msg_elements[7];            //take lac
-            info.mnc = (msg_elements[5]>>4);                            //take mnc
-
+            info.mcc =  ((msg_elements[3] & 0xF) * 100) + (((msg_elements[3] & 0xF0) >> 4) * 10) + ((msg_elements[3] & 0xF)); // take mcc
+            info.mnc = (msg_elements[5] & 0xF) * 10 + (msg_elements[5]>>4); //take mnc
+            
             boost::mutex::scoped_lock lock(extract_mutex);
             if(d_c0_channels.find(info.id) != d_c0_channels.end()){
                 d_c0_channels[info.id].copy_nonzero_elements(info);
@@ -157,6 +161,27 @@
                 }
             }
         }
+        else if(msg_elements[2]==0x19)
+        { //System Information Type 1
+            memset(freq, 0, sizeof(freq));
+            chan_info info;
+            info.id = be16toh(header->arfcn);                            //take arfcn
+            info.pwr_db = header->signal_dbm;
+            boost::mutex::scoped_lock lock(extract_mutex);
+            //read cell arfcn's
+            gsm48_decode_freq_list(freq, &msg_elements[3], 16, 0x8c, 0x01);
+            if(d_c0_channels.find(info.id) != d_c0_channels.end()){
+                d_c0_channels[info.id].copy_nonzero_elements(info);
+            } else {
+                d_c0_channels[info.id] = info;
+            }
+            
+            for(int arfcn=0; arfcn<sizeof(freq); arfcn++){
+                if(freq[arfcn].mask==0x01){
+                    d_c0_channels[info.id].cell_arfcns.insert(arfcn);
+                }
+            }
+        }
     }
     
     std::vector<int> extract_system_info_impl::get_chans()
@@ -177,6 +202,15 @@
         return lacs;
     }
     
+    std::vector<int> extract_system_info_impl::get_mcc()
+    {
+        std::vector<int> mccs;
+        BOOST_FOREACH(chan_info_map::value_type &i, d_c0_channels){
+            mccs.push_back(i.second.mcc);
+        }
+        return mccs;
+    }
+    
     std::vector<int> extract_system_info_impl::get_mnc()
     {
         std::vector<int> mncs;
@@ -203,6 +237,16 @@
         }
         return pwrs;
     }
+    
+    std::vector<int> extract_system_info_impl::get_ccch_conf()
+    {
+        std::vector<int> ccch_confs;
+        BOOST_FOREACH(chan_info_map::value_type &i, d_c0_channels){
+            ccch_confs.push_back(i.second.ccch_conf);
+        }
+        return ccch_confs;
+    }
+    
     std::vector<int> extract_system_info_impl::get_neighbours(int chan_id)
     {
         std::vector<int> neighbour_cells;
@@ -211,6 +255,15 @@
         }
         return neighbour_cells;
     }
+        
+    std::vector<int> extract_system_info_impl::get_cell_arfcns(int chan_id)
+    {
+        std::vector<int> cell_arfcns;
+        BOOST_FOREACH(int n, d_c0_channels[chan_id].cell_arfcns){
+            cell_arfcns.push_back(n);
+        }
+        return cell_arfcns;
+    }
     
     void extract_system_info_impl::reset()
     {
diff --git a/lib/misc_utils/extract_system_info_impl.h b/lib/misc_utils/extract_system_info_impl.h
index b88a586..20f5adc 100644
--- a/lib/misc_utils/extract_system_info_impl.h
+++ b/lib/misc_utils/extract_system_info_impl.h
@@ -37,11 +37,14 @@
         unsigned int arfcn;
         unsigned int lac;
         unsigned int cell_id;
+        unsigned int mcc;
         unsigned int mnc;
+        unsigned int ccch_conf;
         std::set<int> neighbour_cells;
+        std::set<int> cell_arfcns;
         
-        chan_info() :  id(-1), pwr_db(0), arfcn(0), lac(0), cell_id(0), mnc(0){}
-        chan_info(const chan_info & info) : id(info.id), pwr_db(info.pwr_db), arfcn(info.arfcn), lac(info.lac), cell_id(info.cell_id), mnc(info.mnc){}
+        chan_info() :  id(-1), pwr_db(0), arfcn(0), lac(0), cell_id(0), mcc(0), mnc(0), ccch_conf(-1){}
+        chan_info(const chan_info & info) : id(info.id), pwr_db(info.pwr_db), arfcn(info.arfcn), lac(info.lac), cell_id(info.cell_id), mcc(info.mcc), mnc(info.mnc), ccch_conf(info.ccch_conf){}
         ~chan_info(){}
         void copy_nonzero_elements(const chan_info & info){
             id = info.id;
@@ -49,7 +52,9 @@
             arfcn = info.arfcn;
             lac = (info.lac!=0) ? info.lac : lac;
             cell_id = (info.cell_id!=0) ? info.cell_id : cell_id;
+            mcc = (info.mcc!=0) ? info.mcc : mcc;
             mnc = (info.mnc!=0) ? info.mnc : mnc;
+            ccch_conf = (info.ccch_conf!=-1) ? info.ccch_conf : ccch_conf;
         }
     };
 
@@ -82,7 +87,10 @@
       virtual std::vector<int> get_pwrs();
       virtual std::vector<int> get_lac();
       virtual std::vector<int> get_cell_id();
+      virtual std::vector<int> get_mcc();
       virtual std::vector<int> get_mnc();
+      virtual std::vector<int> get_ccch_conf();
+      virtual std::vector<int> get_cell_arfcns(int chan_id);
       virtual std::vector<int> get_neighbours(int chan_id);
       virtual void reset();
       extract_system_info_impl();
diff --git a/python/misc_utils/arfcn.py b/python/misc_utils/arfcn.py
index afe8ab6..e7edfb5 100644
--- a/python/misc_utils/arfcn.py
+++ b/python/misc_utils/arfcn.py
@@ -44,6 +44,24 @@
     return __band_conf.keys()
 
 
+def get_first_arfcn(band):
+    """
+    Returns the first arfcn (i.e. the one with the lowest number) in the given band.
+    """
+    if band in __band_conf:
+        conf = __band_conf.get(band)
+        return conf['first_arfcn']
+
+
+def get_last_arfcn(band):
+    """
+    Returns the last arfcn (i.e. the one with the highest number) in the given band
+    """
+    if band in __band_conf:
+        conf = __band_conf.get(band)
+        return conf['last_arfcn']
+    
+    
 def is_valid_arfcn(arfcn, band):
     """
     Returns True if arfcn is valid in the given band, else False
diff --git a/python/qa_arfcn.py b/python/qa_arfcn.py
index 64775ff..85be110 100755
--- a/python/qa_arfcn.py
+++ b/python/qa_arfcn.py
@@ -262,6 +262,25 @@
         self.assertEqual(512, grgsm.arfcn.downlink2arfcn(1930.2e6, 'PCS1900'))
         self.assertEqual(810, grgsm.arfcn.downlink2arfcn(1989.8e6, 'PCS1900'))
         
+    def test_008_firstarfcn(self):
+        self.assertEqual(259, grgsm.arfcn.get_first_arfcn('GSM450'))
+        self.assertEqual(306, grgsm.arfcn.get_first_arfcn('GSM480'))
+        self.assertEqual(128, grgsm.arfcn.get_first_arfcn('GSM850'))
+        self.assertEqual(1, grgsm.arfcn.get_first_arfcn('P-GSM'))
+        self.assertEqual(975, grgsm.arfcn.get_first_arfcn('E-GSM'))
+        self.assertEqual(955, grgsm.arfcn.get_first_arfcn('R-GSM'))
+        self.assertEqual(512, grgsm.arfcn.get_first_arfcn('DCS1800'))
+        self.assertEqual(512, grgsm.arfcn.get_first_arfcn('PCS1900'))
+
+    def test_009_firstarfcn(self):
+        self.assertEqual(293, grgsm.arfcn.get_last_arfcn('GSM450'))
+        self.assertEqual(340, grgsm.arfcn.get_last_arfcn('GSM480'))
+        self.assertEqual(251, grgsm.arfcn.get_last_arfcn('GSM850'))
+        self.assertEqual(124, grgsm.arfcn.get_last_arfcn('P-GSM'))
+        self.assertEqual(1023, grgsm.arfcn.get_last_arfcn('E-GSM'))
+        self.assertEqual(1023, grgsm.arfcn.get_last_arfcn('R-GSM'))
+        self.assertEqual(885, grgsm.arfcn.get_last_arfcn('DCS1800'))
+        self.assertEqual(810, grgsm.arfcn.get_last_arfcn('PCS1900'))        
         
 if __name__ == '__main__':
     gr_unittest.run(qa_arfcn, "qa_arfcn.xml")