• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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