Home | History | Annotate | Download | only in src
      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 
     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 
     39 ProtoOutputStream::~ProtoOutputStream()
     40 {
     41 }
     42 
     43 
     44 void
     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
     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
    100 ProtoOutputStream::write(uint64_t fieldId, double val)
    101 {
    102     return internalWrite(fieldId, val, "double");
    103 }
    104 
    105 
    106 bool
    107 ProtoOutputStream::write(uint64_t fieldId, float val)
    108 {
    109     return internalWrite(fieldId, val, "float");
    110 }
    111 
    112 bool
    113 ProtoOutputStream::write(uint64_t fieldId, int val)
    114 {
    115     return internalWrite(fieldId, val, "int");
    116 }
    117 
    118 bool
    119 ProtoOutputStream::write(uint64_t fieldId, long long val)
    120 {
    121     return internalWrite(fieldId, val, "long long");
    122 }
    123 
    124 bool
    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
    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
    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
    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  */
    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  */
    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  */
    216 static uint32_t getSizePosFromToken(uint64_t token) {
    217     return (uint32_t)token;
    218 }
    219 
    220 uint64_t
    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
    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
    278 ProtoOutputStream::bytesWritten()
    279 {
    280     return mBuffer->size();
    281 }
    282 
    283 bool
    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
    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
    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
    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
    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
    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
    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>
    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
    495 ProtoOutputStream::writeRawVarint(uint64_t varint)
    496 {
    497     mBuffer->writeRawVarint64(varint);
    498 }
    499 
    500 void
    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
    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>
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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