1 /* 2 * Copyright (C) 2008 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.ddmlib.log; 18 19 20 import com.android.ddmlib.utils.ArrayHelper; 21 22 import java.security.InvalidParameterException; 23 24 /** 25 * Receiver able to provide low level parsing for device-side log services. 26 */ 27 public final class LogReceiver { 28 29 private final static int ENTRY_HEADER_SIZE = 20; // 2*2 + 4*4; see LogEntry. 30 31 /** 32 * Represents a log entry and its raw data. 33 */ 34 public final static class LogEntry { 35 /* 36 * See //device/include/utils/logger.h 37 */ 38 /** 16bit unsigned: length of the payload. */ 39 public int len; /* This is normally followed by a 16 bit padding */ 40 /** pid of the process that generated this {@link LogEntry} */ 41 public int pid; 42 /** tid of the process that generated this {@link LogEntry} */ 43 public int tid; 44 /** Seconds since epoch. */ 45 public int sec; 46 /** nanoseconds. */ 47 public int nsec; 48 /** The entry's raw data. */ 49 public byte[] data; 50 }; 51 52 /** 53 * Classes which implement this interface provide a method that deals 54 * with {@link LogEntry} objects coming from log service through a {@link LogReceiver}. 55 * <p/>This interface provides two methods. 56 * <ul> 57 * <li>{@link #newEntry(com.android.ddmlib.log.LogReceiver.LogEntry)} provides a 58 * first level of parsing, extracting {@link LogEntry} objects out of the log service output.</li> 59 * <li>{@link #newData(byte[], int, int)} provides a way to receive the raw information 60 * coming directly from the log service.</li> 61 * </ul> 62 */ 63 public interface ILogListener { 64 /** 65 * Sent when a new {@link LogEntry} has been parsed by the {@link LogReceiver}. 66 * @param entry the new log entry. 67 */ newEntry(LogEntry entry)68 public void newEntry(LogEntry entry); 69 70 /** 71 * Sent when new raw data is coming from the log service. 72 * @param data the raw data buffer. 73 * @param offset the offset into the buffer signaling the beginning of the new data. 74 * @param length the length of the new data. 75 */ newData(byte[] data, int offset, int length)76 public void newData(byte[] data, int offset, int length); 77 } 78 79 /** Current {@link LogEntry} being read, before sending it to the listener. */ 80 private LogEntry mCurrentEntry; 81 82 /** Temp buffer to store partial entry headers. */ 83 private byte[] mEntryHeaderBuffer = new byte[ENTRY_HEADER_SIZE]; 84 /** Offset in the partial header buffer */ 85 private int mEntryHeaderOffset = 0; 86 /** Offset in the partial entry data */ 87 private int mEntryDataOffset = 0; 88 89 /** Listener waiting for receive fully read {@link LogEntry} objects */ 90 private ILogListener mListener; 91 92 private boolean mIsCancelled = false; 93 94 /** 95 * Creates a {@link LogReceiver} with an {@link ILogListener}. 96 * <p/> 97 * The {@link ILogListener} will receive new log entries as they are parsed, in the form 98 * of {@link LogEntry} objects. 99 * @param listener the listener to receive new log entries. 100 */ LogReceiver(ILogListener listener)101 public LogReceiver(ILogListener listener) { 102 mListener = listener; 103 } 104 105 106 /** 107 * Parses new data coming from the log service. 108 * @param data the data buffer 109 * @param offset the offset into the buffer signaling the beginning of the new data. 110 * @param length the length of the new data. 111 */ parseNewData(byte[] data, int offset, int length)112 public void parseNewData(byte[] data, int offset, int length) { 113 // notify the listener of new raw data 114 if (mListener != null) { 115 mListener.newData(data, offset, length); 116 } 117 118 // loop while there is still data to be read and the receiver has not be cancelled. 119 while (length > 0 && mIsCancelled == false) { 120 // first check if we have no current entry. 121 if (mCurrentEntry == null) { 122 if (mEntryHeaderOffset + length < ENTRY_HEADER_SIZE) { 123 // if we don't have enough data to finish the header, save 124 // the data we have and return 125 System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset, length); 126 mEntryHeaderOffset += length; 127 return; 128 } else { 129 // we have enough to fill the header, let's do it. 130 // did we store some part at the beginning of the header? 131 if (mEntryHeaderOffset != 0) { 132 // copy the rest of the entry header into the header buffer 133 int size = ENTRY_HEADER_SIZE - mEntryHeaderOffset; 134 System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset, 135 size); 136 137 // create the entry from the header buffer 138 mCurrentEntry = createEntry(mEntryHeaderBuffer, 0); 139 140 // since we used the whole entry header buffer, we reset the offset 141 mEntryHeaderOffset = 0; 142 143 // adjust current offset and remaining length to the beginning 144 // of the entry data 145 offset += size; 146 length -= size; 147 } else { 148 // create the entry directly from the data array 149 mCurrentEntry = createEntry(data, offset); 150 151 // adjust current offset and remaining length to the beginning 152 // of the entry data 153 offset += ENTRY_HEADER_SIZE; 154 length -= ENTRY_HEADER_SIZE; 155 } 156 } 157 } 158 159 // at this point, we have an entry, and offset/length have been updated to skip 160 // the entry header. 161 162 // if we have enough data for this entry or more, we'll need to end this entry 163 if (length >= mCurrentEntry.len - mEntryDataOffset) { 164 // compute and save the size of the data that we have to read for this entry, 165 // based on how much we may already have read. 166 int dataSize = mCurrentEntry.len - mEntryDataOffset; 167 168 // we only read what we need, and put it in the entry buffer. 169 System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, dataSize); 170 171 // notify the listener of a new entry 172 if (mListener != null) { 173 mListener.newEntry(mCurrentEntry); 174 } 175 176 // reset some flags: we have read 0 data of the current entry. 177 // and we have no current entry being read. 178 mEntryDataOffset = 0; 179 mCurrentEntry = null; 180 181 // and update the data buffer info to the end of the current entry / start 182 // of the next one. 183 offset += dataSize; 184 length -= dataSize; 185 } else { 186 // we don't have enough data to fill this entry, so we store what we have 187 // in the entry itself. 188 System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, length); 189 190 // save the amount read for the data. 191 mEntryDataOffset += length; 192 return; 193 } 194 } 195 } 196 197 /** 198 * Returns whether this receiver is canceling the remote service. 199 */ isCancelled()200 public boolean isCancelled() { 201 return mIsCancelled; 202 } 203 204 /** 205 * Cancels the current remote service. 206 */ cancel()207 public void cancel() { 208 mIsCancelled = true; 209 } 210 211 /** 212 * Creates a {@link LogEntry} from the array of bytes. This expects the data buffer size 213 * to be at least <code>offset + {@link #ENTRY_HEADER_SIZE}</code>. 214 * @param data the data buffer the entry is read from. 215 * @param offset the offset of the first byte from the buffer representing the entry. 216 * @return a new {@link LogEntry} or <code>null</code> if some error happened. 217 */ createEntry(byte[] data, int offset)218 private LogEntry createEntry(byte[] data, int offset) { 219 if (data.length < offset + ENTRY_HEADER_SIZE) { 220 throw new InvalidParameterException( 221 "Buffer not big enough to hold full LoggerEntry header"); 222 } 223 224 // create the new entry and fill it. 225 LogEntry entry = new LogEntry(); 226 entry.len = ArrayHelper.swapU16bitFromArray(data, offset); 227 228 // we've read only 16 bits, but since there's also a 16 bit padding, 229 // we can skip right over both. 230 offset += 4; 231 232 entry.pid = ArrayHelper.swap32bitFromArray(data, offset); 233 offset += 4; 234 entry.tid = ArrayHelper.swap32bitFromArray(data, offset); 235 offset += 4; 236 entry.sec = ArrayHelper.swap32bitFromArray(data, offset); 237 offset += 4; 238 entry.nsec = ArrayHelper.swap32bitFromArray(data, offset); 239 offset += 4; 240 241 // allocate the data 242 entry.data = new byte[entry.len]; 243 244 return entry; 245 } 246 247 } 248