blob: b7a37b9e464f6708f8afae5ebda388b1bca8f9da [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:zircon/zircon.dart';
import 'codec.dart';
import 'error.dart';
import 'struct.dart';
import 'types.dart';
import 'wire_format.dart';
// ignore_for_file: public_member_api_docs
const int kMessageHeaderSize = 16;
const int kLargeMessageInfoSize = 16;
const int kLargeMessageVmoRights = ZX.RIGHT_GET_PROPERTY |
ZX.RIGHT_INSPECT |
ZX.RIGHT_READ |
ZX.RIGHT_TRANSFER |
ZX.RIGHT_WAIT;
const int kMessageTxidOffset = 0;
const int kMessageFlagOffset = 4;
const int kMessageDynamicFlagOffset = 6;
const int kMessageMagicOffset = 7;
const int kMessageOrdinalOffset = 8;
const int kMagicNumberInitial = 1;
const int kWireFormatV2FlagMask = 2;
const int _kDynamicFlagsFlexible = 0x80;
const int _kDynamicFlagsByteOverflow = 0x40;
enum CallStrictness {
strict,
flexible,
}
enum CallOverflowing {
large,
small,
}
/// Convert CallStrictness to a byte that can be inserted into the dynamic flags
/// portion of a message.
int strictnessToFlags(CallStrictness strictness) {
switch (strictness) {
case CallStrictness.strict:
return 0x00;
case CallStrictness.flexible:
return _kDynamicFlagsFlexible;
}
}
/// Extract the CallStrictness from the dynamic flags byte of the message
/// header.
CallStrictness strictnessFromFlags(int dynamicFlags) {
if ((dynamicFlags & _kDynamicFlagsFlexible) != 0) {
return CallStrictness.flexible;
}
return CallStrictness.strict;
}
// TODO(fxbug.dev/114263): Add `overflowingToFlags`.
/// Extract the CallOverflowing from the dynamic flags byte of the message
/// header.
CallOverflowing overflowingFromFlags(int dynamicFlags) {
if ((dynamicFlags & _kDynamicFlagsByteOverflow) != 0) {
return CallOverflowing.large;
}
return CallOverflowing.small;
}
class _BaseMessage {
_BaseMessage(this.data);
final ByteData data;
int get txid => data.getUint32(kMessageTxidOffset, Endian.little);
int get ordinal => data.getUint64(kMessageOrdinalOffset, Endian.little);
int get magic => data.getUint8(kMessageMagicOffset);
WireFormat parseWireFormat() {
if ((data.getUint8(kMessageFlagOffset) & kWireFormatV2FlagMask) != 0) {
return WireFormat.v2;
}
throw FidlError(
'unknown wire format', FidlErrorCode.fidlUnsupportedWireFormat);
}
CallStrictness get strictness =>
strictnessFromFlags(data.getUint8(kMessageDynamicFlagOffset));
CallOverflowing get overflowing =>
overflowingFromFlags(data.getUint8(kMessageDynamicFlagOffset));
bool isCompatible() => magic == kMagicNumberInitial;
void hexDump() {
const int width = 16;
Uint8List list = Uint8List.view(data.buffer, 0);
StringBuffer buffer = StringBuffer();
final RegExp isPrintable = RegExp(r'\w');
for (int i = 0; i < data.lengthInBytes; i += width) {
StringBuffer hex = StringBuffer();
StringBuffer printable = StringBuffer();
for (int j = 0; j < width && i + j < data.lengthInBytes; j++) {
int v = list[i + j];
String s = v.toRadixString(16);
if (s.length == 1)
hex.write('0$s ');
else
hex.write('$s ');
s = String.fromCharCode(v);
if (isPrintable.hasMatch(s)) {
printable.write(s);
} else {
printable.write('.');
}
}
buffer.write('${hex.toString().padRight(3 * width)} $printable\n');
}
print('==================================================\n'
'$buffer'
'==================================================');
}
}
class IncomingMessage extends _BaseMessage {
IncomingMessage(data, this.handleInfos) : super(data);
IncomingMessage.fromReadEtcResult(ReadEtcResult result)
: assert(result.status == ZX.OK),
handleInfos = result.handleInfos,
super(result.bytes);
@visibleForTesting
IncomingMessage.fromOutgoingMessage(OutgoingMessage outgoingMessage)
: handleInfos = outgoingMessage.handleDispositions
.map((handleDisposition) => HandleInfo(handleDisposition.handle,
handleDisposition.type, handleDisposition.rights))
.toList(),
super(outgoingMessage.data);
final List<HandleInfo> handleInfos;
void closeHandles() {
for (int i = 0; i < handleInfos.length; ++i) {
handleInfos[i].handle.close();
}
}
@override
String toString() {
return 'IncomingMessage(numBytes=${data.lengthInBytes}, numHandles=${handleInfos.length})';
}
}
class OutgoingMessage extends _BaseMessage {
OutgoingMessage(data, this.handleDispositions) : super(data);
set txid(int value) =>
data.setUint32(kMessageTxidOffset, value, Endian.little);
final List<HandleDisposition> handleDispositions;
void closeHandles() {
for (int i = 0; i < handleDispositions.length; ++i) {
handleDispositions[i].handle.close();
}
}
@override
String toString() {
return 'OutgoingMessage(numBytes=${data.lengthInBytes}, numHandles=${handleDispositions.length})';
}
}
/// Encodes a FIDL message that contains a single parameter.
void encodeMessage<T>(
Encoder encoder, int inlineSize, MemberType typ, T value) {
encoder.alloc(inlineSize, 0);
typ.encode(encoder, value, kMessageHeaderSize, 1);
}
/// Encodes a FIDL message with multiple parameters. The callback parameter
/// provides a decoder that is initialized on the provided Message, which
/// callers can use to decode specific types. This functionality (encoding
/// multiple parameters) is implemented using a callback because each call to
/// MemberType.encode() must pass in a concrete type, rather than an element
/// popped from a List<FidlType>.
void encodeMessageWithCallback(Encoder encoder, int inlineSize, Function() f) {
encoder.alloc(inlineSize, 0);
f();
}
void _validateDecoding(Decoder decoder) {
// The ordering of the following two checks is important: if there is both unclaimed memory and
// unclaimed handles, we should do the unclaimed handles clean up first (namely, closing all open)
// handles.
if (decoder.countUnclaimedHandles() > 0) {
// If there are unclaimed handles at the end of the decoding, close all
// handles to the best of our ability, and throw an error.
for (var handleInfo in decoder.handleInfos) {
try {
handleInfo.handle.close();
// ignore: avoid_catches_without_on_clauses
} catch (e) {
// best effort
}
}
int unclaimed = decoder.countUnclaimedHandles();
int total = decoder.handleInfos.length;
throw FidlError(
'Message contains extra handles (unclaimed: $unclaimed, total: $total)',
FidlErrorCode.fidlTooManyHandles);
}
if (decoder.countUnclaimedBytes() > 0) {
int unclaimed = decoder.countUnclaimedBytes();
int total = decoder.data.lengthInBytes;
throw FidlError(
'Message contains unread bytes (unclaimed: $unclaimed, total: $total)',
FidlErrorCode.fidlTooManyBytes);
}
}
/// Decodes a FIDL message that contains a single parameter.
T decodeMessage<T>(IncomingMessage message, int inlineSize, MemberType typ) {
return decodeMessageWithCallback(message, inlineSize,
(Decoder decoder, int offset) {
return typ.decode(decoder, offset, 1);
});
}
/// Decodes a (possibly large) FIDL message that contains a single parameter.
T decodeMaybeLargeMessage<T>(
IncomingMessage message, int inlineSize, MemberType typ) {
return decodeMaybeLargeMessageWithCallback(message, inlineSize,
(Decoder decoder, int offset) {
return typ.decode(decoder, offset, 1);
});
}
/// Decodes a FIDL message with multiple parameters. The callback parameter
/// provides a decoder that is initialized on the provided Message, which
/// callers can use to decode specific types. The return result of the callback
/// (e.g. the decoded parameters, wrapped in a containing class/struct) is
/// returned as the result of the function. This functionality (decoding
/// multiple parameters) is implemented using a callback because returning a
/// list would be insufficient: the list would be of type List<FidlType>,
/// whereas we want to retain concrete types of each decoded parameter. The
/// only way to accomplish this in Dart is to pass in a function that collects
/// these multiple values into a bespoke, properly typed class.
T decodeMessageWithCallback<T>(
IncomingMessage message, int inlineSize, DecodeMessageCallback<T> f) {
final int size = kMessageHeaderSize + inlineSize;
final Decoder decoder = Decoder(message)..claimBytes(size, 0);
T out = f(decoder, kMessageHeaderSize);
final int padding = align(size) - size;
decoder.checkPadding(size, padding);
_validateDecoding(decoder);
return out;
}
/// This is a special-case of [Struct], which, unlike other codegen-created instances, does not have
/// a static [_structDecode] method, as this type is only every meant to be decoded as a top-level
/// payload body.
class LargeMessageInfo extends Struct {
const LargeMessageInfo({
required this.flags,
required this.reserved,
required this.msgByteCount,
});
final int flags;
final int reserved;
final int msgByteCount;
@override
List<Object?> get $fields {
return <Object?>[
flags,
reserved,
msgByteCount,
];
}
static const $fieldType0 = Uint32Type();
static const $fieldType1 = Uint32Type();
static const $fieldType2 = Uint64Type();
// TODO(fxbug.dev/114263): currently untested/unimplemented encode logic.
@override
void $encode(Encoder $encoder, int $offset, int $depth) {
$fieldType0.encode($encoder, flags, $offset + 0, $depth);
$fieldType1.encode($encoder, reserved, $offset + 4, $depth);
$fieldType2.encode($encoder, msgByteCount, $offset + 8, $depth);
}
}
const kLargeMessageInfoFlagsType =
MemberType<int>(type: Uint32Type(), offset: 0);
const kLargeMessageInfoReservedType =
MemberType<int>(type: Uint32Type(), offset: 4);
const kLargeMessageInfoMsgByteCountType =
MemberType<int>(type: Uint64Type(), offset: 8);
T decodeMaybeLargeMessageWithCallback<T>(
IncomingMessage message, int inlineSize, DecodeMessageCallback<T> f) {
if (message.overflowing == CallOverflowing.small) {
return decodeMessageWithCallback<T>(message, inlineSize, f);
}
// Ensure that the handle for the overflow buffer containing VMO exists and is well-formed before
// attempting to read from it.
if (message.handleInfos.isEmpty) {
throw FidlError(
'Large FIDL messages must have at least 1 handle pointing to the overflow VMO',
FidlErrorCode.fidlLargeMessageMissingHandles);
}
HandleInfo overflowHandleInfo = message.handleInfos.last;
if (overflowHandleInfo.type != ZX.OBJ_TYPE_VMO ||
overflowHandleInfo.rights != kLargeMessageVmoRights) {
throw FidlError(
'Large FIDL messages must have properly formed overflow buffer handles',
FidlErrorCode.fidlLargeMessageInvalidOverflowBufferHandle);
}
// Perform a bit of surgery - remove the handles array attached to the original
// [IncomingMessage], and create a new [IncomingMessage] with only the original's bytes. This
// will be used to decode the outer [LargeMessageInfo] struct, which we can then use to properly
// read the VMO containing the remainder of the data.
IncomingMessage handlesStrippedMessage = IncomingMessage(message.data, []);
if (handlesStrippedMessage.data.lengthInBytes !=
kMessageHeaderSize + kLargeMessageInfoSize) {
throw FidlError(
'Large FIDL messages must have a well-formed 16-byte info struct',
FidlErrorCode.fidlLargeMessageInfoMissized);
}
LargeMessageInfo largeMessageInfo =
decodeMessageWithCallback<LargeMessageInfo>(
handlesStrippedMessage, kLargeMessageInfoSize,
(Decoder decoder, int offset) {
return LargeMessageInfo(
flags: kLargeMessageInfoFlagsType.decode(decoder, offset, 1),
reserved: kLargeMessageInfoReservedType.decode(decoder, offset, 1),
msgByteCount:
kLargeMessageInfoMsgByteCountType.decode(decoder, offset, 1));
});
if (largeMessageInfo.flags != 0 || largeMessageInfo.reserved != 0) {
throw FidlError(
'Large FIDL messages must have a properly formed info struct',
FidlErrorCode.fidlLargeMessageInfoMalformed);
}
if (largeMessageInfo.msgByteCount <=
Channel.MAX_MSG_BYTES - kLargeMessageInfoSize) {
throw FidlError(
'Large FIDL messages must have a properly formed info struct',
FidlErrorCode.fidlLargeMessageTooSmall);
}
// Read the overflow bytes from the overflow buffer containing VMO, then combine those bytes
// with the handles from the original message to make the arguments passed to the decoder
// callback.
SizedVmo overflowVmo =
SizedVmo(overflowHandleInfo.handle, largeMessageInfo.msgByteCount);
ReadResult result = overflowVmo.read(largeMessageInfo.msgByteCount);
// Decode the body bytes only - no need to redo the header.
final Decoder decoder = Decoder.fromRawArgs(
ByteData.view(
result.bytes.buffer, result.bytes.offsetInBytes, result.numBytes),
message.handleInfos.sublist(0, message.handleInfos.length - 1),
WireFormat.v2)
..claimBytes(inlineSize, 0);
T out = f(decoder, 0);
final int padding = align(inlineSize) - inlineSize;
decoder.checkPadding(inlineSize, padding);
_validateDecoding(decoder);
return out;
}
typedef DecodeMessageCallback<T> = T Function(Decoder decoder, int offset);
typedef IncomingMessageSink = void Function(IncomingMessage message);
typedef OutgoingMessageSink = void Function(OutgoingMessage message);