Home | History | Annotate | Download | only in server
      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