content: /*
   * Copyright (C) 2011 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server;
  
  import android.util.Slog;
  import com.google.android.collect.Lists;
  
  import java.io.FileDescriptor;
  import java.util.ArrayList;
  
  /**
   * Parsed event from native side of {@link NativeDaemonConnector}.
   */
  public class NativeDaemonEvent {
  
      // TODO: keep class ranges in sync with ResponseCode.h
      // TODO: swap client and server error ranges to roughly mirror HTTP spec
  
      private final int mCmdNumber;
      private final int mCode;
      private final String mMessage;
      private final String mRawEvent;
      private final String mLogMessage;
      private String[] mParsed;
      private FileDescriptor[] mFdList;
  
      private NativeDaemonEvent(int cmdNumber, int code, String message,
                                String rawEvent, String logMessage, FileDescriptor[] fdList) {
          mCmdNumber = cmdNumber;
          mCode = code;
          mMessage = message;
          mRawEvent = rawEvent;
          mLogMessage = logMessage;
          mParsed = null;
          mFdList = fdList;
      }
  
      static public final String SENSITIVE_MARKER = "{{sensitive}}";
  
      public int getCmdNumber() {
          return mCmdNumber;
      }
  
      public int getCode() {
          return mCode;
      }
  
      public String getMessage() {
          return mMessage;
      }
  
      public FileDescriptor[] getFileDescriptors() {
          return mFdList;
      }
  
      @Deprecated
      public String getRawEvent() {
          return mRawEvent;
      }
  
      @Override
      public String toString() {
          return mLogMessage;
      }
  
      /**
       * Test if event represents a partial response which is continued in
       * additional subsequent events.
       */
      public boolean isClassContinue() {
          return mCode >= 100 && mCode < 200;
      }
  
      /**
       * Test if event represents a command success.
       */
      public boolean isClassOk() {
          return mCode >= 200 && mCode < 300;
      }
  
      /**
       * Test if event represents a remote native daemon error.
       */
      public boolean isClassServerError() {
          return mCode >= 400 && mCode < 500;
      }
  
      /**
       * Test if event represents a command syntax or argument error.
       */
      public boolean isClassClientError() {
          return mCode >= 500 && mCode < 600;
      }
  
      /**
       * Test if event represents an unsolicited event from native daemon.
       */
      public boolean isClassUnsolicited() {
          return isClassUnsolicited(mCode);
      }
  
      private static boolean isClassUnsolicited(int code) {
          return code >= 600 && code < 700;
      }
  
      /**
       * Verify this event matches the given code.
       *
       * @throws IllegalStateException if {@link #getCode()} doesn't match.
       */
      public void checkCode(int code) {
          if (mCode != code) {
              throw new IllegalStateException("Expected " + code + " but was: " + this);
          }
      }
  
      /**
       * Parse the given raw event into {@link NativeDaemonEvent} instance.
       *
       * @throws IllegalArgumentException when line doesn't match format expected
       *             from native side.
       */
      public static NativeDaemonEvent parseRawEvent(String rawEvent, FileDescriptor[] fdList) {
          final String[] parsed = rawEvent.split(" ");
          if (parsed.length < 2) {
              throw new IllegalArgumentException("Insufficient arguments");
          }
  
          int skiplength = 0;
  
          final int code;
          try {
              code = Integer.parseInt(parsed[0]);
              skiplength = parsed[0].length() + 1;
          } catch (NumberFormatException e) {
              throw new IllegalArgumentException("problem parsing code", e);
          }
  
          int cmdNumber = -1;
          if (isClassUnsolicited(code) == false) {
              if (parsed.length < 3) {
                  throw new IllegalArgumentException("Insufficient arguemnts");
              }
              try {
                  cmdNumber = Integer.parseInt(parsed[1]);
                  skiplength += parsed[1].length() + 1;
              } catch (NumberFormatException e) {
                  throw new IllegalArgumentException("problem parsing cmdNumber", e);
              }
          }
  
          String logMessage = rawEvent;
          if (parsed.length > 2 && parsed[2].equals(SENSITIVE_MARKER)) {
              skiplength += parsed[2].length() + 1;
              logMessage = parsed[0] + " " + parsed[1] + " {}";
          }
  
          final String message = rawEvent.substring(skiplength);
  
          return new NativeDaemonEvent(cmdNumber, code, message, rawEvent, logMessage, fdList);
      }
  
      /**
       * Filter the given {@link NativeDaemonEvent} list, returning
       * {@link #getMessage()} for any events matching the requested code.
       */
      public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
          final ArrayList<String> result = Lists.newArrayList();
          for (NativeDaemonEvent event : events) {
              if (event.getCode() == matchCode) {
                  result.add(event.getMessage());
              }
          }
          return result.toArray(new String[result.size()]);
      }
  
      /**
       * Find the Nth field of the event.
       *
       * This ignores and code or cmdNum, the first return value is given for N=0.
       * Also understands "\"quoted\" multiword responses" and tries them as a single field
       */
      public String getField(int n) {
          if (mParsed == null) {
              mParsed = unescapeArgs(mRawEvent);
          }
          n += 2; // skip code and command#
          if (n > mParsed.length) return null;
              return mParsed[n];
          }
  
      public static String[] unescapeArgs(String rawEvent) {
          final boolean DEBUG_ROUTINE = false;
          final String LOGTAG = "unescapeArgs";
          final ArrayList<String> parsed = new ArrayList<String>();
          final int length = rawEvent.length();
          int current = 0;
          int wordEnd = -1;
          boolean quoted = false;
  
          if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'");
          if (rawEvent.charAt(current) == '\"') {
              quoted = true;
              current++;
          }
          while (current < length) {
              // find the end of the word
              char terminator = quoted ? '\"' : ' ';
              wordEnd = current;
              while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) {
                  if (rawEvent.charAt(wordEnd) == '\\') {
                      // skip the escaped char
                      ++wordEnd;
                  }
                  ++wordEnd;
              }
              if (wordEnd > length) wordEnd = length;
              String word = rawEvent.substring(current, wordEnd);
              current += word.length();
              if (!quoted) {
                  word = word.trim();
              } else {
                  current++;  // skip the trailing quote
              }
              // unescape stuff within the word
              word = word.replace("\\\\", "\\");
              word = word.replace("\\\"", "\"");
  
              if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'");
              parsed.add(word);
  
              // find the beginning of the next word - either of these options
              int nextSpace = rawEvent.indexOf(' ', current);
              int nextQuote = rawEvent.indexOf(" \"", current);
              if (DEBUG_ROUTINE) {
                  Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
              }
              if (nextQuote > -1 && nextQuote <= nextSpace) {
                  quoted = true;
                  current = nextQuote + 2;
              } else {
                  quoted = false;
                  if (nextSpace > -1) {
                      current = nextSpace + 1;
                  }
              } // else we just start the next word after the current and read til the end
              if (DEBUG_ROUTINE) {
                  Slog.e(LOGTAG, "next loop - current=" + current +
                          ", length=" + length + ", quoted=" + quoted);
              }
          }
          return parsed.toArray(new String[parsed.size()]);
      }
  }
  