| # Life of a protocol open (Components v2) |
| |
| <<../_v2_banner.md>> |
| |
| This document describes the steps that occur when a component attempts to |
| connect to a protocol in its namespace. |
| |
| These steps apply to the components v2 model as run under component manager. |
| Portions of it also apply to the components v1 model as run under appmgr. |
| |
| At a high level these steps are: |
| |
| - Component manager will [construct a component's namespace][ns-construction] |
| based on the `use` declarations in its manifest. |
| - Once running, a component will attempt to [open a protocol][protocol-open] |
| in its namespace. |
| - This `Open` request is received by component manager, which performs the |
| [capability routing][cap-routing] necessary to find the component providing |
| the protocol. |
| - Component manager [binds to the component providing the protocol][binding] |
| and connects the `Open` request to it |
| |
| ## Constructing a component's namespace |
| |
| A [_namespace_][namespaces] is a set of directories that are offered to a |
| component when it is started. Each directory is associated with a file system |
| path through which the component may access files and protocols offered by other |
| components. |
| |
| These directories take the form of [handles][handle] to [channels][channel], |
| over which the component can use |
| [the `fuchsia.io.Directory` FIDL protocol][fuchsia.io]. |
| |
| For example, all components will receive a handle to the contents of the package |
| from which they were created at `/pkg`. This means that a component can see what |
| binaries are available in their package by reading the contents of `/pkg/bin`. |
| |
| The `use` declarations in [the component's manifest][component-manifests] |
| determine how the namespace is populated. When a protocol capability is used... |
| |
| ```json5 |
| use: [ |
| { |
| protocol: "fuchsia.example.Foo", |
| }, |
| ] |
| ``` |
| |
| ...component manager will add an entry to the component's namespace for the |
| parent directory of the protocol. In this example, the namespace path of the |
| protocol is `/svc/fuchsia.example.Foo` (the default path assignment), which |
| means that component manager will add a handle for `/svc` to the namespace. |
| |
| The `/svc` directory is provided by component manager itself, and component |
| manager will respond to requests for protocols to this directory for the |
| lifetime of the component. |
| |
| The exact semantics of what appears in the namespace varies based on capability |
| type. For example if a directory capability is used instead of the protocol |
| capability... |
| |
| ```json5 |
| "use": [ |
| { |
| directory: "example-data", |
| rights: [ "r*" ], |
| path: "/example/data", |
| }, |
| ] |
| ``` |
| |
| ...a handle for the directory itself appears in the namespace instead of a |
| handle for the parent directory. In this example, this means that a handle for |
| `/example/data` will appear in the namespace, whereas if this path was used for |
| a protocol capability `/example` would appear in the namespace. |
| |
| ## A component opens a protocol |
| |
| When a component wants to open a protocol it creates a new channel pair, and |
| sends one end of this pair via an `Open` request over a channel in its |
| namespace. For example, if the component wanted to open a connection to |
| `/svc/fuchsia.example.Foo`, one end of the new channel pair would be sent over |
| the `/svc` handle in its namespace. The component may then call the |
| `fuchsia.example.Foo` protocol over the channel. |
| |
| Since the directory containing the protocol (`/svc`) is provided by component |
| manager, it is component manager that will receive the server end of the new |
| channel via the `Open` request sent by the component. Component manager then |
| must identify the component providing the protocol over this channel. |
| |
| ## The `Open` triggers capability routing |
| |
| To determine the component that provides the protocol over the channel, |
| component manager must walk the tree of components, following `offer` and |
| `expose` declarations to find the capability's source. This process is referred |
| to as _capability routing_. |
| |
| Starting at the parent of the component that triggered the capability routing, |
| component manager will inspect each component's manifest, looking for an `offer` |
| declaration whose destination matches the child. The offer will specify a source |
| of either `parent`, `self`, or the name of a child. If the offer came from the |
| component's realm it will continue to walk up the tree, and if the offer came |
| from one of the component's children it will walk down the tree to that child. |
| |
| Once the routing begins walking down the tree it will look for `expose` |
| declarations, which will specify a source of either `self` or the name of a |
| child. If the capability came from a child then component manager will continue |
| to walk down the tree. |
| |
| Once an `offer` or `expose` declaration with a source of `self` is found, then |
| component manager can hand off the channel to that component. |
| |
| If at any step of the way the chain is invalid, component manager will log an |
| error and close the channel it received from the `Open` call. This can be caused |
| by various situations, such as: |
| |
| - A component `C` offered a capability from `parent`, but its parent `R` did |
| not offer the capability to `C`. |
| - A component `C` offered a capability from its child `D`, but child `D` did |
| not expose the capability to `C`. |
| |
| For example, consider the following tree of components and their manifests |
| (`program` blocks and runner setup omitted for brevity): |
| |
| ``` |
| C |
| / \ |
| B D |
| / |
| A |
| |
| A.cml: |
| { |
| // ... |
| capabilities: [ |
| { |
| protocol: "fuchsia.example.Foo", |
| }, |
| ], |
| expose: [ |
| { |
| protocol: "fuchsia.example.Foo", |
| from: "self", |
| }, |
| ], |
| } |
| |
| B.cml: |
| { |
| // ... |
| expose: [ |
| { |
| protocol: "fuchsia.example.Foo", |
| from: "#A", |
| }, |
| ], |
| children: [ |
| { |
| name: "A", |
| url: "fuchsia-pkg://fuchsia.com/a#meta/a.cm", |
| }, |
| ] |
| } |
| |
| C.cml: |
| { |
| // ... |
| offer: [ |
| { |
| protocol: "fuchsia.example.Foo", |
| from: "#B", |
| to: [ "#D" ], |
| }, |
| ] |
| children: [ |
| { |
| name: "B", |
| url: "fuchsia-pkg://fuchsia.com/b#meta/b.cm", |
| }, |
| { |
| name: "D", |
| url: "fuchsia-pkg://fuchsia.com/d#meta/d.cm", |
| }, |
| ] |
| } |
| |
| D.cml: |
| { |
| // ... |
| use: [ |
| { |
| "protocol": "fuchsia.example.Foo", |
| }, |
| ], |
| } |
| ``` |
| |
| When `D` calls `Open` on `/svc/fuchsia.example.Foo` in its namespace, component |
| manager will walk the tree to find the component that should provide this |
| protocol. It will start at `D`'s parent, `C`, and from there: |
| |
| - Look for the `offer` declaration for `fuchsia.example.Foo` to `D`, and see |
| that it comes from child `B`. |
| - Look for the `expose` declaration for `fuchsia.example.Foo` from `B`, and |
| see that it comes from `A`. |
| - Look for the `expose` declaration for `fuchsia.example.Foo` from `A`, and |
| see that it comes from `self`. This means that `A` is the component |
| providing the capability that `D` is attempting to use. |
| |
| Now that the provider component has been found, component manager can attempt to |
| hand off the channel it received via the `Open` request. |
| |
| ## Binding to a component and sending a protocol channel |
| |
| With the provider found the client component is now bound to the provider. This |
| will cause the component to start running if it is currently stopped. |
| |
| Every component upon being started receives a server handle to an |
| [_outgoing directory_][abi-system] in its handle table. When a component is |
| bound to, component manager forwards the server end of the protocol channel to |
| the providing component's outgoing directory, under the source path in the |
| providing component's `offer` or `expose` declaration |
| |
| In the above example component manager will send an `Open` request over the |
| outgoing directory handle for component `A` to the `/svc/fuchsia.example.Foo` |
| path, providing the channel handle that it received from component `D` when it |
| called `Open` to component manager. |
| |
| It is then up to component `A` to receive this request and start responding to |
| messages over the channel it was given. |
| |
| Since component manager directly forwards the server end of the protocol channel |
| to the provider component's outgoing directory, it is not involved in message |
| proxying and is entirely out of the picture after capability routing is |
| completed. Once a connection to another component is established, they talk |
| directly to each other with no arbiter in the middle. |
| |
| ## Caveats |
| |
| ### Runtime unpredictability |
| |
| Due to the runtime nature of capability routing and the behavior of the |
| components providing capabilities, there is no way to know if a given component |
| can successfully access a capability in its namespace before it attempts to do |
| so. Even if a valid offer/expose chain exists for the capability, package |
| updates could break this chain at runtime, and it's entirely possible a |
| component that claims to provide a capability in its manifest will fail to do so |
| when run. |
| |
| ### Offered vs ambient capabilities |
| |
| Some capabilities are provided by the component framework itself, and can be |
| directly used by (or will be implicitly provided to) components without their |
| parent offering these capabilities. Currently these are: |
| |
| - `/pkg`: a handle to the package from which the component was created. |
| - [`/svc/fuchsia.sys2.Realm`][realm.fidl]: a protocol which components can use |
| to manage their own realm. |
| |
| ### Parent may not `use` capabilities exposed to it |
| |
| Parent components can access capabilities offered by their children at runtime |
| by calling the [`fuchsia.sys2.Realm.BindChild`][realm.fidl] method to start the |
| child and receive a directory containing the child's exposed protocols. |
| |
| To prevent dependency cycles from occurring in component namespaces, a parent |
| component cannot declare a static dependency on the protocols of its children |
| with `use` declarations; it must use `BindChild()`. |
| |
| [ns-construction]: #constructing-a-components-namespace |
| [protocol-open]: #a-component-opens-a-protocol |
| [cap-routing]: #the-open-triggers-capability-routing |
| [binding]: #binding-to-a-component-and-sending-a-protocol-channel |
| [handle]: /docs/concepts/kernel/handles.md |
| [channel]: /docs/reference/kernel_objects/channel.md |
| [namespaces]: /docs/concepts/framework/namespaces.md |
| [component-manifests]: component_manifests.md |
| [fuchsia.io]: /sdk/fidl/fuchsia.io/io.fidl |
| [abi-system]: /docs/concepts/system/abi/system.md |
| [realm.fidl]: /sdk/fidl/fuchsia.sys2/realm.fidl |