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