Python+tkinter版3GPP Decoder

1. 3GPP Decoder工具调研

众所周知,3GPP Decoder工具在蜂窝网络OTA消息的解析上有独到的效率。比如Qualcomm、UNISOC、MediaTek的平台工具对于OTA消息的解析格式各不相同,比较内容时使用3GPP Decoder工具解析的结果是统一格式的,使用beyond compare等工具可实现快速比较。
当前大家使用的3GPP Decoder工具一般是如下三个:
http://3gppdecoder.free.fr
https://www.3glteinfo.com/
https://gitee.com/konglinglon...
这三个工具没有python写的,python是比较流行的编程语言,广泛应用于自动化测试。

2. 使用Python构建3GPP Decoder工具

2.1. 需要学习的资料

2.1.1. tkinter学习

学习tkinter的grid布局就足够了,别的布局方式已过时。为了避免学习各个版本上的混淆,建议统一学习如下文档。
https://tkdocs.com/tutorial/i...

2.1.2. wireshark命令

该文档具备启发性,对dissector的理解也是重中之重:
如何让wireshark解码任意层的数据:https://blog.csdn.net/peng_yw...
该文档介绍了DLT=147的方法:
The Wireshark Network Analyzer:https://users.cs.fiu.edu/~esj...
-o "uat:user_dlts:\"User 0 (DLT=147)\",\"cops\",\"0\",\"\",\"0\",\"\""
该文档介绍了上述147的含义:
LINK-LAYER HEADER TYPES: https://www.tcpdump.org/linkt...

2.2. 程序源码

import os
import re
import subprocess
import configparser
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
from tkinter.scrolledtext import ScrolledText

