Python+tkinter版3GPP Decoder

1. 3GPP Decoder工具调研

众所周知,3GPP Decoder工具在蜂窝网络OTA消息的解析上有独到的效率。比如Qualcomm、UNISOC、MediaTek的平台工具对于OTA消息的解析格式各不相同,要比较的时候使用3GPP Decoder工具可以统一格式,确保在遇到问题时可以快速比较等。
当前大家使用的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\",\"\""

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:
    WiresharkPath = ''
    
    NROTAList = (
                "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")

    LTEOTAList = (
                "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")

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

    WCDMAOTAList = (
                "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")

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

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

    # If it is not defined here, it cannot be used in callback function GetOTAList().
    selectcb = ''
    inputText = ''
    outputText = ''
    TGPPDecoder = ''
    
    def __init__(self, root):
        root.title('3gppdecoder 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.inputText = ScrolledText(inputlf, height=8, font='TkFixedFont')  
        self.inputText.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.GetOTAList)
        lterb = ttk.Radiobutton(protocollf, text='LTE', variable=self.protocol, value='LTE', command=self.GetOTAList)
        ltenbrb = ttk.Radiobutton(protocollf, text='LTE-NB', variable=self.protocol, value='LTE-NB', command=self.GetOTAList)
        wcdmarb = ttk.Radiobutton(protocollf, text='WCDMA', variable=self.protocol, value='WCDMA', command=self.GetOTAList)
        gsmrb = ttk.Radiobutton(protocollf, text='GSM', variable=self.protocol, value='GSM', command=self.GetOTAList)
        simrb = ttk.Radiobutton(protocollf, text='SIM', variable=self.protocol, value='SIM', command=self.GetOTAList)
        self.otatype = StringVar()
        self.selectcb = ttk.Combobox(protocollf, width=35, value=self.LTEOTAList, 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.TextButtonCallback)
        wiresharkbn = ttk.Button(decodinglf, text='Wireshark', command=self.WiresharkButtonCallback)
        wiresharkpathlb = ttk.Label(decodinglf, text='Wireshark Path:')
        self.GetWiresharkPath()
        self.wiresharkpathet = ttk.Entry(decodinglf, width=35)
        self.wiresharkpathet.delete(0, "end")
        self.wiresharkpathet.insert(0, self.WiresharkPath)
        self.wiresharkpathet['state'] = 'readonly'
        settingsbn = ttk.Button(decodinglf, width=5, text='...', command=self.SetWiresharkPath)
        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.outputText = ScrolledText(mainframe, font='TkFixedFont')  
        self.outputText.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 GetOTAList(self):
        p = self.protocol.get()
        if p == 'NR':
            self.selectcb['value'] = self.NROTAList
        elif p == 'LTE':
            self.selectcb['value'] = self.LTEOTAList
        elif p == 'LTE-NB':
            self.selectcb['value'] = self.LTENBOTAList
        elif p == 'WCDMA':
            self.selectcb['value'] = self.WCDMAOTAList
        elif p == 'GSM':
            self.selectcb['value'] = self.GSMOTAList
        elif p == 'SIM':
            self.selectcb['value'] = self.SIMProtocolList
        else:
            self.selectcb['value'] = self.LTEOTAList
        self.selectcb.current(0)

    def WriteASCIIHexDumpFile(self, rawStr, ASCIIHexDumpFile):
        offset = 0
        with open(ASCIIHexDumpFile, '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 CreatePcap(self, ASCIIHexDumpFile):
        cmd1 = '"' + self.WiresharkPath + 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)
        #improt ctypes
        #whnd = ctypes.windll.kernel32.GetConsoleWindow()
        #if whnd != 0:
        #    ctypes.windll.user32.ShowWindow(whnd, 0)
        p.wait()

    def AnalyzeByTshark(self, ASCIIHexDumpFile, channelType):
        self.CreatePcap(ASCIIHexDumpFile)
        cmd2 = '"' + self.WiresharkPath + 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 AnalyzeByWireshark(self, ASCIIHexDumpFile, channelType):
        self.CreatePcap(ASCIIHexDumpFile)
        cmd2 = '"' + self.WiresharkPath + 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 GetInputRawData(self):
        text = self.inputText.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 TextButtonCallback(self):
        self.outputText.delete(1.0,'end')
        if not os.path.isfile(self.WiresharkPath + r'/tshark.exe'):
            self.outputText.insert(1.0, 'Invalid Wireshark path.')
        elif self.GetInputRawData():
            text = self.inputText.get('0.0','end')
            strText = re.sub('\s','',text)
            pattern = re.compile('.{2}')
            fmtStr = ' '.join(pattern.findall(strText))
            self.WriteASCIIHexDumpFile(fmtStr, 'ASCII_Hex_Dump.txt')
            output = self.AnalyzeByTshark('ASCII_Hex_Dump.txt', self.otatype.get())
            self.outputText.insert(1.0, output)
        else:
            self.outputText.insert(1.0, 'Invalid hex string.')

    def WiresharkButtonCallback(self):
        self.outputText.delete(1.0,'end')
        self.outputText.update()
        if not os.path.isfile(self.WiresharkPath + r'/tshark.exe'):
            self.outputText.insert(1.0, 'Invalid Wireshark path.')
        elif self.GetInputRawData():
            text = self.inputText.get('0.0','end')
            strText = re.sub('\s','',text)
            pattern = re.compile('.{2}')
            fmtStr = ' '.join(pattern.findall(strText))
            self.WriteASCIIHexDumpFile(fmtStr, 'ASCII_Hex_Dump.txt')
            output = self.AnalyzeByWireshark('ASCII_Hex_Dump.txt', self.otatype.get())
        else:
            self.outputText.insert(1.0, 'Invalid hex string.')
    
    def GetWiresharkPath(self):
        config = configparser.ConfigParser()
        config.read('config.ini', encoding='utf-8')
        if config.has_option('DEFAULT', 'path'):
            self.WiresharkPath = config.get("DEFAULT", "path")
        else:
            self.WiresharkPath = ''

    def SetWiresharkPath(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.GetWiresharkPath()

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

3. 打包方法

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

你可能感兴趣的:(pythontkinter)