├── LICENSE ├── README.md ├── codemap ├── __init__.py ├── codemap.py ├── server │ ├── __init__.py │ ├── socketserver.py │ └── webserver.py └── ui │ ├── dygraph.js │ ├── interaction.js │ └── skel.htm ├── idapythonrc.py └── install.py /LICENSE: -------------------------------------------------------------------------------- 1 | Codemap is free to non-commercial individual users. 2 | redistribution, using for commercial purpose is prohibited. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CODEMAP 2 | 3 | ## Introduction 4 | 5 | Codemap is a binary analysis tool for "run-trace visualization" provided as IDA plugin. 6 | 7 | Unlike DBI(Dynamic Binary Instrumentation) based tools such as Intel PIN or QEMU, 8 | Codemap uses 'tracepoints' for generating the register/memory information while 9 | the program's control flow passes the tracepoint. 10 | 11 | If the program hits a tracepoint, Codemap call-back handler is invoked 12 | then proper action for trace is taken; and program continues. 13 | This might sound like a slow/inefficient approach for execution tracing. 14 | However, there are two major advantages by tracing the binary in this manner. 15 | 16 | ### 1. Selective Tracing 17 | 18 | When you trace a binary with Codemap, you can "selectively set break-points only against instructions of your interest". 19 | 20 | These selective break-points avoid unneccessary tracing against meaningless part (out of your interest) of program. 21 | In most of case when you reverse-engineer a software, you want to analyze a very specific portion of the binary. 22 | For example, if you are analyzing a reason of program crash, you probably want to 23 | extract the execution log around that particular crashing point only. 24 | The "selective tracing" capability of Codemap perfectly suits for such cases. 25 | 26 | 27 | ### 2. Semantic Tracing 28 | 29 | Note that 'program execution trace' is not only thing that Codemap can trace. 30 | In fact, Codemap can trace entire register context. 31 | This enables Codemap to generate very concrete and flexible trace results. 32 | We refer this feature as 'Semantic Tracing' 33 | 34 | For example, to trace the "size of allocated heap chunks", you can set a break-point against an instruction which references 35 | a register that contains the size of heap chunk. Likely candidate would be an instruction from the starting part of malloc(). 36 | 37 | For another example, to trace the entire "heap chunk address", you can set a break-point at the end of malloc() 38 | and trace the EAX register value(always holds the chunk location at that moment) to visualize the overall location of heap chunks. 39 | 40 | As long as you understand the 'semantic of register context' while specific instruction is being executed 41 | you can effectivly trace/visualize these semantics, which can be very helpful to understand the behaviour of binary. 42 | you can also make your own SQL statement and specify how to visualize these results. 43 | 44 | 45 | ## Requirements (essential) 46 | - IDA Pro 6.5 or after. (6.6 is recommended, don't use cracked version, Codemap might not work) 47 | - Python 2.x 48 | 49 | ## Requirements (recommended) 50 | - Chrome web browser 51 | - Large screen / Dual monitor environment 52 | 53 | ## How to install 54 | - run `python install.py` from Codemap home directory 55 | 56 | ## How to use 57 | Basically, codemap hooks IDA and place its own break-point event handler. 58 | every time when a program hits break-point, codemap will save register/memory information of that moment into DB. 59 | Later, codemap visualizes this trace information in the web-browser with SQL query. 60 | 61 | There are 5 commands for Codemap. 62 | 63 | ### ALT-1 : Start/Pause Codemap 64 | 65 | This button starts the Codemap tracing. 66 | before you press this button, make sure setup break-points where you wan to trace the binary. 67 | if you press this button again, Codemap will pause. to resume, press the button again. 68 | You must pause the Codemap to see accurate result. If you don't pause, 69 | some trace information can be buffered and not showed in browser screen. 70 | 71 | ### ALT-2 : Set Function BP 72 | 73 | This button set break-points against entire instruction inside the function that you currently put cursor. 74 | note that, the function (pointed by your cursor) should be recognized by IDA. 75 | - in order to force IDA to disassemble the byte stream, put the cursor on the byte that you think as the 76 | starting point of instructions, then press 'C' button. 77 | - in order to force IDA to recognize a function, put the cursor on the instruction that you think as the 78 | starting point of function, then press 'P' button. 79 | 80 | 81 | ### ALT-3 : Set Range BP 82 | 83 | This button will ask you the address range in which the break-points will be set. 84 | By using this button, you can set break-points against instructions in the range of 0x8048100 ~ 0x8048200 per se. 85 | 86 | 87 | 88 | ### ALT-4 : Create/Setup Module BP 89 | 90 | This button helps you to setup breakpoints against starting point of entire functions inside a module(.dll, or .so). 91 | There are two steps in order to do this. 92 | 93 | First, you need to open up the module(dll or so) file with IDA. 94 | After IDA finishes the static analysis of module, press this button to 'Create break-point information and save it into a file'. 95 | Codemap will ask you the file name of this information. 96 | 97 | Second, while you are debugging an application which uses the module(dll or so), 98 | put your cursor inside the memory area of the loaded module and press this button again. 99 | then Codemap will ask you the name of the file that you stored the break-point information for this module. 100 | 101 | 102 | ### ALT-5 : Connect Codemap Browser Screen to IDA View 103 | 104 | This button allows you to connect the IDA with Codemap graph browser. 105 | if you press this button and refresh the Codemap graph browser, the browser and IDA will be connected. 106 | after the connection, the IDA screen will follow your cursor from the graph browser. 107 | 108 | If you used 'module breakpoint' feature, Codemap will use the base address of the module as the 109 | base address of EIP, so in such case, you need to specify the 'static module base' address. 110 | (see video explanation in codemap.kr) 111 | 112 | 113 | ### Limitation 114 | 115 | Codemap supports all binaries that can be debugged with IDA Pro. 116 | However, ARM binary is not supported yet. (will be supported in future) 117 | 118 | 119 | ### Example Usage 120 | 121 | 1. Attach/Run binary with IDA Pro. 122 | 2. Setup breakpoints where you want to trace with Codemap while program is paused 123 | 3. Press alt-1 (Browser will popup and codemap will continue the program) 124 | 4. Let program run and generate trace results 125 | 5. Press alt-1 again (codemap will pause the program) 126 | 6. Execute your SQL statement to visualize the trace. 127 | 128 | You can use following SQL statements for example. 129 | - select eip from trace 130 | - select eax,ebx,ecx from trace where eax > ebx - ecx 131 | - select eax from trace where ebx*3 = edx 132 | - select eip from trace limit 100 133 | - select eax from trace order by eax 134 | - select eip from trace where m_edi like 'bla' (m_ prefix means the memory dump pointed by register) 135 | - select eip from trace where m_arg1 like 'bla' (in x86, arg1~arg4 is equivalent to *(esp+4) ~ *(esp+16)) 136 | - select ecx from trace where m_ecx like 'bla' order by edx limit 10 137 | - select edx+1000 from trace where esi < edi and eax > 100 138 | 139 | 140 | ### Contact for developers 141 | 142 | - daehee87@kaist.ac.kr 143 | - zzoru@kaist.ac.kr 144 | - dinggul@kaist.ac.kr 145 | 146 | 147 | ### Academic Reference 148 | 149 | KAIST CysecLab (Graduate School of Information Security, School of Computing) 150 | Advisor (Prof. B. Kang) 151 | 152 | 153 | ### Visit http://codemap.kr for more details. 154 | -------------------------------------------------------------------------------- /codemap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c0demap/codemap/c0a2f0079f19b5e8b56dd89da7471d61f21f49bb/codemap/__init__.py -------------------------------------------------------------------------------- /codemap/codemap.py: -------------------------------------------------------------------------------- 1 | from idaapi import * 2 | import idautils 3 | import idc 4 | import time 5 | import os 6 | import platform 7 | import threading 8 | import datetime 9 | import sqlite3 10 | from server.webserver import CodemapHTTPServer 11 | from server.webserver import CodemapHTTPRequestHandler 12 | from server.socketserver import * 13 | 14 | 15 | class Codemap(object): 16 | def __init__(self): 17 | self.homedir = '' 18 | sysname = platform.system() 19 | if sysname == 'Darwin' or sysname == 'Linux': 20 | self.homedir = '%s/.idapro/codemap/' % os.environ['HOME'] 21 | elif sysname == 'Windows': 22 | self.homedir = '%s\\Hex-Rays\\IDA Pro\\codemap\\' % os.environ['APPDATA'] 23 | 24 | self.server = '127.0.0.1' 25 | self.port = 9165 26 | self.bpevent_bufsize = 1 27 | self.bpevent_bufsize_max = 128 28 | self.bpevent_buffer = [] 29 | self.func_min_size = 16 30 | self.start = False 31 | self.pause = False 32 | self.data = '' 33 | self.mem_size = 48 34 | self.base = 0 35 | self.arch = 'x86' 36 | self.uid = None 37 | self.prev_uids = [] # remember previous uids for multi-client 38 | self.sqlite_conn = None 39 | self.sqlite_cursor = None 40 | self.dygraph = None 41 | self.skel = None 42 | self.interaction = None 43 | self.regs = '"id","eip"' 44 | self.thread_lock = threading.Lock() 45 | self.thread_http = None 46 | self.thread_ws = None 47 | self.db_name = 'codemap.db' 48 | self.websocket_server = None 49 | self.web_server = None 50 | self.seq_dict = {} 51 | self.init_codemap() 52 | 53 | self.query = '' 54 | 55 | if os.path.exists(self.homedir + 'codemap.db'): 56 | try: 57 | os.remove(self.homedir + 'codemap.db') 58 | except: 59 | print 'Codemap Database is locked.' 60 | print 'Please close another instance of Codemap and run IDA again' 61 | 62 | # init_arch func must called in BP. 63 | def init_arch(self): 64 | try: 65 | a = idc.GetRegValue('rip') 66 | self.arch = X64() 67 | except: 68 | self.arch = X86() 69 | 70 | def init_codemap(self): 71 | if not os.path.exists(self.homedir): 72 | os.makedirs(self.homedir) 73 | 74 | self.bpevent_buffer = [] 75 | self.bpevent_bufsize = 1 76 | self.start = False 77 | self.pause = False 78 | self.regs = '"id","eip"' 79 | return 80 | 81 | def is_buffer_full(self): 82 | return len(self.bpevent_buffer) >= self.bpevent_bufsize 83 | 84 | def clear_bpevent_buffer(self): 85 | self.bpevent_buffer = [] 86 | return 87 | 88 | def db_create(self): 89 | try: 90 | if self.sqlite_conn is None: 91 | self.sqlite_conn = sqlite3.connect(self.homedir + self.db_name) 92 | self.sqlite_cursor = self.sqlite_conn.cursor() 93 | 94 | if self.arch.name is 'x86': 95 | state_create = 'CREATE TABLE trace{0}'.format( 96 | self.uid) + '(id INTEGER PRIMARY KEY AUTOINCREMENT, ' 97 | for i in range(0, len(self.arch.reg_list)): 98 | state_create += self.arch.reg_list[i] 99 | state_create += ' INT8, ' 100 | state_create += 'm_' + self.arch.reg_list[i] 101 | state_create += ' VARCHAR(2048),' 102 | state_create = state_create.rstrip(',') 103 | state_create += ');' 104 | print state_create 105 | self.sqlite_cursor.execute(state_create) 106 | 107 | # sqlite3 does not support UINT8... fuck... 108 | if self.arch.name is 'x64': 109 | state_create = 'CREATE TABLE trace{0}'.format( 110 | self.uid) + '(id INTEGER PRIMARY KEY AUTOINCREMENT, ' 111 | for i in range(0, len(self.arch.reg_list)): 112 | if i == len(self.arch.reg_list) - 1: 113 | state_create += self.arch.reg_list[i] 114 | state_create += ' VARCHAR(32), ' 115 | state_create += 'm_' + self.arch.reg_list[i] 116 | state_create += ' VARCHAR(2048)' 117 | else: 118 | state_create += self.arch.reg_list[i] 119 | state_create += ' VARCHAR(32), ' 120 | state_create += 'm_' + self.arch.reg_list[i] 121 | state_create += ' VARCHAR(2048),' 122 | state_create += ');' 123 | self.sqlite_cursor.execute(state_create) 124 | 125 | return True 126 | except sqlite3.Error as e: 127 | print 'DB create error! Error message:' + e.args[0] 128 | print 'Create statement: ' + state_create 129 | return False 130 | 131 | def db_insert_queue(self): 132 | _dict = self.arch.dict_all() 133 | self.bpevent_buffer.append(_dict) 134 | if self.is_buffer_full(): 135 | self.db_insert() 136 | if self.bpevent_bufsize < self.bpevent_bufsize_max: 137 | self.bpevent_bufsize *= 2 138 | print 'buffser size up to ', self.bpevent_bufsize 139 | 140 | def db_insert(self): 141 | try: 142 | if self.sqlite_conn is None: 143 | self.sqlite_conn = sqlite3.connect(self.homedir + self.db_name) 144 | self.sqlite_cursor = self.sqlite_conn.cursor() 145 | state_insert = "insert into trace{0} ".format(self.uid) 146 | 147 | state_cols = '(' 148 | for i in range(0, len(self.arch.reg_list)): 149 | if(i == len(self.arch.reg_list) - 1): 150 | state_cols += self.arch.reg_list[i] + ', ' 151 | state_cols += 'm_' + self.arch.reg_list[i] 152 | else: 153 | state_cols += self.arch.reg_list[i] + ', ' 154 | state_cols += 'm_' + self.arch.reg_list[i] + ', ' 155 | state_cols += ')' 156 | 157 | state_vals = 'VALUES(' 158 | for i in range(0, len(self.arch.reg_list)): 159 | if(i == len(self.arch.reg_list) - 1): 160 | state_vals += ":" + self.arch.reg_list[i] + ', ' 161 | state_vals += ":m_" + self.arch.reg_list[i] 162 | else: 163 | state_vals += ":" + self.arch.reg_list[i] + ', ' 164 | state_vals += ":m_" + self.arch.reg_list[i] + ', ' 165 | state_vals += ')' 166 | 167 | state_insert = state_insert + ' ' + state_cols + ' ' + state_vals 168 | with self.thread_lock: 169 | self.sqlite_cursor.executemany( 170 | state_insert, self.bpevent_buffer) 171 | self.sqlite_conn.commit() 172 | self.clear_bpevent_buffer() 173 | 174 | return True 175 | except sqlite3.Error as e: 176 | print 'DB insert error! Error message: ' + e.args[0] 177 | print 'Insert statement: ' + state_insert 178 | return False 179 | 180 | def set_data(self): 181 | self.arch.set_reg() 182 | self.arch.set_memory(self.mem_size) 183 | 184 | def start_webserver(self): 185 | def webthread_start(): 186 | print time.asctime(), "Server Starts - %s:%s" % (self.server, self.port) 187 | self.web_server = CodemapHTTPServer( 188 | (self.server, self.port), CodemapHTTPRequestHandler) 189 | self.web_server.set_codemap(self) 190 | self.seq_dict.update({self.uid: []}) 191 | try: 192 | self.web_server.serve_forever() 193 | except KeyboardInterrupt: 194 | pass 195 | self.web_server.server_close() 196 | print time.asctime(), "Server Stops - %s:%s" % (self.server, self.port) 197 | 198 | if self.thread_http is not None: 199 | pass 200 | self.thread_http = threading.Thread(target=webthread_start) 201 | self.thread_http.daemon = True 202 | self.thread_http.start() 203 | 204 | def start_websocketserver(self): 205 | def ws_start(): 206 | self.websocket_server = SimpleWebSocketServer('', 4116, CodemapWSD) 207 | self.websocket_server.serveforever() 208 | if self.thread_ws is not None: 209 | return 210 | self.thread_ws = threading.Thread(target=ws_start) 211 | self.thread_ws.daemon = True 212 | self.thread_ws.start() 213 | 214 | def ws_send(msg): 215 | if self.websocket_server is not None: 216 | for conn in self.wsbsocket_server.connections.itervalues(): 217 | conn.sendMessage(msg) 218 | 219 | 220 | class BasicArchitecture(object): 221 | def __init__(self): 222 | self.name = "" 223 | self.reg_list = [] 224 | self.reg = {} 225 | self.memory = {} 226 | 227 | # default set_reg handles everything as string(x64 use this). 228 | def set_reg(self): 229 | for i in self.reg_list: 230 | self.reg[i] = str(idc.GetRegValue(i)) 231 | 232 | def set_memory(self, mem_size): 233 | for i in self.reg_list: 234 | # consider type of reg[i]. 235 | self.memory[i] = dbg_read_memory(int(self.reg[i]), mem_size) 236 | if self.memory[i] is not None: 237 | self.memory[i] = self.memory[i].encode('hex') 238 | else: 239 | self.memory[i] = '' 240 | 241 | # TODO: Rename!!!! 242 | def dict_all(self): 243 | _dict = {} 244 | _dict.update(self.reg) 245 | for k in self.memory.keys(): 246 | _dict.update({'m_' + k: self.memory[k]}) 247 | return _dict 248 | 249 | 250 | class X86(BasicArchitecture): 251 | def __init__(self): 252 | self.name = "x86" 253 | self.reg_list = ['eip', 'eax', 'ebx', 'ecx', 'edx', 'esi', 254 | 'edi', 'ebp', 'esp', 'arg1', 'arg2', 'arg3', 'arg4'] 255 | self.reg = {} 256 | self.memory = {} 257 | for i in self.reg_list: 258 | self.reg[i] = 0 # int 259 | self.memory[i] = '' 260 | 261 | def get_stack_arg(self, n): 262 | esp = idc.GetRegValue('esp') 263 | esp += n * 4 264 | val = dbg_read_memory(esp, 4)[::-1].encode('hex') 265 | return int(val, 16) 266 | 267 | # x86 overrides set_reg for integer version? -> maybe 268 | def set_reg(self): 269 | for i in self.reg_list: 270 | if 'arg' not in i: 271 | self.reg[i] = idc.GetRegValue(i) 272 | 273 | self.reg['arg1'] = self.get_stack_arg(1) 274 | self.reg['arg2'] = self.get_stack_arg(2) 275 | self.reg['arg3'] = self.get_stack_arg(3) 276 | self.reg['arg4'] = self.get_stack_arg(4) 277 | 278 | 279 | class X64(BasicArchitecture): 280 | def __init__(self): 281 | self.name = "x64" 282 | self.reg_list = ['rip', 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi', 283 | 'rbp', 'rsp', 'r8', 'r9', 'r10', 'r11', 'r12', 284 | 'r13', 'r14', 'r15'] 285 | self.reg = {} 286 | self.memory = {} 287 | for i in self.reg_list: 288 | self.reg[i] = '' # string 289 | self.memory[i] = '' 290 | -------------------------------------------------------------------------------- /codemap/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c0demap/codemap/c0a2f0079f19b5e8b56dd89da7471d61f21f49bb/codemap/server/__init__.py -------------------------------------------------------------------------------- /codemap/server/socketserver.py: -------------------------------------------------------------------------------- 1 | # websocket server for IDA static view interaction. 2 | # SimpleWebSocketServer 3 | # https://github.com/opiate/SimpleWebSocketServer.git 4 | ''' 5 | The MIT License (MIT) 6 | Copyright (c) 2013 Dave P. 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | ''' 11 | from idaapi import * 12 | import hashlib 13 | import base64 14 | import socket 15 | import struct 16 | import time 17 | import traceback 18 | import errno 19 | from BaseHTTPServer import BaseHTTPRequestHandler 20 | from StringIO import StringIO 21 | from select import select 22 | 23 | 24 | class HTTPRequest(BaseHTTPRequestHandler): 25 | def __init__(self, request_text): 26 | self.rfile = StringIO(request_text) 27 | self.raw_requestline = self.rfile.readline() 28 | self.error_code = self.error_message = None 29 | self.parse_request() 30 | 31 | 32 | class WebSocket(object): 33 | 34 | handshakeStr = ( 35 | "HTTP/1.1 101 Switching Protocols\r\n" 36 | "Upgrade: WebSocket\r\n" 37 | "Connection: Upgrade\r\n" 38 | "Sec-WebSocket-Protocol: codemap\r\n" 39 | "Sec-WebSocket-Accept: %(acceptstr)s\r\n\r\n" 40 | ) 41 | 42 | hixiehandshakedStr = ( 43 | "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" 44 | "Upgrade: WebSocket\r\n" 45 | "Connection: Upgrade\r\n" 46 | "Sec-WebSocket-Origin: %(origin)s\r\n" 47 | "Sec-WebSocket-Protocol: codemap\r\n" 48 | "Sec-WebSocket-Location: %(type)s://%(host)s%(location)s\r\n\r\n" 49 | ) 50 | 51 | GUIDStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 52 | 53 | STREAM = 0x0 54 | TEXT = 0x1 55 | BINARY = 0x2 56 | CLOSE = 0x8 57 | PING = 0x9 58 | PONG = 0xA 59 | 60 | HEADERB1 = 1 61 | HEADERB2 = 3 62 | LENGTHSHORT = 4 63 | LENGTHLONG = 5 64 | MASK = 6 65 | PAYLOAD = 7 66 | 67 | def __init__(self, server, sock, address): 68 | self.server = server 69 | self.client = sock 70 | self.address = address 71 | 72 | self.handshaked = False 73 | self.headerbuffer = '' 74 | self.readdraftkey = False 75 | self.draftkey = '' 76 | self.headertoread = 2048 77 | self.hixie76 = False 78 | 79 | self.fin = 0 80 | self.data = None 81 | self.opcode = 0 82 | self.hasmask = 0 83 | self.maskarray = None 84 | self.length = 0 85 | self.lengtharray = None 86 | self.index = 0 87 | self.request = None 88 | self.usingssl = False 89 | 90 | self.state = self.HEADERB1 91 | 92 | # restrict the size of header and payload for security reasons 93 | self.maxheader = 65536 94 | self.maxpayload = 4194304 95 | 96 | def close(self): 97 | self.client.close() 98 | self.state = self.HEADERB1 99 | self.hasmask = False 100 | self.handshaked = False 101 | self.readdraftkey = False 102 | self.hixie76 = False 103 | self.headertoread = 2048 104 | self.headerbuffer = '' 105 | self.data = '' 106 | 107 | def handleMessage(self): 108 | pass 109 | 110 | def handleConnected(self): 111 | pass 112 | 113 | def handleClose(self): 114 | pass 115 | 116 | def handlePacket(self): 117 | if self.opcode == self.CLOSE: 118 | self.sendClose() 119 | raise Exception("received client close") 120 | elif self.opcode == self.PING: 121 | pass 122 | elif self.opcode == self.PONG: 123 | pass 124 | elif self.opcode == self.STREAM or self.opcode == self.TEXT or self.opcode == self.BINARY: 125 | self.handleMessage() 126 | 127 | def handleData(self): 128 | # do the HTTP header and handshake 129 | if self.handshaked is False: 130 | data = self.client.recv(self.headertoread) 131 | if data: 132 | # accumulate 133 | self.headerbuffer += data 134 | if len(self.headerbuffer) >= self.maxheader: 135 | raise Exception('header exceeded allowable size') 136 | # we need to read the entire 8 bytes of after the HTTP header, ensure we do 137 | if self.readdraftkey is True: 138 | self.draftkey += self.headerbuffer 139 | read = self.headertoread - len(self.headerbuffer) 140 | if read != 0: 141 | self.headertoread = read 142 | else: 143 | # complete hixie76 handshake 144 | self.handshake_hixie76() 145 | # indicates end of HTTP header 146 | elif '\r\n\r\n' in self.headerbuffer: 147 | self.request = HTTPRequest(self.headerbuffer) 148 | # hixie handshake 149 | if self.request.headers.has_key('Sec-WebSocket-Key1'.lower()) and self.request.headers.has_key('Sec-WebSocket-Key2'.lower()): 150 | # check if we have the key in our buffer 151 | index = self.headerbuffer.find('\r\n\r\n') + 4 152 | # determine how much of the 8 byte key we have 153 | read = len(self.headerbuffer) - index 154 | # do we have all the 8 bytes we need? 155 | if read < 8: 156 | self.headertoread = 8 - read 157 | self.readdraftkey = True 158 | if read > 0: 159 | self.draftkey += self.headerbuffer[index:index + read] 160 | else: 161 | # get the key 162 | self.draftkey += self.headerbuffer[index:index + 8] 163 | # complete hixie handshake 164 | self.handshake_hixie76() 165 | # handshake rfc 6455 166 | elif self.request.headers.has_key('Sec-WebSocket-Key'.lower()): 167 | key = self.request.headers['Sec-WebSocket-Key'.lower()] 168 | hStr = self.handshakeStr % { 169 | 'acceptstr': base64.b64encode(hashlib.sha1(key + self.GUIDStr).digest())} 170 | self.sendBuffer(hStr) 171 | self.handshaked = True 172 | self.headerbuffer = '' 173 | try: 174 | self.handleConnected() 175 | except: 176 | pass 177 | else: 178 | raise Exception('Sec-WebSocket-Key does not exist') 179 | # remote connection has been closed 180 | else: 181 | raise Exception("remote socket closed") 182 | # else do normal data 183 | else: 184 | data = self.client.recv(2048) 185 | if data: 186 | for val in data: 187 | if self.hixie76 is False: 188 | self.parseMessage(ord(val)) 189 | else: 190 | self.parseMessage_hixie76(ord(val)) 191 | else: 192 | raise Exception("remote socket closed") 193 | 194 | def handshake_hixie76(self): 195 | k1 = self.request.headers['Sec-WebSocket-Key1'.lower()] 196 | k2 = self.request.headers['Sec-WebSocket-Key2'.lower()] 197 | spaces1 = k1.count(" ") 198 | spaces2 = k2.count(" ") 199 | num1 = int("".join([c for c in k1 if c.isdigit()])) / spaces1 200 | num2 = int("".join([c for c in k2 if c.isdigit()])) / spaces2 201 | key = '' 202 | key += struct.pack('>I', num1) 203 | key += struct.pack('>I', num2) 204 | key += self.draftkey 205 | typestr = 'ws' 206 | if self.usingssl is True: 207 | typestr = 'wss' 208 | response = self.hixiehandshakedStr % {'type': typestr, 'origin': self.request.headers[ 209 | 'Origin'.lower()], 'host': self.request.headers['Host'.lower()], 'location': self.request.path} 210 | self.sendBuffer(response) 211 | self.sendBuffer(hashlib.md5(key).digest()) 212 | self.handshaked = True 213 | self.hixie76 = True 214 | self.headerbuffer = '' 215 | 216 | try: 217 | self.handleConnected() 218 | except: 219 | pass 220 | 221 | def sendClose(self): 222 | msg = bytearray() 223 | if self.hixie76 is False: 224 | msg.append(0x88) 225 | msg.append(0x00) 226 | self.sendBuffer(msg) 227 | else: 228 | pass 229 | 230 | def sendBuffer(self, buff): 231 | size = len(buff) 232 | tosend = size 233 | index = 0 234 | 235 | while tosend > 0: 236 | try: 237 | # i should be able to send a bytearray 238 | sent = self.client.send(str(buff[index:size])) 239 | if sent == 0: 240 | raise RuntimeError("socket connection broken") 241 | 242 | index += sent 243 | tosend -= sent 244 | 245 | except socket.error as e: 246 | # if we have full buffers then wait for them to drain and try 247 | # again 248 | if e.errno == errno.EAGAIN: 249 | time.sleep(0.001) 250 | else: 251 | raise e 252 | 253 | # if s is a string then websocket TEXT is sent else BINARY 254 | def sendMessage(self, s): 255 | 256 | if self.hixie76 is False: 257 | header = bytearray() 258 | isString = isinstance(s, str) 259 | if isString is True: 260 | header.append(0x81) 261 | else: 262 | header.append(0x82) 263 | 264 | b2 = 0 265 | length = len(s) 266 | 267 | if length <= 125: 268 | b2 |= length 269 | header.append(b2) 270 | elif length >= 126 and length <= 65535: 271 | b2 |= 126 272 | header.append(b2) 273 | header.extend(struct.pack("!H", length)) 274 | else: 275 | b2 |= 127 276 | header.append(b2) 277 | header.extend(struct.pack("!Q", length)) 278 | 279 | if length > 0: 280 | self.sendBuffer(header + s) 281 | else: 282 | self.sendBuffer(header) 283 | header = None 284 | 285 | else: 286 | msg = bytearray() 287 | msg.append(0) 288 | if len(s) > 0: 289 | msg.extend(str(s).encode("UTF8")) 290 | msg.append(0xFF) 291 | 292 | self.sendBuffer(msg) 293 | msg = None 294 | 295 | def parseMessage_hixie76(self, byte): 296 | if self.state == self.HEADERB1: 297 | if byte == 0: 298 | self.state = self.PAYLOAD 299 | self.data = bytearray() 300 | 301 | elif self.state == self.PAYLOAD: 302 | if byte == 0xFF: 303 | self.opcode = 1 304 | self.length = len(self.data) 305 | try: 306 | self.handlePacket() 307 | finally: 308 | self.data = None 309 | self.state = self.HEADERB1 310 | else: 311 | self.data.append(byte) 312 | # if length exceeds allowable size then we except and remove 313 | # the connection 314 | if len(self.data) >= self.maxpayload: 315 | raise Exception('payload exceeded allowable size') 316 | 317 | def parseMessage(self, byte): 318 | # read in the header 319 | if self.state == self.HEADERB1: 320 | # fin 321 | self.fin = (byte & 0x80) 322 | # get opcode 323 | self.opcode = (byte & 0x0F) 324 | 325 | self.state = self.HEADERB2 326 | 327 | elif self.state == self.HEADERB2: 328 | mask = byte & 0x80 329 | length = byte & 0x7F 330 | 331 | if mask == 128: 332 | self.hasmask = True 333 | else: 334 | self.hasmask = False 335 | 336 | if length <= 125: 337 | self.length = length 338 | 339 | # if we have a mask we must read it 340 | if self.hasmask is True: 341 | self.maskarray = bytearray() 342 | self.state = self.MASK 343 | else: 344 | # if there is no mask and no payload we are done 345 | if self.length <= 0: 346 | try: 347 | self.handlePacket() 348 | finally: 349 | self.state = self.HEADERB1 350 | self.data = None 351 | 352 | # we have no mask and some payload 353 | else: 354 | self.index = 0 355 | self.data = bytearray() 356 | self.state = self.PAYLOAD 357 | 358 | elif length == 126: 359 | self.lengtharray = bytearray() 360 | self.state = self.LENGTHSHORT 361 | 362 | elif length == 127: 363 | self.lengtharray = bytearray() 364 | self.state = self.LENGTHLONG 365 | 366 | elif self.state == self.LENGTHSHORT: 367 | self.lengtharray.append(byte) 368 | 369 | if len(self.lengtharray) > 2: 370 | raise Exception('short length exceeded allowable size') 371 | 372 | if len(self.lengtharray) == 2: 373 | self.length = struct.unpack_from( 374 | '!H', str(self.lengtharray))[0] 375 | 376 | if self.hasmask is True: 377 | self.maskarray = bytearray() 378 | self.state = self.MASK 379 | else: 380 | # if there is no mask and no payload we are done 381 | if self.length <= 0: 382 | try: 383 | self.handlePacket() 384 | finally: 385 | self.state = self.HEADERB1 386 | self.data = None 387 | 388 | # we have no mask and some payload 389 | else: 390 | self.index = 0 391 | self.data = bytearray() 392 | self.state = self.PAYLOAD 393 | 394 | elif self.state == self.LENGTHLONG: 395 | self.lengtharray.append(byte) 396 | if len(self.lengtharray) > 8: 397 | raise Exception('long length exceeded allowable size') 398 | 399 | if len(self.lengtharray) == 8: 400 | self.length = struct.unpack_from( 401 | '!Q', str(self.lengtharray))[0] 402 | 403 | if self.hasmask is True: 404 | self.maskarray = bytearray() 405 | self.state = self.MASK 406 | else: 407 | # if there is no mask and no payload we are done 408 | if self.length <= 0: 409 | try: 410 | self.handlePacket() 411 | finally: 412 | self.state = self.HEADERB1 413 | self.data = None 414 | 415 | # we have no mask and some payload 416 | else: 417 | self.index = 0 418 | self.data = bytearray() 419 | self.state = self.PAYLOAD 420 | 421 | # MASK STATE 422 | elif self.state == self.MASK: 423 | self.maskarray.append(byte) 424 | 425 | if len(self.maskarray) > 4: 426 | raise Exception('mask exceeded allowable size') 427 | 428 | if len(self.maskarray) == 4: 429 | # if there is no mask and no payload we are done 430 | if self.length <= 0: 431 | try: 432 | self.handlePacket() 433 | finally: 434 | self.state = self.HEADERB1 435 | self.data = None 436 | 437 | # we have no mask and some payload 438 | else: 439 | self.index = 0 440 | self.data = bytearray() 441 | self.state = self.PAYLOAD 442 | 443 | # PAYLOAD STATE 444 | elif self.state == self.PAYLOAD: 445 | if self.hasmask is True: 446 | self.data.append(byte ^ self.maskarray[self.index % 4]) 447 | else: 448 | self.data.append(byte) 449 | 450 | # if length exceeds allowable size then we except and remove the 451 | # connection 452 | if len(self.data) >= self.maxpayload: 453 | raise Exception('payload exceeded allowable size') 454 | 455 | # check if we have processed length bytes; if so we are done 456 | if (self.index + 1) == self.length: 457 | try: 458 | self.handlePacket() 459 | finally: 460 | self.state = self.HEADERB1 461 | self.data = None 462 | else: 463 | self.index += 1 464 | 465 | 466 | class SimpleWebSocketServer(object): 467 | def __init__(self, host, port, websocketclass): 468 | self.websocketclass = websocketclass 469 | self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 470 | self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 471 | self.serversocket.bind((host, port)) 472 | self.serversocket.listen(5) 473 | self.connections = {} 474 | self.listeners = [self.serversocket] 475 | self.forceclose = False 476 | 477 | def decorateSocket(self, sock): 478 | return sock 479 | 480 | def constructWebSocket(self, sock, address): 481 | return self.websocketclass(self, sock, address) 482 | 483 | def close(self): 484 | self.serversocket.close() 485 | for conn in self.connections.itervalues(): 486 | try: 487 | conn.handleClose() 488 | except: 489 | pass 490 | conn.close() 491 | self.forceclose = True 492 | 493 | def serveforever(self): 494 | while True: 495 | rList, wList, xList = select(self.listeners, [], self.listeners, 1) 496 | if self.forceclose: 497 | break 498 | for ready in rList: 499 | if ready == self.serversocket: 500 | try: 501 | sock, address = self.serversocket.accept() 502 | newsock = self.decorateSocket(sock) 503 | newsock.setblocking(0) 504 | fileno = newsock.fileno() 505 | self.listeners.append(fileno) 506 | self.connections[fileno] = self.constructWebSocket( 507 | newsock, address) 508 | except: 509 | # logging.debug(str(address) + ' ' + str(n)) 510 | if sock is not None: 511 | sock.close() 512 | else: 513 | client = self.connections[ready] 514 | try: 515 | client.handleData() 516 | except: 517 | # logging.debug(str(client.address) + ' ' + str(n)) 518 | try: 519 | client.handleClose() 520 | except: 521 | pass 522 | client.close() 523 | del self.connections[ready] 524 | self.listeners.remove(ready) 525 | 526 | for failed in xList: 527 | if failed == self.serversocket: 528 | self.close() 529 | raise Exception("server socket failed") 530 | else: 531 | client = self.connections[failed] 532 | 533 | try: 534 | client.handleClose() 535 | except: 536 | pass 537 | 538 | client.close() 539 | 540 | del self.connections[failed] 541 | self.listeners.remove(failed) 542 | 543 | 544 | class CodemapWSD(WebSocket): 545 | jump_ip = 0 546 | 547 | def handleMessage(self): 548 | dat = self.data.split(" ") 549 | if dat[0] == "move": 550 | try: 551 | self.jump_ip = idaapi.toEA(0, int(str(dat[1]))) 552 | 553 | def my_jump(): 554 | try: 555 | jumpto(int(self.jump_ip)) # this is important. 556 | except: 557 | print traceback.print_exc(file=sys.stdout) 558 | # this is important. 559 | idaapi.execute_sync(my_jump, idaapi.MFF_FAST) 560 | except e: 561 | idaapi.msg("Error processing the address\n") 562 | 563 | def handleConnected(self): 564 | print "Codemap is connected" 565 | 566 | def handleClose(self): 567 | print "Codemap disconnected" 568 | -------------------------------------------------------------------------------- /codemap/server/webserver.py: -------------------------------------------------------------------------------- 1 | # http server for dygraph visualization. 2 | import sqlite3 3 | import BaseHTTPServer 4 | 5 | 6 | def hexdump(src, length=16): 7 | FILTER = ''.join( 8 | [(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) 9 | lines = [] 10 | for c in xrange(0, len(src), length): 11 | chars = src[c:c + length] 12 | hex = ' '.join(["%02x" % ord(x) for x in chars]) 13 | printable = ''.join( 14 | ["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or '.') for x in chars]) 15 | lines.append("%04x %-*s %s\n" % (c, length * 3, hex, printable)) 16 | 17 | res = ''.join(lines) 18 | res = res.replace('<', '<') 19 | res = res.replace('>', '>') 20 | res = res.replace('\n', '
') 21 | res += '
' * (2 - res.count('
')) 22 | return res 23 | 24 | 25 | class CodemapHTTPServer(BaseHTTPServer.HTTPServer): 26 | def set_codemap(self, codemap): 27 | self.codemap = codemap 28 | 29 | 30 | class CodemapHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 31 | 32 | def log_message(self, format, *args): 33 | return 34 | 35 | def do_GET(self): 36 | page = self.path[1:] 37 | codemap = self.server.codemap 38 | 39 | # find the requested uid(r_uid) among current uid + prev_uids. 40 | r_uid = 'invalid_request' 41 | if page.find(codemap.uid) != -1: 42 | r_uid = codemap.uid 43 | else: 44 | for uid in codemap.prev_uids: 45 | if page.find(uid) != -1: 46 | r_uid = uid 47 | break 48 | 49 | if page.startswith(r_uid): 50 | if len(page.split('?')) > 1: 51 | params = page.split('?')[1].split('&') 52 | for p in params: 53 | if p.startswith('sql='): 54 | sql = p.split('sql=')[1] 55 | # sql query is encoded with b64 56 | codemap.query = sql.decode('base64') 57 | codemap.query = codemap.query.lower() 58 | 59 | # if query does not start with [select], consider as hex string search parameter. 60 | if not codemap.query.startswith('select'): 61 | tmpsql = '' 62 | for i in range(0, len(codemap.arch.reg_list)): # skip ip register. 63 | if i==0: 64 | tmpsql += 'select '+codemap.arch.reg_list[i]+' from trace'+r_uid+' where 2=1 ' 65 | continue 66 | tmpsql += "or m_{0} like '%{1}%' ".format(codemap.arch.reg_list[i], codemap.query.replace(' ','')) 67 | 68 | codemap.query = tmpsql 69 | 70 | regs = codemap.query.split( 71 | 'select')[1].split('from')[0].split(',') 72 | codemap.regs = '"id",' 73 | for r in regs: 74 | codemap.regs += '"{0}",'.format(r) 75 | codemap.regs = codemap.regs[:-1] 76 | 77 | self.send_response(200) 78 | self.send_header("Content-type", "text/html") 79 | self.end_headers() 80 | 81 | skel = open(codemap.homedir + 'ui/skel.htm', 'rb').read() 82 | skel = skel.replace('--REPLACE--', codemap.uid) 83 | skel = skel.replace('--ARCH--', codemap.arch.name) 84 | # if no baseaddr is configured then 0 85 | if codemap.base == 0: 86 | skel = skel.replace('--BASEADDR--', '0') 87 | else: 88 | skel = skel.replace( 89 | '--BASEADDR--', hex(codemap.base).replace('0x', '')) 90 | skel = skel.replace( 91 | '--REGISTERS--', codemap.regs).replace('--SQL--', codemap.query) 92 | 93 | self.wfile.write( skel ) 94 | 95 | # dynamically generate csv data set. 96 | elif page == 'data' + r_uid + '.csv': 97 | codemap.seq_dict[r_uid] = [] 98 | self.send_response(200) 99 | self.send_header("Content-type", "text/html") 100 | self.end_headers() 101 | 102 | con = sqlite3.connect(codemap.homedir + "codemap.db") 103 | cur = con.cursor() 104 | 105 | sql = codemap.query.replace('select', 'select id,') 106 | cur.execute(sql) 107 | 108 | result = bytearray() 109 | seq = 1 110 | 111 | while True: 112 | r = cur.fetchone() 113 | if r is None: 114 | break 115 | line = '{0},'.format(seq) 116 | for i in xrange(len(codemap.regs.split(','))): 117 | if i == 0: 118 | codemap.seq_dict[r_uid].append(r[i]) 119 | continue 120 | line += '{0},'.format(r[i]) 121 | 122 | result = result + line[:-1] + '\n' # exception 123 | seq += 1 124 | self.wfile.write(str(result)) 125 | con.close() 126 | 127 | elif page == 'dygraph-combined.jself.map': 128 | self.send_response(200) 129 | self.send_header("Content-type", "text/html") 130 | self.end_headers() 131 | self.wfile.write('') 132 | 133 | elif page == 'favicon.ico': 134 | self.send_response(200) 135 | self.send_header("Content-type", "text/html") 136 | self.end_headers() 137 | self.wfile.write('') 138 | 139 | elif page == 'dygraph.js': 140 | dygraph = open(codemap.homedir + 'ui/dygraph.js', 'rb').read() 141 | self.send_response(200) 142 | self.send_header("Content-type", "text/javascript") 143 | self.end_headers() 144 | self.wfile.write(dygraph) 145 | 146 | elif page == 'interaction.js': 147 | interaction = open(codemap.homedir + 'ui/interaction.js', 'rb').read() 148 | self.send_response(200) 149 | self.send_header("Content-type", "text/javascript") 150 | self.end_headers() 151 | self.wfile.write(interaction) 152 | 153 | elif page.startswith('mapx86.php?'): 154 | params = page.split('?')[1].split('&') 155 | base = 0 156 | guid = 0 157 | param = 0 158 | for p in params: 159 | if p.startswith('base='): 160 | base = p.split('=')[1] 161 | if p.startswith('guid='): 162 | guid = p.split('=')[1] 163 | if p.startswith('param='): 164 | param = p.split('=')[1] 165 | 166 | # implement mapx86.php 167 | sql = "select * from trace{0} where id={1}".format( 168 | guid, codemap.seq_dict[guid][int(param) - 1]) 169 | 170 | with codemap.thread_lock: 171 | con = sqlite3.connect(codemap.homedir + "codemap.db") 172 | con.row_factory = sqlite3.Row 173 | cur = con.cursor() 174 | cur.execute(sql) 175 | r = cur.fetchone() 176 | 177 | response = ''' 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
arg1[{arg1}]
{m_arg1}
arg2[{arg2}]
{m_arg2}
arg3[{arg3}]
{m_arg3}
arg4[{arg4}]
{m_arg4}
eax[{eax}]
{m_eax}
ebx[{ebx}]
{m_ebx}
ecx[{ecx}]
{m_ecx}
edx[{edx}]
{m_edx}
esi[{esi}]
{m_esi}
edi[{edi}]
{m_edi}
ebp[{ebp}]
{m_ebp}
esp[{esp}]
{m_esp}
192 | 193 | 194 | '''.format( 195 | eip=hex(int(r['eip'])), 196 | arg1=hex(int(r['arg1'])), m_arg1=hexdump(r['m_arg1'].decode('hex')), arg2=hex(int(r['arg2'])), m_arg2=hexdump(r['m_arg2'].decode('hex')), 197 | arg3=hex(int(r['arg3'])), m_arg3=hexdump(r['m_arg3'].decode('hex')), arg4=hex(int(r['arg4'])), m_arg4=hexdump(r['m_arg4'].decode('hex')), 198 | eax=hex(int(r['eax'])), m_eax=hexdump(r['m_eax'].decode('hex')), ebx=hex(int(r['ebx'])), m_ebx=hexdump(r['m_ebx'].decode('hex')), 199 | ecx=hex(int(r['ecx'])), m_ecx=hexdump(r['m_ecx'].decode('hex')), edx=hex(int(r['edx'])), m_edx=hexdump(r['m_edx'].decode('hex')), 200 | esi=hex(int(r['esi'])), m_esi=hexdump(r['m_esi'].decode('hex')), edi=hex(int(r['edi'])), m_edi=hexdump(r['m_edi'].decode('hex')), 201 | ebp=hex(int(r['ebp'])), m_ebp=hexdump(r['m_ebp'].decode('hex')), esp=hex(int(r['esp'])), m_esp=hexdump(r['m_esp'].decode('hex')) 202 | ) 203 | con.close() 204 | 205 | self.send_response(200) 206 | self.send_header("Content-type", "text/html") 207 | self.end_headers() 208 | self.wfile.write(response) 209 | 210 | elif page.startswith('mapx64.php?'): 211 | params = page.split('?')[1].split('&') 212 | base = 0 213 | guid = 0 214 | param = 0 215 | for p in params: 216 | if p.startswith('base='): 217 | base = p.split('=')[1] 218 | if p.startswith('guid='): 219 | guid = p.split('=')[1] 220 | if p.startswith('param='): 221 | param = p.split('=')[1] 222 | # implement mapx64.php 223 | with codemap.thread_lock: 224 | con = sqlite3.connect(codemap.homedir + "codemap.db") 225 | con.row_factory = sqlite3.Row 226 | cur = con.cursor() 227 | sql = "select * from trace{0} where id={1}".format( 228 | guid, codemap.seq_dict[guid][int(param) - 1]) 229 | cur.execute(sql) 230 | r = cur.fetchone() 231 | response = ''' 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 |
rax[{rax}]
{m_rax}
rbx[{rbx}]
{m_rbx}
rcx[{rcx}]
{m_rcx}
rdx[{rdx}]
{m_rdx}
rsi[{rsi}]
{m_rsi}
rdi[{rdi}]
{m_rdi}
rbp[{rbp}]
{m_rbp}
rsp[{rsp}]
{m_rsp}
r8[{r8}]
{m_r8}
r9[{r9}]
{m_r9}
r10[{r10}]
{m_r11}
r11[{r11}]
{m_r11}
r12[{r12}]
{m_r12}
r13[{r13}]
{m_r13}
r14[{r14}]
{m_r14}
r15[{r15}]
{m_r15}
248 | 249 | 250 | '''.format( 251 | rip=hex(int(r['rip'])), 252 | rax=hex(int(r['rax'])), m_rax=hexdump(r['m_rax'].decode('hex')), rbx=hex(int(r['rbx'])), m_rbx=hexdump(r['m_rbx'].decode('hex')), 253 | rcx=hex(int(r['rcx'])), m_rcx=hexdump(r['m_rcx'].decode('hex')), rdx=hex(int(r['rdx'])), m_rdx=hexdump(r['m_rdx'].decode('hex')), 254 | rsi=hex(int(r['rsi'])), m_rsi=hexdump(r['m_rsi'].decode('hex')), rdi=hex(int(r['rdi'])), m_rdi=hexdump(r['m_rdi'].decode('hex')), 255 | rbp=hex(int(r['rbp'])), m_rbp=hexdump(r['m_rbp'].decode('hex')), rsp=hex(int(r['rsp'])), m_rsp=hexdump(r['m_rsp'].decode('hex')), 256 | r8=hex(int(r['r8'])), m_r8=hexdump(r['m_r8'].decode('hex')), r9=hex(int(r['r9'])), m_r9=hexdump(r['m_r9'].decode('hex')), 257 | r10=hex(int(r['r10'])), m_r10=hexdump(r['m_r10'].decode('hex')), r11=hex(int(r['r11'])), m_r11=hexdump(r['m_r11'].decode('hex')), 258 | r12=hex(int(r['r12'])), m_r12=hexdump(r['m_r12'].decode('hex')), r13=hex(int(r['r13'])), m_r13=hexdump(r['m_r13'].decode('hex')), 259 | r14=hex(int(r['r14'])), m_r14=hexdump(r['m_r14'].decode('hex')), r15=hex(int(r['r15'])), m_r15=hexdump(r['m_r15'].decode('hex')) 260 | ) 261 | con.close() 262 | 263 | self.send_response(200) 264 | self.send_header("Content-type", "text/html") 265 | self.end_headers() 266 | self.wfile.write(response) 267 | 268 | else: 269 | self.send_response(404) 270 | self.send_header("Content-type", "text/html") 271 | self.end_headers() 272 | self.wfile.write('') 273 | print 'unknown page ', page 274 | -------------------------------------------------------------------------------- /codemap/ui/dygraph.js: -------------------------------------------------------------------------------- 1 | /*! @license Copyright 2014 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */ 2 | !function(t){"use strict";for(var e,a,i={},r=function(){},n="memory".split(","),o="assert,clear,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn".split(",");e=n.pop();)t[e]=t[e]||i;for(;a=o.pop();)t[a]=t[a]||r}(this.console=this.console||{}),function(){"use strict";CanvasRenderingContext2D.prototype.installPattern=function(t){if("undefined"!=typeof this.isPatternInstalled)throw"Must un-install old line pattern before installing a new one.";this.isPatternInstalled=!0;var e=[0,0],a=[],i=this.beginPath,r=this.lineTo,n=this.moveTo,o=this.stroke;this.uninstallPattern=function(){this.beginPath=i,this.lineTo=r,this.moveTo=n,this.stroke=o,this.uninstallPattern=void 0,this.isPatternInstalled=void 0},this.beginPath=function(){a=[],i.call(this)},this.moveTo=function(t,e){a.push([[t,e]]),n.call(this,t,e)},this.lineTo=function(t,e){var i=a[a.length-1];i.push([t,e])},this.stroke=function(){if(0===a.length)return void o.call(this);for(var i=0;if;){var x=t[v];f+=e[1]?e[1]:x,f>y?(e=[v,f-y],f=y):e=[(v+1)%t.length,0],v%2===0?r.call(this,f,0):n.call(this,f,0),v=(v+1)%t.length}this.restore(),l=g,h=d}o.call(this),a=[]}},CanvasRenderingContext2D.prototype.uninstallPattern=function(){throw"Must install a line pattern before uninstalling it."}}();var DygraphOptions=function(){return function(){"use strict";var t=function(t){this.dygraph_=t,this.yAxes_=[],this.xAxis_={},this.series_={},this.global_=this.dygraph_.attrs_,this.user_=this.dygraph_.user_attrs_||{},this.labels_=[],this.highlightSeries_=this.get("highlightSeriesOpts")||{},this.reparseSeries()};t.AXIS_STRING_MAPPINGS_={y:0,Y:0,y1:0,Y1:0,y2:1,Y2:1},t.axisToIndex_=function(e){if("string"==typeof e){if(t.AXIS_STRING_MAPPINGS_.hasOwnProperty(e))return t.AXIS_STRING_MAPPINGS_[e];throw"Unknown axis : "+e}if("number"==typeof e){if(0===e||1===e)return e;throw"Dygraphs only supports two y-axes, indexed from 0-1."}if(e)throw"Unknown axis : "+e;return 0},t.prototype.reparseSeries=function(){var e=this.get("labels");if(e){this.labels_=e.slice(1),this.yAxes_=[{series:[],options:{}}],this.xAxis_={options:{}},this.series_={};var a=!this.user_.series;if(a){for(var i=0,r=0;r1&&Dygraph.update(this.yAxes_[1].options,h.y2||{}),Dygraph.update(this.xAxis_.options,h.x||{})}},t.prototype.get=function(t){var e=this.getGlobalUser_(t);return null!==e?e:this.getGlobalDefault_(t)},t.prototype.getGlobalUser_=function(t){return this.user_.hasOwnProperty(t)?this.user_[t]:null},t.prototype.getGlobalDefault_=function(t){return this.global_.hasOwnProperty(t)?this.global_[t]:Dygraph.DEFAULT_ATTRS.hasOwnProperty(t)?Dygraph.DEFAULT_ATTRS[t]:null},t.prototype.getForAxis=function(t,e){var a,i;if("number"==typeof e)a=e,i=0===a?"y":"y2";else{if("y1"==e&&(e="y"),"y"==e)a=0;else if("y2"==e)a=1;else{if("x"!=e)throw"Unknown axis "+e;a=-1}i=e}var r=-1==a?this.xAxis_:this.yAxes_[a];if(r){var n=r.options;if(n.hasOwnProperty(t))return n[t]}if("x"!==e||"logscale"!==t){var o=this.getGlobalUser_(t);if(null!==o)return o}var s=Dygraph.DEFAULT_ATTRS.axes[i];return s.hasOwnProperty(t)?s[t]:this.getGlobalDefault_(t)},t.prototype.getForSeries=function(t,e){if(e===this.dygraph_.getHighlightSeries()&&this.highlightSeries_.hasOwnProperty(t))return this.highlightSeries_[t];if(!this.series_.hasOwnProperty(e))throw"Unknown series: "+e;var a=this.series_[e],i=a.options;return i.hasOwnProperty(t)?i[t]:this.getForAxis(t,a.yAxis)},t.prototype.numAxes=function(){return this.yAxes_.length},t.prototype.axisForSeries=function(t){return this.series_[t].yAxis},t.prototype.axisOptions=function(t){return this.yAxes_[t].options},t.prototype.seriesForAxis=function(t){return this.yAxes_[t].series},t.prototype.seriesNames=function(){return this.labels_};return t}()}(),DygraphLayout=function(){"use strict";var t=function(t){this.dygraph_=t,this.points=[],this.setNames=[],this.annotations=[],this.yAxes_=null,this.xTicks_=null,this.yTicks_=null};return t.prototype.addDataset=function(t,e){this.points.push(e),this.setNames.push(t)},t.prototype.getPlotArea=function(){return this.area_},t.prototype.computePlotArea=function(){var t={x:0,y:0};t.w=this.dygraph_.width_-t.x-this.dygraph_.getOption("rightGap"),t.h=this.dygraph_.height_;var e={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(e){var a={x:t.x,y:t.y,w:e,h:t.h};return t.x+=e,t.w-=e,a},reserveSpaceRight:function(e){var a={x:t.x+t.w-e,y:t.y,w:e,h:t.h};return t.w-=e,a},reserveSpaceTop:function(e){var a={x:t.x,y:t.y,w:t.w,h:e};return t.y+=e,t.h-=e,a},reserveSpaceBottom:function(e){var a={x:t.x,y:t.y+t.h-e,w:t.w,h:e};return t.h-=e,a},chartRect:function(){return{x:t.x,y:t.y,w:t.w,h:t.h}}};this.dygraph_.cascadeEvents_("layout",e),this.area_=t},t.prototype.setAnnotations=function(t){this.annotations=[];for(var e=this.dygraph_.getOption("xValueParser")||function(t){return t},a=0;a=0&&1>i&&this.xticks.push([i,a]);for(this.yticks=[],t=0;t0&&1>=i&&this.yticks.push([t,i,a])},t.prototype._evaluateAnnotations=function(){var t,e={};for(t=0;t=0;i--)a.childNodes[i].className==e&&a.removeChild(a.childNodes[i]);for(var r=document.bgColor,n=this.dygraph_.graphDiv;n!=document;){var o=n.currentStyle.backgroundColor;if(o&&"transparent"!=o){r=o;break}n=n.parentNode}var s=this.area;t({x:0,y:0,w:s.x,h:this.height}),t({x:s.x,y:0,w:this.width-s.x,h:s.y}),t({x:s.x+s.w,y:0,w:this.width-s.x-s.w,h:this.height}),t({x:s.x,y:s.y+s.h,w:this.width-s.x,h:this.height-s.h-s.y})},t._getIteratorPredicate=function(e){return e?t._predicateThatSkipsEmptyPoints:null},t._predicateThatSkipsEmptyPoints=function(t,e){return null!==t[e].yval},t._drawStyledLine=function(e,a,i,r,n,o,s){var l=e.dygraph,h=l.getBooleanOption("stepPlot",e.setName);Dygraph.isArrayLike(r)||(r=null);var p=l.getBooleanOption("drawGapEdgePoints",e.setName),g=e.points,d=e.setName,u=Dygraph.createIterator(g,0,g.length,t._getIteratorPredicate(l.getBooleanOption("connectSeparatedPoints",d))),c=r&&r.length>=2,y=e.drawingContext;y.save(),c&&y.installPattern(r);var _=t._drawSeries(e,u,i,s,n,p,h,a);t._drawPointsOnLine(e,_,o,a,s),c&&y.uninstallPattern(),y.restore()},t._drawSeries=function(t,e,a,i,r,n,o,s){var l,h,p=null,g=null,d=null,u=[],c=!0,y=t.drawingContext;y.beginPath(),y.strokeStyle=s,y.lineWidth=a;for(var _=e.array_,v=e.end_,f=e.predicate_,x=e.start_;v>x;x++){if(h=_[x],f){for(;v>x&&!f(_,x);)x++;if(x==v)break;h=_[x]}if(null===h.canvasy||h.canvasy!=h.canvasy)o&&null!==p&&(y.moveTo(p,g),y.lineTo(h.canvasx,g)),p=g=null;else{if(l=!1,n||!p){e.nextIdx_=x,e.next(),d=e.hasNext?e.peek.canvasy:null;var m=null===d||d!=d;l=!p&&m,n&&(!c&&!p||e.hasNext&&m)&&(l=!0)}null!==p?a&&(o&&(y.moveTo(p,g),y.lineTo(h.canvasx,g)),y.lineTo(h.canvasx,h.canvasy)):y.moveTo(h.canvasx,h.canvasy),(r||l)&&u.push([h.canvasx,h.canvasy,h.idx]),p=h.canvasx,g=h.canvasy}c=!1}return y.stroke(),u},t._drawPointsOnLine=function(t,e,a,i,r){for(var n=t.drawingContext,o=0;o0;a--){var i=e[a];if(i[0]==n){var o=e[a-1];o[1]==i[1]&&o[2]==i[2]&&e.splice(a,1)}}for(var a=0;a2&&!t){var s=0;e[0][0]==n&&s++;for(var l=null,h=null,a=s;ae[h][2]&&(h=a)}}var g=e[l],d=e[h];e.splice(s,e.length-s),h>l?(e.push(g),e.push(d)):l>h?(e.push(d),e.push(g)):e.push(g)}}},l=function(a){s(a);for(var l=0,h=e.length;h>l;l++){var p=e[l];p[0]==r?t.lineTo(p[1],p[2]):p[0]==n&&t.moveTo(p[1],p[2])}e.length&&(i=e[e.length-1][1]),o+=e.length,e=[]},h=function(t,r,n){var o=Math.round(r);if(null===a||o!=a){var s=a-i>1,h=o-a>1,p=s||h;l(p),a=o}e.push([t,r,n])};return{moveTo:function(t,e){h(n,t,e)},lineTo:function(t,e){h(r,t,e)},stroke:function(){l(!0),t.stroke()},fill:function(){l(!0),t.fill()},beginPath:function(){l(!0),t.beginPath()},closePath:function(){l(!0),t.closePath()},_count:function(){return o}}},t._fillPlotter=function(e){if(!e.singleSeriesName&&0===e.seriesIndex){for(var a=e.dygraph,i=a.getLabels().slice(1),r=i.length;r>=0;r--)a.visibility()[r]||i.splice(r,1);var n=function(){for(var t=0;t=0;r--){var n=i[r];t.lineTo(n[0],n[1])}},_=p-1;_>=0;_--){var v=e.drawingContext,f=i[_];if(a.getBooleanOption("fillGraph",f)){var x=a.getBooleanOption("stepPlot",f),m=u[_],D=a.axisPropertiesForSeries(f),w=1+D.minyval*D.yscale;0>w?w=0:w>1&&(w=1),w=l.h*w+l.y;var A,b=h[_],T=Dygraph.createIterator(b,0,b.length,t._getIteratorPredicate(a.getBooleanOption("connectSeparatedPoints",f))),E=0/0,C=[-1,-1],L=Dygraph.toRGB_(m),P="rgba("+L.r+","+L.g+","+L.b+","+g+")";v.fillStyle=P,v.beginPath();var S,O=!0;(b.length>2*a.width_||Dygraph.FORCE_FAST_PROXY)&&(v=t._fastCanvasProxy(v));for(var M,R=[];T.hasNext;)if(M=T.next(),Dygraph.isOK(M.y)||x){if(d){if(!O&&S==M.xval)continue;O=!1,S=M.xval,o=c[M.canvasx];var F;F=void 0===o?w:s?o[0]:o,A=[M.canvasy,F],x?-1===C[0]?c[M.canvasx]=[M.canvasy,w]:c[M.canvasx]=[M.canvasy,C[0]]:c[M.canvasx]=M.canvasy}else A=isNaN(M.canvasy)&&x?[l.y+l.h,w]:[M.canvasy,w];isNaN(E)?(v.moveTo(M.canvasx,A[1]),v.lineTo(M.canvasx,A[0])):(x?(v.lineTo(M.canvasx,C[0]),v.lineTo(M.canvasx,A[0])):v.lineTo(M.canvasx,A[0]),d&&(R.push([E,C[1]]),R.push(s&&o?[M.canvasx,o[1]]:[M.canvasx,A[1]]))),C=A,E=M.canvasx}else y(v,E,C[1],R),R=[],E=0/0,null===M.y_stacked||isNaN(M.y_stacked)||(c[M.canvasx]=l.h*M.y_stacked+l.y);s=x,A&&M&&(y(v,M.canvasx,A[1],R),R=[]),v.fill()}}}},t}(),Dygraph=function(){"use strict";var t=function(t,e,a,i){this.is_initial_draw_=!0,this.readyFns_=[],void 0!==i?(console.warn("Using deprecated four-argument dygraph constructor"),this.__old_init__(t,e,a,i)):this.__init__(t,e,a)};return t.NAME="Dygraph",t.VERSION="1.1.1",t.__repr__=function(){return"["+t.NAME+" "+t.VERSION+"]"},t.toString=function(){return t.__repr__()},t.DEFAULT_ROLL_PERIOD=1,t.DEFAULT_WIDTH=480,t.DEFAULT_HEIGHT=320,t.ANIMATION_STEPS=12,t.ANIMATION_DURATION=200,t.KMB_LABELS=["K","M","B","T","Q"],t.KMG2_BIG_LABELS=["k","M","G","T","P","E","Z","Y"],t.KMG2_SMALL_LABELS=["m","u","n","p","f","a","z","y"],t.numberValueFormatter=function(e,a){var i=a("sigFigs");if(null!==i)return t.floatFormat(e,i);var r,n=a("digitsAfterDecimal"),o=a("maxNumberWidth"),s=a("labelsKMB"),l=a("labelsKMG2");if(r=0!==e&&(Math.abs(e)>=Math.pow(10,o)||Math.abs(e)=0;c--,u/=h)if(d>=u){r=t.round_(e/u,n)+p[c];break}if(l){var y=String(e.toExponential()).split("e-");2===y.length&&y[1]>=3&&y[1]<=24&&(r=y[1]%3>0?t.round_(y[0]/t.pow(10,y[1]%3),n):Number(y[0]).toFixed(2),r+=g[Math.floor(y[1]/3)-1])}}return r},t.numberAxisLabelFormatter=function(e,a,i){return t.numberValueFormatter.call(this,e,i)},t.SHORT_MONTH_NAMES_=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],t.dateAxisLabelFormatter=function(e,a,i){var r=i("labelsUTC"),n=r?t.DateAccessorsUTC:t.DateAccessorsLocal,o=n.getFullYear(e),s=n.getMonth(e),l=n.getDate(e),h=n.getHours(e),p=n.getMinutes(e),g=n.getSeconds(e),d=n.getSeconds(e);if(a>=t.DECADAL)return""+o;if(a>=t.MONTHLY)return t.SHORT_MONTH_NAMES_[s]+" "+o;var u=3600*h+60*p+g+.001*d;return 0===u||a>=t.DAILY?t.zeropad(l)+" "+t.SHORT_MONTH_NAMES_[s]:t.hmsString_(h,p,g)},t.dateAxisFormatter=t.dateAxisLabelFormatter,t.dateValueFormatter=function(e,a){return t.dateString_(e,a("labelsUTC"))},t.Plotters=DygraphCanvasRenderer._Plotters,t.DEFAULT_ATTRS={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:.5,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:!1,labelsShowZeroValues:!0,labelsKMB:!1,labelsKMG2:!1,showLabelsOnHighlight:!0,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,rightGap:5,showRoller:!1,xValueParser:t.dateParser,delimiter:",",sigma:2,errorBars:!1,fractions:!1,wilsonInterval:!0,customBars:!1,fillGraph:!1,fillAlpha:.15,connectSeparatedPoints:!1,stackedGraph:!1,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:!0,legend:"onmouseover",stepPlot:!1,avoidMinZero:!1,xRangePad:0,yRangePad:null,drawAxesAtZero:!1,titleHeight:28,xLabelHeight:18,yLabelWidth:18,drawXAxis:!0,drawYAxis:!0,axisLineColor:"black",axisLineWidth:.3,gridLineWidth:.3,axisLabelColor:"black",axisLabelWidth:50,drawYGrid:!0,drawXGrid:!0,gridLineColor:"rgb(128,128,128)",interactionModel:null,animatedZooms:!1,showRangeSelector:!1,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillColor:"#A7B1C4",showInRangeSelector:null,plotter:[t.Plotters.fillPlotter,t.Plotters.errorPlotter,t.Plotters.linePlotter],plugins:[],axes:{x:{pixelsPerLabel:70,axisLabelWidth:60,axisLabelFormatter:t.dateAxisLabelFormatter,valueFormatter:t.dateValueFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:null},y:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:null},y2:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawAxis:!0,drawGrid:!1,independentTicks:!1,ticker:null}}},t.HORIZONTAL=1,t.VERTICAL=2,t.PLUGINS=[],t.addedAnnotationCSS=!1,t.prototype.__old_init__=function(e,a,i,r){if(null!==i){for(var n=["Date"],o=0;o=0;n--){var o=r[n][0],s=r[n][1];if(s.call(o,i),i.propagationStopped)break}return i.defaultPrevented},t.prototype.getPluginInstance_=function(t){for(var e=0;et||t>=this.axes_.length)return null;var e=this.axes_[t];return[e.computedValueRange[0],e.computedValueRange[1]]},t.prototype.yAxisRanges=function(){for(var t=[],e=0;et||t>this.rawData_.length?null:0>e||e>this.rawData_[t].length?null:this.rawData_[t][e]},t.prototype.createInterface_=function(){var e=this.maindiv_;this.graphDiv=document.createElement("div"),this.graphDiv.style.textAlign="left",this.graphDiv.style.position="relative",e.appendChild(this.graphDiv),this.canvas_=t.createCanvas(),this.canvas_.style.position="absolute",this.hidden_=this.createPlotKitCanvas_(this.canvas_),this.canvas_ctx_=t.getContext(this.canvas_),this.hidden_ctx_=t.getContext(this.hidden_),this.resizeElements_(),this.graphDiv.appendChild(this.hidden_),this.graphDiv.appendChild(this.canvas_),this.mouseEventElement_=this.createMouseEventElement_(),this.layout_=new DygraphLayout(this);var a=this;this.mouseMoveHandler_=function(t){a.mouseMove_(t)},this.mouseOutHandler_=function(e){var i=e.target||e.fromElement,r=e.relatedTarget||e.toElement;t.isNodeContainedBy(i,a.graphDiv)&&!t.isNodeContainedBy(r,a.graphDiv)&&a.mouseOut_(e)},this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_),this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeHandler_||(this.resizeHandler_=function(t){a.resize()},this.addAndTrackEvent(window,"resize",this.resizeHandler_))},t.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px",this.graphDiv.style.height=this.height_+"px";var e=t.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width=this.width_*e,this.canvas_.height=this.height_*e,this.canvas_.style.width=this.width_+"px",this.canvas_.style.height=this.height_+"px",1!==e&&this.canvas_ctx_.scale(e,e);var a=t.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width=this.width_*a,this.hidden_.height=this.height_*a,this.hidden_.style.width=this.width_+"px",this.hidden_.style.height=this.height_+"px",1!==a&&this.hidden_ctx_.scale(a,a)},t.prototype.destroy=function(){this.canvas_ctx_.restore(),this.hidden_ctx_.restore();for(var e=this.plugins_.length-1;e>=0;e--){var a=this.plugins_.pop();a.plugin.destroy&&a.plugin.destroy()}var i=function(t){for(;t.hasChildNodes();)i(t.firstChild),t.removeChild(t.firstChild)};this.removeTrackedEvents_(),t.removeEvent(window,"mouseout",this.mouseOutHandler_),t.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),t.removeEvent(window,"resize",this.resizeHandler_),this.resizeHandler_=null,i(this.maindiv_);var r=function(t){for(var e in t)"object"==typeof t[e]&&(t[e]=null)};r(this.layout_),r(this.plotter_),r(this)},t.prototype.createPlotKitCanvas_=function(e){var a=t.createCanvas();return a.style.position="absolute",a.style.top=e.style.top,a.style.left=e.style.left,a.width=this.width_,a.height=this.height_,a.style.width=this.width_+"px",a.style.height=this.height_+"px",a},t.prototype.createMouseEventElement_=function(){if(this.isUsingExcanvas_){var t=document.createElement("div");return t.style.position="absolute",t.style.backgroundColor="white",t.style.filter="alpha(opacity=0)",t.style.width=this.width_+"px",t.style.height=this.height_+"px",this.graphDiv.appendChild(t),t}return this.canvas_},t.prototype.setColors_=function(){var e=this.getLabels(),a=e.length-1;this.colors_=[],this.colorsMap_={};for(var i=this.getNumericOption("colorSaturation")||1,r=this.getNumericOption("colorValue")||.5,n=Math.ceil(a/2),o=this.getOption("colors"),s=this.visibility(),l=0;a>l;l++)if(s[l]){ 3 | var h=e[l+1],p=this.attributes_.getForSeries("color",h);if(!p)if(o)p=o[l%o.length];else{var g=l%2?n+(l+1)/2:Math.ceil((l+1)/2),d=1*g/(1+a);p=t.hsvToRGB(d,i,r)}this.colors_.push(p),this.colorsMap_[h]=p}},t.prototype.getColors=function(){return this.colors_},t.prototype.getPropertiesForSeries=function(t){for(var e=-1,a=this.getLabels(),i=1;i=o;o++)s=t.zoomAnimationFunction(o,l),h[o-1]=[e[0]*(1-s)+s*a[0],e[1]*(1-s)+s*a[1]];if(null!==i&&null!==r)for(o=1;l>=o;o++){s=t.zoomAnimationFunction(o,l);for(var g=[],d=0;dl;l++){var h=o[l];if(t.isValidPoint(h,!0)){var p=Math.abs(h.canvasx-e);a>p&&(a=p,i=h.idx)}}return i},t.prototype.findClosestPoint=function(e,a){for(var i,r,n,o,s,l,h,p=1/0,g=this.layout_.points.length-1;g>=0;--g)for(var d=this.layout_.points[g],u=0;ui&&(p=i,s=o,l=g,h=o.idx));var c=this.layout_.setNames[l];return{row:h,seriesName:c,point:s}},t.prototype.findStackedPoint=function(e,a){for(var i,r,n=this.findClosestRow(e),o=0;o=h.length)){var p=h[l];if(t.isValidPoint(p)){var g=p.canvasy;if(e>p.canvasx&&l+10){var c=(e-p.canvasx)/u;g+=c*(d.canvasy-p.canvasy)}}}else if(e0){var y=h[l-1];if(t.isValidPoint(y)){var u=p.canvasx-y.canvasx;if(u>0){var c=(p.canvasx-e)/u;g+=c*(y.canvasy-p.canvasy)}}}(0===o||a>g)&&(i=p,r=o)}}}var _=this.layout_.setNames[r];return{row:n,seriesName:_,point:i}},t.prototype.mouseMove_=function(t){var e=this.layout_.points;if(void 0!==e&&null!==e){var a=this.eventToDomCoords(t),i=a[0],r=a[1],n=this.getOption("highlightSeriesOpts"),o=!1;if(n&&!this.isSeriesLocked()){var s;s=this.getBooleanOption("stackedGraph")?this.findStackedPoint(i,r):this.findClosestPoint(i,r),o=this.setSelection(s.row,s.seriesName)}else{var l=this.findClosestRow(i);o=this.setSelection(l)}var h=this.getFunctionOption("highlightCallback");h&&o&&h.call(this,t,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_)}},t.prototype.getLeftBoundary_=function(t){if(this.boundaryIds_[t])return this.boundaryIds_[t][0];for(var e=0;ee?r:a-r;if(0>=n)return void(this.fadeLevel&&this.updateSelection_(1));var o=++this.animateId,s=this;t.repeatAndCleanup(function(t){s.animateId==o&&(s.fadeLevel+=e,0===s.fadeLevel?s.clearSelection():s.updateSelection_(s.fadeLevel/a))},n,i,function(){})},t.prototype.updateSelection_=function(e){this.cascadeEvents_("select",{selectedRow:this.lastRow_,selectedX:this.lastx_,selectedPoints:this.selPoints_});var a,i=this.canvas_ctx_;if(this.getOption("highlightSeriesOpts")){i.clearRect(0,0,this.width_,this.height_);var r=1-this.getNumericOption("highlightSeriesBackgroundAlpha");if(r){var n=!0;if(n){if(void 0===e)return void this.animateSelection_(1);r*=e}i.fillStyle="rgba(255,255,255,"+r+")",i.fillRect(0,0,this.width_,this.height_)}this.plotter_._renderLineChart(this.highlightSet_,i)}else if(this.previousVerticalX_>=0){var o=0,s=this.attr_("labels");for(a=1;ao&&(o=l)}var h=this.previousVerticalX_;i.clearRect(h-o-1,0,2*o+2,this.height_)}if(this.isUsingExcanvas_&&this.currentZoomRectArgs_&&t.prototype.drawZoomRect_.apply(this,this.currentZoomRectArgs_),this.selPoints_.length>0){var p=this.selPoints_[0].canvasx;for(i.save(),a=0;a=0){t!=this.lastRow_&&(i=!0),this.lastRow_=t;for(var r=0;r=0&&(i=!0),this.lastRow_=-1;return this.selPoints_.length?this.lastx_=this.selPoints_[0].xval:this.lastx_=-1,void 0!==e&&(this.highlightSet_!==e&&(i=!0),this.highlightSet_=e),void 0!==a&&(this.lockedSet_=a),i&&this.updateSelection_(void 0),i},t.prototype.mouseOut_=function(t){this.getFunctionOption("unhighlightCallback")&&this.getFunctionOption("unhighlightCallback").call(this,t),this.getBooleanOption("hideOverlayOnMouseOut")&&!this.lockedSet_&&this.clearSelection()},t.prototype.clearSelection=function(){return this.cascadeEvents_("deselect",{}),this.lockedSet_=!1,this.fadeLevel?void this.animateSelection_(-1):(this.canvas_ctx_.clearRect(0,0,this.width_,this.height_),this.fadeLevel=0,this.selPoints_=[],this.lastx_=-1,this.lastRow_=-1,void(this.highlightSet_=null))},t.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1)return-1;for(var t=0;t1&&(a=this.dataHandler_.rollingAverage(a,this.rollPeriod_,this.attributes_)),this.rolledSeries_.push(a)}this.drawGraph_();var i=new Date;this.drawingTimeMs_=i-t},t.PointType=void 0,t.stackPoints_=function(t,e,a,i){for(var r=null,n=null,o=null,s=-1,l=function(e){if(!(s>=e))for(var a=e;aa[1]&&(a[1]=u),u=1;i--)if(this.visibility()[i-1]){if(a){l=e[i];var c=a[0],y=a[1];for(n=null,o=null,r=0;r=c&&null===n&&(n=r),l[r][0]<=y&&(o=r);null===n&&(n=0);for(var _=n,v=!0;v&&_>0;)_--,v=null===l[_][1];null===o&&(o=l.length-1);var f=o;for(v=!0;v&&f0&&(this.setIndexByName_[n[0]]=0);for(var o=0,s=1;s0;){var a=this.readyFns_.pop();a(this)}},t.prototype.computeYAxes_=function(){var e,a,i,r,n;if(void 0!==this.axes_&&this.user_attrs_.hasOwnProperty("valueRange")===!1)for(e=[],i=0;ii;i++)this.axes_[i].valueWindow=e[i]}for(a=0;al;l++){var h=this.axes_[l],p=this.attributes_.getForAxis("logscale",l),g=this.attributes_.getForAxis("includeZero",l),d=this.attributes_.getForAxis("independentTicks",l);if(i=this.attributes_.seriesForAxis(l),e=!0,r=.1,null!==this.getNumericOption("yRangePad")&&(e=!1,r=this.getNumericOption("yRangePad")/this.plotter_.area.h),0===i.length)h.extremeRange=[0,1];else{for(var u,c,y=1/0,_=-(1/0),v=0;v0&&(y=0),0>_&&(_=0)),y==1/0&&(y=0),_==-(1/0)&&(_=1),a=_-y,0===a&&(0!==_?a=Math.abs(_):(_=1,a=1));var f,x;if(p)if(e)f=_+r*a,x=y;else{var m=Math.exp(Math.log(a)*r);f=_*m,x=y/m}else f=_+r*a,x=y-r*a,e&&!this.getBooleanOption("avoidMinZero")&&(0>x&&y>=0&&(x=0),f>0&&0>=_&&(f=0));h.extremeRange=[x,f]}if(h.valueWindow)h.computedValueRange=[h.valueWindow[0],h.valueWindow[1]];else if(h.valueRange){var D=o(h.valueRange[0])?h.extremeRange[0]:h.valueRange[0],w=o(h.valueRange[1])?h.extremeRange[1]:h.valueRange[1];if(!e)if(h.logscale){var m=Math.exp(Math.log(a)*r);D*=m,w/=m}else a=w-D,D-=a*r,w+=a*r;h.computedValueRange=[D,w]}else h.computedValueRange=h.extremeRange;if(d){h.independentTicks=d;var A=this.optionsViewForAxis_("y"+(l?"2":"")),b=A("ticker");h.ticks=b(h.computedValueRange[0],h.computedValueRange[1],this.plotter_.area.h,A,this),n||(n=h)}}if(void 0===n)throw'Configuration Error: At least one axis has to have the "independentTicks" option activated.';for(var l=0;s>l;l++){var h=this.axes_[l];if(!h.independentTicks){for(var A=this.optionsViewForAxis_("y"+(l?"2":"")),b=A("ticker"),T=n.ticks,E=n.computedValueRange[1]-n.computedValueRange[0],C=h.computedValueRange[1]-h.computedValueRange[0],L=[],P=0;P0&&"e"!=t[a-1]&&"E"!=t[a-1]||t.indexOf("/")>=0||isNaN(parseFloat(t))?e=!0:8==t.length&&t>"19700101"&&"20371231">t&&(e=!0),this.setXAxisOptions_(e)},t.prototype.setXAxisOptions_=function(e){e?(this.attrs_.xValueParser=t.dateParser,this.attrs_.axes.x.valueFormatter=t.dateValueFormatter,this.attrs_.axes.x.ticker=t.dateTicker,this.attrs_.axes.x.axisLabelFormatter=t.dateAxisLabelFormatter):(this.attrs_.xValueParser=function(t){return parseFloat(t)},this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=t.numericTicks,this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter)},t.prototype.parseCSV_=function(e){var a,i,r=[],n=t.detectLineDelimiter(e),o=e.split(n||"\n"),s=this.getStringOption("delimiter");-1==o[0].indexOf(s)&&o[0].indexOf(" ")>=0&&(s=" ");var l=0;"labels"in this.user_attrs_||(l=1,this.attrs_.labels=o[0].split(s),this.attributes_.reparseSeries());for(var h,p=0,g=!1,d=this.attr_("labels").length,u=!1,c=l;c0&&v[0]0;)e=String.fromCharCode(65+(t-1)%26)+e.toLowerCase(),t=Math.floor((t-1)/26);return e},i=e.getNumberOfColumns(),r=e.getNumberOfRows(),n=e.getColumnType(0);if("date"==n||"datetime"==n)this.attrs_.xValueParser=t.dateParser,this.attrs_.axes.x.valueFormatter=t.dateValueFormatter,this.attrs_.axes.x.ticker=t.dateTicker,this.attrs_.axes.x.axisLabelFormatter=t.dateAxisLabelFormatter;else{if("number"!=n)return console.error("only 'date', 'datetime' and 'number' types are supported for column 1 of DataTable input (Got '"+n+"')"),null;this.attrs_.xValueParser=function(t){return parseFloat(t)},this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=t.numericTicks,this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter}var o,s,l=[],h={},p=!1;for(o=1;i>o;o++){var g=e.getColumnType(o);if("number"==g)l.push(o);else if("string"==g&&this.getBooleanOption("displayAnnotations")){var d=l[l.length-1];h.hasOwnProperty(d)?h[d].push(o):h[d]=[o],p=!0}else console.error("Only 'number' is supported as a dependent type with Gviz. 'string' is only supported if displayAnnotations is true")}var u=[e.getColumnLabel(0)];for(o=0;oo;o++){var v=[];if("undefined"!=typeof e.getValue(o,0)&&null!==e.getValue(o,0)){if(v.push("date"==n||"datetime"==n?e.getValue(o,0).getTime():e.getValue(o,0)),this.getBooleanOption("errorBars"))for(s=0;i-1>s;s++)v.push([e.getValue(o,1+2*s),e.getValue(o,2+2*s)]);else{for(s=0;s0&&v[0]0&&this.setAnnotations(_,!0),this.attributes_.reparseSeries()},t.prototype.cascadeDataDidUpdateEvent_=function(){this.cascadeEvents_("dataDidUpdate",{})},t.prototype.start_=function(){var e=this.file_;if("function"==typeof e&&(e=e()),t.isArrayLike(e))this.rawData_=this.parseArray_(e),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("object"==typeof e&&"function"==typeof e.getColumnRange)this.parseDataTable_(e),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("string"==typeof e){var a=t.detectLineDelimiter(e);if(a)this.loadedEvent_(e);else{var i;i=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");var r=this;i.onreadystatechange=function(){4==i.readyState&&(200===i.status||0===i.status)&&r.loadedEvent_(i.responseText)},i.open("GET",e,!0),i.send(null)}}else console.error("Unknown data format: "+typeof e)},t.prototype.updateOptions=function(e,a){"undefined"==typeof a&&(a=!1);var i=e.file,r=t.mapLegacyOptions_(e);"rollPeriod"in r&&(this.rollPeriod_=r.rollPeriod),"dateWindow"in r&&(this.dateWindow_=r.dateWindow,"isZoomedIgnoreProgrammaticZoom"in r||(this.zoomed_x_=null!==r.dateWindow)),"valueRange"in r&&!("isZoomedIgnoreProgrammaticZoom"in r)&&(this.zoomed_y_=null!==r.valueRange);var n=t.isPixelChangingOptionList(this.attr_("labels"),r);t.updateDeep(this.user_attrs_,r),this.attributes_.reparseSeries(),i?(this.cascadeEvents_("dataWillUpdate",{}),this.file_=i,a||this.start_()):a||(n?this.predraw_():this.renderGraph_(!1))},t.mapLegacyOptions_=function(t){var e={};for(var a in t)t.hasOwnProperty(a)&&"file"!=a&&t.hasOwnProperty(a)&&(e[a]=t[a]);var i=function(t,a,i){e.axes||(e.axes={}),e.axes[t]||(e.axes[t]={}),e.axes[t][a]=i},r=function(a,r,n){"undefined"!=typeof t[a]&&(console.warn("Option "+a+" is deprecated. Use the "+n+" option for the "+r+" axis instead. (e.g. { axes : { "+r+" : { "+n+" : ... } } } (see http://dygraphs.com/per-axis.html for more information."),i(r,n,t[a]),delete e[a])};return r("xValueFormatter","x","valueFormatter"),r("pixelsPerXLabel","x","pixelsPerLabel"),r("xAxisLabelFormatter","x","axisLabelFormatter"),r("xTicker","x","ticker"),r("yValueFormatter","y","valueFormatter"),r("pixelsPerYLabel","y","pixelsPerLabel"),r("yAxisLabelFormatter","y","axisLabelFormatter"),r("yTicker","y","ticker"),r("drawXGrid","x","drawGrid"),r("drawXAxis","x","drawAxis"),r("drawYGrid","y","drawGrid"),r("drawYAxis","y","drawAxis"),r("xAxisLabelWidth","x","axisLabelWidth"),r("yAxisLabelWidth","y","axisLabelWidth"),e},t.prototype.resize=function(t,e){if(!this.resize_lock){this.resize_lock=!0,null===t!=(null===e)&&(console.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero."),t=e=null);var a=this.width_,i=this.height_;t?(this.maindiv_.style.width=t+"px",this.maindiv_.style.height=e+"px",this.width_=t,this.height_=e):(this.width_=this.maindiv_.clientWidth,this.height_=this.maindiv_.clientHeight),(a!=this.width_||i!=this.height_)&&(this.resizeElements_(),this.predraw_()),this.resize_lock=!1}},t.prototype.adjustRoll=function(t){this.rollPeriod_=t,this.predraw_()},t.prototype.visibility=function(){for(this.getOption("visibility")||(this.attrs_.visibility=[]);this.getOption("visibility").lengtht||t>=a.length?console.warn("invalid series number in setVisibility: "+t):(a[t]=e,this.predraw_())},t.prototype.size=function(){return{width:this.width_,height:this.height_}},t.prototype.setAnnotations=function(e,a){return t.addAnnotationRule(),this.annotations_=e,this.layout_?(this.layout_.setAnnotations(this.annotations_),void(a||this.predraw_())):void console.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html")},t.prototype.annotations=function(){return this.annotations_},t.prototype.getLabels=function(){var t=this.attr_("labels");return t?t.slice():null},t.prototype.indexFromSetName=function(t){return this.setIndexByName_[t]},t.prototype.ready=function(t){this.is_initial_draw_?this.readyFns_.push(t):t.call(this,this)},t.addAnnotationRule=function(){if(!t.addedAnnotationCSS){var e="border: 1px solid black; background-color: white; text-align: center;",a=document.createElement("style");a.type="text/css",document.getElementsByTagName("head")[0].appendChild(a);for(var i=0;it?"0"+t:""+t},Dygraph.DateAccessorsLocal={getFullYear:function(t){return t.getFullYear()},getMonth:function(t){return t.getMonth()},getDate:function(t){return t.getDate()},getHours:function(t){return t.getHours()},getMinutes:function(t){return t.getMinutes()},getSeconds:function(t){return t.getSeconds()},getMilliseconds:function(t){return t.getMilliseconds()},getDay:function(t){return t.getDay()},makeDate:function(t,e,a,i,r,n,o){return new Date(t,e,a,i,r,n,o)}},Dygraph.DateAccessorsUTC={getFullYear:function(t){return t.getUTCFullYear()},getMonth:function(t){return t.getUTCMonth()},getDate:function(t){return t.getUTCDate()},getHours:function(t){return t.getUTCHours()},getMinutes:function(t){return t.getUTCMinutes()},getSeconds:function(t){return t.getUTCSeconds()},getMilliseconds:function(t){return t.getUTCMilliseconds()},getDay:function(t){return t.getUTCDay()},makeDate:function(t,e,a,i,r,n,o){return new Date(Date.UTC(t,e,a,i,r,n,o))}},Dygraph.hmsString_=function(t,e,a){var i=Dygraph.zeropad,r=i(t)+":"+i(e);return a&&(r+=":"+i(a)),r},Dygraph.dateString_=function(t,e){var a=Dygraph.zeropad,i=e?Dygraph.DateAccessorsUTC:Dygraph.DateAccessorsLocal,r=new Date(t),n=i.getFullYear(r),o=i.getMonth(r),s=i.getDate(r),l=i.getHours(r),h=i.getMinutes(r),p=i.getSeconds(r),g=""+n,d=a(o+1),u=a(s),c=3600*l+60*h+p,y=g+"/"+d+"/"+u;return c&&(y+=" "+Dygraph.hmsString_(l,h,p)),y},Dygraph.round_=function(t,e){var a=Math.pow(10,e);return Math.round(t*a)/a},Dygraph.binarySearch=function(t,e,a,i,r){if((null===i||void 0===i||null===r||void 0===r)&&(i=0,r=e.length-1),i>r)return-1;(null===a||void 0===a)&&(a=0);var n,o=function(t){return t>=0&&tt?a>0&&(n=s-1,o(n)&&e[n]l?0>a&&(n=s+1,o(n)&&e[n]>t)?s:Dygraph.binarySearch(t,e,a,s+1,r):-1},Dygraph.dateParser=function(t){var e,a;if((-1==t.search("-")||-1!=t.search("T")||-1!=t.search("Z"))&&(a=Dygraph.dateStrToMillis(t),a&&!isNaN(a)))return a;if(-1!=t.search("-")){for(e=t.replace("-","/","g");-1!=e.search("-");)e=e.replace("-","/");a=Dygraph.dateStrToMillis(e)}else 8==t.length?(e=t.substr(0,4)+"/"+t.substr(4,2)+"/"+t.substr(6,2),a=Dygraph.dateStrToMillis(e)):a=Dygraph.dateStrToMillis(t);return(!a||isNaN(a))&&console.error("Couldn't parse "+t+" as a date"),a},Dygraph.dateStrToMillis=function(t){return new Date(t).getTime()},Dygraph.update=function(t,e){if("undefined"!=typeof e&&null!==e)for(var a in e)e.hasOwnProperty(a)&&(t[a]=e[a]);return t},Dygraph.updateDeep=function(t,e){function a(t){return"object"==typeof Node?t instanceof Node:"object"==typeof t&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName}if("undefined"!=typeof e&&null!==e)for(var i in e)e.hasOwnProperty(i)&&(null===e[i]?t[i]=null:Dygraph.isArrayLike(e[i])?t[i]=e[i].slice():a(e[i])?t[i]=e[i]:"object"==typeof e[i]?(("object"!=typeof t[i]||null===t[i])&&(t[i]={}),Dygraph.updateDeep(t[i],e[i])):t[i]=e[i]);return t},Dygraph.isArrayLike=function(t){var e=typeof t;return"object"!=e&&("function"!=e||"function"!=typeof t.item)||null===t||"number"!=typeof t.length||3===t.nodeType?!1:!0},Dygraph.isDateLike=function(t){return"object"!=typeof t||null===t||"function"!=typeof t.getTime?!1:!0},Dygraph.clone=function(t){for(var e=[],a=0;a=e||Dygraph.requestAnimFrame.call(window,function(){var e=(new Date).getTime(),h=e-o;r=n,n=Math.floor(h/a);var p=n-r,g=n+p>s;g||n>=s?(t(s),i()):(0!==p&&t(n),l())})}()};var e={annotationClickHandler:!0,annotationDblClickHandler:!0,annotationMouseOutHandler:!0,annotationMouseOverHandler:!0,axisLabelColor:!0,axisLineColor:!0,axisLineWidth:!0,clickCallback:!0,drawCallback:!0,drawHighlightPointCallback:!0,drawPoints:!0,drawPointCallback:!0,drawXGrid:!0,drawYGrid:!0,fillAlpha:!0,gridLineColor:!0,gridLineWidth:!0,hideOverlayOnMouseOut:!0,highlightCallback:!0,highlightCircleSize:!0,interactionModel:!0,isZoomedIgnoreProgrammaticZoom:!0,labelsDiv:!0,labelsDivStyles:!0,labelsDivWidth:!0,labelsKMB:!0,labelsKMG2:!0,labelsSeparateLines:!0,labelsShowZeroValues:!0,legend:!0,panEdgeFraction:!0,pixelsPerYLabel:!0,pointClickCallback:!0,pointSize:!0,rangeSelectorPlotFillColor:!0,rangeSelectorPlotStrokeColor:!0,showLabelsOnHighlight:!0,showRoller:!0,strokeWidth:!0,underlayCallback:!0,unhighlightCallback:!0,zoomCallback:!0};Dygraph.isPixelChangingOptionList=function(t,a){var i={};if(t)for(var r=1;re?1/Math.pow(t,-e):Math.pow(t,e)};var a=/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([01](?:\.\d+)?))?\)$/;Dygraph.toRGB_=function(e){var a=t(e);if(a)return a;var i=document.createElement("div");i.style.backgroundColor=e,i.style.visibility="hidden",document.body.appendChild(i);var r;return r=window.getComputedStyle?window.getComputedStyle(i,null).backgroundColor:i.currentStyle.backgroundColor,document.body.removeChild(i),t(r)},Dygraph.isCanvasSupported=function(t){var e;try{e=t||document.createElement("canvas"),e.getContext("2d")}catch(a){var i=navigator.appVersion.match(/MSIE (\d\.\d)/),r=-1!=navigator.userAgent.toLowerCase().indexOf("opera");return!i||i[1]<6||r?!1:!0}return!0},Dygraph.parseFloat_=function(t,e,a){var i=parseFloat(t);if(!isNaN(i))return i;if(/^ *$/.test(t))return null;if(/^ *nan *$/i.test(t))return 0/0;var r="Unable to parse '"+t+"' as a number";return void 0!==a&&void 0!==e&&(r+=" on line "+(1+(e||0))+" ('"+a+"') of CSV."),console.error(r),null}}(),function(){"use strict";Dygraph.GVizChart=function(t){this.container=t},Dygraph.GVizChart.prototype.draw=function(t,e){this.container.innerHTML="","undefined"!=typeof this.date_graph&&this.date_graph.destroy(),this.date_graph=new Dygraph(this.container,t,e)},Dygraph.GVizChart.prototype.setSelection=function(t){var e=!1;t.length&&(e=t[0].row),this.date_graph.setSelection(e)},Dygraph.GVizChart.prototype.getSelection=function(){var t=[],e=this.date_graph.getSelection();if(0>e)return t;for(var a=this.date_graph.layout_.points,i=0;ii&&2>r&&void 0!==e.lastx_&&-1!=e.lastx_&&Dygraph.Interaction.treatMouseOpAsClick(e,t,a),a.regionWidth=i,a.regionHeight=r},Dygraph.Interaction.startPan=function(t,e,a){var i,r;a.isPanning=!0;var n=e.xAxisRange();if(e.getOptionForAxis("logscale","x")?(a.initialLeftmostDate=Dygraph.log10(n[0]),a.dateRange=Dygraph.log10(n[1])-Dygraph.log10(n[0])):(a.initialLeftmostDate=n[0],a.dateRange=n[1]-n[0]),a.xUnitsPerPixel=a.dateRange/(e.plotter_.area.w-1),e.getNumericOption("panEdgeFraction")){var o=e.width_*e.getNumericOption("panEdgeFraction"),s=e.xAxisExtremes(),l=e.toDomXCoord(s[0])-o,h=e.toDomXCoord(s[1])+o,p=e.toDataXCoord(l),g=e.toDataXCoord(h);a.boundedDates=[p,g];var d=[],u=e.height_*e.getNumericOption("panEdgeFraction");for(i=0;ia.boundedDates[1]&&(i-=r-a.boundedDates[1],r=i+a.dateRange),e.getOptionForAxis("logscale","x")?e.dateWindow_=[Math.pow(Dygraph.LOG_SCALE,i),Math.pow(Dygraph.LOG_SCALE,r)]:e.dateWindow_=[i,r],a.is2DPan)for(var n=a.dragEndY-a.dragStartY,o=0;oi?Dygraph.VERTICAL:Dygraph.HORIZONTAL,e.drawZoomRect_(a.dragDirection,a.dragStartX,a.dragEndX,a.dragStartY,a.dragEndY,a.prevDragDirection,a.prevEndX,a.prevEndY),a.prevEndX=a.dragEndX,a.prevEndY=a.dragEndY,a.prevDragDirection=a.dragDirection},Dygraph.Interaction.treatMouseOpAsClick=function(t,e,a){for(var i=t.getFunctionOption("clickCallback"),r=t.getFunctionOption("pointClickCallback"),n=null,o=-1,s=Number.MAX_VALUE,l=0;lp)&&(s=p,o=l)}var g=t.getNumericOption("highlightCircleSize")+2;if(g*g>=s&&(n=t.selPoints_[o]),n){var d={cancelable:!0,point:n,canvasx:a.dragEndX,canvasy:a.dragEndY},u=t.cascadeEvents_("pointClick",d);if(u)return;r&&r.call(t,e,n)}var d={cancelable:!0,xval:t.lastx_,pts:t.selPoints_,canvasx:a.dragEndX,canvasy:a.dragEndY};t.cascadeEvents_("click",d)||i&&i.call(t,e,t.lastx_,t.selPoints_)},Dygraph.Interaction.endZoom=function(t,e,a){e.clearZoomRect_(),a.isZooming=!1,Dygraph.Interaction.maybeTreatMouseOpAsClick(t,e,a);var i=e.getArea();if(a.regionWidth>=10&&a.dragDirection==Dygraph.HORIZONTAL){var r=Math.min(a.dragStartX,a.dragEndX),n=Math.max(a.dragStartX,a.dragEndX);r=Math.max(r,i.x),n=Math.min(n,i.x+i.w),n>r&&e.doZoomX_(r,n),a.cancelNextDblclick=!0}else if(a.regionHeight>=10&&a.dragDirection==Dygraph.VERTICAL){var o=Math.min(a.dragStartY,a.dragEndY),s=Math.max(a.dragStartY,a.dragEndY);o=Math.max(o,i.y),s=Math.min(s,i.y+i.h),s>o&&e.doZoomY_(o,s),a.cancelNextDblclick=!0}a.dragStartX=null,a.dragStartY=null},Dygraph.Interaction.startTouch=function(t,e,a){t.preventDefault(),t.touches.length>1&&(a.startTimeForDoubleTapMs=null);for(var i=[],r=0;r=2){a.initialPinchCenter={pageX:.5*(i[0].pageX+i[1].pageX),pageY:.5*(i[0].pageY+i[1].pageY),dataX:.5*(i[0].dataX+i[1].dataX),dataY:.5*(i[0].dataY+i[1].dataY)};var o=180/Math.PI*Math.atan2(a.initialPinchCenter.pageY-i[0].pageY,i[0].pageX-a.initialPinchCenter.pageX);o=Math.abs(o),o>90&&(o=90-o),a.touchDirections={x:67.5>o,y:o>22.5}}a.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}},Dygraph.Interaction.moveTouch=function(t,e,a){a.startTimeForDoubleTapMs=null;var i,r=[];for(i=0;i=2){var c=s[1].pageX-l.pageX;d=(r[1].pageX-o.pageX)/c;var y=s[1].pageY-l.pageY;u=(r[1].pageY-o.pageY)/y}d=Math.min(8,Math.max(.125,d)),u=Math.min(8,Math.max(.125,u));var _=!1;if(a.touchDirections.x&&(e.dateWindow_=[l.dataX-h.dataX+(a.initialRange.x[0]-l.dataX)/d,l.dataX-h.dataX+(a.initialRange.x[1]-l.dataX)/d],_=!0),a.touchDirections.y)for(i=0;1>i;i++){var v=e.axes_[i],f=e.attributes_.getForAxis("logscale",i);f||(v.valueWindow=[l.dataY-h.dataY+(a.initialRange.y[0]-l.dataY)/u,l.dataY-h.dataY+(a.initialRange.y[1]-l.dataY)/u],_=!0)}if(e.drawGraph_(!1),_&&r.length>1&&e.getFunctionOption("zoomCallback")){var x=e.xAxisRange();e.getFunctionOption("zoomCallback").call(e,x[0],x[1],e.yAxisRanges())}},Dygraph.Interaction.endTouch=function(t,e,a){if(0!==t.touches.length)Dygraph.Interaction.startTouch(t,e,a);else if(1==t.changedTouches.length){var i=(new Date).getTime(),r=t.changedTouches[0];a.startTimeForDoubleTapMs&&i-a.startTimeForDoubleTapMs<500&&a.doubleTapX&&Math.abs(a.doubleTapX-r.screenX)<50&&a.doubleTapY&&Math.abs(a.doubleTapY-r.screenY)<50?e.resetZoom():(a.startTimeForDoubleTapMs=i,a.doubleTapX=r.screenX,a.doubleTapY=r.screenY)}};var e=function(t,e,a){return e>t?e-t:t>a?t-a:0},a=function(t,a){var i=Dygraph.findPos(a.canvas_),r={left:i.x,right:i.x+a.canvas_.offsetWidth,top:i.y,bottom:i.y+a.canvas_.offsetHeight},n={x:Dygraph.pageX(t),y:Dygraph.pageY(t)},o=e(n.x,r.left,r.right),s=e(n.y,r.top,r.bottom);return Math.max(o,s)};Dygraph.Interaction.defaultModel={mousedown:function(e,i,r){if(!e.button||2!=e.button){r.initializeMouseDown(e,i,r),e.altKey||e.shiftKey?Dygraph.startPan(e,i,r):Dygraph.startZoom(e,i,r);var n=function(e){if(r.isZooming){var n=a(e,i);t>n?Dygraph.moveZoom(e,i,r):null!==r.dragEndX&&(r.dragEndX=null,r.dragEndY=null,i.clearZoomRect_())}else r.isPanning&&Dygraph.movePan(e,i,r)},o=function(t){r.isZooming?null!==r.dragEndX?Dygraph.endZoom(t,i,r):Dygraph.Interaction.maybeTreatMouseOpAsClick(t,i,r):r.isPanning&&Dygraph.endPan(t,i,r),Dygraph.removeEvent(document,"mousemove",n),Dygraph.removeEvent(document,"mouseup",o),r.destroy()};i.addAndTrackEvent(document,"mousemove",n),i.addAndTrackEvent(document,"mouseup",o)}},willDestroyContextMyself:!0,touchstart:function(t,e,a){Dygraph.Interaction.startTouch(t,e,a)},touchmove:function(t,e,a){Dygraph.Interaction.moveTouch(t,e,a)},touchend:function(t,e,a){Dygraph.Interaction.endTouch(t,e,a)},dblclick:function(t,e,a){if(a.cancelNextDblclick)return void(a.cancelNextDblclick=!1);var i={canvasx:a.dragEndX,canvasy:a.dragEndY};e.cascadeEvents_("dblclick",i)||t.altKey||t.shiftKey||e.resetZoom()}},Dygraph.DEFAULT_ATTRS.interactionModel=Dygraph.Interaction.defaultModel,Dygraph.defaultInteractionModel=Dygraph.Interaction.defaultModel,Dygraph.endZoom=Dygraph.Interaction.endZoom,Dygraph.moveZoom=Dygraph.Interaction.moveZoom,Dygraph.startZoom=Dygraph.Interaction.startZoom,Dygraph.endPan=Dygraph.Interaction.endPan,Dygraph.movePan=Dygraph.Interaction.movePan,Dygraph.startPan=Dygraph.Interaction.startPan,Dygraph.Interaction.nonInteractiveModel_={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a)},mouseup:Dygraph.Interaction.maybeTreatMouseOpAsClick},Dygraph.Interaction.dragIsPanInteractionModel={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a),Dygraph.startPan(t,e,a)},mousemove:function(t,e,a){a.isPanning&&Dygraph.movePan(t,e,a)},mouseup:function(t,e,a){a.isPanning&&Dygraph.endPan(t,e,a)}}}(),function(){"use strict";Dygraph.TickList=void 0,Dygraph.Ticker=void 0,Dygraph.numericLinearTicks=function(t,e,a,i,r,n){var o=function(t){return"logscale"===t?!1:i(t)};return Dygraph.numericTicks(t,e,a,o,r,n)},Dygraph.numericTicks=function(t,e,a,i,r,n){var o,s,l,h,p=i("pixelsPerLabel"),g=[];if(n)for(o=0;o=h/4){for(var y=u;y>=d;y--){var _=Dygraph.PREFERRED_LOG_TICK_VALUES[y],v=Math.log(_/t)/Math.log(e/t)*a,f={v:_};null===c?c={tickValue:_,pixel_coord:v}:Math.abs(v-c.pixel_coord)>=p?c={tickValue:_,pixel_coord:v}:f.label="",g.push(f)}g.reverse()}}if(0===g.length){var x,m,D=i("labelsKMG2");D?(x=[1,2,4,8,16,32,64,128,256],m=16):(x=[1,2,5,10,20,50,100],m=10);var w,A,b,T,E=Math.ceil(a/p),C=Math.abs(e-t)/E,L=Math.floor(Math.log(C)/Math.log(m)),P=Math.pow(m,L);for(s=0;sp));s++);for(A>b&&(w*=-1),o=0;h>=o;o++)l=A+o*w,g.push({v:l})}}var S=i("axisLabelFormatter");for(o=0;o=0?Dygraph.getDateAxis(t,e,o,i,r):[]},Dygraph.SECONDLY=0,Dygraph.TWO_SECONDLY=1,Dygraph.FIVE_SECONDLY=2,Dygraph.TEN_SECONDLY=3,Dygraph.THIRTY_SECONDLY=4,Dygraph.MINUTELY=5,Dygraph.TWO_MINUTELY=6,Dygraph.FIVE_MINUTELY=7,Dygraph.TEN_MINUTELY=8,Dygraph.THIRTY_MINUTELY=9,Dygraph.HOURLY=10,Dygraph.TWO_HOURLY=11,Dygraph.SIX_HOURLY=12,Dygraph.DAILY=13,Dygraph.TWO_DAILY=14,Dygraph.WEEKLY=15,Dygraph.MONTHLY=16,Dygraph.QUARTERLY=17,Dygraph.BIANNUAL=18,Dygraph.ANNUAL=19,Dygraph.DECADAL=20,Dygraph.CENTENNIAL=21,Dygraph.NUM_GRANULARITIES=22,Dygraph.DATEFIELD_Y=0,Dygraph.DATEFIELD_M=1,Dygraph.DATEFIELD_D=2,Dygraph.DATEFIELD_HH=3,Dygraph.DATEFIELD_MM=4,Dygraph.DATEFIELD_SS=5,Dygraph.DATEFIELD_MS=6,Dygraph.NUM_DATEFIELDS=7,Dygraph.TICK_PLACEMENT=[],Dygraph.TICK_PLACEMENT[Dygraph.SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:1,spacing:1e3},Dygraph.TICK_PLACEMENT[Dygraph.TWO_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:2,spacing:2e3},Dygraph.TICK_PLACEMENT[Dygraph.FIVE_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:5,spacing:5e3},Dygraph.TICK_PLACEMENT[Dygraph.TEN_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:10,spacing:1e4},Dygraph.TICK_PLACEMENT[Dygraph.THIRTY_SECONDLY]={datefield:Dygraph.DATEFIELD_SS,step:30,spacing:3e4},Dygraph.TICK_PLACEMENT[Dygraph.MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:1,spacing:6e4},Dygraph.TICK_PLACEMENT[Dygraph.TWO_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:2,spacing:12e4},Dygraph.TICK_PLACEMENT[Dygraph.FIVE_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:5,spacing:3e5},Dygraph.TICK_PLACEMENT[Dygraph.TEN_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:10,spacing:6e5},Dygraph.TICK_PLACEMENT[Dygraph.THIRTY_MINUTELY]={datefield:Dygraph.DATEFIELD_MM,step:30,spacing:18e5},Dygraph.TICK_PLACEMENT[Dygraph.HOURLY]={datefield:Dygraph.DATEFIELD_HH,step:1,spacing:36e5},Dygraph.TICK_PLACEMENT[Dygraph.TWO_HOURLY]={datefield:Dygraph.DATEFIELD_HH,step:2,spacing:72e5},Dygraph.TICK_PLACEMENT[Dygraph.SIX_HOURLY]={datefield:Dygraph.DATEFIELD_HH,step:6,spacing:216e5},Dygraph.TICK_PLACEMENT[Dygraph.DAILY]={datefield:Dygraph.DATEFIELD_D,step:1,spacing:864e5},Dygraph.TICK_PLACEMENT[Dygraph.TWO_DAILY]={datefield:Dygraph.DATEFIELD_D,step:2,spacing:1728e5},Dygraph.TICK_PLACEMENT[Dygraph.WEEKLY]={datefield:Dygraph.DATEFIELD_D,step:7,spacing:6048e5},Dygraph.TICK_PLACEMENT[Dygraph.MONTHLY]={datefield:Dygraph.DATEFIELD_M,step:1,spacing:2629817280},Dygraph.TICK_PLACEMENT[Dygraph.QUARTERLY]={datefield:Dygraph.DATEFIELD_M,step:3,spacing:216e5*365.2524},Dygraph.TICK_PLACEMENT[Dygraph.BIANNUAL]={datefield:Dygraph.DATEFIELD_M,step:6,spacing:432e5*365.2524},Dygraph.TICK_PLACEMENT[Dygraph.ANNUAL]={datefield:Dygraph.DATEFIELD_Y,step:1,spacing:864e5*365.2524},Dygraph.TICK_PLACEMENT[Dygraph.DECADAL]={datefield:Dygraph.DATEFIELD_Y,step:10,spacing:315578073600},Dygraph.TICK_PLACEMENT[Dygraph.CENTENNIAL]={datefield:Dygraph.DATEFIELD_Y,step:100,spacing:3155780736e3},Dygraph.PREFERRED_LOG_TICK_VALUES=function(){for(var t=[],e=-39;39>=e;e++)for(var a=Math.pow(10,e),i=1;9>=i;i++){var r=a*i;t.push(r)}return t}(),Dygraph.pickDateTickGranularity=function(t,e,a,i){for(var r=i("pixelsPerLabel"),n=0;n=r)return n}return-1},Dygraph.numDateTicks=function(t,e,a){var i=Dygraph.TICK_PLACEMENT[a].spacing;return Math.round(1*(e-t)/i)},Dygraph.getDateAxis=function(t,e,a,i,r){var n=i("axisLabelFormatter"),o=i("labelsUTC"),s=o?Dygraph.DateAccessorsUTC:Dygraph.DateAccessorsLocal,l=Dygraph.TICK_PLACEMENT[a].datefield,h=Dygraph.TICK_PLACEMENT[a].step,p=Dygraph.TICK_PLACEMENT[a].spacing,g=new Date(t),d=[];d[Dygraph.DATEFIELD_Y]=s.getFullYear(g),d[Dygraph.DATEFIELD_M]=s.getMonth(g),d[Dygraph.DATEFIELD_D]=s.getDate(g),d[Dygraph.DATEFIELD_HH]=s.getHours(g),d[Dygraph.DATEFIELD_MM]=s.getMinutes(g),d[Dygraph.DATEFIELD_SS]=s.getSeconds(g),d[Dygraph.DATEFIELD_MS]=s.getMilliseconds(g);var u=d[l]%h;a==Dygraph.WEEKLY&&(u=s.getDay(g)),d[l]-=u;for(var c=l+1;cv&&(v+=p,_=new Date(v));e>=v;)y.push({v:v,label:n.call(r,_,a,i,r)}),v+=p,_=new Date(v);else for(t>v&&(d[l]+=h,_=s.makeDate.apply(null,d),v=_.getTime());e>=v;)(a>=Dygraph.DAILY||s.getHours(_)%h===0)&&y.push({v:v,label:n.call(r,_,a,i,r)}),d[l]+=h,_=s.makeDate.apply(null,d),v=_.getTime();return y},Dygraph&&Dygraph.DEFAULT_ATTRS&&Dygraph.DEFAULT_ATTRS.axes&&Dygraph.DEFAULT_ATTRS.axes.x&&Dygraph.DEFAULT_ATTRS.axes.y&&Dygraph.DEFAULT_ATTRS.axes.y2&&(Dygraph.DEFAULT_ATTRS.axes.x.ticker=Dygraph.dateTicker,Dygraph.DEFAULT_ATTRS.axes.y.ticker=Dygraph.numericTicks,Dygraph.DEFAULT_ATTRS.axes.y2.ticker=Dygraph.numericTicks)}(),Dygraph.Plugins={},Dygraph.Plugins.Annotations=function(){"use strict";var t=function(){this.annotations_=[]};return t.prototype.toString=function(){return"Annotations Plugin"},t.prototype.activate=function(t){return{clearChart:this.clearChart,didDrawChart:this.didDrawChart}},t.prototype.detachLabels=function(){for(var t=0;to.x+o.w||h.canvasyo.y+o.h)){var p=h.annotation,g=6;p.hasOwnProperty("tickHeight")&&(g=p.tickHeight);var d=document.createElement("div");for(var u in r)r.hasOwnProperty(u)&&(d.style[u]=r[u]);p.hasOwnProperty("icon")||(d.className="dygraphDefaultAnnotation"),p.hasOwnProperty("cssClass")&&(d.className+=" "+p.cssClass);var c=p.hasOwnProperty("width")?p.width:16,y=p.hasOwnProperty("height")?p.height:16;if(p.hasOwnProperty("icon")){var _=document.createElement("img");_.src=p.icon,_.width=c,_.height=y,d.appendChild(_)}else h.annotation.hasOwnProperty("shortText")&&d.appendChild(document.createTextNode(h.annotation.shortText));var v=h.canvasx-c/2;d.style.left=v+"px";var f=0;if(p.attachAtBottom){var x=o.y+o.h-y-g;s[v]?x-=s[v]:s[v]=0,s[v]+=g+y,f=x}else f=h.canvasy-y-g;d.style.top=f+"px",d.style.width=c+"px",d.style.height=y+"px",d.title=h.annotation.text,d.style.color=e.colorsMap_[h.name],d.style.borderColor=e.colorsMap_[h.name],p.div=d,e.addAndTrackEvent(d,"click",n("clickHandler","annotationClickHandler",h,this)),e.addAndTrackEvent(d,"mouseover",n("mouseOverHandler","annotationMouseOverHandler",h,this)),e.addAndTrackEvent(d,"mouseout",n("mouseOutHandler","annotationMouseOutHandler",h,this)),e.addAndTrackEvent(d,"dblclick",n("dblClickHandler","annotationDblClickHandler",h,this)),i.appendChild(d),this.annotations_.push(d);var m=t.drawingContext;if(m.save(),m.strokeStyle=e.colorsMap_[h.name],m.beginPath(),p.attachAtBottom){var x=f+y;m.moveTo(h.canvasx,x),m.lineTo(h.canvasx,x+g)}else m.moveTo(h.canvasx,h.canvasy),m.lineTo(h.canvasx,h.canvasy-2-g);m.closePath(),m.stroke(),m.restore()}}},t.prototype.destroy=function(){this.detachLabels()},t}(),Dygraph.Plugins.Axes=function(){"use strict";var t=function(){this.xlabels_=[],this.ylabels_=[]};return t.prototype.toString=function(){return"Axes Plugin"},t.prototype.activate=function(t){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}},t.prototype.layout=function(t){var e=t.dygraph;if(e.getOptionForAxis("drawAxis","y")){var a=e.getOptionForAxis("axisLabelWidth","y")+2*e.getOptionForAxis("axisTickSize","y");t.reserveSpaceLeft(a)}if(e.getOptionForAxis("drawAxis","x")){var i;i=e.getOption("xAxisHeight")?e.getOption("xAxisHeight"):e.getOptionForAxis("axisLabelFontSize","x")+2*e.getOptionForAxis("axisTickSize","x"),t.reserveSpaceBottom(i)}if(2==e.numAxes()){if(e.getOptionForAxis("drawAxis","y2")){var a=e.getOptionForAxis("axisLabelWidth","y2")+2*e.getOptionForAxis("axisTickSize","y2");t.reserveSpaceRight(a)}}else e.numAxes()>2&&e.error("Only two y-axes are supported at this time. (Trying to use "+e.numAxes()+")")},t.prototype.detachLabels=function(){function t(t){for(var e=0;e0){var x=i.numAxes(),m=[f("y"),f("y2")];for(l=0;l<_.yticks.length;l++){if(s=_.yticks[l],"function"==typeof s)return;n=v.x;var D=1,w="y1",A=m[0];1==s[0]&&(n=v.x+v.w,D=-1,w="y2",A=m[1]);var b=A("axisLabelFontSize");o=v.y+s[1]*v.h,r=y(s[2],"y",2==x?w:null);var T=o-b/2;0>T&&(T=0),T+b+3>d?r.style.bottom="0":r.style.top=T+"px",0===s[0]?(r.style.left=v.x-A("axisLabelWidth")-A("axisTickSize")+"px",r.style.textAlign="right"):1==s[0]&&(r.style.left=v.x+v.w+A("axisTickSize")+"px",r.style.textAlign="left"),r.style.width=A("axisLabelWidth")+"px",p.appendChild(r),this.ylabels_.push(r)}var E=this.ylabels_[0],b=i.getOptionForAxis("axisLabelFontSize","y"),C=parseInt(E.style.top,10)+b;C>d-b&&(E.style.top=parseInt(E.style.top,10)-b/2+"px")}var L;if(i.getOption("drawAxesAtZero")){var P=i.toPercentXCoord(0);(P>1||0>P||isNaN(P))&&(P=0),L=e(v.x+P*v.w)}else L=e(v.x);h.strokeStyle=i.getOptionForAxis("axisLineColor","y"),h.lineWidth=i.getOptionForAxis("axisLineWidth","y"),h.beginPath(),h.moveTo(L,a(v.y)),h.lineTo(L,a(v.y+v.h)),h.closePath(),h.stroke(),2==i.numAxes()&&(h.strokeStyle=i.getOptionForAxis("axisLineColor","y2"),h.lineWidth=i.getOptionForAxis("axisLineWidth","y2"),h.beginPath(),h.moveTo(a(v.x+v.w),a(v.y)),h.lineTo(a(v.x+v.w),a(v.y+v.h)),h.closePath(),h.stroke())}if(i.getOptionForAxis("drawAxis","x")){if(_.xticks){var A=f("x");for(l=0;l<_.xticks.length;l++){s=_.xticks[l],n=v.x+s[0]*v.w,o=v.y+v.h,r=y(s[1],"x"),r.style.textAlign="center",r.style.top=o+A("axisTickSize")+"px";var S=n-A("axisLabelWidth")/2;S+A("axisLabelWidth")>g&&(S=g-A("axisLabelWidth"),r.style.textAlign="right"),0>S&&(S=0,r.style.textAlign="left"),r.style.left=S+"px",r.style.width=A("axisLabelWidth")+"px", 5 | p.appendChild(r),this.xlabels_.push(r)}}h.strokeStyle=i.getOptionForAxis("axisLineColor","x"),h.lineWidth=i.getOptionForAxis("axisLineWidth","x"),h.beginPath();var O;if(i.getOption("drawAxesAtZero")){var P=i.toPercentYCoord(0,0);(P>1||0>P)&&(P=1),O=a(v.y+P*v.h)}else O=a(v.y+v.h);h.moveTo(e(v.x),O),h.lineTo(e(v.x+v.w),O),h.closePath(),h.stroke()}h.restore()}},t}(),Dygraph.Plugins.ChartLabels=function(){"use strict";var t=function(){this.title_div_=null,this.xlabel_div_=null,this.ylabel_div_=null,this.y2label_div_=null};t.prototype.toString=function(){return"ChartLabels Plugin"},t.prototype.activate=function(t){return{layout:this.layout,didDrawChart:this.didDrawChart}};var e=function(t){var e=document.createElement("div");return e.style.position="absolute",e.style.left=t.x+"px",e.style.top=t.y+"px",e.style.width=t.w+"px",e.style.height=t.h+"px",e};t.prototype.detachLabels_=function(){for(var t=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_],e=0;e=2);for(o=h.yticks,l.save(),n=0;n=2;for(y&&l.installPattern(_),l.strokeStyle=s.getOptionForAxis("gridLineColor","x"),l.lineWidth=s.getOptionForAxis("gridLineWidth","x"),n=0;n/g,">")};return t.prototype.select=function(e){var a=e.selectedX,i=e.selectedPoints,r=e.selectedRow,n=e.dygraph.getOption("legend");if("never"===n)return void(this.legend_div_.style.display="none");if("follow"===n){var o=e.dygraph.plotter_.area,s=e.dygraph.getOption("labelsDivWidth"),l=e.dygraph.getOptionForAxis("axisLabelWidth","y"),h=i[0].x*o.w+20,p=i[0].y*o.h-20;h+s+1>window.scrollX+window.innerWidth&&(h=h-40-s-(l-o.x)),e.dygraph.graphDiv.appendChild(this.legend_div_),this.legend_div_.style.left=l+h+"px",this.legend_div_.style.top=p+"px"}var g=t.generateLegendHTML(e.dygraph,a,i,this.one_em_width_,r);this.legend_div_.innerHTML=g,this.legend_div_.style.display=""},t.prototype.deselect=function(e){var i=e.dygraph.getOption("legend");"always"!==i&&(this.legend_div_.style.display="none");var r=a(this.legend_div_);this.one_em_width_=r;var n=t.generateLegendHTML(e.dygraph,void 0,void 0,r,null);this.legend_div_.innerHTML=n},t.prototype.didDrawChart=function(t){this.deselect(t)},t.prototype.predraw=function(t){if(this.is_generated_div_){t.dygraph.graphDiv.appendChild(this.legend_div_);var e=t.dygraph.plotter_.area,a=t.dygraph.getOption("labelsDivWidth");this.legend_div_.style.left=e.x+e.w-a-1+"px",this.legend_div_.style.top=e.y+"px",this.legend_div_.style.width=a+"px"}},t.prototype.destroy=function(){this.legend_div_=null},t.generateLegendHTML=function(t,a,r,n,o){if(t.getOption("showLabelsOnHighlight")!==!0)return"";var s,l,h,p,g,d=t.getLabels();if("undefined"==typeof a){if("always"!=t.getOption("legend"))return"";for(l=t.getOption("labelsSeparateLines"),s="",h=1;h":" "),g=t.getOption("strokePattern",d[h]),p=e(g,u.color,n),s+=""+p+" "+i(d[h])+"")}return s}var c=t.optionsViewForAxis_("x"),y=c("valueFormatter");s=y.call(t,a,c,d[0],t,o,0),""!==s&&(s+=":");var _=[],v=t.numAxes();for(h=0;v>h;h++)_[h]=t.optionsViewForAxis_("y"+(h?1+h:""));var f=t.getOption("labelsShowZeroValues");l=t.getOption("labelsSeparateLines");var x=t.getHighlightSeries();for(h=0;h");var u=t.getPropertiesForSeries(m.name),D=_[u.axis-1],w=D("valueFormatter"),A=w.call(t,m.yval,D,m.name,t,o,d.indexOf(m.name)),b=m.name==x?" class='highlight'":"";s+=" "+i(m.name)+": "+A+""}}return s},e=function(t,e,a){var i=/MSIE/.test(navigator.userAgent)&&!window.opera;if(i)return"—";if(!t||t.length<=1)return'
';var r,n,o,s,l,h=0,p=0,g=[];for(r=0;r<=t.length;r++)h+=t[r%t.length];if(l=Math.floor(a/(h-t[0])),l>1){for(r=0;rn;n++)for(r=0;p>r;r+=2)o=g[r%g.length],s=r';return d},t}(),Dygraph.Plugins.RangeSelector=function(){"use strict";var t=function(){this.isIE_=/MSIE/.test(navigator.userAgent)&&!window.opera,this.hasTouchInterface_="undefined"!=typeof TouchEvent,this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion),this.interfaceCreated_=!1};return t.prototype.toString=function(){return"RangeSelector Plugin"},t.prototype.activate=function(t){return this.dygraph_=t,this.isUsingExcanvas_=t.isUsingExcanvas_,this.getOption_("showRangeSelector")&&this.createInterface_(),{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}},t.prototype.destroy=function(){this.bgcanvas_=null,this.fgcanvas_=null,this.leftZoomHandle_=null,this.rightZoomHandle_=null,this.iePanOverlay_=null},t.prototype.getOption_=function(t,e){return this.dygraph_.getOption(t,e)},t.prototype.setDefaultOption_=function(t,e){this.dygraph_.attrs_[t]=e},t.prototype.createInterface_=function(){this.createCanvases_(),this.isUsingExcanvas_&&this.createIEPanOverlay_(),this.createZoomHandles_(),this.initInteraction_(),this.getOption_("animatedZooms")&&(console.warn("Animated zooms and range selector are not compatible; disabling animatedZooms."),this.dygraph_.updateOptions({animatedZooms:!1},!0)),this.interfaceCreated_=!0,this.addToGraph_()},t.prototype.addToGraph_=function(){var t=this.graphDiv_=this.dygraph_.graphDiv;t.appendChild(this.bgcanvas_),t.appendChild(this.fgcanvas_),t.appendChild(this.leftZoomHandle_),t.appendChild(this.rightZoomHandle_)},t.prototype.removeFromGraph_=function(){var t=this.graphDiv_;t.removeChild(this.bgcanvas_),t.removeChild(this.fgcanvas_),t.removeChild(this.leftZoomHandle_),t.removeChild(this.rightZoomHandle_),this.graphDiv_=null},t.prototype.reserveSpace_=function(t){this.getOption_("showRangeSelector")&&t.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)},t.prototype.renderStaticLayer_=function(){this.updateVisibility_()&&(this.resize_(),this.drawStaticLayer_())},t.prototype.renderInteractiveLayer_=function(){this.updateVisibility_()&&!this.isChangingRange_&&(this.placeZoomHandles_(),this.drawInteractiveLayer_())},t.prototype.updateVisibility_=function(){var t=this.getOption_("showRangeSelector");if(t)this.interfaceCreated_?this.graphDiv_&&this.graphDiv_.parentNode||this.addToGraph_():this.createInterface_();else if(this.graphDiv_){this.removeFromGraph_();var e=this.dygraph_;setTimeout(function(){e.width_=0,e.resize()},1)}return t},t.prototype.resize_=function(){function t(t,e,a){var i=Dygraph.getContextPixelRatio(e);t.style.top=a.y+"px",t.style.left=a.x+"px",t.width=a.w*i,t.height=a.h*i,t.style.width=a.w+"px",t.style.height=a.h+"px",1!=i&&e.scale(i,i)}var e=this.dygraph_.layout_.getPlotArea(),a=0;this.dygraph_.getOptionForAxis("drawAxis","x")&&(a=this.getOption_("xAxisHeight")||this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize")),this.canvasRect_={x:e.x,y:e.y+e.h+a+4,w:e.w,h:this.getOption_("rangeSelectorHeight")},t(this.bgcanvas_,this.bgcanvas_ctx_,this.canvasRect_),t(this.fgcanvas_,this.fgcanvas_ctx_,this.canvasRect_)},t.prototype.createCanvases_=function(){this.bgcanvas_=Dygraph.createCanvas(),this.bgcanvas_.className="dygraph-rangesel-bgcanvas",this.bgcanvas_.style.position="absolute",this.bgcanvas_.style.zIndex=9,this.bgcanvas_ctx_=Dygraph.getContext(this.bgcanvas_),this.fgcanvas_=Dygraph.createCanvas(),this.fgcanvas_.className="dygraph-rangesel-fgcanvas",this.fgcanvas_.style.position="absolute",this.fgcanvas_.style.zIndex=9,this.fgcanvas_.style.cursor="default",this.fgcanvas_ctx_=Dygraph.getContext(this.fgcanvas_)},t.prototype.createIEPanOverlay_=function(){this.iePanOverlay_=document.createElement("div"),this.iePanOverlay_.style.position="absolute",this.iePanOverlay_.style.backgroundColor="white",this.iePanOverlay_.style.filter="alpha(opacity=0)",this.iePanOverlay_.style.display="none",this.iePanOverlay_.style.cursor="move",this.fgcanvas_.appendChild(this.iePanOverlay_)},t.prototype.createZoomHandles_=function(){var t=new Image;t.className="dygraph-rangesel-zoomhandle",t.style.position="absolute",t.style.zIndex=10,t.style.visibility="hidden",t.style.cursor="col-resize",/MSIE 7/.test(navigator.userAgent)?(t.width=7,t.height=14,t.style.backgroundColor="white",t.style.border="1px solid #333333"):(t.width=9,t.height=16,t.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII="),this.isMobileDevice_&&(t.width*=2,t.height*=2),this.leftZoomHandle_=t,this.rightZoomHandle_=t.cloneNode(!1)},t.prototype.initInteraction_=function(){var t,e,a,i,r,n,o,s,l,h,p,g,d,u,c=this,y=document,_=0,v=null,f=!1,x=!1,m=!this.isMobileDevice_&&!this.isUsingExcanvas_,D=new Dygraph.IFrameTarp;t=function(t){var e=c.dygraph_.xAxisExtremes(),a=(e[1]-e[0])/c.canvasRect_.w,i=e[0]+(t.leftHandlePos-c.canvasRect_.x)*a,r=e[0]+(t.rightHandlePos-c.canvasRect_.x)*a;return[i,r]},e=function(t){return Dygraph.cancelEvent(t),f=!0,_=t.clientX,v=t.target?t.target:t.srcElement,("mousedown"===t.type||"dragstart"===t.type)&&(Dygraph.addEvent(y,"mousemove",a),Dygraph.addEvent(y,"mouseup",i)),c.fgcanvas_.style.cursor="col-resize",D.cover(),!0},a=function(t){if(!f)return!1;Dygraph.cancelEvent(t);var e=t.clientX-_;if(Math.abs(e)<4)return!0;_=t.clientX;var a,i=c.getZoomHandleStatus_();v==c.leftZoomHandle_?(a=i.leftHandlePos+e,a=Math.min(a,i.rightHandlePos-v.width-3),a=Math.max(a,c.canvasRect_.x)):(a=i.rightHandlePos+e,a=Math.min(a,c.canvasRect_.x+c.canvasRect_.w),a=Math.max(a,i.leftHandlePos+v.width+3));var n=v.width/2;return v.style.left=a-n+"px",c.drawInteractiveLayer_(),m&&r(),!0},i=function(t){return f?(f=!1,D.uncover(),Dygraph.removeEvent(y,"mousemove",a),Dygraph.removeEvent(y,"mouseup",i),c.fgcanvas_.style.cursor="default",m||r(),!0):!1},r=function(){try{var e=c.getZoomHandleStatus_();if(c.isChangingRange_=!0,e.isZoomed){var a=t(e);c.dygraph_.doZoomXDates_(a[0],a[1])}else c.dygraph_.resetZoom()}finally{c.isChangingRange_=!1}},n=function(t){if(c.isUsingExcanvas_)return t.srcElement==c.iePanOverlay_;var e=c.leftZoomHandle_.getBoundingClientRect(),a=e.left+e.width/2;e=c.rightZoomHandle_.getBoundingClientRect();var i=e.left+e.width/2;return t.clientX>a&&t.clientX=c.canvasRect_.x+c.canvasRect_.w?(r=c.canvasRect_.x+c.canvasRect_.w,i=r-n):(i+=e,r+=e);var o=c.leftZoomHandle_.width/2;return c.leftZoomHandle_.style.left=i-o+"px",c.rightZoomHandle_.style.left=r-o+"px",c.drawInteractiveLayer_(),m&&h(),!0},l=function(t){return x?(x=!1,Dygraph.removeEvent(y,"mousemove",s),Dygraph.removeEvent(y,"mouseup",l),m||h(),!0):!1},h=function(){try{c.isChangingRange_=!0,c.dygraph_.dateWindow_=t(c.getZoomHandleStatus_()),c.dygraph_.drawGraph_(!1)}finally{c.isChangingRange_=!1}},p=function(t){if(!f&&!x){var e=n(t)?"move":"default";e!=c.fgcanvas_.style.cursor&&(c.fgcanvas_.style.cursor=e)}},g=function(t){"touchstart"==t.type&&1==t.targetTouches.length?e(t.targetTouches[0])&&Dygraph.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?a(t.targetTouches[0])&&Dygraph.cancelEvent(t):i(t)},d=function(t){"touchstart"==t.type&&1==t.targetTouches.length?o(t.targetTouches[0])&&Dygraph.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?s(t.targetTouches[0])&&Dygraph.cancelEvent(t):l(t)},u=function(t,e){for(var a=["touchstart","touchend","touchmove","touchcancel"],i=0;it;t++){var s=this.getOption_("showInRangeSelector",r[t]);n[t]=s,null!==s&&(o=!0)}if(!o)for(t=0;t1&&(g=h.rollingAverage(g,e.rollPeriod(),p)),l.push(g)}var d=[];for(t=0;t0)&&(v=Math.min(v,x),f=Math.max(f,x))}var m=.25;if(a)for(f=Dygraph.log10(f),f+=f*m,v=Dygraph.log10(v),t=0;tthis.canvasRect_.x||a+10&&t[r][0]>o;)i--,r--}return i>=a?[a,i]:[0,t.length-1]},t.parseFloat=function(t){return null===t?0/0:t}}(),function(){"use strict";Dygraph.DataHandlers.DefaultHandler=function(){};var t=Dygraph.DataHandlers.DefaultHandler;t.prototype=new Dygraph.DataHandler,t.prototype.extractSeries=function(t,e,a){for(var i=[],r=a.get("logscale"),n=0;n=s&&(s=null),i.push([o,s])}return i},t.prototype.rollingAverage=function(t,e,a){e=Math.min(e,t.length);var i,r,n,o,s,l=[];if(1==e)return t;for(i=0;ir;r++)n=t[r][1],null===n||isNaN(n)||(s++,o+=t[r][1]);s?l[i]=[t[i][0],o/s]:l[i]=[t[i][0],null]}return l},t.prototype.getExtremeYValues=function(t,e,a){for(var i,r=null,n=null,o=0,s=t.length-1,l=o;s>=l;l++)i=t[l][1],null===i||isNaN(i)||((null===n||i>n)&&(n=i),(null===r||r>i)&&(r=i));return[r,n]}}(),function(){"use strict";Dygraph.DataHandlers.DefaultFractionHandler=function(){};var t=Dygraph.DataHandlers.DefaultFractionHandler;t.prototype=new Dygraph.DataHandlers.DefaultHandler,t.prototype.extractSeries=function(t,e,a){for(var i,r,n,o,s,l,h=[],p=100,g=a.get("logscale"),d=0;d=0&&(n-=t[i-e][2][0],o-=t[i-e][2][1]);var l=t[i][0],h=o?n/o:0;r[i]=[l,s*h]}return r}}(),function(){"use strict";Dygraph.DataHandlers.BarsHandler=function(){Dygraph.DataHandler.call(this)},Dygraph.DataHandlers.BarsHandler.prototype=new Dygraph.DataHandler;var t=Dygraph.DataHandlers.BarsHandler;t.prototype.extractSeries=function(t,e,a){},t.prototype.rollingAverage=function(t,e,a){},t.prototype.onPointsCreated_=function(t,e){for(var a=0;a=l;l++)if(i=t[l][1],null!==i&&!isNaN(i)){var h=t[l][2][0],p=t[l][2][1];h>i&&(h=i),i>p&&(p=i),(null===n||p>n)&&(n=p),(null===r||r>h)&&(r=h)}return[r,n]},t.prototype.onLineEvaluated=function(t,e,a){for(var i,r=0;r=0){var g=t[l-e];null===g[1]||isNaN(g[1])||(r-=g[2][0],o-=g[1],n-=g[2][1],s-=1)}s?p[l]=[t[l][0],1*o/s,[1*r/s,1*n/s]]:p[l]=[t[l][0],null,[null,null]]}return p}}(),function(){"use strict";Dygraph.DataHandlers.ErrorBarsHandler=function(){};var t=Dygraph.DataHandlers.ErrorBarsHandler;t.prototype=new Dygraph.DataHandlers.BarsHandler,t.prototype.extractSeries=function(t,e,a){for(var i,r,n,o,s=[],l=a.get("sigma"),h=a.get("logscale"),p=0;pr;r++)n=t[r][1],null===n||isNaN(n)||(l++,s+=n,p+=Math.pow(t[r][2][2],2));l?(h=Math.sqrt(p)/l,g=s/l,d[i]=[t[i][0],g,[g-u*h,g+u*h]]):(o=1==e?t[i][1]:null,d[i]=[t[i][0],o,[o,o]])}return d}}(),function(){"use strict";Dygraph.DataHandlers.FractionsBarsHandler=function(){};var t=Dygraph.DataHandlers.FractionsBarsHandler;t.prototype=new Dygraph.DataHandlers.BarsHandler,t.prototype.extractSeries=function(t,e,a){for(var i,r,n,o,s,l,h,p,g=[],d=100,u=a.get("sigma"),c=a.get("logscale"),y=0;y=0&&(p-=t[n-e][2][2],g-=t[n-e][2][3]);var u=t[n][0],c=g?p/g:0;if(h)if(g){var y=0>c?0:c,_=g,v=l*Math.sqrt(y*(1-y)/_+l*l/(4*_*_)),f=1+l*l/g;i=(y+l*l/(2*g)-v)/f,r=(y+l*l/(2*g)+v)/f,s[n]=[u,y*d,[i*d,r*d]]}else s[n]=[u,0,[0,0]];else o=g?l*Math.sqrt(c*(1-c)/g):1,s[n]=[u,d*c,[d*(c-o),d*(c+o)]]}return s}}(); 6 | //# sourceMappingURL=dygraph-combined.js.map -------------------------------------------------------------------------------- /codemap/ui/interaction.js: -------------------------------------------------------------------------------- 1 | // Code for a variety of interaction models. Used in interaction.html, but split out from 2 | // that file so they can be tested in isolation. 3 | // 4 | function downV3(event, g, context) { 5 | context.initializeMouseDown(event, g, context); 6 | if (event.altKey || event.shiftKey) { 7 | Dygraph.startZoom(event, g, context); 8 | } else { 9 | Dygraph.startPan(event, g, context); 10 | } 11 | } 12 | 13 | function moveV3(event, g, context) { 14 | if (context.isPanning) { 15 | Dygraph.movePan(event, g, context); 16 | } else if (context.isZooming) { 17 | Dygraph.moveZoom(event, g, context); 18 | } 19 | } 20 | 21 | function upV3(event, g, context) { 22 | if (context.isPanning) { 23 | Dygraph.endPan(event, g, context); 24 | } else if (context.isZooming) { 25 | Dygraph.endZoom(event, g, context); 26 | } 27 | } 28 | 29 | // Take the offset of a mouse event on the dygraph canvas and 30 | // convert it to a pair of percentages from the bottom left. 31 | // (Not top left, bottom is where the lower value is.) 32 | function offsetToPercentage(g, offsetX, offsetY) { 33 | // This is calculating the pixel offset of the leftmost date. 34 | var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0]; 35 | var yar0 = g.yAxisRange(0); 36 | 37 | // This is calculating the pixel of the higest value. (Top pixel) 38 | var yOffset = g.toDomCoords(null, yar0[1])[1]; 39 | 40 | // x y w and h are relative to the corner of the drawing area, 41 | // so that the upper corner of the drawing area is (0, 0). 42 | var x = offsetX - xOffset; 43 | var y = offsetY - yOffset; 44 | 45 | // This is computing the rightmost pixel, effectively defining the 46 | // width. 47 | var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset; 48 | 49 | // This is computing the lowest pixel, effectively defining the height. 50 | var h = g.toDomCoords(null, yar0[0])[1] - yOffset; 51 | 52 | // Percentage from the left. 53 | var xPct = w == 0 ? 0 : (x / w); 54 | // Percentage from the top. 55 | var yPct = h == 0 ? 0 : (y / h); 56 | 57 | // The (1-) part below changes it from "% distance down from the top" 58 | // to "% distance up from the bottom". 59 | return [xPct, (1-yPct)]; 60 | } 61 | 62 | function dblClickV3(event, g, context) { 63 | // Reducing by 20% makes it 80% the original size, which means 64 | // to restore to original size it must grow by 25% 65 | 66 | if (!(event.offsetX && event.offsetY)){ 67 | event.offsetX = event.layerX - event.target.offsetLeft; 68 | event.offsetY = event.layerY - event.target.offsetTop; 69 | } 70 | 71 | var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); 72 | var xPct = percentages[0]; 73 | var yPct = percentages[1]; 74 | 75 | if (event.ctrlKey) { 76 | zoom(g, -.25, xPct, yPct); 77 | } else { 78 | zoom(g, +.2, xPct, yPct); 79 | } 80 | } 81 | 82 | var lastClickedGraph = null; 83 | 84 | function clickV3(event, g, context) { 85 | lastClickedGraph = g; 86 | Dygraph.cancelEvent(event); 87 | } 88 | 89 | function scrollV3(event, g, context) { 90 | if (lastClickedGraph != g) { 91 | return; 92 | } 93 | var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40; 94 | // For me the normalized value shows 0.075 for one click. If I took 95 | // that verbatim, it would be a 7.5%. 96 | var percentage = normal / 50; 97 | 98 | if (!(event.offsetX && event.offsetY)){ 99 | event.offsetX = event.layerX - event.target.offsetLeft; 100 | event.offsetY = event.layerY - event.target.offsetTop; 101 | } 102 | 103 | var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); 104 | var xPct = percentages[0]; 105 | var yPct = percentages[1]; 106 | 107 | zoom(g, percentage, xPct, yPct); 108 | Dygraph.cancelEvent(event); 109 | } 110 | 111 | // Adjusts [x, y] toward each other by zoomInPercentage% 112 | // Split it so the left/bottom axis gets xBias/yBias of that change and 113 | // tight/top gets (1-xBias)/(1-yBias) of that change. 114 | // 115 | // If a bias is missing it splits it down the middle. 116 | function zoom(g, zoomInPercentage, xBias, yBias) { 117 | xBias = xBias || 0.5; 118 | yBias = yBias || 0.5; 119 | function adjustAxis(axis, zoomInPercentage, bias) { 120 | var delta = axis[1] - axis[0]; 121 | var increment = delta * zoomInPercentage; 122 | var foo = [increment * bias, increment * (1-bias)]; 123 | return [ axis[0] + foo[0], axis[1] - foo[1] ]; 124 | } 125 | var yAxes = g.yAxisRanges(); 126 | var newYAxes = []; 127 | for (var i = 0; i < yAxes.length; i++) { 128 | newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias); 129 | } 130 | 131 | g.updateOptions({ 132 | dateWindow: adjustAxis(g.xAxisRange(), zoomInPercentage, xBias), 133 | valueRange: newYAxes[0] 134 | }); 135 | } 136 | 137 | var v4Active = false; 138 | var v4Canvas = null; 139 | 140 | function downV4(event, g, context) { 141 | context.initializeMouseDown(event, g, context); 142 | v4Active = true; 143 | moveV4(event, g, context); // in case the mouse went down on a data point. 144 | } 145 | 146 | var processed = []; 147 | 148 | function moveV4(event, g, context) { 149 | var RANGE = 7; 150 | 151 | if (v4Active) { 152 | var graphPos = Dygraph.findPos(g.graphDiv); 153 | var canvasx = Dygraph.pageX(event) - graphPos.x; 154 | var canvasy = Dygraph.pageY(event) - graphPos.y; 155 | 156 | var rows = g.numRows(); 157 | // Row layout: 158 | // [date, [val1, stdev1], [val2, stdev2]] 159 | for (var row = 0; row < rows; row++) { 160 | var date = g.getValue(row, 0); 161 | var x = g.toDomCoords(date, null)[0]; 162 | var diff = Math.abs(canvasx - x); 163 | if (diff < RANGE) { 164 | for (var col = 1; col < 3; col++) { 165 | // TODO(konigsberg): these will throw exceptions as data is removed. 166 | var vals = g.getValue(row, col); 167 | if (vals == null) { continue; } 168 | var val = vals[0]; 169 | var y = g.toDomCoords(null, val)[1]; 170 | var diff2 = Math.abs(canvasy - y); 171 | if (diff2 < RANGE) { 172 | var found = false; 173 | for (var i in processed) { 174 | var stored = processed[i]; 175 | if(stored[0] == row && stored[1] == col) { 176 | found = true; 177 | break; 178 | } 179 | } 180 | if (!found) { 181 | processed.push([row, col]); 182 | drawV4(x, y); 183 | } 184 | return; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | function upV4(event, g, context) { 193 | if (v4Active) { 194 | v4Active = false; 195 | } 196 | } 197 | 198 | function dblClickV4(event, g, context) { 199 | restorePositioning(g); 200 | } 201 | 202 | function drawV4(x, y) { 203 | var ctx = v4Canvas; 204 | 205 | ctx.strokeStyle = "#000000"; 206 | ctx.fillStyle = "#FFFF00"; 207 | ctx.beginPath(); 208 | ctx.arc(x,y,5,0,Math.PI*2,true); 209 | ctx.closePath(); 210 | ctx.stroke(); 211 | ctx.fill(); 212 | } 213 | 214 | function captureCanvas(canvas, area, g) { 215 | v4Canvas = canvas; 216 | } 217 | 218 | function restorePositioning(g) { 219 | g.updateOptions({ 220 | dateWindow: null, 221 | valueRange: null 222 | }); 223 | } -------------------------------------------------------------------------------- /codemap/ui/skel.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | Codemap - IDA plugin for runtrace visualization. 4 | 5 | 6 | 7 | 8 | 9 | 40 | 41 | 42 | 43 | / runtime module base : --BASEADDR-- / input IDA static module base : 44 | 45 | / pause Codemap to see accurate trace result. 46 |
47 |
Move cursor to execution flow graph.
48 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /idapythonrc.py: -------------------------------------------------------------------------------- 1 | # C:\Users\[USERNAME]\AppData\Roaming\Hex-Rays\IDA Pro\idapythonrc.py 2 | from idaapi import * 3 | import idc 4 | import zlib 5 | import traceback 6 | import webbrowser 7 | import datetime 8 | from codemap import codemap 9 | 10 | __version__ = '2.0' 11 | 12 | codemap = codemap.Codemap() 13 | 14 | 15 | def IDA_State(): 16 | if get_root_filename() is None: 17 | return 'empty' 18 | try: 19 | a = idc.GetRegValue('esp') 20 | return 'running' 21 | except: 22 | return 'static' 23 | 24 | 25 | # batch function break point script - daehee 26 | def Functions(start=None, end=None): 27 | if not start: 28 | start = cvar.inf.minEA 29 | if not end: 30 | end = cvar.inf.maxEA 31 | chunk = get_fchunk(start) 32 | if not chunk: 33 | chunk = get_next_fchunk(start) 34 | while chunk and chunk.startEA < end and (chunk.flags & FUNC_TAIL) != 0: 35 | chunk = get_next_fchunk(chunk.startEA) 36 | func = chunk 37 | while func and func.startEA < end: 38 | yield (func) 39 | func = get_next_func(func.startEA) 40 | 41 | 42 | def FuncItems(start): 43 | func = get_func(start) 44 | if not func: 45 | return 46 | fii = func_item_iterator_t() 47 | ok = fii.set(func) 48 | while ok: 49 | yield fii.current() 50 | ok = fii.next_code() 51 | 52 | ''' 53 | Returns a list of module objects with name,size,base and the rebase_to 54 | ''' 55 | def Modules(): 56 | mod = idaapi.module_info_t() 57 | result = idaapi.get_first_module(mod) 58 | while result: 59 | yield idaapi.object_t(name=mod.name, size=mod.size, base=mod.base, rebase_to=mod.rebase_to) 60 | result = idaapi.get_next_module(mod) 61 | 62 | 63 | # print slows IDA down 64 | class IDAHook(DBG_Hooks): 65 | global codemap 66 | 67 | def dbg_process_exit(self, pid, tid, ea, code): 68 | codemap.db_insert() 69 | codemap.init_codemap() 70 | print("Process exited pid=%d tid=%d ea=0x%x code=%d" % 71 | (pid, tid, ea, code)) 72 | 73 | def dbg_bpt(self, tid, ea): 74 | if codemap.pause is True: 75 | return 0 # stop visualizing 76 | 77 | codemap.set_data() 78 | codemap.db_insert_queue() 79 | continue_process() # continue 80 | return 0 # no warning 81 | 82 | 83 | def hook_ida(): 84 | global debughook 85 | # Remove an existing debug hook 86 | try: 87 | if debughook: 88 | print("Removing previous hook ...") 89 | debughook.unhook() 90 | except: 91 | pass 92 | # Install the debug hook 93 | debughook = IDAHook() 94 | debughook.hook() 95 | debughook.steps = 0 96 | 97 | ''' 98 | - SetRangeBP - 99 | Get address range from user and setup bp to all instruction in that range. 100 | ''' 101 | def SetRangeBP(): 102 | if IDA_State() == 'empty': 103 | print 'no program loaded' 104 | return 105 | start_addr = AskStr('', 'Start Addr? (e.g. 0x8000) : ') 106 | end_addr = AskStr('', 'End Addr? (e.g. 0xC000) : ') 107 | 108 | start_addr = int(start_addr.replace('0x', ''), 16) 109 | end_addr = int(end_addr.replace('0x', ''), 16) 110 | 111 | for e in Heads(start_addr, end_addr): 112 | if get_bpt(e, bpt_t()) is False: 113 | add_bpt(e, 0, BPT_SOFT) 114 | else: 115 | del_bpt(e) 116 | 117 | 118 | ''' 119 | - SetFunctionBP - 120 | put cursor inside the IDA-recognized function then call this. 121 | bp will be set to all instructions of function 122 | ''' 123 | def SetFunctionBP(): 124 | if IDA_State() == 'empty': 125 | print 'no program loaded' 126 | return 127 | ea = ScreenEA() 128 | target = 0 129 | for e in Functions(): 130 | if e.startEA <= ea and ea <= e.endEA: 131 | target = e.startEA 132 | 133 | if target != 0: 134 | for e in FuncItems(target): 135 | if get_bpt(e, bpt_t()) is False: 136 | add_bpt(e, 0, BPT_SOFT) 137 | else: 138 | del_bpt(e) 139 | else: 140 | Warning('put cursor in the function body') 141 | 142 | 143 | ''' 144 | - Start Trace - 145 | setup all bp's you want(maybe using GETBPLIST or SetFunctionBP or manually) 146 | then execute this script in order to create dygraph trace. 147 | ''' 148 | def StartTracing(): 149 | global codemap 150 | if codemap.start is False and IDA_State() != 'running': 151 | print 'IDA debugger not running' 152 | return 153 | 154 | if codemap.start is True: 155 | codemap.pause = not codemap.pause 156 | print 'Codemap Paused? : ', codemap.pause 157 | if codemap.pause is False: # resume tracing 158 | continue_process() 159 | else: 160 | codemap.db_insert() 161 | suspend_process() 162 | return 163 | 164 | # set current uid. if there is existing codemap instance, save it to prev_uids 165 | if codemap.uid != None: 166 | codemap.prev_uids.append( codemap.uid ) 167 | 168 | codemap.uid = datetime.datetime.fromtimestamp( 169 | time.time()).strftime('%Y%m%d%H%M%S') 170 | 171 | 172 | codemap.init_arch() 173 | hook_ida() 174 | 175 | print 'hook ida done.' 176 | print 'homedir : ', codemap.homedir 177 | print 'making table...' 178 | # initialize sqlite3 db 179 | print codemap.db_create() 180 | 181 | # set default SQL 182 | if codemap.arch.name == 'x86': 183 | codemap.query = "select eip from trace{0}".format(codemap.uid) 184 | if codemap.arch.name == 'x64': 185 | codemap.query = "select rip from trace{0}".format(codemap.uid) 186 | 187 | print 'start HTTP server' 188 | # start HTTP server 189 | codemap.start_webserver() 190 | 191 | # fire up chrome! 192 | result = 'http://{0}:{1}/{2}'.format(codemap.server, 193 | codemap.port, 194 | codemap.uid) 195 | webbrowser.open(result) 196 | print 'start tracing...' 197 | codemap.start = True 198 | continue_process() 199 | 200 | 201 | ''' 202 | - SaveModuleBP - 203 | open a dll file with IDA and execute this script after IDA analysis is done. 204 | the function offset information of dll will be saved to file inside Codemap directory 205 | ''' 206 | def SaveModuleBP(): 207 | global codemap 208 | try: 209 | modname = AskStr('', 'module name : ') 210 | bpo = '' 211 | for e in Functions(): 212 | func = e.startEA 213 | length = e.endEA - e.startEA 214 | if length < codemap.func_min_size: 215 | continue 216 | offset = func - get_imagebase() 217 | bpo += str(offset) + '\n' 218 | print 'bp offset generation complete! ' + str(len(bpo)) 219 | payload = bpo 220 | with open(codemap.homedir + modname + '.bpo', 'wb') as f: 221 | f.write(zlib.compress(payload)) 222 | except: 223 | traceback.print_exc(file=sys.stdout) 224 | 225 | ''' 226 | - LoadModuleBP - 227 | while debugging the target app, put cursor somewhere inside the target module code. 228 | execute the script and bp will be set for all functions specified in .bpo file 229 | ''' 230 | def LoadModuleBP(): 231 | global codemap 232 | try: 233 | # get current cursor 234 | cur = get_screen_ea() 235 | baseaddr = 0 236 | modname = '' 237 | # what module is my cursor pointing? 238 | for i in Modules(): 239 | if cur > i.base and cur < i.base + i.size: 240 | modname = i.name.split('\x00')[0] 241 | modname = modname.split('\\')[-1:][0] 242 | baseaddr = i.base 243 | 244 | codemap.base = baseaddr # this is needed. 245 | modname = AskStr('', 'module name : ') 246 | payload = '' 247 | with open(codemap.homedir + modname + '.bpo', 'rb') as f: 248 | payload = zlib.decompress(f.read()) 249 | bps = payload.split() 250 | code = bytearray() 251 | for bp in bps: 252 | code += 'add_bpt({0}, 0, BPT_SOFT);'.format(baseaddr + int(bp)) 253 | print 'setting breakpoints...' 254 | # set bp! 255 | exec(str(code)) 256 | except: 257 | traceback.print_exc(file=sys.stdout) 258 | 259 | def SetModuleBP(): 260 | global codemap 261 | if codemap.start is False and IDA_State() is 'static': 262 | SaveModuleBP() 263 | if codemap.start is False and IDA_State() is 'running': 264 | LoadModuleBP() 265 | 266 | def ListenCodemap(): 267 | global codemap 268 | codemap.start_websocketserver() 269 | print "Listning to codemap connection..." 270 | 271 | CompileLine('static key_1() { RunPythonStatement("StartTracing()"); }') 272 | CompileLine('static key_2() { RunPythonStatement("SetFunctionBP()"); }') 273 | CompileLine('static key_3() { RunPythonStatement("SetRangeBP()"); }') 274 | CompileLine('static key_4() { RunPythonStatement("SetModuleBP()"); }') 275 | CompileLine('static key_5() { RunPythonStatement("ListenCodemap()"); }') 276 | 277 | AddHotkey('Alt-1', 'key_1') 278 | AddHotkey('Alt-2', 'key_2') 279 | AddHotkey('Alt-3', 'key_3') 280 | AddHotkey('Alt-4', 'key_4') 281 | AddHotkey('Alt-5', 'key_5') 282 | 283 | print 'ALT-1 : Start(Resume)/Pause Codemap' 284 | print 'ALT-2 : Set Function BP' 285 | print 'ALT-3 : Set Range BP' 286 | print 'ALT-4 : Create/Setup Module BP' 287 | print 'ALT-5 : Connect Codemap Graph with IDA' 288 | print 'Codemap Python Plugin is ready. enjoy. - by daehee' 289 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import platform 4 | 5 | sysname = platform.system() 6 | g_idadir = '' 7 | g_homedir = '' 8 | 9 | if sysname == 'Darwin' or sysname == 'Linux': 10 | g_idadir = '%s/.idapro' % os.environ['HOME'] 11 | g_homedir = '%s/.idapro/codemap/' % os.environ['HOME'] 12 | elif sysname == 'Windows': 13 | g_idadir = '%s\\Hex-Rays\\IDA Pro\\' % os.environ['APPDATA'] 14 | g_homedir = '%s\\Hex-Rays\\IDA Pro\\codemap\\' % os.environ['APPDATA'] 15 | 16 | if not os.path.exists(g_idadir): 17 | print 'IDA Pro is not installed!' 18 | os._exit(0) 19 | 20 | if os.path.exists(g_homedir): 21 | print 'Codemap is already installed. Do you want to reinstall it? (y/n)' 22 | cmd = raw_input() 23 | if cmd is not 'y': 24 | os._exit(0) 25 | else: 26 | try: 27 | shutil.rmtree(g_homedir) 28 | except: 29 | print 'cannot remove existing files!! maybe Codemap files are locked by some application..' 30 | 31 | if os.path.exists(g_idadir + 'idapythonrc.py'): 32 | print 'Codemap uses idapythonrc.py to place its code. it seems there already is idapythonrc.py' 33 | print 'Do you want to overwrite this? (y/n)' 34 | cmd = raw_input() 35 | if cmd is not 'y': 36 | os._exit(0) 37 | 38 | shutil.copytree('codemap', g_homedir) 39 | shutil.copy2('idapythonrc.py', g_idadir) 40 | 41 | print 'Codemap install complete! run IDA Pro now.' 42 | --------------------------------------------------------------------------------