PyXR

c:\projects\bitpim\src \ analyser.py



0001 #!/usr/bin/env python
0002 
0003 ### BITPIM
0004 ###
0005 ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com>
0006 ###
0007 ### This program is free software; you can redistribute it and/or modify
0008 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0009 ###
0010 ### $Id: analyser.py 3037 2006-04-03 00:30:28Z rogerb $
0011 
0012 """Graphical view of protocol data and a decode of it"""
0013 
0014 import sys
0015 import re
0016 import traceback
0017 import wx
0018 import StringIO
0019 import struct
0020 
0021 import common
0022 import prototypes
0023 
0024 import hexeditor
0025 
0026 class Eventlist(wx.ListCtrl):
0027     "List control showing the various events"
0028 
0029     def __init__(self, parent, id=-1, events=[]):
0030         self.events=events
0031         wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT|wx.LC_VIRTUAL)
0032         self.InsertColumn(0, "Time")
0033         self.InsertColumn(1, "Size")
0034         self.InsertColumn(2, "Class")
0035         self.InsertColumn(3, "Description")
0036 
0037         self.SetColumnWidth(0, 100)
0038         self.SetColumnWidth(1, 50)
0039         self.SetColumnWidth(2, 200)
0040         self.SetColumnWidth(3, 1000)
0041 
0042         self.SetItemCount(len(events))
0043 
0044     def newdata(self, events):
0045         self.DeleteAllItems()
0046         self.events=events
0047         self.SetItemCount(len(events))
0048 
0049     def OnGetItemText(self, index, col):
0050         curtime, curdesc, curclass, curdata=self.events[index]
0051         if col==0:
0052             return curtime
0053         if col==1:
0054             if len(curdata):
0055                 return "%5d" % (len(curdata),)
0056             return ""
0057         if col==2:
0058             return curclass
0059         if col==3:
0060             return curdesc
0061         assert False
0062 
0063     def OnGetItemImage(self, item):
0064         return -1
0065         
0066     
0067 
0068 class Analyser(wx.Frame):
0069     """A top level frame for analysing protocol data"""
0070     _pane_widths=[-2, -3, -4]
0071     _pos_pane_index=0
0072     _sel_pane_index=1
0073     _val_pane_index=2
0074 
0075     def __init__(self, parent=None, id=-1, title="BitPim Protocol Analyser", data=None):
0076         """Start the show
0077 
0078         @param data: data to show.  If None, then it will be obtained from the clipboard
0079         """
0080         wx.Frame.__init__(self, parent, id, title, size=(800,750),
0081                          style=wx.DEFAULT_FRAME_STYLE)
0082         # add a status bar to display various status items
0083         self.CreateStatusBar(len(self._pane_widths))
0084         self.SetStatusWidths(self._pane_widths)
0085 
0086         topsplit=wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_LIVE_UPDATE)
0087 
0088         self.list=Eventlist(topsplit, 12)
0089 
0090         botsplit=wx.SplitterWindow(topsplit, -1, style=wx.SP_3D|wx.SP_LIVE_UPDATE)
0091         topsplit.SplitHorizontally(self.list, botsplit, 300)
0092 
0093         self.tree=wx.TreeCtrl(botsplit, 23, style=wx.TR_DEFAULT_STYLE)
0094         self.hex=hexeditor.HexEditor(botsplit,
0095                                      _set_pos=self.set_pos,
0096                                      _set_sel=self.set_sel,
0097                                      _set_val=self.set_val)
0098         botsplit.SplitHorizontally(self.tree, self.hex, 200)
0099         
0100         if data is None:
0101             data=self.getclipboarddata()
0102 
0103         self.newdata(data)
0104 
0105         wx.EVT_LIST_ITEM_SELECTED(self, self.list.GetId(), self.OnListBoxItem)
0106         wx.EVT_LIST_ITEM_ACTIVATED(self, self.list.GetId(), self.OnListBoxItem)
0107 
0108         wx.EVT_TREE_SEL_CHANGED(self, self.tree.GetId(), self.OnTreeSelection)
0109         
0110         self.Show()
0111 
0112     def newdata(self, data):
0113         "We have new data - the old data is tossed"
0114         self.parsedata(data)
0115         self.list.newdata(self.packets)
0116 
0117     def OnListBoxItem(self,evt):
0118         "The user selected an event in the listbox"
0119         index=evt.m_itemIndex
0120         curtime, curdesc, curclass, curdata=self.packets[index]
0121         self.errorinfo=""
0122         self.hex.SetData("")
0123         self.hex.highlightrange(-1,-1)
0124         if len(curdata):
0125             self.hex.SetData(curdata)
0126             # self.hex.ShowPosition(self.hex.XYToPosition(0,0))
0127         else:
0128             self.hex.SetData(curdesc)
0129             # self.hex.ShowPosition(self.hex.XYToPosition(0,0))
0130 
0131         self.tree.DeleteAllItems()
0132         if len(curclass):
0133             b=prototypes.buffer(curdata)
0134             try:
0135                 klass=common.getfullname(curclass)
0136             except Exception,e:
0137                 self.errorme("Finding class",e)
0138                 wx.TipWindow(self.tree,self.errorinfo)
0139                 return
0140             try:
0141                 obj=klass()
0142             except Exception,e:
0143                 self.errorme("Instantiating object",e)
0144                 wx.TipWindow(self.tree,self.errorinfo)
0145                 return
0146 
0147             try:
0148                 obj.readfrombuffer(b, autolog=False)
0149             except Exception,e:
0150                 self.errorme("Reading from buffer",e)
0151                 # no return, we persevere
0152 
0153             root=self.tree.AddRoot(curclass)
0154             try:
0155                 self.tree.SetPyData(root, obj.packetspan())
0156             except:
0157                 self.errorme("Object did not construct correctly")
0158                 # no return, we persevere
0159             self.addtreeitems(obj, root)
0160         if len(self.errorinfo):
0161             wx.TipWindow(self.tree,self.errorinfo)
0162 
0163     def addtreeitems(self, obj, parent):
0164         "Add fields from obj to parent node"
0165         try:
0166             for name,field,desc in obj.containerelements():
0167                 if desc is None:
0168                     desc=""
0169                 else:
0170                     desc="      - "+desc
0171                 iscontainer=False
0172                 try:
0173                     iscontainer=field.iscontainer()
0174                 except:
0175                     pass
0176                 # Add ourselves
0177                 s=field.__class__.__name__+" "+name
0178                 if iscontainer:
0179                     c=field.__class__
0180                     s+=": <%s.%s>" % (c.__module__, c.__name__)
0181                 else:
0182                     try:
0183                         v=field.getvalue()
0184                     except Exception,e:
0185                         v="<Exception: "+e.__str__()+">"
0186                     s+=": "
0187                     if isinstance(v, int) and not isinstance(v, type(True)):
0188                         s+="%d 0x%x" % (v,v)
0189                     else:
0190                         s+=`v`
0191                     if len(desc):
0192                         s+=desc
0193                 node=self.tree.AppendItem(parent, s)
0194                 try:
0195                     self.tree.SetPyData(node, field.packetspan())
0196                 except:
0197                     pass
0198                 if iscontainer:
0199                     self.addtreeitems(field, node)
0200         except Exception,e:
0201             str="<Exception: "+e.__str__()+">"
0202             self.tree.AppendItem(parent,str)
0203 
0204     def OnTreeSelection(self, evt):
0205         "User selected an item in the tree"
0206         item=evt.GetItem()
0207         try:
0208             start,end=self.tree.GetPyData(item)
0209         except:
0210             self.hex.highlightrange(-1,-1)
0211             return
0212         self.hex.highlightrange(start,end)
0213         # self.hex.ShowPosition(begin)
0214 
0215     def errorme(self, desc, exception=None):
0216         "Put exception information into the hex pane and output traceback to console"
0217         if exception is not None:
0218             x=StringIO.StringIO()
0219             print >>x,exception.__str__(),
0220             self.errorinfo+=x.getvalue()+" : "
0221             print >>sys.stderr, common.formatexception()
0222         self.errorinfo+=desc+"\n"
0223 
0224     def getclipboarddata(self):
0225         """Gets text data on clipboard"""
0226         do=wx.TextDataObject()
0227         wx.TheClipboard.Open()
0228         success=wx.TheClipboard.GetData(do)
0229         wx.TheClipboard.Close()
0230         if not success:
0231             wx.MessageBox("Whatever is in the clipboard isn't text", "No way Dude")
0232             return ""
0233         return do.GetText()
0234 
0235     patevent=re.compile(r"^(\d?\d:\d\d:\d\d\.\d\d\d)(.*)")
0236     patdataevent=re.compile(r"^(\d?\d:\d\d:\d\d\.\d\d\d)(.*)(Data - \d+ bytes.*)")
0237     patdatarow=re.compile(r"^([0-9A-Fa-f]{8})(.*)")
0238     patclass=re.compile(r"^<#!\s+(.*)\s+!#>")
0239 
0240     def parsedata(self, data):
0241         """Fills in our internal data structures based on contents of data"""
0242 
0243         # santise all the data by doing the eol nonsense
0244         data=data.replace("\r", "\n")
0245         lastlen=0
0246         while lastlen!=len(data):
0247             lastlen=len(data)
0248             data=data.replace("\n\n", "\n")
0249         
0250         self.packets=[]
0251 
0252         curtime=curdesc=curclass=curdata=""
0253 
0254         indata=False
0255         
0256         for line in data.split('\n'):
0257             # ignore blank lines
0258             if len(line.strip())==0:
0259                 continue
0260             mo=self.patclass.match(line)
0261             if mo is not None:
0262                 # found a class description
0263                 curclass=mo.group(1)
0264                 indata=True
0265                 continue
0266             # if indata, try for some more
0267             if indata:
0268                 mo=self.patdatarow.match(line)
0269                 if mo is not None:
0270                     # found another data row
0271                     pos=int(mo.group(1), 16)
0272                     assert pos==len(curdata)
0273                     for i in range(9, min(len(line), 9+16*3), 3): # at most 16 bytes
0274                         s=line[i:i+2]
0275                         if len(s)!=2 or s=="  ":
0276                             # last line with trailing spaces
0277                             continue
0278                         b=int(s,16)
0279                         curdata+=chr(b)
0280                     continue
0281                 # end of data, save it
0282                 indata=False
0283                 self.packets.append( (curtime, curdesc, curclass, curdata) )
0284                 curtime=curdesc=curclass=curdata=""
0285                 # and move on
0286             # data event?
0287             mo=self.patdataevent.match(line)
0288             if mo is not None:
0289                 self.packets.append( (curtime, curdesc, curclass, curdata) )
0290                 curtime=curdesc=curclass=curdata=""
0291                 curtime=mo.group(1)
0292                 curdesc=mo.group(2)+mo.group(3)
0293                 indata=True
0294                 continue
0295             # ordinary event?
0296             mo=self.patevent.match(line)
0297             if mo is not None:
0298                 self.packets.append( (curtime, curdesc, curclass, curdata) )
0299                 curtime=curdesc=curclass=curdata=""
0300                 curtime=mo.group(1)
0301                 curdesc=mo.group(2)
0302                 indata=True
0303                 continue
0304             # No idea what it is, just add on end of desc
0305             if len(curdesc):
0306                 curdesc+="\n"
0307             curdesc+=line
0308 
0309         # Add whatever is in variables at end
0310         self.packets.append( (curtime, curdesc, curclass, curdata) )
0311 
0312         # remove all blank lines
0313         # filter, reduce, map and lambda all in one go!
0314         self.packets=filter(lambda item: reduce(lambda x,y: x+y, map(len, item)), self.packets)
0315                     
0316     def set_pos(self, pos):
0317         """Display the current buffer offset in the format of
0318         Pos: 0x12=18
0319         """
0320         if pos is None:
0321             s=''
0322         else:
0323             s='Pos: 0x%X=%d'%(pos, pos)
0324         self.SetStatusText(s, self._pos_pane_index)
0325     def set_sel(self, sel_start, sel_end):
0326         if sel_start is None or sel_start==-1 or\
0327            sel_end is None or sel_end==-1:
0328             s=''
0329         else:
0330             sel_len=sel_end-sel_start
0331             sel_end-=1
0332             s='Sel: 0x%X=%d to 0x%X=%d (0x%X=%d bytes)'%(
0333                 sel_start, sel_start, sel_end, sel_end,
0334                 sel_len, sel_len)
0335         self.SetStatusText(s, self._sel_pane_index)
0336     def set_val(self, v):
0337         if v:
0338             # char
0339             s='Val: 0x%02X=%d'%(ord(v[0]), ord(v[0]))
0340             if len(v)>1:
0341                 # short
0342                 u_s=struct.unpack('<H', v[:struct.calcsize('<H')])[0]
0343                 s+=' 0x%04X=%d'%(u_s,  u_s)
0344             if len(v)>3:
0345                 # int/long
0346                 u_i=struct.unpack('<I', v[:struct.calcsize('<I')])[0]
0347                 s+=' 0x%08X=%d'%(u_i, u_i)
0348         else:
0349             s=''
0350         self.SetStatusText(s, self._val_pane_index)
0351 
0352 if __name__=='__main__':
0353     app=wx.PySimpleApp()
0354     # Find the data source
0355     data=None
0356     if len(sys.argv)==2:
0357         # From a file
0358         data=common.opentextfile(sys.argv[1]).read()
0359     frame=Analyser(data=data)
0360     app.MainLoop()
0361 

Generated by PyXR 0.9.4