• 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.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 
NativeDaemonEvent(int cmdNumber, int code, String message, String rawEvent)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 
getCmdNumber()46     public int getCmdNumber() {
47         return mCmdNumber;
48     }
49 
getCode()50     public int getCode() {
51         return mCode;
52     }
53 
getMessage()54     public String getMessage() {
55         return mMessage;
56     }
57 
58     @Deprecated
getRawEvent()59     public String getRawEvent() {
60         return mRawEvent;
61     }
62 
63     @Override
toString()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      */
isClassContinue()72     public boolean isClassContinue() {
73         return mCode >= 100 && mCode < 200;
74     }
75 
76     /**
77      * Test if event represents a command success.
78      */
isClassOk()79     public boolean isClassOk() {
80         return mCode >= 200 && mCode < 300;
81     }
82 
83     /**
84      * Test if event represents a remote native daemon error.
85      */
isClassServerError()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      */
isClassClientError()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      */
isClassUnsolicited()100     public boolean isClassUnsolicited() {
101         return isClassUnsolicited(mCode);
102     }
103 
isClassUnsolicited(int code)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      */
checkCode(int code)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      */
parseRawEvent(String rawEvent)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      */
filterMessageList(NativeDaemonEvent[] events, int matchCode)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      */
getField(int n)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 
unescapeArgs(String rawEvent)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.replace("\\\\", "\\");
227             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