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.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 
     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 
     54     public int getCmdNumber() {
     55         return mCmdNumber;
     56     }
     57 
     58     public int getCode() {
     59         return mCode;
     60     }
     61 
     62     public String getMessage() {
     63         return mMessage;
     64     }
     65 
     66     public FileDescriptor[] getFileDescriptors() {
     67         return mFdList;
     68     }
     69 
     70     @Deprecated
     71     public String getRawEvent() {
     72         return mRawEvent;
     73     }
     74 
     75     @Override
     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      */
     84     public boolean isClassContinue() {
     85         return mCode >= 100 && mCode < 200;
     86     }
     87 
     88     /**
     89      * Test if event represents a command success.
     90      */
     91     public boolean isClassOk() {
     92         return mCode >= 200 && mCode < 300;
     93     }
     94 
     95     /**
     96      * Test if event represents a remote native daemon error.
     97      */
     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      */
    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      */
    112     public boolean isClassUnsolicited() {
    113         return isClassUnsolicited(mCode);
    114     }
    115 
    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      */
    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      */
    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      */
    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      */
    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 
    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