import json import os def decode_gwt_rpc(payload): """ Decodes a GWT-RPC payload to extract outage data for Counties. """ # 1. Clean the payload # GWT responses often start with //OK. We strip that. if payload.startswith("//OK"): payload = payload[4:] # 2. Parse the FULL payload as JSON # The GWT payload is structurally a JSON array: [stream_data..., [string_table], flags...] try: full_data = json.loads(payload) except json.JSONDecodeError as e: print(f"Error parsing payload JSON: {e}") return None # 3. Separate Stream and String Table # The String Table is a list of strings located near the end of the main array. # The "Stream" is everything before that string table. string_table = None stream_raw = [] # Iterate through the parsed array to find the string table (which is a list) for item in full_data: if isinstance(item, list): string_table = item # Once we find the table, we assume the rest are flags and stop adding to stream break else: stream_raw.append(item) if not string_table: print("Error: String table not found in payload.") return None # 4. Normalize the Stream # The decoder logic relies on integers (1-based indices). # The raw stream might contain floats or strings that we need to cast or filter. stream = [] for token in stream_raw: if isinstance(token, int): stream.append(token) elif isinstance(token, float): stream.append(int(token)) elif isinstance(token, str): # Sometimes numeric values are sent as strings in the stream try: stream.append(int(float(token))) except ValueError: # If it's a non-numeric string token (like a cache ID), ignore it pass # 5. Decode Logic try: # Define the signatures we are looking for in the String Table REGION_SIG = "cc.nisc.oms.clientandserver.v2.pojo.Region/3192921568" INTEGER_SIG = "java.lang.Integer/3438268394" CATEGORY_KEY = "County" # Helper to find 1-based index def get_index(val): try: return string_table.index(val) + 1 except ValueError: return 0 region_type_id = get_index(REGION_SIG) integer_type_id = get_index(INTEGER_SIG) county_type_id = get_index(CATEGORY_KEY) if region_type_id == 0: print("Error: Region type signature not found in string table.") # Debug: Print first few strings to verify if signatures changed # print("Available strings:", string_table[:10]) return None results = [] i = 0 stream_len = len(stream) # Iterate through the stream looking for Region objects while i < stream_len: if stream[i] == region_type_id: try: # We found a Region. The next few integers define its properties. # Pointer 'p' is relative to current index 'i' p = i + 1 # --- Field 1: Total Served --- # Logic: Value is valid if followed by Integer Type ID served = 0 val1 = stream[p] p += 1 if p < stream_len and stream[p] == integer_type_id: served = val1 p += 1 # Skip type ID # --- Field 2: Number Out --- out = 0 val2 = stream[p] p += 1 if p < stream_len and stream[p] == integer_type_id: out = val2 p += 1 # Skip type ID # --- Field 3: Name Index --- name_idx = stream[p] p += 1 # --- Field 4: Category Index --- cat_idx = stream[p] # Check if this is a County if cat_idx == county_type_id: name = "Unknown" if 0 < name_idx <= len(string_table): name = string_table[name_idx - 1] percent = 0.0 if served > 0: percent = (out / served) * 100 results.append({ "county": name, "served": served, "out": out, "percent": percent }) except IndexError: pass i += 1 return results except Exception as e: print(f"Error during stream traversal: {e}") return None if __name__ == "__main__": filename = "outage_data.txt" if os.path.exists(filename): with open(filename, "r", encoding="utf-8") as f: raw_content = f.read().strip() data = decode_gwt_rpc(raw_content) if data: # Sort A-Z data.sort(key=lambda x: x['county']) print(f"{'County':<20} | {'Served':>8} | {'Out':>8} | {'Percent':>8}") print("-" * 55) for row in data: print(f"{row['county']:<20} | {row['served']:>8} | {row['out']:>8} | {row['percent']:>7.2f}%") else: print("No data found.") else: print(f"File '{filename}' not found. Please create it and paste the payload.")