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 String[] mParsed; 37 38 private NativeDaemonEvent(int cmdNumber, int code, String message, String rawEvent) { 39 mCmdNumber = cmdNumber; 40 mCode = code; 41 mMessage = message; 42 mRawEvent = rawEvent; 43 mParsed = null; 44 } 45 46 public int getCmdNumber() { 47 return mCmdNumber; 48 } 49 50 public int getCode() { 51 return mCode; 52 } 53 54 public String getMessage() { 55 return mMessage; 56 } 57 58 @Deprecated 59 public String getRawEvent() { 60 return mRawEvent; 61 } 62 63 @Override 64 public String toString() { 65 return mRawEvent; 66 } 67 68 /** 69 * Test if event represents a partial response which is continued in 70 * additional subsequent events. 71 */ 72 public boolean isClassContinue() { 73 return mCode >= 100 && mCode < 200; 74 } 75 76 /** 77 * Test if event represents a command success. 78 */ 79 public boolean isClassOk() { 80 return mCode >= 200 && mCode < 300; 81 } 82 83 /** 84 * Test if event represents a remote native daemon error. 85 */ 86 public boolean isClassServerError() { 87 return mCode >= 400 && mCode < 500; 88 } 89 90 /** 91 * Test if event represents a command syntax or argument error. 92 */ 93 public boolean isClassClientError() { 94 return mCode >= 500 && mCode < 600; 95 } 96 97 /** 98 * Test if event represents an unsolicited event from native daemon. 99 */ 100 public boolean isClassUnsolicited() { 101 return isClassUnsolicited(mCode); 102 } 103 104 private static boolean isClassUnsolicited(int code) { 105 return code >= 600 && code < 700; 106 } 107 108 /** 109 * Verify this event matches the given code. 110 * 111 * @throws IllegalStateException if {@link #getCode()} doesn't match. 112 */ 113 public void checkCode(int code) { 114 if (mCode != code) { 115 throw new IllegalStateException("Expected " + code + " but was: " + this); 116 } 117 } 118 119 /** 120 * Parse the given raw event into {@link NativeDaemonEvent} instance. 121 * 122 * @throws IllegalArgumentException when line doesn't match format expected 123 * from native side. 124 */ 125 public static NativeDaemonEvent parseRawEvent(String rawEvent) { 126 final String[] parsed = rawEvent.split(" "); 127 if (parsed.length < 2) { 128 throw new IllegalArgumentException("Insufficient arguments"); 129 } 130 131 int skiplength = 0; 132 133 final int code; 134 try { 135 code = Integer.parseInt(parsed[0]); 136 skiplength = parsed[0].length() + 1; 137 } catch (NumberFormatException e) { 138 throw new IllegalArgumentException("problem parsing code", e); 139 } 140 141 int cmdNumber = -1; 142 if (isClassUnsolicited(code) == false) { 143 if (parsed.length < 3) { 144 throw new IllegalArgumentException("Insufficient arguemnts"); 145 } 146 try { 147 cmdNumber = Integer.parseInt(parsed[1]); 148 skiplength += parsed[1].length() + 1; 149 } catch (NumberFormatException e) { 150 throw new IllegalArgumentException("problem parsing cmdNumber", e); 151 } 152 } 153 154 final String message = rawEvent.substring(skiplength); 155 156 return new NativeDaemonEvent(cmdNumber, code, message, rawEvent); 157 } 158 159 /** 160 * Filter the given {@link NativeDaemonEvent} list, returning 161 * {@link #getMessage()} for any events matching the requested code. 162 */ 163 public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) { 164 final ArrayList<String> result = Lists.newArrayList(); 165 for (NativeDaemonEvent event : events) { 166 if (event.getCode() == matchCode) { 167 result.add(event.getMessage()); 168 } 169 } 170 return result.toArray(new String[result.size()]); 171 } 172 173 /** 174 * Find the Nth field of the event. 175 * 176 * This ignores and code or cmdNum, the first return value is given for N=0. 177 * Also understands "\"quoted\" multiword responses" and tries them as a single field 178 */ 179 public String getField(int n) { 180 if (mParsed == null) { 181 mParsed = unescapeArgs(mRawEvent); 182 } 183 n += 2; // skip code and command# 184 if (n > mParsed.length) return null; 185 return mParsed[n]; 186 } 187 188 public static String[] unescapeArgs(String rawEvent) { 189 final boolean DEBUG_ROUTINE = false; 190 final String LOGTAG = "unescapeArgs"; 191 final ArrayList<String> parsed = new ArrayList<String>(); 192 final int length = rawEvent.length(); 193 int current = 0; 194 int wordEnd = -1; 195 boolean quoted = false; 196 197 if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'"); 198 if (rawEvent.charAt(current) == '\"') { 199 quoted = true; 200 current++; 201 } 202 while (current < length) { 203 // find the end of the word 204 if (quoted) { 205 wordEnd = current; 206 while ((wordEnd = rawEvent.indexOf('\"', wordEnd)) != -1) { 207 if (rawEvent.charAt(wordEnd - 1) != '\\') { 208 break; 209 } else { 210 wordEnd++; // skip this escaped quote and keep looking 211 } 212 } 213 } else { 214 wordEnd = rawEvent.indexOf(' ', current); 215 } 216 // if we didn't find the end-o-word token, take the rest of the string 217 if (wordEnd == -1) wordEnd = length; 218 String word = rawEvent.substring(current, wordEnd); 219 current += word.length(); 220 if (!quoted) { 221 word = word.trim(); 222 } else { 223 current++; // skip the trailing quote 224 } 225 // unescape stuff within the word 226 word = word.replace("\\\\", "\\"); 227 word = word.replace("\\\"", "\""); 228 229 if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'"); 230 parsed.add(word); 231 232 // find the beginning of the next word - either of these options 233 int nextSpace = rawEvent.indexOf(' ', current); 234 int nextQuote = rawEvent.indexOf(" \"", current); 235 if (DEBUG_ROUTINE) { 236 Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote); 237 } 238 if (nextQuote > -1 && nextQuote <= nextSpace) { 239 quoted = true; 240 current = nextQuote + 2; 241 } else { 242 quoted = false; 243 if (nextSpace > -1) { 244 current = nextSpace + 1; 245 } 246 } // else we just start the next word after the current and read til the end 247 if (DEBUG_ROUTINE) { 248 Slog.e(LOGTAG, "next loop - current=" + current + 249 ", length=" + length + ", quoted=" + quoted); 250 } 251 } 252 return parsed.toArray(new String[parsed.size()]); 253 } 254 } 255