1 /*
2 * Copyright (C) 2017 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 #define LOG_TAG "libprotoutil"
17
18 #include <cinttypes>
19 #include <type_traits>
20
21 #include <android-base/file.h>
22 #include <android/util/protobuf.h>
23 #include <android/util/ProtoOutputStream.h>
24 #include <cutils/log.h>
25
26 namespace android {
27 namespace util {
28
ProtoOutputStream()29 ProtoOutputStream::ProtoOutputStream()
30 :mBuffer(new EncodedBuffer()),
31 mCopyBegin(0),
32 mCompact(false),
33 mDepth(0),
34 mObjectId(0),
35 mExpectedObjectToken(UINT64_C(-1))
36 {
37 }
38
~ProtoOutputStream()39 ProtoOutputStream::~ProtoOutputStream()
40 {
41 }
42
43
44 void
clear()45 ProtoOutputStream::clear()
46 {
47 mBuffer->clear();
48 mCopyBegin = 0;
49 mCompact = false;
50 mDepth = 0;
51 mObjectId = 0;
52 mExpectedObjectToken = UINT64_C(-1);
53 }
54
55 template<typename T>
56 bool
internalWrite(uint64_t fieldId,T val,const char * typeName)57 ProtoOutputStream::internalWrite(uint64_t fieldId, T val, const char* typeName)
58 {
59 if (mCompact) return false;
60 const uint32_t id = (uint32_t)fieldId;
61 switch (fieldId & FIELD_TYPE_MASK) {
62 case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
63 case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
64 case FIELD_TYPE_INT64: writeInt64Impl(id, (int64_t)val); break;
65 case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
66 case FIELD_TYPE_INT32: writeInt32Impl(id, (int32_t)val); break;
67 case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
68 case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
69 case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
70 case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int32_t)val); break;
71 case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (int64_t)val); break;
72 case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int32_t)val); break;
73 case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (int64_t)val); break;
74 case FIELD_TYPE_ENUM:
75 if (std::is_integral<T>::value) {
76 writeEnumImpl(id, (int)val);
77 } else {
78 goto unsupported;
79 }
80 break;
81 case FIELD_TYPE_BOOL:
82 if (std::is_integral<T>::value) {
83 writeBoolImpl(id, val != 0);
84 } else {
85 goto unsupported;
86 }
87 break;
88 default:
89 goto unsupported;
90 }
91 return true;
92
93 unsupported:
94 ALOGW("Field type %" PRIu64 " is not supported when writing %s val.",
95 (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT, typeName);
96 return false;
97 }
98
99 bool
write(uint64_t fieldId,double val)100 ProtoOutputStream::write(uint64_t fieldId, double val)
101 {
102 return internalWrite(fieldId, val, "double");
103 }
104
105
106 bool
write(uint64_t fieldId,float val)107 ProtoOutputStream::write(uint64_t fieldId, float val)
108 {
109 return internalWrite(fieldId, val, "float");
110 }
111
112 bool
write(uint64_t fieldId,int val)113 ProtoOutputStream::write(uint64_t fieldId, int val)
114 {
115 return internalWrite(fieldId, val, "int");
116 }
117
118 bool
write(uint64_t fieldId,long long val)119 ProtoOutputStream::write(uint64_t fieldId, long long val)
120 {
121 return internalWrite(fieldId, val, "long long");
122 }
123
124 bool
write(uint64_t fieldId,bool val)125 ProtoOutputStream::write(uint64_t fieldId, bool val)
126 {
127 if (mCompact) return false;
128 const uint32_t id = (uint32_t)fieldId;
129 switch (fieldId & FIELD_TYPE_MASK) {
130 case FIELD_TYPE_BOOL:
131 writeBoolImpl(id, val);
132 return true;
133 default:
134 ALOGW("Field type %" PRIu64 " is not supported when writing bool val.",
135 (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT);
136 return false;
137 }
138 }
139
140 bool
write(uint64_t fieldId,std::string val)141 ProtoOutputStream::write(uint64_t fieldId, std::string val)
142 {
143 if (mCompact) return false;
144 const uint32_t id = (uint32_t)fieldId;
145 switch (fieldId & FIELD_TYPE_MASK) {
146 case FIELD_TYPE_STRING:
147 writeUtf8StringImpl(id, val.c_str(), val.size());
148 return true;
149 default:
150 ALOGW("Field type %" PRIu64 " is not supported when writing string val.",
151 (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT);
152 return false;
153 }
154 }
155
156 bool
write(uint64_t fieldId,const char * val,size_t size)157 ProtoOutputStream::write(uint64_t fieldId, const char* val, size_t size)
158 {
159 if (mCompact) return false;
160 const uint32_t id = (uint32_t)fieldId;
161 switch (fieldId & FIELD_TYPE_MASK) {
162 case FIELD_TYPE_STRING:
163 case FIELD_TYPE_BYTES:
164 writeUtf8StringImpl(id, val, size);
165 return true;
166 case FIELD_TYPE_MESSAGE:
167 // can directly write valid format of message bytes into ProtoOutputStream without calling start/end
168 writeMessageBytesImpl(id, val, size);
169 return true;
170 default:
171 ALOGW("Field type %" PRIu64 " is not supported when writing char[] val.",
172 (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT);
173 return false;
174 }
175 }
176
177 /**
178 * Make a token.
179 * Bits 61-63 - tag size (So we can go backwards later if the object had not data)
180 * - 3 bits, max value 7, max value needed 5
181 * Bit 60 - true if the object is repeated
182 * Bits 59-51 - depth (For error checking)
183 * - 9 bits, max value 511, when checking, value is masked (if we really
184 * are more than 511 levels deep)
185 * Bits 32-50 - objectId (For error checking)
186 * - 19 bits, max value 524,287. that's a lot of objects. IDs will wrap
187 * because of the overflow, and only the tokens are compared.
188 * Bits 0-31 - offset of the first size field in the buffer.
189 */
190 static uint64_t
makeToken(uint32_t tagSize,bool repeated,uint32_t depth,uint32_t objectId,size_t sizePos)191 makeToken(uint32_t tagSize, bool repeated, uint32_t depth, uint32_t objectId, size_t sizePos) {
192 return ((UINT64_C(0x07) & (uint64_t)tagSize) << 61)
193 | (repeated ? (UINT64_C(1) << 60) : 0)
194 | (UINT64_C(0x01ff) & (uint64_t)depth) << 51
195 | (UINT64_C(0x07ffff) & (uint64_t)objectId) << 32
196 | (UINT64_C(0x0ffffffff) & (uint64_t)sizePos);
197 }
198
199 /**
200 * Get the encoded tag size from the token.
201 */
getTagSizeFromToken(uint64_t token)202 static uint32_t getTagSizeFromToken(uint64_t token) {
203 return 0x7 & (token >> 61);
204 }
205
206 /**
207 * Get the nesting depth of startObject calls from the token.
208 */
getDepthFromToken(uint64_t token)209 static uint32_t getDepthFromToken(uint64_t token) {
210 return 0x01ff & (token >> 51);
211 }
212
213 /**
214 * Get the location of the childRawSize (the first 32 bit size field) in this object.
215 */
getSizePosFromToken(uint64_t token)216 static uint32_t getSizePosFromToken(uint64_t token) {
217 return (uint32_t)token;
218 }
219
220 uint64_t
start(uint64_t fieldId)221 ProtoOutputStream::start(uint64_t fieldId)
222 {
223 if ((fieldId & FIELD_TYPE_MASK) != FIELD_TYPE_MESSAGE) {
224 ALOGE("Can't call start for non-message type field: 0x%" PRIx64, fieldId);
225 return 0;
226 }
227
228 uint32_t id = (uint32_t)fieldId;
229 size_t prevPos = mBuffer->wp()->pos();
230 mBuffer->writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED);
231 size_t sizePos = mBuffer->wp()->pos();
232
233 mDepth++;
234 mObjectId++;
235 mBuffer->writeRawFixed64(mExpectedObjectToken); // push previous token into stack.
236
237 mExpectedObjectToken = makeToken(sizePos - prevPos,
238 (bool)(fieldId & FIELD_COUNT_REPEATED), mDepth, mObjectId, sizePos);
239 return mExpectedObjectToken;
240 }
241
242 void
end(uint64_t token)243 ProtoOutputStream::end(uint64_t token)
244 {
245 if (token != mExpectedObjectToken) {
246 ALOGE("Unexpected token: 0x%" PRIx64 ", should be 0x%" PRIx64, token, mExpectedObjectToken);
247 mDepth = UINT32_C(-1); // make depth invalid
248 return;
249 }
250
251 uint32_t depth = getDepthFromToken(token);
252 if (depth != (mDepth & 0x01ff)) {
253 ALOGE("Unexpected depth: %" PRIu32 ", should be %" PRIu32, depth, mDepth);
254 mDepth = UINT32_C(-1); // make depth invalid
255 return;
256 }
257 mDepth--;
258
259 uint32_t sizePos = getSizePosFromToken(token);
260 // number of bytes written in this start-end session.
261 int childRawSize = mBuffer->wp()->pos() - sizePos - 8;
262
263 // retrieve the old token from stack.
264 mBuffer->ep()->rewind()->move(sizePos);
265 mExpectedObjectToken = mBuffer->readRawFixed64();
266
267 // If raw size is larger than 0, write the negative value here to indicate a compact is needed.
268 if (childRawSize > 0) {
269 mBuffer->editRawFixed32(sizePos, -childRawSize);
270 mBuffer->editRawFixed32(sizePos+4, -1);
271 } else {
272 // reset wp which erase the header tag of the message when its size is 0.
273 mBuffer->wp()->rewind()->move(sizePos - getTagSizeFromToken(token));
274 }
275 }
276
277 size_t
bytesWritten()278 ProtoOutputStream::bytesWritten()
279 {
280 return mBuffer->size();
281 }
282
283 bool
compact()284 ProtoOutputStream::compact() {
285 if (mCompact) return true;
286 if (mDepth != 0) {
287 ALOGE("Can't compact when depth(%" PRIu32 ") is not zero. Missing or extra calls to end.", mDepth);
288 return false;
289 }
290 // record the size of the original buffer.
291 size_t rawBufferSize = mBuffer->size();
292 if (rawBufferSize == 0) return true; // nothing to do if the buffer is empty;
293
294 // reset edit pointer and recursively compute encoded size of messages.
295 mBuffer->ep()->rewind();
296 if (editEncodedSize(rawBufferSize) == 0) {
297 ALOGE("Failed to editEncodedSize.");
298 return false;
299 }
300
301 // reset both edit pointer and write pointer, and compact recursively.
302 mBuffer->ep()->rewind();
303 mBuffer->wp()->rewind();
304 if (!compactSize(rawBufferSize)) {
305 ALOGE("Failed to compactSize.");
306 return false;
307 }
308 // copy the reset to the buffer.
309 if (mCopyBegin < rawBufferSize) {
310 mBuffer->copy(mCopyBegin, rawBufferSize - mCopyBegin);
311 }
312
313 // mark true means it is not legal to write to this ProtoOutputStream anymore
314 mCompact = true;
315 return true;
316 }
317
318 /**
319 * First compaction pass. Iterate through the data, and fill in the
320 * nested object sizes so the next pass can compact them.
321 */
322 size_t
editEncodedSize(size_t rawSize)323 ProtoOutputStream::editEncodedSize(size_t rawSize)
324 {
325 size_t objectStart = mBuffer->ep()->pos();
326 size_t objectEnd = objectStart + rawSize;
327 size_t encodedSize = 0;
328 int childRawSize, childEncodedSize;
329 size_t childEncodedSizePos;
330
331 while (mBuffer->ep()->pos() < objectEnd) {
332 uint32_t tag = (uint32_t)mBuffer->readRawVarint();
333 encodedSize += get_varint_size(tag);
334 switch (read_wire_type(tag)) {
335 case WIRE_TYPE_VARINT:
336 do {
337 encodedSize++;
338 } while ((mBuffer->readRawByte() & 0x80) != 0);
339 break;
340 case WIRE_TYPE_FIXED64:
341 encodedSize += 8;
342 mBuffer->ep()->move(8);
343 break;
344 case WIRE_TYPE_LENGTH_DELIMITED:
345 childRawSize = (int)mBuffer->readRawFixed32();
346 childEncodedSizePos = mBuffer->ep()->pos();
347 childEncodedSize = (int)mBuffer->readRawFixed32();
348 if (childRawSize >= 0 && childRawSize == childEncodedSize) {
349 mBuffer->ep()->move(childRawSize);
350 } else if (childRawSize < 0 && childEncodedSize == -1){
351 childEncodedSize = editEncodedSize(-childRawSize);
352 mBuffer->editRawFixed32(childEncodedSizePos, childEncodedSize);
353 } else {
354 ALOGE("Bad raw or encoded values: raw=%d, encoded=%d at %zu",
355 childRawSize, childEncodedSize, childEncodedSizePos);
356 return 0;
357 }
358 encodedSize += get_varint_size(childEncodedSize) + childEncodedSize;
359 break;
360 case WIRE_TYPE_FIXED32:
361 encodedSize += 4;
362 mBuffer->ep()->move(4);
363 break;
364 default:
365 ALOGE("Unexpected wire type %d in editEncodedSize at [%zu, %zu]",
366 read_wire_type(tag), objectStart, objectEnd);
367 return 0;
368 }
369 }
370 return encodedSize;
371 }
372
373 /**
374 * Second compaction pass. Iterate through the data, and copy the data
375 * forward in the buffer, converting the pairs of uint32s into a single
376 * unsigned varint of the size.
377 */
378 bool
compactSize(size_t rawSize)379 ProtoOutputStream::compactSize(size_t rawSize)
380 {
381 size_t objectStart = mBuffer->ep()->pos();
382 size_t objectEnd = objectStart + rawSize;
383 int childRawSize, childEncodedSize;
384
385 while (mBuffer->ep()->pos() < objectEnd) {
386 uint32_t tag = (uint32_t)mBuffer->readRawVarint();
387 switch (read_wire_type(tag)) {
388 case WIRE_TYPE_VARINT:
389 while ((mBuffer->readRawByte() & 0x80) != 0) {}
390 break;
391 case WIRE_TYPE_FIXED64:
392 mBuffer->ep()->move(8);
393 break;
394 case WIRE_TYPE_LENGTH_DELIMITED:
395 mBuffer->copy(mCopyBegin, mBuffer->ep()->pos() - mCopyBegin);
396
397 childRawSize = (int)mBuffer->readRawFixed32();
398 childEncodedSize = (int)mBuffer->readRawFixed32();
399 mCopyBegin = mBuffer->ep()->pos();
400
401 // write encoded size to buffer.
402 mBuffer->writeRawVarint32(childEncodedSize);
403 if (childRawSize >= 0 && childRawSize == childEncodedSize) {
404 mBuffer->ep()->move(childEncodedSize);
405 } else if (childRawSize < 0){
406 if (!compactSize(-childRawSize)) return false;
407 } else {
408 ALOGE("Bad raw or encoded values: raw=%d, encoded=%d",
409 childRawSize, childEncodedSize);
410 return false;
411 }
412 break;
413 case WIRE_TYPE_FIXED32:
414 mBuffer->ep()->move(4);
415 break;
416 default:
417 ALOGE("Unexpected wire type %d in compactSize at [%zu, %zu]",
418 read_wire_type(tag), objectStart, objectEnd);
419 return false;
420 }
421 }
422 return true;
423 }
424
425 size_t
size()426 ProtoOutputStream::size()
427 {
428 if (!compact()) {
429 ALOGE("compact failed, the ProtoOutputStream data is corrupted!");
430 return 0;
431 }
432 return mBuffer->size();
433 }
434
435 bool
flush(int fd)436 ProtoOutputStream::flush(int fd)
437 {
438 if (fd < 0) return false;
439 if (!compact()) return false;
440
441 sp<ProtoReader> reader = mBuffer->read();
442 while (reader->readBuffer() != NULL) {
443 if (!android::base::WriteFully(fd, reader->readBuffer(), reader->currentToRead())) {
444 return false;
445 }
446 reader->move(reader->currentToRead());
447 }
448 return true;
449 }
450
451 bool
serializeToString(std::string * out)452 ProtoOutputStream::serializeToString(std::string* out)
453 {
454 if (out == nullptr) return false;
455 if (!compact()) return false;
456
457 sp<ProtoReader> reader = mBuffer->read();
458 out->reserve(reader->size());
459 while (reader->hasNext()) {
460 out->append(static_cast<const char*>(static_cast<const void*>(reader->readBuffer())),
461 reader->currentToRead());
462 reader->move(reader->currentToRead());
463 }
464 return true;
465 }
466
467 bool
serializeToVector(std::vector<uint8_t> * out)468 ProtoOutputStream::serializeToVector(std::vector<uint8_t>* out)
469 {
470 if (out == nullptr) return false;
471 if (!compact()) return false;
472
473 sp<ProtoReader> reader = mBuffer->read();
474 out->reserve(reader->size());
475 while (reader->hasNext()) {
476 const uint8_t* buf = reader->readBuffer();
477 size_t size = reader->currentToRead();
478 out->insert(out->end(), buf, buf + size);
479 reader->move(size);
480 }
481 return true;
482 }
483
484 sp<ProtoReader>
data()485 ProtoOutputStream::data()
486 {
487 if (!compact()) {
488 ALOGE("compact failed, the ProtoOutputStream data is corrupted!");
489 mBuffer->clear();
490 }
491 return mBuffer->read();
492 }
493
494 void
writeRawVarint(uint64_t varint)495 ProtoOutputStream::writeRawVarint(uint64_t varint)
496 {
497 mBuffer->writeRawVarint64(varint);
498 }
499
500 void
writeLengthDelimitedHeader(uint32_t id,size_t size)501 ProtoOutputStream::writeLengthDelimitedHeader(uint32_t id, size_t size)
502 {
503 mBuffer->writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED);
504 // reserves 64 bits for length delimited fields, if first field is negative, compact it.
505 mBuffer->writeRawFixed32(size);
506 mBuffer->writeRawFixed32(size);
507 }
508
509 void
writeRawByte(uint8_t byte)510 ProtoOutputStream::writeRawByte(uint8_t byte)
511 {
512 mBuffer->writeRawByte(byte);
513 }
514
515
516 // =========================================================================
517 // Private functions
518
519 /**
520 * bit_cast
521 */
522 template <class From, class To>
bit_cast(From const & from)523 inline To bit_cast(From const &from) {
524 To to;
525 memcpy(&to, &from, sizeof(to));
526 return to;
527 }
528
529 inline void
writeDoubleImpl(uint32_t id,double val)530 ProtoOutputStream::writeDoubleImpl(uint32_t id, double val)
531 {
532 mBuffer->writeHeader(id, WIRE_TYPE_FIXED64);
533 mBuffer->writeRawFixed64(bit_cast<double, uint64_t>(val));
534 }
535
536 inline void
writeFloatImpl(uint32_t id,float val)537 ProtoOutputStream::writeFloatImpl(uint32_t id, float val)
538 {
539 mBuffer->writeHeader(id, WIRE_TYPE_FIXED32);
540 mBuffer->writeRawFixed32(bit_cast<float, uint32_t>(val));
541 }
542
543 inline void
writeInt64Impl(uint32_t id,int64_t val)544 ProtoOutputStream::writeInt64Impl(uint32_t id, int64_t val)
545 {
546 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
547 mBuffer->writeRawVarint64(val);
548 }
549
550 inline void
writeInt32Impl(uint32_t id,int32_t val)551 ProtoOutputStream::writeInt32Impl(uint32_t id, int32_t val)
552 {
553 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
554 mBuffer->writeRawVarint32(val);
555 }
556
557 inline void
writeUint64Impl(uint32_t id,uint64_t val)558 ProtoOutputStream::writeUint64Impl(uint32_t id, uint64_t val)
559 {
560 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
561 mBuffer->writeRawVarint64(val);
562 }
563
564 inline void
writeUint32Impl(uint32_t id,uint32_t val)565 ProtoOutputStream::writeUint32Impl(uint32_t id, uint32_t val)
566 {
567 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
568 mBuffer->writeRawVarint32(val);
569 }
570
571 inline void
writeFixed64Impl(uint32_t id,uint64_t val)572 ProtoOutputStream::writeFixed64Impl(uint32_t id, uint64_t val)
573 {
574 mBuffer->writeHeader(id, WIRE_TYPE_FIXED64);
575 mBuffer->writeRawFixed64(val);
576 }
577
578 inline void
writeFixed32Impl(uint32_t id,uint32_t val)579 ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val)
580 {
581 mBuffer->writeHeader(id, WIRE_TYPE_FIXED32);
582 mBuffer->writeRawFixed32(val);
583 }
584
585 inline void
writeSFixed64Impl(uint32_t id,int64_t val)586 ProtoOutputStream::writeSFixed64Impl(uint32_t id, int64_t val)
587 {
588 mBuffer->writeHeader(id, WIRE_TYPE_FIXED64);
589 mBuffer->writeRawFixed64(val);
590 }
591
592 inline void
writeSFixed32Impl(uint32_t id,int32_t val)593 ProtoOutputStream::writeSFixed32Impl(uint32_t id, int32_t val)
594 {
595 mBuffer->writeHeader(id, WIRE_TYPE_FIXED32);
596 mBuffer->writeRawFixed32(val);
597 }
598
599 inline void
writeZigzagInt64Impl(uint32_t id,int64_t val)600 ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, int64_t val)
601 {
602 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
603 mBuffer->writeRawVarint64((val << 1) ^ (val >> 63));
604 }
605
606 inline void
writeZigzagInt32Impl(uint32_t id,int32_t val)607 ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int32_t val)
608 {
609 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
610 mBuffer->writeRawVarint32((val << 1) ^ (val >> 31));
611 }
612
613 inline void
writeEnumImpl(uint32_t id,int val)614 ProtoOutputStream::writeEnumImpl(uint32_t id, int val)
615 {
616 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
617 mBuffer->writeRawVarint32((uint32_t) val);
618 }
619
620 inline void
writeBoolImpl(uint32_t id,bool val)621 ProtoOutputStream::writeBoolImpl(uint32_t id, bool val)
622 {
623 mBuffer->writeHeader(id, WIRE_TYPE_VARINT);
624 mBuffer->writeRawVarint32(val ? 1 : 0);
625 }
626
627 inline void
writeUtf8StringImpl(uint32_t id,const char * val,size_t size)628 ProtoOutputStream::writeUtf8StringImpl(uint32_t id, const char* val, size_t size)
629 {
630 if (val == NULL) return;
631 writeLengthDelimitedHeader(id, size);
632 for (size_t i=0; i<size; i++) {
633 mBuffer->writeRawByte((uint8_t)val[i]);
634 }
635 }
636
637 inline void
writeMessageBytesImpl(uint32_t id,const char * val,size_t size)638 ProtoOutputStream::writeMessageBytesImpl(uint32_t id, const char* val, size_t size)
639 {
640 if (val == NULL) return;
641 writeLengthDelimitedHeader(id, size);
642 for (size_t i=0; i<size; i++) {
643 mBuffer->writeRawByte(val[i]);
644 }
645 }
646
647 } // util
648 } // android
649
650