blob: 1ee25e8a4b9e07f4dba182fe1542cb4caaaae7ca [file] [log] [blame] [view] [edit]
# 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