Python+tkinter版3GPP Decoder

1. 3GPP Decoder工具调研 【Python+tkinter版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 ScrolledTextclass 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='https://www.it610.com/article/NR', command=self.GetOTAList) lterb = ttk.Radiobutton(protocollf, text='LTE', variable=self.protocol, value='https://www.it610.com/article/LTE', command=self.GetOTAList) ltenbrb = ttk.Radiobutton(protocollf, text='LTE-NB', variable=self.protocol, value='https://www.it610.com/article/LTE-NB', command=self.GetOTAList) wcdmarb = ttk.Radiobutton(protocollf, text='WCDMA', variable=self.protocol, value='https://www.it610.com/article/WCDMA', command=self.GetOTAList) gsmrb = ttk.Radiobutton(protocollf, text='GSM', variable=self.protocol, value='https://www.it610.com/article/GSM', command=self.GetOTAList) simrb = ttk.Radiobutton(protocollf, text='SIM', variable=self.protocol, value='https://www.it610.com/article/SIM', command=self.GetOTAList) self.otatype = StringVar() self.selectcb = ttk.Combobox(protocollf, width=35, value=https://www.it610.com/article/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 + 1def 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 Falsedef 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

    推荐阅读