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