1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.util.Slog; 20 import com.google.android.collect.Lists; 21 22 import java.util.ArrayList; 23 24 /** 25 * Parsed event from native side of {@link NativeDaemonConnector}. 26 */ 27 public class NativeDaemonEvent { 28 29 // TODO: keep class ranges in sync with ResponseCode.h 30 // TODO: swap client and server error ranges to roughly mirror HTTP spec 31 32 private final int mCmdNumber; 33 private final int mCode; 34 private final String mMessage; 35 private final String mRawEvent; 36 private final String mLogMessage; 37 private String[] mParsed; 38 39 private NativeDaemonEvent(int cmdNumber, int code, String message, 40 String rawEvent, String logMessage) { 41 mCmdNumber = cmdNumber; 42 mCode = code; 43 mMessage = message; 44 mRawEvent = rawEvent; 45 mLogMessage = logMessage; 46 mParsed = null; 47 } 48 49 static public final String SENSITIVE_MARKER = "{{sensitive}}"; 50 51 public int getCmdNumber() { 52 return mCmdNumber; 53 } 54 55 public int getCode() { 56 return mCode; 57 } 58 59 public String getMessage() { 60 return mMessage; 61 } 62 63 @Deprecated 64 public String getRawEvent() { 65 return mRawEvent; 66 } 67 68 @Override 69 public String toString() { 70 return mLogMessage; 71 } 72 73 /** 74 * Test if event represents a partial response which is continued in 75 * additional subsequent events. 76 */ 77 public boolean isClassContinue() { 78 return mCode >= 100 && mCode < 200; 79 } 80 81 /** 82 * Test if event represents a command success. 83 */ 84 public boolean isClassOk() { 85 return mCode >= 200 && mCode < 300; 86 } 87 88 /** 89 * Test if event represents a remote native daemon error. 90 */ 91 public boolean isClassServerError() { 92 return mCode >= 400 && mCode < 500; 93 } 94 95 /** 96 * Test if event represents a command syntax or argument error. 97 */ 98 public boolean isClassClientError() { 99 return mCode >= 500 && mCode < 600; 100 } 101 102 /** 103 * Test if event represents an unsolicited event from native daemon. 104 */ 105 public boolean isClassUnsolicited() { 106 return isClassUnsolicited(mCode); 107 } 108 109 private static boolean isClassUnsolicited(int code) { 110 return code >= 600 && code < 700; 111 } 112 113 /** 114 * Verify this event matches the given code. 115 * 116 * @throws IllegalStateException if {@link #getCode()} doesn't match. 117 */ 118 public void checkCode(int code) { 119 if (mCode != code) { 120 throw new IllegalStateException("Expected " + code + " but was: " + this); 121 } 122 } 123 124 /** 125 * Parse the given raw event into {@link NativeDaemonEvent} instance. 126 * 127 * @throws IllegalArgumentException when line doesn't match format expected 128 * from native side. 129 */ 130 public static NativeDaemonEvent parseRawEvent(String rawEvent) { 131 final String[] parsed = rawEvent.split(" "); 132 if (parsed.length < 2) { 133 throw new IllegalArgumentException("Insufficient arguments"); 134 } 135 136 int skiplength = 0; 137 138 final int code; 139 try { 140 code = Integer.parseInt(parsed[0]); 141 skiplength = parsed[0].length() + 1; 142 } catch (NumberFormatException e) { 143 throw new IllegalArgumentException("problem parsing code", e); 144 } 145 146 int cmdNumber = -1; 147 if (isClassUnsolicited(code) == false) { 148 if (parsed.length < 3) { 149 throw new IllegalArgumentException("Insufficient arguemnts"); 150 } 151 try { 152 cmdNumber = Integer.parseInt(parsed[1]); 153 skiplength += parsed[1].length() + 1; 154 } catch (NumberFormatException e) { 155 throw new IllegalArgumentException("problem parsing cmdNumber", e); 156 } 157 } 158 159 String logMessage = rawEvent; 160 if (parsed.length > 2 && parsed[2].equals(SENSITIVE_MARKER)) { 161 skiplength += parsed[2].length() + 1; 162 logMessage = parsed[0] + " " + parsed[1] + " {}"; 163 } 164 165 final String message = rawEvent.substring(skiplength); 166 167 return new NativeDaemonEvent(cmdNumber, code, message, rawEvent, logMessage); 168 } 169 170 /** 171 * Filter the given {@link NativeDaemonEvent} list, returning 172 * {@link #getMessage()} for any events matching the requested code. 173 */ 174 public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) { 175 final ArrayList<String> result = Lists.newArrayList(); 176 for (NativeDaemonEvent event : events) { 177 if (event.getCode() == matchCode) { 178 result.add(event.getMessage()); 179 } 180 } 181 return result.toArray(new String[result.size()]); 182 } 183 184 /** 185 * Find the Nth field of the event. 186 * 187 * This ignores and code or cmdNum, the first return value is given for N=0. 188 * Also understands "\"quoted\" multiword responses" and tries them as a single field 189 */ 190 public String getField(int n) { 191 if (mParsed == null) { 192 mParsed = unescapeArgs(mRawEvent); 193 } 194 n += 2; // skip code and command# 195 if (n > mParsed.length) return null; 196 return mParsed[n]; 197 } 198 199 public static String[] unescapeArgs(String rawEvent) { 200 final boolean DEBUG_ROUTINE = false; 201 final String LOGTAG = "unescapeArgs"; 202 final ArrayList<String> parsed = new ArrayList<String>(); 203 final int length = rawEvent.length(); 204 int current = 0; 205 int wordEnd = -1; 206 boolean quoted = false; 207 208 if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'"); 209 if (rawEvent.charAt(current) == '\"') { 210 quoted = true; 211 current++; 212 } 213 while (current < length) { 214 // find the end of the word 215 char terminator = quoted ? '\"' : ' '; 216 wordEnd = current; 217 while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) { 218 if (rawEvent.charAt(wordEnd) == '\\') { 219 // skip the escaped char 220 ++wordEnd; 221 } 222 ++wordEnd; 223 } 224 if (wordEnd > length) wordEnd = length; 225 String word = rawEvent.substring(current, wordEnd); 226 current += word.length(); 227 if (!quoted) { 228 word = word.trim(); 229 } else { 230 current++; // skip the trailing quote 231 } 232 // unescape stuff within the word 233 word = word.replace("\\\\", "\\"); 234 word = word.replace("\\\"", "\""); 235 236 if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'"); 237 parsed.add(word); 238 239 // find the beginning of the next word - either of these options 240 int nextSpace = rawEvent.indexOf(' ', current); 241 int nextQuote = rawEvent.indexOf(" \"", current); 242 if (DEBUG_ROUTINE) { 243 Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote); 244 } 245 if (nextQuote > -1 && nextQuote <= nextSpace) { 246 quoted = true; 247 current = nextQuote + 2; 248 } else { 249 quoted = false; 250 if (nextSpace > -1) { 251 current = nextSpace + 1; 252 } 253 } // else we just start the next word after the current and read til the end 254 if (DEBUG_ROUTINE) { 255 Slog.e(LOGTAG, "next loop - current=" + current + 256 ", length=" + length + ", quoted=" + quoted); 257 } 258 } 259 return parsed.toArray(new String[parsed.size()]); 260 } 261 } 262