• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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