Loading .clang-format +3 −0 Original line number Diff line number Diff line Loading @@ -12,3 +12,6 @@ IndentWidth: 4 PenaltyBreakBeforeFirstCallParameter: 100000 SpacesBeforeTrailingComments: 1 IncludeBlocks: Preserve DerivePointerAlignment: false PointerAlignment: Left include/android/input.h +26 −1 Original line number Diff line number Diff line Loading @@ -778,6 +778,9 @@ enum { * proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of * -0.1. * * These values are relative to the state from the last event, not accumulated, so developers * should make sure to process this axis value for all batched historical events. */ AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48, /** Loading @@ -791,6 +794,9 @@ enum { * * - For a touch pad, reports the distance that should be scrolled in the X axis as a result of * the user's two-finger scroll gesture, in display pixels. * * These values are relative to the state from the last event, not accumulated, so developers * should make sure to process this axis value for all batched historical events. */ AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE = 50, /** Loading @@ -799,6 +805,18 @@ enum { * The same as {@link AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE}, but for the Y axis. */ AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE = 51, /** * Axis constant: pinch scale factor of a motion event. * * - For a touch pad, reports the change in distance between the fingers when the user is making * a pinch gesture, as a proportion of that distance when the gesture was last reported. For * example, if the fingers were 50 units apart and are now 52 units apart, the scale factor * would be 1.04. * * These values are relative to the state from the last event, not accumulated, so developers * should make sure to process this axis value for all batched historical events. */ AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR = 52, /** * Note: This is not an "Axis constant". It does not represent any axis, nor should it be used Loading @@ -806,7 +824,7 @@ enum { * to make some computations (like iterating through all possible axes) cleaner. * Please update the value accordingly if you add a new axis. */ AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, // NOTE: If you add a new axis here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list. Loading Loading @@ -891,6 +909,13 @@ enum AMotionClassification : uint32_t { * why they have a separate constant from two-finger swipes. */ AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE = 4, /** * Classification constant: pinch. * * The current event stream represents the user pinching with two fingers on a touchpad. The * gesture is centered around the current cursor position. */ AMOTION_EVENT_CLASSIFICATION_PINCH = 5, }; /** Loading include/input/Input.h +5 −0 Original line number Diff line number Diff line Loading @@ -308,6 +308,11 @@ enum class MotionClassification : uint8_t { * have a separate constant from two-finger swipes. */ MULTI_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE, /** * The current gesture represents the user pinching with two fingers on a touchpad. The gesture * is centered around the current cursor position. */ PINCH = AMOTION_EVENT_CLASSIFICATION_PINCH, }; /** Loading include/input/KeyCharacterMap.h +3 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,9 @@ public: /* Combines this key character map with the provided overlay. */ void combine(const KeyCharacterMap& overlay); /* Clears already applied layout overlay */ void clearLayoutOverlay(); /* Gets the keyboard type. */ KeyboardType getKeyboardType() const; Loading libs/binder/BinderRecordReplay.cpp +133 −110 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <android-base/logging.h> #include <android-base/unique_fd.h> #include <binder/BinderRecordReplay.h> #include <sys/mman.h> #include <algorithm> using android::Parcel; Loading @@ -42,34 +43,38 @@ static_assert(PADDING8(8) == 0); // // A RecordedTransaction is written to a file as a sequence of Chunks. // // A Chunk consists of a ChunkDescriptor, Data, and Padding. // A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum. // // Data and Padding may each be zero-length as specified by the // ChunkDescriptor. // The ChunkDescriptor identifies the type of Data in the chunk, and the size // of the Data. // // The ChunkDescriptor identifies the type of data in the chunk, the size of // the data in bytes, and the number of zero-bytes padding to land on an // 8-byte boundary by the end of the Chunk. // The Data may be any uint32 number of bytes in length in [0-0xfffffff0]. // // Padding is between [0-7] zero-bytes after the Data such that the Chunk ends // on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the // size of Padding. // // The checksum is a 64-bit wide XOR of all previous data from the start of the // ChunkDescriptor to the end of Padding. // // ┌───────────────────────────┐ // │Chunk │ // │┌─────────────────────────┐│ // │┌────────────────────────┐ │ // ││ChunkDescriptor │ │ // ││┌───────────┬───────────┐││ // │││chunkType │paddingSize│││ // │││uint32_t │uint32_t ├┼┼───┐ // ││├───────────┴───────────┤││ │ // │││dataSize │││ │ // │││uint64_t ├┼┼─┐ │ // ││└───────────────────────┘││ │ │ // │└─────────────────────────┘│ │ │ // │┌─────────────────────────┐│ │ │ // ││Data ││ │ │ // ││bytes * dataSize │◀─┘ │ // │└─────────────────────────┘│ │ // ││┌───────────┬──────────┐│ │ // │││chunkType │dataSize ├┼─┼─┐ // │││uint32_t │uint32_t ││ │ │ // ││└───────────┴──────────┘│ │ │ // │└────────────────────────┘ │ │ // │┌─────────────────────────┐│ │ // ││Padding ││ │ // ││bytes * paddingSize │◀───┘ // ││Data ││ │ // ││bytes * dataSize │◀─┘ // ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│ // ││ Padding ││ // │└───┴─────────────────────┘│ // │┌─────────────────────────┐│ // ││checksum ││ // ││uint64_t ││ // │└─────────────────────────┘│ // └───────────────────────────┘ // Loading @@ -85,20 +90,20 @@ static_assert(PADDING8(8) == 0); // ║ End Chunk ║ // ╚══════════════════════╝ // // On reading a RecordedTransaction, an unrecognized chunk is skipped using // the size information in the ChunkDescriptor. Chunks are read and either // assimilated or skipped until an End Chunk is encountered. This has three // notable implications: // On reading a RecordedTransaction, an unrecognized chunk is checksummed // then skipped according to size information in the ChunkDescriptor. Chunks // are read and either assimilated or skipped until an End Chunk is // encountered. This has three notable implications: // // 1. Older and newer implementations should be able to read one another's // Transactions, though there will be loss of information. // 2. With the exception of the End Chunk, Chunks can appear in any // order and even repeat, though this is not recommended. // 2. With the exception of the End Chunk, Chunks can appear in any order // and even repeat, though this is not recommended. // 3. If any Chunk is repeated, old values will be overwritten by versions // encountered later in the file. // // No effort is made to ensure the expected chunks are present. A single // End Chunk may therefore produce a empty, meaningless RecordedTransaction. // End Chunk may therefore produce an empty, meaningless RecordedTransaction. RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept { mHeader = t.mHeader; Loading @@ -121,12 +126,12 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t cod 0}; if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) { LOG(INFO) << "Failed to set sent parcel data."; LOG(ERROR) << "Failed to set sent parcel data."; return std::nullopt; } if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) { LOG(INFO) << "Failed to set reply parcel data."; LOG(ERROR) << "Failed to set reply parcel data."; return std::nullopt; } Loading @@ -134,94 +139,111 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t cod } enum { HEADER_CHUNK = 0x00000001, DATA_PARCEL_CHUNK = 0x00000002, REPLY_PARCEL_CHUNK = 0x00000003, INVALID_CHUNK = 0x00fffffe, HEADER_CHUNK = 1, DATA_PARCEL_CHUNK = 2, REPLY_PARCEL_CHUNK = 3, END_CHUNK = 0x00ffffff, }; struct ChunkDescriptor { uint32_t chunkType = 0; uint32_t padding = 0; uint32_t dataSize = 0; uint32_t reserved = 0; // Future checksum }; static_assert(sizeof(ChunkDescriptor) % 8 == 0); constexpr uint32_t kMaxChunkDataSize = 0xfffffff0; typedef uint64_t transaction_checksum_t; static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut) { static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut, transaction_checksum_t* sum) { if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) { LOG(INFO) << "Failed to read Chunk Descriptor from fd " << fd.get(); LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get(); return android::UNKNOWN_ERROR; } if (PADDING8(chunkOut->dataSize) != chunkOut->padding) { chunkOut->chunkType = INVALID_CHUNK; LOG(INFO) << "Chunk data and padding sizes do not align." << fd.get(); return android::BAD_VALUE; } *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut); return android::NO_ERROR; } std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) { RecordedTransaction t; ChunkDescriptor chunk; const long pageSize = sysconf(_SC_PAGE_SIZE); do { if (NO_ERROR != readChunkDescriptor(fd, &chunk)) { LOG(INFO) << "Failed to read chunk descriptor."; transaction_checksum_t checksum = 0; if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) { LOG(ERROR) << "Failed to read chunk descriptor."; return std::nullopt; } off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize; off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart; if (chunk.dataSize > kMaxChunkDataSize) { LOG(ERROR) << "Chunk data exceeds maximum size."; return std::nullopt; } size_t chunkPayloadSize = chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t); if (PADDING8(chunkPayloadSize) != 0) { LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize; return std::nullopt; } transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>( mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED, fd.get(), mmapPageAlignedStart)); payloadMap += mmapPayloadStartOffset / sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap // page-alignment if (payloadMap == MAP_FAILED) { LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " " << strerror(errno); return std::nullopt; } for (size_t checksumIndex = 0; checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) { checksum ^= payloadMap[checksumIndex]; } if (checksum != 0) { LOG(ERROR) << "Checksum failed."; return std::nullopt; } lseek(fd.get(), chunkPayloadSize, SEEK_CUR); switch (chunk.chunkType) { case HEADER_CHUNK: { if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) { LOG(INFO) << "Header Chunk indicated size " << chunk.dataSize << "; Expected " LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected " << sizeof(TransactionHeader) << "."; return std::nullopt; } if (!android::base::ReadFully(fd, &t.mHeader, chunk.dataSize)) { LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get(); return std::nullopt; } lseek(fd.get(), chunk.padding, SEEK_CUR); t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap); break; } case DATA_PARCEL_CHUNK: { std::vector<uint8_t> bytes; bytes.resize(chunk.dataSize); if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) { LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get(); if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap), chunk.dataSize) != android::NO_ERROR) { LOG(ERROR) << "Failed to set sent parcel data."; return std::nullopt; } if (t.mSent.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { LOG(INFO) << "Failed to set sent parcel data."; return std::nullopt; } lseek(fd.get(), chunk.padding, SEEK_CUR); break; } case REPLY_PARCEL_CHUNK: { std::vector<uint8_t> bytes; bytes.resize(chunk.dataSize); if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) { LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get(); if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap), chunk.dataSize) != android::NO_ERROR) { LOG(ERROR) << "Failed to set reply parcel data."; return std::nullopt; } if (t.mReply.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { LOG(INFO) << "Failed to set reply parcel data."; return std::nullopt; } lseek(fd.get(), chunk.padding, SEEK_CUR); break; } case INVALID_CHUNK: LOG(INFO) << "Invalid chunk."; return std::nullopt; case END_CHUNK: LOG(INFO) << "Read end chunk"; FALLTHROUGH_INTENDED; default: // Unrecognized or skippable chunk lseek(fd.get(), chunk.dataSize + chunk.padding, SEEK_CUR); break; default: LOG(INFO) << "Unrecognized chunk."; continue; } } while (chunk.chunkType != END_CHUNK); Loading @@ -230,36 +252,37 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType, size_t byteCount, const uint8_t* data) const { // Write Chunk Descriptor // - Chunk Type if (!android::base::WriteFully(fd, &chunkType, sizeof(uint32_t))) { LOG(INFO) << "Failed to write chunk header to fd " << fd.get(); return UNKNOWN_ERROR; } // - Chunk Data Padding Size uint32_t additionalPaddingCount = static_cast<uint32_t>(PADDING8(byteCount)); if (!android::base::WriteFully(fd, &additionalPaddingCount, sizeof(uint32_t))) { LOG(INFO) << "Failed to write chunk padding size to fd " << fd.get(); return UNKNOWN_ERROR; } // - Chunk Data Size uint64_t byteCountToWrite = (uint64_t)byteCount; if (!android::base::WriteFully(fd, &byteCountToWrite, sizeof(uint64_t))) { LOG(INFO) << "Failed to write chunk size to fd " << fd.get(); return UNKNOWN_ERROR; } if (byteCount == 0) { return NO_ERROR; } if (byteCount > kMaxChunkDataSize) { LOG(ERROR) << "Chunk data exceeds maximum size"; return BAD_VALUE; } ChunkDescriptor descriptor = {.chunkType = chunkType, .dataSize = static_cast<uint32_t>(byteCount)}; // Prepare Chunk content as byte * const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor); const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data); if (!android::base::WriteFully(fd, data, byteCount)) { LOG(INFO) << "Failed to write chunk data to fd " << fd.get(); return UNKNOWN_ERROR; // Add Chunk to intermediate buffer, except checksum std::vector<std::byte> buffer; buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor)); buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount); std::byte zero{0}; buffer.insert(buffer.end(), PADDING8(byteCount), zero); // Calculate checksum from buffer transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data()); transaction_checksum_t checksumValue = 0; for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) { checksumValue ^= checksumData[idx]; } const uint8_t zeros[7] = {0}; if (!android::base::WriteFully(fd, zeros, additionalPaddingCount)) { LOG(INFO) << "Failed to write chunk padding to fd " << fd.get(); // Write checksum to buffer std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue); buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t)); // Write buffer to file if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) { LOG(ERROR) << "Failed to write chunk fd " << fd.get(); return UNKNOWN_ERROR; } return NO_ERROR; Loading @@ -269,19 +292,19 @@ android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const { if (NO_ERROR != writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader), reinterpret_cast<const uint8_t*>(&mHeader))) { LOG(INFO) << "Failed to write transactionHeader to fd " << fd.get(); LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) { LOG(INFO) << "Failed to write sent Parcel to fd " << fd.get(); LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) { LOG(INFO) << "Failed to write reply Parcel to fd " << fd.get(); LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) { LOG(INFO) << "Failed to write end chunk to fd " << fd.get(); LOG(ERROR) << "Failed to write end chunk to fd " << fd.get(); return UNKNOWN_ERROR; } return NO_ERROR; Loading Loading
.clang-format +3 −0 Original line number Diff line number Diff line Loading @@ -12,3 +12,6 @@ IndentWidth: 4 PenaltyBreakBeforeFirstCallParameter: 100000 SpacesBeforeTrailingComments: 1 IncludeBlocks: Preserve DerivePointerAlignment: false PointerAlignment: Left
include/android/input.h +26 −1 Original line number Diff line number Diff line Loading @@ -778,6 +778,9 @@ enum { * proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of * -0.1. * * These values are relative to the state from the last event, not accumulated, so developers * should make sure to process this axis value for all batched historical events. */ AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48, /** Loading @@ -791,6 +794,9 @@ enum { * * - For a touch pad, reports the distance that should be scrolled in the X axis as a result of * the user's two-finger scroll gesture, in display pixels. * * These values are relative to the state from the last event, not accumulated, so developers * should make sure to process this axis value for all batched historical events. */ AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE = 50, /** Loading @@ -799,6 +805,18 @@ enum { * The same as {@link AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE}, but for the Y axis. */ AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE = 51, /** * Axis constant: pinch scale factor of a motion event. * * - For a touch pad, reports the change in distance between the fingers when the user is making * a pinch gesture, as a proportion of that distance when the gesture was last reported. For * example, if the fingers were 50 units apart and are now 52 units apart, the scale factor * would be 1.04. * * These values are relative to the state from the last event, not accumulated, so developers * should make sure to process this axis value for all batched historical events. */ AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR = 52, /** * Note: This is not an "Axis constant". It does not represent any axis, nor should it be used Loading @@ -806,7 +824,7 @@ enum { * to make some computations (like iterating through all possible axes) cleaner. * Please update the value accordingly if you add a new axis. */ AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, // NOTE: If you add a new axis here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list. Loading Loading @@ -891,6 +909,13 @@ enum AMotionClassification : uint32_t { * why they have a separate constant from two-finger swipes. */ AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE = 4, /** * Classification constant: pinch. * * The current event stream represents the user pinching with two fingers on a touchpad. The * gesture is centered around the current cursor position. */ AMOTION_EVENT_CLASSIFICATION_PINCH = 5, }; /** Loading
include/input/Input.h +5 −0 Original line number Diff line number Diff line Loading @@ -308,6 +308,11 @@ enum class MotionClassification : uint8_t { * have a separate constant from two-finger swipes. */ MULTI_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE, /** * The current gesture represents the user pinching with two fingers on a touchpad. The gesture * is centered around the current cursor position. */ PINCH = AMOTION_EVENT_CLASSIFICATION_PINCH, }; /** Loading
include/input/KeyCharacterMap.h +3 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,9 @@ public: /* Combines this key character map with the provided overlay. */ void combine(const KeyCharacterMap& overlay); /* Clears already applied layout overlay */ void clearLayoutOverlay(); /* Gets the keyboard type. */ KeyboardType getKeyboardType() const; Loading
libs/binder/BinderRecordReplay.cpp +133 −110 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <android-base/logging.h> #include <android-base/unique_fd.h> #include <binder/BinderRecordReplay.h> #include <sys/mman.h> #include <algorithm> using android::Parcel; Loading @@ -42,34 +43,38 @@ static_assert(PADDING8(8) == 0); // // A RecordedTransaction is written to a file as a sequence of Chunks. // // A Chunk consists of a ChunkDescriptor, Data, and Padding. // A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum. // // Data and Padding may each be zero-length as specified by the // ChunkDescriptor. // The ChunkDescriptor identifies the type of Data in the chunk, and the size // of the Data. // // The ChunkDescriptor identifies the type of data in the chunk, the size of // the data in bytes, and the number of zero-bytes padding to land on an // 8-byte boundary by the end of the Chunk. // The Data may be any uint32 number of bytes in length in [0-0xfffffff0]. // // Padding is between [0-7] zero-bytes after the Data such that the Chunk ends // on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the // size of Padding. // // The checksum is a 64-bit wide XOR of all previous data from the start of the // ChunkDescriptor to the end of Padding. // // ┌───────────────────────────┐ // │Chunk │ // │┌─────────────────────────┐│ // │┌────────────────────────┐ │ // ││ChunkDescriptor │ │ // ││┌───────────┬───────────┐││ // │││chunkType │paddingSize│││ // │││uint32_t │uint32_t ├┼┼───┐ // ││├───────────┴───────────┤││ │ // │││dataSize │││ │ // │││uint64_t ├┼┼─┐ │ // ││└───────────────────────┘││ │ │ // │└─────────────────────────┘│ │ │ // │┌─────────────────────────┐│ │ │ // ││Data ││ │ │ // ││bytes * dataSize │◀─┘ │ // │└─────────────────────────┘│ │ // ││┌───────────┬──────────┐│ │ // │││chunkType │dataSize ├┼─┼─┐ // │││uint32_t │uint32_t ││ │ │ // ││└───────────┴──────────┘│ │ │ // │└────────────────────────┘ │ │ // │┌─────────────────────────┐│ │ // ││Padding ││ │ // ││bytes * paddingSize │◀───┘ // ││Data ││ │ // ││bytes * dataSize │◀─┘ // ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│ // ││ Padding ││ // │└───┴─────────────────────┘│ // │┌─────────────────────────┐│ // ││checksum ││ // ││uint64_t ││ // │└─────────────────────────┘│ // └───────────────────────────┘ // Loading @@ -85,20 +90,20 @@ static_assert(PADDING8(8) == 0); // ║ End Chunk ║ // ╚══════════════════════╝ // // On reading a RecordedTransaction, an unrecognized chunk is skipped using // the size information in the ChunkDescriptor. Chunks are read and either // assimilated or skipped until an End Chunk is encountered. This has three // notable implications: // On reading a RecordedTransaction, an unrecognized chunk is checksummed // then skipped according to size information in the ChunkDescriptor. Chunks // are read and either assimilated or skipped until an End Chunk is // encountered. This has three notable implications: // // 1. Older and newer implementations should be able to read one another's // Transactions, though there will be loss of information. // 2. With the exception of the End Chunk, Chunks can appear in any // order and even repeat, though this is not recommended. // 2. With the exception of the End Chunk, Chunks can appear in any order // and even repeat, though this is not recommended. // 3. If any Chunk is repeated, old values will be overwritten by versions // encountered later in the file. // // No effort is made to ensure the expected chunks are present. A single // End Chunk may therefore produce a empty, meaningless RecordedTransaction. // End Chunk may therefore produce an empty, meaningless RecordedTransaction. RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept { mHeader = t.mHeader; Loading @@ -121,12 +126,12 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t cod 0}; if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) { LOG(INFO) << "Failed to set sent parcel data."; LOG(ERROR) << "Failed to set sent parcel data."; return std::nullopt; } if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) { LOG(INFO) << "Failed to set reply parcel data."; LOG(ERROR) << "Failed to set reply parcel data."; return std::nullopt; } Loading @@ -134,94 +139,111 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t cod } enum { HEADER_CHUNK = 0x00000001, DATA_PARCEL_CHUNK = 0x00000002, REPLY_PARCEL_CHUNK = 0x00000003, INVALID_CHUNK = 0x00fffffe, HEADER_CHUNK = 1, DATA_PARCEL_CHUNK = 2, REPLY_PARCEL_CHUNK = 3, END_CHUNK = 0x00ffffff, }; struct ChunkDescriptor { uint32_t chunkType = 0; uint32_t padding = 0; uint32_t dataSize = 0; uint32_t reserved = 0; // Future checksum }; static_assert(sizeof(ChunkDescriptor) % 8 == 0); constexpr uint32_t kMaxChunkDataSize = 0xfffffff0; typedef uint64_t transaction_checksum_t; static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut) { static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut, transaction_checksum_t* sum) { if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) { LOG(INFO) << "Failed to read Chunk Descriptor from fd " << fd.get(); LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get(); return android::UNKNOWN_ERROR; } if (PADDING8(chunkOut->dataSize) != chunkOut->padding) { chunkOut->chunkType = INVALID_CHUNK; LOG(INFO) << "Chunk data and padding sizes do not align." << fd.get(); return android::BAD_VALUE; } *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut); return android::NO_ERROR; } std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) { RecordedTransaction t; ChunkDescriptor chunk; const long pageSize = sysconf(_SC_PAGE_SIZE); do { if (NO_ERROR != readChunkDescriptor(fd, &chunk)) { LOG(INFO) << "Failed to read chunk descriptor."; transaction_checksum_t checksum = 0; if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) { LOG(ERROR) << "Failed to read chunk descriptor."; return std::nullopt; } off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize; off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart; if (chunk.dataSize > kMaxChunkDataSize) { LOG(ERROR) << "Chunk data exceeds maximum size."; return std::nullopt; } size_t chunkPayloadSize = chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t); if (PADDING8(chunkPayloadSize) != 0) { LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize; return std::nullopt; } transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>( mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED, fd.get(), mmapPageAlignedStart)); payloadMap += mmapPayloadStartOffset / sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap // page-alignment if (payloadMap == MAP_FAILED) { LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " " << strerror(errno); return std::nullopt; } for (size_t checksumIndex = 0; checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) { checksum ^= payloadMap[checksumIndex]; } if (checksum != 0) { LOG(ERROR) << "Checksum failed."; return std::nullopt; } lseek(fd.get(), chunkPayloadSize, SEEK_CUR); switch (chunk.chunkType) { case HEADER_CHUNK: { if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) { LOG(INFO) << "Header Chunk indicated size " << chunk.dataSize << "; Expected " LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected " << sizeof(TransactionHeader) << "."; return std::nullopt; } if (!android::base::ReadFully(fd, &t.mHeader, chunk.dataSize)) { LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get(); return std::nullopt; } lseek(fd.get(), chunk.padding, SEEK_CUR); t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap); break; } case DATA_PARCEL_CHUNK: { std::vector<uint8_t> bytes; bytes.resize(chunk.dataSize); if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) { LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get(); if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap), chunk.dataSize) != android::NO_ERROR) { LOG(ERROR) << "Failed to set sent parcel data."; return std::nullopt; } if (t.mSent.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { LOG(INFO) << "Failed to set sent parcel data."; return std::nullopt; } lseek(fd.get(), chunk.padding, SEEK_CUR); break; } case REPLY_PARCEL_CHUNK: { std::vector<uint8_t> bytes; bytes.resize(chunk.dataSize); if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) { LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get(); if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap), chunk.dataSize) != android::NO_ERROR) { LOG(ERROR) << "Failed to set reply parcel data."; return std::nullopt; } if (t.mReply.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { LOG(INFO) << "Failed to set reply parcel data."; return std::nullopt; } lseek(fd.get(), chunk.padding, SEEK_CUR); break; } case INVALID_CHUNK: LOG(INFO) << "Invalid chunk."; return std::nullopt; case END_CHUNK: LOG(INFO) << "Read end chunk"; FALLTHROUGH_INTENDED; default: // Unrecognized or skippable chunk lseek(fd.get(), chunk.dataSize + chunk.padding, SEEK_CUR); break; default: LOG(INFO) << "Unrecognized chunk."; continue; } } while (chunk.chunkType != END_CHUNK); Loading @@ -230,36 +252,37 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType, size_t byteCount, const uint8_t* data) const { // Write Chunk Descriptor // - Chunk Type if (!android::base::WriteFully(fd, &chunkType, sizeof(uint32_t))) { LOG(INFO) << "Failed to write chunk header to fd " << fd.get(); return UNKNOWN_ERROR; } // - Chunk Data Padding Size uint32_t additionalPaddingCount = static_cast<uint32_t>(PADDING8(byteCount)); if (!android::base::WriteFully(fd, &additionalPaddingCount, sizeof(uint32_t))) { LOG(INFO) << "Failed to write chunk padding size to fd " << fd.get(); return UNKNOWN_ERROR; } // - Chunk Data Size uint64_t byteCountToWrite = (uint64_t)byteCount; if (!android::base::WriteFully(fd, &byteCountToWrite, sizeof(uint64_t))) { LOG(INFO) << "Failed to write chunk size to fd " << fd.get(); return UNKNOWN_ERROR; } if (byteCount == 0) { return NO_ERROR; } if (byteCount > kMaxChunkDataSize) { LOG(ERROR) << "Chunk data exceeds maximum size"; return BAD_VALUE; } ChunkDescriptor descriptor = {.chunkType = chunkType, .dataSize = static_cast<uint32_t>(byteCount)}; // Prepare Chunk content as byte * const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor); const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data); if (!android::base::WriteFully(fd, data, byteCount)) { LOG(INFO) << "Failed to write chunk data to fd " << fd.get(); return UNKNOWN_ERROR; // Add Chunk to intermediate buffer, except checksum std::vector<std::byte> buffer; buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor)); buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount); std::byte zero{0}; buffer.insert(buffer.end(), PADDING8(byteCount), zero); // Calculate checksum from buffer transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data()); transaction_checksum_t checksumValue = 0; for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) { checksumValue ^= checksumData[idx]; } const uint8_t zeros[7] = {0}; if (!android::base::WriteFully(fd, zeros, additionalPaddingCount)) { LOG(INFO) << "Failed to write chunk padding to fd " << fd.get(); // Write checksum to buffer std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue); buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t)); // Write buffer to file if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) { LOG(ERROR) << "Failed to write chunk fd " << fd.get(); return UNKNOWN_ERROR; } return NO_ERROR; Loading @@ -269,19 +292,19 @@ android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const { if (NO_ERROR != writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader), reinterpret_cast<const uint8_t*>(&mHeader))) { LOG(INFO) << "Failed to write transactionHeader to fd " << fd.get(); LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) { LOG(INFO) << "Failed to write sent Parcel to fd " << fd.get(); LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) { LOG(INFO) << "Failed to write reply Parcel to fd " << fd.get(); LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) { LOG(INFO) << "Failed to write end chunk to fd " << fd.get(); LOG(ERROR) << "Failed to write end chunk to fd " << fd.get(); return UNKNOWN_ERROR; } return NO_ERROR; Loading