class TGPPDecoder:
    wireshark_path = ''
    
    nr_ote_list = (
                "nas-5gs",
                "nr-rrc.dl.ccch",
                "nr-rrc.dl.dcch",
                "nr-rrc.ul.ccch",
                "nr-rrc.ul.dcch",
                "nr-rrc.ul.ccch1",
                "nr-rrc.bcch.bch",
                "nr-rrc.bcch.dl.sch",
                "nr-rrc.pcch",
                "nr-rrc.rrc_reconf",
                "nr-rrc.ue_mrdc_cap",
                "nr-rrc.ue_nr_cap",
                "xnap",
                "f1ap",
                "ngap",
                "x2ap")

    lte_ota_list = (
                "lte-rrc.dl.ccch",
                "lte-rrc.dl.dcch",
                "lte-rrc.ul.ccch",
                "lte-rrc.ul.dcch",
                "lte-rrc.bcch.bch",
                "lte-rrc.bcch.dl.sch",
                "lte-rrc.pcch",
                "lte-rrc.mcch",
                "lte-rrc.ue_eutra_cap",
                "nas-eps",
                "nas-eps_plain",
                "s1ap",
                "x2ap",
                "lpp",
                "ulp")

    lte_nb_ota_list = (
                "nr-rrc.dl.ccch.nb",
                "nr-rrc.dl.dcch.nb",
                "nr-rrc.ul.ccch.nb",
                "nr-rrc.ul.dcch.nb",
                "s1ap",
                "x2ap")

    wcdma_ota_list = (
                "rrc.bcch.bch",
                "rrc.bcch.fach",
                "rrc.dl.ccch",
                "rrc.dl.dcch",
                "rrc.pcch",
                "rrc.ul.ccch",
                "rrc.ul.dcch",
                "rrc.irat.ho_to_utran_cmd",
                "rrc.irat.irat_ho_info",
                "rrc.si.mib",
                "rrc.si.sb1",
                "rrc.si.sb2",
                "rrc.si.sib1",
                "rrc.si.sib2",
                "rrc.si.sib3",
                "rrc.si.sib4",
                "rrc.si.sib5",
                "rrc.si.sib5bis",
                "rrc.si.sib6",
                "rrc.si.sib7",
                "rrc.si.sib11",
                "rrc.si.sib11bis",
                "rrc.si.sib12",
                "rrc.si.sib18",
                "rrc.si.sib19",
                "rrc.si.sib20",
                "rrc.si.sib21",
                "rrc.si.sib22")

    gsm_ota_list = (
                "gsm_rlcmac_dl",
                "gsm_rlcmac_ul",
                "llcgprs",
                "gsm_a_dtap",
                "gsm_a_rp",
                "gsm_sms",
                "gsm_a_ccch",
                "gsm_a_sacch")

    sim_protocol_list = (
                "gsm_sim",
                "etsi_cat",
                "gsm_sim.command",
                "gsm_sim.response",)

    # If it is not defined here, it cannot be used in callback function get_ota_list().
    selectcb = ''
    input_text = ''
    output_text = ''
    
    def __init__(self, root):
        root.title('tgppdecoder v0.1')
        root.minsize(760, 508)
        
        mainframe = ttk.Frame(root, padding="3 3 3 3")
        mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
        root.columnconfigure(0, weight=1)
        root.rowconfigure(0, weight=1)
        
        inputlf = ttk.Labelframe(mainframe, text='Input')
        inputlf.grid(column=0, row=0, ipadx=5, ipady=5,padx=5, pady=5, sticky=(W, E))
        self.input_text = ScrolledText(inputlf, height=8, font='TkFixedFont')  
        self.input_text.grid(column=0, row=0, sticky=(W, E))
        inputlf.columnconfigure(0, weight=1)
        
        protocollf = ttk.Labelframe(mainframe, text='Protocol selection')
        protocollf.grid(column=0, row=1, ipadx=5, ipady=5,padx=5, pady=5, sticky=(W, E))
        self.protocol = StringVar()
        self.protocol.set('LTE')
        nrrb = ttk.Radiobutton(protocollf, text='NR', variable=self.protocol, value='NR', command=self.get_ota_list)
        lterb = ttk.Radiobutton(protocollf, text='LTE', variable=self.protocol, value='LTE', command=self.get_ota_list)
        ltenbrb = ttk.Radiobutton(protocollf, text='LTE-NB', variable=self.protocol, value='LTE-NB', command=self.get_ota_list)
        wcdmarb = ttk.Radiobutton(protocollf, text='WCDMA', variable=self.protocol, value='WCDMA', command=self.get_ota_list)
        gsmrb = ttk.Radiobutton(protocollf, text='GSM', variable=self.protocol, value='GSM', command=self.get_ota_list)
        simrb = ttk.Radiobutton(protocollf, text='SIM', variable=self.protocol, value='SIM', command=self.get_ota_list)
        self.otatype = StringVar()
        self.selectcb = ttk.Combobox(protocollf, width=35, value=self.lte_ota_list, textvariable=self.otatype)
        nrrb.grid(column=0, row=0, padx=5, sticky=W) 
        lterb.grid(column=1, row=0, padx=5, sticky=W) 
        ltenbrb.grid(column=2, row=0, padx=5, sticky=W) 
        wcdmarb.grid(column=3, row=0, padx=5, sticky=W) 
        gsmrb.grid(column=4, row=0, padx=5, sticky=W) 
        simrb.grid(column=5, row=0, padx=5, sticky=W) 
        self.selectcb.current(0)
        self.selectcb.grid(column=6, row=0, padx=15, sticky=W) 
        protocollf.columnconfigure(6, weight=1)
        
        decodinglf = ttk.Labelframe(mainframe, text='Output decoding')
        decodinglf.grid(column=0, row=2, ipadx=5, ipady=5,padx=5, pady=5, sticky=(W, E))
        textbn = ttk.Button(decodinglf, text='Text view', command=self.text_button_callback)
        wiresharkbn = ttk.Button(decodinglf, text='Wireshark', command=self.wireshark_button_callback)
        wiresharkpathlb = ttk.Label(decodinglf, text='Wireshark Path:')
        self.get_wireshark_path()
        self.wiresharkpathet = ttk.Entry(decodinglf, width=35)
        self.wiresharkpathet.delete(0, "end")
        self.wiresharkpathet.insert(0, self.wireshark_path)
        self.wiresharkpathet['state'] = 'readonly'
        settingsbn = ttk.Button(decodinglf, width=5, text='...', command=self.set_wireshark_path)
        textbn.grid(column=0, row=0, padx=5, sticky=W)
        wiresharkbn.grid(column=1, row=0, padx=5, sticky=W)
        wiresharkpathlb.grid(column=2, row=0, sticky=E) 
        self.wiresharkpathet.grid(column=3, row=0, padx=5, sticky=E) 
        settingsbn.grid(column=4, row=0, padx=5, sticky=E) 
        decodinglf.columnconfigure(1, weight=1)
        
        self.output_text = ScrolledText(mainframe, font='TkFixedFont')  
        self.output_text.grid(column=0, row=3, padx=5, pady=10, sticky=(N, S, E, W))
        mainframe.columnconfigure(0, weight=1)
        mainframe.rowconfigure(3, weight=1)
    
    def get_ota_list(self):
        p = self.protocol.get()
        if p == 'NR':
            self.selectcb['value'] = self.nr_ote_list
        elif p == 'LTE':
            self.selectcb['value'] = self.lte_ota_list
        elif p == 'LTE-NB':
            self.selectcb['value'] = self.lte_nb_ota_list
        elif p == 'WCDMA':
            self.selectcb['value'] = self.wcdma_ota_list
        elif p == 'GSM':
            self.selectcb['value'] = self.gsm_ota_list
        elif p == 'SIM':
            self.selectcb['value'] = self.sim_protocol_list
        else:
            self.selectcb['value'] = self.lte_ota_list
        self.selectcb.current(0)

    def write_ascii_hex_dump_file(self, rawStr, ascii_hex_dump_file):
        offset = 0
        with open(ascii_hex_dump_file, 'w+') as fp:
            for x in rawStr.strip().split(' '):
                if offset % 16 == 0:
                    if offset != 0:
                        fp.write('\n')
                    fp.write('%.6x' % offset)
                fp.write(' ' + x)
                offset = offset + 1                

    def create_pcap(self, ascii_hex_dump_file):
        cmd1 = '"' + self.wireshark_path + r'/text2pcap.exe" -l 147 ASCII_Hex_Dump.txt fortshark.pcap'
        p = subprocess.Popen(cmd1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        p.wait()

    def analyze_by_tshark(self, ascii_hex_dump_file, channelType):
        self.create_pcap(ascii_hex_dump_file)
        cmd2 = '"' + self.wireshark_path + r'/tshark.exe" -V -T text -o "uat:user_dlts:\"User 0 (DLT=147)\",\"cops\",\"0\",\"\",\"0\",\"\"" -r fortshark.pcap'
        cmd2 = cmd2.replace('cops', channelType)
        p = subprocess.Popen(cmd2, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        output, err = p.communicate()
        output = str(output, encoding = "utf-8")
        #print(output)
        flag = False
        output_ = ''
        for line in output.splitlines():
            if flag:
                output_ += line + '\n'
            if 'DLT: 147' in line.strip():
                flag = True
        #print(output_)
        return output_

    def analyze_by_wireshark(self, ascii_hex_dump_file, channelType):
        self.create_pcap(ascii_hex_dump_file)
        cmd2 = '"' + self.wireshark_path + r'/Wireshark.exe" -o "uat:user_dlts:\"User 0 (DLT=147)\",\"cops\",\"0\",\"\",\"0\",\"\"" -r fortshark.pcap'
        cmd2 = cmd2.replace('cops', channelType)
        p = subprocess.Popen(cmd2, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        output, err = p.communicate()

    def get_input_raw_data(self):
        text = self.input_text.get('0.0','end')
        strText = re.sub('\s','',text)
        strCheckResult = re.search('^[0-9A-Fa-f]+$', strText)
        if strCheckResult:
            return True
        else:
            return False

    def text_button_callback(self):
        self.output_text.delete(1.0,'end')
        if not os.path.isfile(self.wireshark_path + r'/tshark.exe'):
            self.output_text.insert(1.0, 'Invalid Wireshark path.')
        elif self.get_input_raw_data():
            text = self.input_text.get('0.0','end')
            strText = re.sub('\s','',text)
            pattern = re.compile('.{2}')
            fmtStr = ' '.join(pattern.findall(strText))
            self.write_ascii_hex_dump_file(fmtStr, 'ASCII_Hex_Dump.txt')
            output = self.analyze_by_tshark('ASCII_Hex_Dump.txt', self.otatype.get())
            self.output_text.insert(1.0, output)
        else:
            self.output_text.insert(1.0, 'Invalid hex string.')

    def wireshark_button_callback(self):
        self.output_text.delete(1.0,'end')
        self.output_text.update()
        if not os.path.isfile(self.wireshark_path + r'/tshark.exe'):
            self.output_text.insert(1.0, 'Invalid Wireshark path.')
        elif self.get_input_raw_data():
            text = self.input_text.get('0.0','end')
            strText = re.sub('\s','',text)
            pattern = re.compile('.{2}')
            fmtStr = ' '.join(pattern.findall(strText))
            self.write_ascii_hex_dump_file(fmtStr, 'ASCII_Hex_Dump.txt')
            output = self.analyze_by_wireshark('ASCII_Hex_Dump.txt', self.otatype.get())
        else:
            self.output_text.insert(1.0, 'Invalid hex string.')
    
    def get_wireshark_path(self):
        config = configparser.ConfigParser()
        config.read('config.ini', encoding='utf-8')
        if config.has_option('DEFAULT', 'path'):
            self.wireshark_path = config.get("DEFAULT", "path")
        else:
            self.wireshark_path = ''

    def set_wireshark_path(self):
        config = configparser.ConfigParser()
        config.read('config.ini', encoding='utf-8')
        dirname = filedialog.askdirectory()
        if os.path.exists(dirname):
            config.set("DEFAULT", "path", dirname)
            with open("config.ini", "w+", encoding="utf-8") as f:
                config.write(f)
            self.wiresharkpathet['state'] = 'normal'
            self.wiresharkpathet.delete(0, 'end')
            self.wiresharkpathet.insert(0, dirname)
            self.wiresharkpathet['state'] = 'readonly'
            self.get_wireshark_path()

if __name__ == '__main__':
    root = Tk()
    TGPPDecoder(root)
    root.mainloop()

3. 测试数据

lte-rrc.dl.dcch

38 2c 19 55 c4 1c 00 17 08 00 70 00 20 26 80 70 00 30 24 00 20 00 00

如下lte-rrc.ul.dcch的数据只有3个rat的,所以之后显示错误,把第2个数据从04改为03就可以看到正确数据:

38 04 08 13 ad d9 a0 4b 00 10 40 c1 c9 99 36 7d 2a 75 02 e5 ff f9 1f f9 7f fe 47 fe 5f ff 91 ff 97 ff e4 7f e5 ff f9 1f f9 7f fe 47 fe 5f ff 91 ff 97 ff e4 7f e5 ff f9 1f f9 7f fe 47 fe 5f ff 91 ff 97 ff e4 7f ff f3 ff af a2 08 00 8c 87 0c a7 4a 93 bb f1 59 c0 80 00 00 7d bf f0 00 00 00 78 d4 00 c0 0a 00 40 00 34 05 af aa dc bf ff 1a 06 38 00 20 01 80 00 08 20 61 00 02 08 18 80 00 80 06 30 00 20 81 a6 00 08 00 6c 80 02 00 1b 60 00 80 02 f8 40 03 94 00 10 00 e7 00 04 00 3a 00 01 00 1c 00 00 40 47 08 00 e3 80 02 02 00 40 1b 2f ff c8 ff cb ff f2 3f f2 ff fc 8f fc bf ff 23 ff 2f ff c8 ff cb ff f2 3f f2 ff fc 8f fc bf ff 23 ff 2f ff c8 ff cb ff f2 3f f2 ff fc 8f fc bf ff 23 ff 2f ff c8 ff cb ff f2 3f ff fe c0 00 19 8d 00 08 bc 5d 98 a2 27 38 10 70 0c f6 91 84 92 5f ff ff fe ef 73 5e 08 04 cc 11 99 41 24 30 00 80 05 09 08 25 cf 01 80 00 08 06 01 c1 b0 9c 4d 78 08 00 00 08 51 90 00 00 00 cd 07 ff 20 00 70 c1 96 03 80 00 10 08 03 00 e0 d8 4e 26 d8 05 83 15 e1 a0 3f a0 47 4e 7a ef ff ff af 01 40 78 2e 02 03 c1 27 be 7f ff ff 80 07 03 1b ef 80 01 40 dc 04 07 e3 80 f5 17 c0 0f f8 bc 21 d8 be 08 21 fd 50 08 83 d9 51 d2 20 62 4f ac 41 e4 20 f0 81 42 5b e7 e4 ff e3 f8 00 3f f8 fe 00 00 d8 05 00 07 42 a2 f8 05 ff 17 84 3b 17 c1 04 3f aa 01 10 7b 2a 3a 44 0c 49 f5 88 3c 84 1e 10 28 4b 7c fc 9f f8 7e 00 07 fe 1f 80 00 1b 00 a0 00 e8 54 5f 01 3f e2 f0 87 62 f8 20 87 f5 40 22 0f 65 47 48 81 89 3e b1 07 90 83 c2 05 09 6f 9f 93 f8 0e 00 00 fe 03 80 00 03 60 14 00 1d 0a 8b e0 37 fc 5e 10 ec 5f 04 10 fe a8 04 41 ec a8 e9 10 31 27 d6 20 f2 10 78 40 a1 2d f3 f2 7f f1 fc 00 1f fc 7f 00 00 6c 02 80 03 a1 51 7c 07 ff 8b c2 1d 8b e0 82 1f d5 00 88 3d 95 1d 22 06 24 fa c4 1e 42 0f 08 14 25 be 7e 42 00 08 00 0d 80 50 00 74 2a 2f 83 7f f1 78 43 b1 7c 10 43 fa a0 11 07 b2 a3 a4 40 c4 9f 58 83 c8 41 e1 02 84 b7 cf c9 fd 07 40 00 7f 41 d0 00 01 b0 0a 00 0e 85 45 f0 9f fe 2f 08 76 2f 82 08 7f 54 02 20 f6 54 74 88 18 93 eb 10 79 08 3c 20 50 96 f9 f9 08 00 20 00 36 01 40 01 d0 a8 be 26 ff c5 e1 0e c5 f0 41 0f ea 80 44 1e ca 8e 91 03 12 7d 62 0f 21 07 84 0a 12 df 3f 27 7f 1f f0 01 df c7 fc 00 06 e0 28 00 3a 00 d9 40 00 14 00 00 00 4a 0f 00 00 03 09 a0 00 03 d0 2a 0e 00 00 e2 68 00 00 13 d0 20 0e 00 04 04 d0 0e 00 01 50 d8 0a 14 e9 db 24 f8 10 a7 4e d9 27 c0 00 38 10 c6 0f ff f8 00 02 e0 3e 00 28 00 30 1c 04 07 03 10 5c 07 00 07 00 06 03 e0 40 82 00 00 00 41 00 00 02 20 80 00 02 10 40 00 01 88 20 00 01 04 10 00 00 a2 08 00 00 61 04 00 00 38 82 00 00 20 08 60 6d 60 ed 64 ed 60 cd 64 cd 64 6d 65 0d 65 2d e5 4d 02 01 00 00 09 65 94 04 00 00 65 96 50 10 00 02 96 59 40 40 00 0e 59 65 01 00 00 49 65 94 04 00 01 65 96 50 10 00 06 96 59 40 40 00 1e 59 65 01 00 00 89 65 94 02 14 0e 2a 83 c5 52 78 aa 0d 15 49 a2 a9 1c 55 28 8a a5 31 74 aa 28 18 d2 01 1e 6f c0 70 1c 01 e6 fc 07 01 c0 1e 6f c0 70 1c 01 e6 fc 07 01 c0 1e 6f c0 70 1c 01 e6 fc 07 01 c0 1e 6f c0 70 1c 01 e6 fc 07 01 c0 1e 6f c0 70 1c 00 00 01 08 98 01 00 30 04 00 a0 0c 01 c0 18 03 80 40 09 00 a0 16 01 40 2c 00 80 18 06 00 d0 0c 01 84 c0 10 00 80 70 01 00 40 02 01 c0 04 04 00 08 02 00 10 10 00 20 24 00 40 10 00 80 90 00 11 30 02 00 20 08 00 40 18 00 80 30 01 00 80 02 01 40 04 02 80 08 01 00 10 0c 00 20 18 00 09 80 20 05 00 e0 1e 00 80 14 03 80 78 08 01 10 04 00 a0 20 04 40 48 09 80 20 05 01 20 24 6a c2 c0 00 81 01 81 c4 c6 46 c7 c9 49 ca 1d c0 00 00 00 d0 4a 65 3a 18 ff 02 f0 58 38 07 00 00 00 00 10 3d 19 03 e0 b0 00 0e 26 80 00 01 20 7a 32 04 01 c0 00 80 9a 01 c0 00 2a 1a 81 42 9d 3b 64 9f 02 14 e9 db 24 f8 00 07 01 0c 60 e0 04 20 40 10 08 02 01 03 00 40 0a 00 c0 18 40 80 40 10 08 02 06 00 80 14 04 80 9c

gsm_sim

80 12 00 00 44 D0 42 81 03 01 40 01 82 02 81 82 35 07 02 03 04 03 04 1F 02 39 02 03 E8 47 0A 06 54 65 73 74 47 70 02 72 73 0D 08 F4 55 73 65 72 4C 6F 67 0D 08 F4 55 73 65 72 50 77 64 3C 03 01 AD 9C 3E 05 21 01 01 01 01 90 00

4. 打包方法

进入anconda prompt,安装pyinstaller:
pip install pyinstaller
打包.exe文件:
pyinstaller -F -w TGPPDecoder.py

5. 参考文档

构建简单的数据包:
https://blog.csdn.net/force_e...
pcap文件的python解析实例
https://blog.csdn.net/dog250/...
Re: BIP / CAT-TP protocol support
https://seclists.org/wireshar...
Wireshark-dev: Re: [Wireshark-dev] BIP / CAT-TP protocol support
https://www.wireshark.org/lis...
Generate a quick and easy custom pcap file using Python
https://www.codeproject.com/t...

你可能感兴趣的:(pythontkinter5g)