blob: a252343972bea07fe58db3359b8e0c3504d2aa10 [file] [log] [blame]
// Copyright 2021 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.
package fidlgen_cpp
import (
"fmt"
"strings"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
//
// Generate code for sending and receiving FIDL messages i.e. the messaging API.
//
// hlMessagingDetails represents the various generated definitions associated
// with a protocol, in the high-level C++ bindings.
// TODO(fxbug.dev/72798): Use the same approach to pass wireTypeNames and
// hlMessagingDetails to the templates.
type hlMessagingDetails struct {
// ProtocolMarker is a pure-virtual interface corresponding to methods in
// the protocol. Notably, HLCPP shares the same interface type between
// the server and client bindings API.
ProtocolMarker name
// InterfaceAliasForStub is the type alias generated within the
// "Stub" class, that refers to the pure-virtual interface corresponding to
// the protocol.
InterfaceAliasForStub name
// Proxy implements the interface by encoding and making method calls.
Proxy name
// Stub calls into the interface after decoding an incoming message.
// It also implements the EventSender interface.
Stub name
// EventSender is a pure-virtual interface for sending events.
EventSender name
// SyncInterface is a pure-virtual interface for making synchronous calls.
SyncInterface name
// SyncProxy implements the SyncInterface.
SyncProxy name
RequestEncoder name
RequestDecoder name
ResponseEncoder name
ResponseDecoder name
}
func compileHlMessagingDetails(protocol nameVariants) hlMessagingDetails {
p := protocol.Natural
stub := p.appendName("_Stub")
return hlMessagingDetails{
ProtocolMarker: p,
InterfaceAliasForStub: stub.nest(p.appendName("_clazz").Name()),
Proxy: p.appendName("_Proxy"),
Stub: stub,
EventSender: p.appendName("_EventSender"),
SyncInterface: p.appendName("_Sync"),
SyncProxy: p.appendName("_SyncProxy"),
RequestEncoder: p.appendName("_RequestEncoder"),
RequestDecoder: p.appendName("_RequestDecoder"),
ResponseEncoder: p.appendName("_ResponseEncoder"),
ResponseDecoder: p.appendName("_ResponseDecoder"),
}
}
type protocolWithHlMessaging struct {
Protocol
hlMessagingDetails
}
// WithHlMessaging returns a new protocol IR where the HLCPP bindings details
// are promoted to the same naming scope as the protocol. This makes it easier
// to access the HLCPP details in golang templates.
func (p Protocol) WithHlMessaging() protocolWithHlMessaging {
return protocolWithHlMessaging{
Protocol: p,
hlMessagingDetails: p.hlMessaging,
}
}
// TODO(fxbug.dev/60240): Start implementing unified bindings messaging layer
// based on this skeleton.
type unifiedMessagingDetails struct {
ClientImpl name
EventHandlers name
Interface name
EventSender name
RequestEncoder name
RequestDecoder name
ResponseEncoder name
ResponseDecoder name
}
// These correspond to templated classes forward-declared in
// //zircon/system/ulib/fidl/include/lib/fidl/llcpp/wire_messaging.h
var (
// Protocol related
WireSyncClient = fidlNs.member("WireSyncClient")
WireClient = fidlNs.member("WireClient")
WireEventHandlerInterface = internalNs.member("WireEventHandlerInterface")
WireSyncEventHandler = fidlNs.member("WireSyncEventHandler")
WireAsyncEventHandler = fidlNs.member("WireAsyncEventHandler")
WireInterface = fidlNs.member("WireInterface")
WireRawChannelInterface = fidlNs.member("WireRawChannelInterface")
WireServer = fidlNs.member("WireServer")
WireEventSender = fidlNs.member("WireEventSender")
WireWeakEventSender = internalNs.member("WireWeakEventSender")
WireClientImpl = internalNs.member("WireClientImpl")
WireCaller = internalNs.member("WireCaller")
WireDispatcher = internalNs.member("WireDispatcher")
WireServerDispatcher = internalNs.member("WireServerDispatcher")
// MethodRelated
WireRequest = fidlNs.member("WireRequest")
WireResponse = fidlNs.member("WireResponse")
WireResult = fidlNs.member("WireResult")
WireUnownedResult = fidlNs.member("WireUnownedResult")
WireResponseContext = fidlNs.member("WireResponseContext")
)
type wireTypeNames struct {
// WireProtocolMarker is a class only used for containing other definitions
// related to this protocol.
// TODO(fxbug.dev/72798): Golang template should use this instead of the
// nameVariants embedded in Protocol.
WireProtocolMarker name
WireSyncClient name
WireClient name
WireEventHandlerInterface name
WireSyncEventHandler name
WireAsyncEventHandler name
WireInterface name
WireRawChannelInterface name
WireServer name
WireEventSender name
WireWeakEventSender name
WireClientImpl name
WireCaller name
WireDispatcher name
WireServerDispatcher name
}
func newWireTypeNames(protocolVariants nameVariants) wireTypeNames {
p := protocolVariants.Wire
return wireTypeNames{
WireProtocolMarker: p,
WireSyncClient: WireSyncClient.template(p),
WireClient: WireClient.template(p),
WireEventHandlerInterface: WireEventHandlerInterface.template(p),
WireSyncEventHandler: WireSyncEventHandler.template(p),
WireAsyncEventHandler: WireAsyncEventHandler.template(p),
WireInterface: WireInterface.template(p),
WireRawChannelInterface: WireRawChannelInterface.template(p),
WireServer: WireServer.template(p),
WireEventSender: WireEventSender.template(p),
WireWeakEventSender: WireWeakEventSender.template(p),
WireClientImpl: WireClientImpl.template(p),
WireCaller: WireCaller.template(p),
WireDispatcher: WireDispatcher.template(p),
WireServerDispatcher: WireServerDispatcher.template(p),
}
}
// protocolInner contains information about a Protocol that should be
// filled out by the compiler.
type protocolInner struct {
Attributes
// TODO(fxbug.dev/72798): This should be replaced by ProtocolMarker in hlMessagingDetails
// and wireMessagingDetails. In particular, the unified bindings do not declare
// protocol marker classes.
nameVariants
// [Discoverable] protocols are exported to the outgoing namespace under this
// name. This is deprecated by FTP-041 unified services.
// TODO(fxbug.dev/8035): Remove.
DiscoverableName string
hlMessaging hlMessagingDetails
wireTypeNames
// ClientAllocation is the allocation behavior of the client when receiving
// FIDL events over this protocol.
SyncEventAllocation allocation
Methods []Method
FuzzingName string
TestBase nameVariants
}
// Protocol should be created using newProtocol.
type Protocol struct {
protocolInner
// OneWayMethods contains the list of one-way (i.e. fire-and-forget) methods
// in the protocol.
OneWayMethods []*Method
// TwoWayMethods contains the list of two-way (i.e. has both request and
// response) methods in the protocol.
TwoWayMethods []*Method
// ClientMethods contains the list of client-initiated methods (i.e. any
// interaction that is not an event). It is the union of one-way and two-way
// methods.
ClientMethods []*Method
// Events contains the list of events (i.e. initiated by servers)
// in the protocol.
Events []*Method
// Generated struct holding variant-agnostic details about protocol.
ProtocolDetails name
}
func (Protocol) Kind() declKind {
return Kinds.Protocol
}
var _ Kinded = (*Protocol)(nil)
var _ namespaced = (*Protocol)(nil)
func (p Protocol) NaturalType() string {
return p.Natural.String()
}
func (p Protocol) WireType() string {
return p.Wire.String()
}
func newProtocol(inner protocolInner) Protocol {
type kinds []methodKind
filterBy := func(kinds kinds) []*Method {
var out []*Method
for i := 0; i < len(inner.Methods); i++ {
m := &inner.Methods[i]
k := m.methodKind()
for _, want := range kinds {
if want == k {
out = append(out, m)
}
}
}
return out
}
return Protocol{
protocolInner: inner,
OneWayMethods: filterBy(kinds{oneWayMethod}),
TwoWayMethods: filterBy(kinds{twoWayMethod}),
ClientMethods: filterBy(kinds{oneWayMethod, twoWayMethod}),
Events: filterBy(kinds{eventMethod}),
ProtocolDetails: makeName("fidl::internal::ProtocolDetails").template(inner.Wire),
}
}
type argsWrapper []Parameter
// TODO(fxb/7704): We should be able to remove as we align with args with struct
// representation.
func (args argsWrapper) isResource() bool {
for _, arg := range args {
if arg.Type.IsResource {
return true
}
}
return false
}
type messageInner struct {
fidlgen.TypeShape
HlCodingTable name
WireCodingTable name
}
// message contains lower level wire-format information about a request/response
// message.
// message should be created using newMessage.
type message struct {
messageInner
fidlgen.Strictness
IsResource bool
ClientAllocation allocation
ServerAllocation allocation
}
// methodContext indicates where the request/response is used.
// The allocation strategies differ for client and server contexts, in LLCPP.
type methodContext int
const (
_ methodContext = iota
clientContext
serverContext
)
type boundednessQuery func(methodContext, fidlgen.Strictness) boundedness
func newMessage(inner messageInner, args []Parameter, wire wireTypeNames,
direction messageDirection) message {
ts := inner.TypeShape
strictness := fidlgen.Strictness(!ts.HasFlexibleEnvelope)
return message{
messageInner: inner,
Strictness: strictness,
IsResource: argsWrapper(args).isResource(),
ClientAllocation: computeAllocation(
ts.InlineSize,
ts.MaxOutOfLine,
direction.queryBoundedness(clientContext, strictness)),
ServerAllocation: computeAllocation(
ts.InlineSize,
ts.MaxOutOfLine,
direction.queryBoundedness(serverContext, strictness)),
}
}
func (m message) HasPointer() bool {
return m.Depth > 0
}
type wireMethod struct {
WireCompleter name
WireCompleterBase name
WireRequest name
WireRequestView name
WireResponse name
WireResponseContext name
WireResult name
WireUnownedResult name
}
func newWireMethod(name string, wireTypes wireTypeNames, protocolMarker name, methodMarker name) wireMethod {
s := wireTypes.WireServer.nest(name)
return wireMethod{
WireCompleter: s.appendName("Completer"),
WireCompleterBase: s.appendName("CompleterBase"),
WireRequest: WireRequest.template(methodMarker),
WireRequestView: s.appendName("RequestView"),
WireResponse: WireResponse.template(methodMarker),
WireResponseContext: WireResponseContext.template(methodMarker),
WireResult: WireResult.template(methodMarker),
WireUnownedResult: WireUnownedResult.template(methodMarker),
}
}
// methodInner contains information about a Method that should be filled out by
// the compiler.
type methodInner struct {
protocolName nameVariants
Marker nameVariants
wireMethod
baseCodingTableName string
requestTypeShape fidlgen.TypeShape
responseTypeShape fidlgen.TypeShape
Attributes
nameVariants
Ordinal uint64
HasRequest bool
RequestArgs []Parameter
HasResponse bool
ResponseArgs []Parameter
Transitional bool
Result *Result
}
// Method should be created using newMethod.
type Method struct {
methodInner
OrdinalName nameVariants
Request message
Response message
CallbackType *nameVariants
ResponseHandlerType string
ResponderType string
// Protocol is a reference to the containing protocol, for the
// convenience of golang templates.
Protocol *Protocol
}
type messageDirection int
const (
_ messageDirection = iota
messageDirectionRequest
messageDirectionResponse
)
// Compute boundedness based on client/server, request/response, and strictness.
func (d messageDirection) queryBoundedness(c methodContext, s fidlgen.Strictness) boundedness {
switch d {
case messageDirectionRequest:
if c == clientContext {
// Allocation is bounded when sending request from a client.
return boundednessBounded
} else {
return boundedness(s.IsStrict())
}
case messageDirectionResponse:
if c == serverContext {
// Allocation is bounded when sending response from a server.
return boundednessBounded
} else {
return boundedness(s.IsStrict())
}
}
panic(fmt.Sprintf("unexpected message direction: %v", d))
}
func newMethod(inner methodInner, hl hlMessagingDetails, wire wireTypeNames) Method {
hlCodingTableBase := hl.ProtocolMarker.Namespace().append("_internal").member(inner.baseCodingTableName)
wireCodingTableBase := wire.WireProtocolMarker.Namespace().member(inner.baseCodingTableName)
hlRequestCodingTable := hlCodingTableBase.appendName("RequestTable")
wireRequestCodingTable := wireCodingTableBase.appendName("RequestTable")
hlResponseCodingTable := hlCodingTableBase.appendName("ResponseTable")
wireResponseCodingTable := wireCodingTableBase.appendName("ResponseTable")
if !inner.HasRequest {
hlResponseCodingTable = hlCodingTableBase.appendName("EventTable")
wireResponseCodingTable = wireCodingTableBase.appendName("EventTable")
}
var callbackType *nameVariants = nil
if inner.HasResponse {
callbackName := inner.appendName("Callback")
callbackType = &callbackName
}
ordinalName := fmt.Sprintf("k%s_%s_Ordinal",
inner.protocolName.Natural.Name(), inner.Natural.Name())
m := Method{
methodInner: inner,
OrdinalName: nameVariants{
Natural: inner.protocolName.Natural.Namespace().append("internal").member(ordinalName),
Wire: inner.protocolName.Wire.Namespace().member(ordinalName),
},
Request: newMessage(messageInner{
TypeShape: inner.requestTypeShape,
HlCodingTable: hlRequestCodingTable,
WireCodingTable: wireRequestCodingTable,
}, inner.RequestArgs, wire, messageDirectionRequest),
Response: newMessage(messageInner{
TypeShape: inner.responseTypeShape,
HlCodingTable: hlResponseCodingTable,
WireCodingTable: wireResponseCodingTable,
}, inner.ResponseArgs, wire, messageDirectionResponse),
CallbackType: callbackType,
ResponseHandlerType: fmt.Sprintf("%s_%s_ResponseHandler",
inner.protocolName.Natural.Name(), inner.Natural.Name()),
ResponderType: fmt.Sprintf("%s_%s_Responder",
inner.protocolName.Natural.Name(), inner.Natural.Name()),
Protocol: nil,
}
return m
}
type methodKind int
const (
oneWayMethod = methodKind(iota)
twoWayMethod
eventMethod
)
func (m *Method) methodKind() methodKind {
if m.HasRequest {
if m.HasResponse {
return twoWayMethod
}
return oneWayMethod
}
if !m.HasResponse {
panic("A method should have at least either a request or a response")
}
return eventMethod
}
func (m *Method) CallbackWrapper() string {
return "fit::function"
}
type Parameter struct {
nameVariants
Type Type
Offset int
HandleInformation *HandleInformation
}
func (p Parameter) NameAndType() (string, Type) {
return p.Name(), p.Type
}
func allEventsStrict(methods []Method) fidlgen.Strictness {
strictness := fidlgen.IsStrict
for _, m := range methods {
if !m.HasRequest && m.HasResponse && m.Response.IsFlexible() {
strictness = fidlgen.IsFlexible
break
}
}
return strictness
}
func (c *compiler) compileProtocol(p fidlgen.Protocol) Protocol {
protocolName := c.compileNameVariants(p.Name)
codingTableName := codingTableName(p.Name)
hlMessaging := compileHlMessagingDetails(protocolName)
wireTypeNames := newWireTypeNames(protocolName)
methods := []Method{}
for _, v := range p.Methods {
name := methodNameContext.transform(v.Name)
var result *Result
if v.MethodResult != nil {
// If the method uses the error syntax, Response[0] will be a union
// that was placed in c.resultForUnion. Otherwise, this will be nil.
result = c.resultForUnion[v.Response[0].Type.Identifier]
}
methodMarker := protocolName.nest(name.Wire.Name())
method := newMethod(methodInner{
nameVariants: name,
protocolName: protocolName,
// Using the raw identifier v.Name instead of the name after
// reserved words logic, since that's the behavior in fidlc.
baseCodingTableName: codingTableName + string(v.Name),
Marker: methodMarker,
requestTypeShape: v.RequestTypeShapeV1,
responseTypeShape: v.ResponseTypeShapeV1,
wireMethod: newWireMethod(name.Wire.Name(), wireTypeNames, protocolName.Wire, methodMarker.Wire),
Attributes: Attributes{v.Attributes},
Ordinal: v.Ordinal,
HasRequest: v.HasRequest,
RequestArgs: c.compileParameterArray(v.Request),
HasResponse: v.HasResponse,
ResponseArgs: c.compileParameterArray(v.Response),
Transitional: v.IsTransitional(),
Result: result,
}, hlMessaging, wireTypeNames)
methods = append(methods, method)
}
var maxResponseSize int
for _, method := range methods {
if size := method.Response.InlineSize + method.Response.MaxOutOfLine; size > maxResponseSize {
maxResponseSize = size
}
}
fuzzingName := strings.ReplaceAll(strings.ReplaceAll(string(p.Name), ".", "_"), "/", "_")
r := newProtocol(protocolInner{
Attributes: Attributes{p.Attributes},
nameVariants: protocolName,
hlMessaging: hlMessaging,
wireTypeNames: wireTypeNames,
DiscoverableName: p.GetServiceName(),
SyncEventAllocation: computeAllocation(
maxResponseSize, 0, messageDirectionResponse.queryBoundedness(
clientContext, allEventsStrict(methods))),
Methods: methods,
FuzzingName: fuzzingName,
TestBase: protocolName.appendName("_TestBase").appendNamespace("testing"),
})
for i := 0; i < len(methods); i++ {
methods[i].Protocol = &r
}
return r
}
func (c *compiler) compileParameterArray(val []fidlgen.Parameter) []Parameter {
var params []Parameter = []Parameter{}
for _, v := range val {
params = append(params, Parameter{
Type: c.compileType(v.Type),
nameVariants: structMemberContext.transform(v.Name),
Offset: v.FieldShapeV1.Offset,
HandleInformation: c.fieldHandleInformation(&v.Type),
})
}
return params
}
//
// Functions for calculating message buffer size bounds
//
func fidlAlign(size int) int {
return (size + 7) & ^7
}
type boundedness bool
const (
boundednessBounded = true
boundednessUnbounded = false
)
// This value needs to be kept in sync with the one defined in
// zircon/system/ulib/fidl/include/lib/fidl/llcpp/sync_call.h
const llcppMaxStackAllocSize = 512
const channelMaxMessageSize = 65536
// allocation describes the allocation strategy of some operation, such as
// sending requests, receiving responses, or handling events. Note that the
// allocation strategy may depend on client/server context, direction of the
// message, and the content/shape of the message, as we make optimizations.
type allocation struct {
IsStack bool
Size int
bufferType bufferType
size string
}
func (alloc allocation) BackingBufferType() string {
switch alloc.bufferType {
case inlineBuffer:
return fmt.Sprintf("::fidl::internal::InlineMessageBuffer<%s>", alloc.size)
case boxedBuffer:
return fmt.Sprintf("::fidl::internal::BoxedMessageBuffer<%s>", alloc.size)
}
panic(fmt.Sprintf("unexpected buffer type: %v", alloc.bufferType))
}
type bufferType int
const (
_ bufferType = iota
inlineBuffer
boxedBuffer
)
func computeAllocation(primarySize int, maxOutOfLine int, boundedness boundedness) allocation {
var sizeString string
var size int
if boundedness == boundednessUnbounded || primarySize+maxOutOfLine > channelMaxMessageSize {
sizeString = "ZX_CHANNEL_MAX_MSG_BYTES"
size = channelMaxMessageSize
} else {
size = fidlAlign(primarySize + maxOutOfLine)
sizeString = fmt.Sprintf("%d", size)
}
if size > llcppMaxStackAllocSize {
return allocation{
IsStack: false,
Size: 0,
bufferType: boxedBuffer,
size: sizeString,
}
} else {
return allocation{
IsStack: true,
Size: size,
bufferType: inlineBuffer,
size: sizeString,
}
}
}