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 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