Repository: project-oak/oak
Pull request #1641 (2020-10-29 10:26:08)

Install `rust-analyzer` in docker image
Also configure VSCode dev container to use it, so that it is guaranteed
to work with the Rust version we use, and also fix settings so that it
works well with protobuf imports.

# Checklist

- [ ] Pull request affects core Oak functionality (e.g. runtime, SDK, ABI)
  - [ ] I have written tests that cover the code changes.
  - [ ] I have checked that these tests are run by
        [Cloudbuild](/cloudbuild.yaml)
  - [ ] I have updated [documentation](/docs/) accordingly.
  - [ ] I have raised an [issue](https://github.com/project-oak/oak/issues) to
        cover any TODOs and/or unfinished work.
- [ ] Pull request includes prototype/experimental work that is under
      construction.



Patch:
diff --git a/.devcontainer.json b/.devcontainer.json
index 2977bc25fb..534d68c92a 100644
--- a/.devcontainer.json
+++ b/.devcontainer.json
@@ -8,5 +8,11 @@
     "esbenp.prettier-vscode",
     "matklad.rust-analyzer",
     "xaver.clang-format"
-  ]
+  ],
+  "settings": {
+    // Use the `rust-analyzer` binary installed in the Docker image.
+    "rust-analyzer.serverPath": "rust-analyzer",
+    // Ensure protobuf generated files are loaded correctly.
+    "rust-analyzer.cargo.loadOutDirsFromCheck": true
+  }
 }
diff --git a/Dockerfile b/Dockerfile
index ea4a9d4517..6cc4ca691c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -250,6 +250,13 @@ ARG udeps_location=https://github.com/est31/cargo-udeps/releases/download/${udep
 RUN curl --location ${udeps_location} | tar --extract --gzip --directory=${install_dir} --strip-components=2 ./${udeps_dir}/cargo-udeps
 RUN chmod +x ${install_dir}/cargo-udeps
 
+# Install rust-analyzer
+# https://github.com/rust-analyzer/rust-analyzer
+ARG rust_analyzer_version=2020-10-26
+ARG rust_analyzer_location=https://github.com/rust-analyzer/rust-analyzer/releases/download/${rust_analyzer_version}/rust-analyzer-linux
+RUN curl --location ${rust_analyzer_location} > ${install_dir}/rust-analyzer
+RUN chmod +x ${install_dir}/rust-analyzer
+
 # Unset $CARGO_HOME so that the new user will use the default value for it, which will point it to
 # its own home folder.
 ENV CARGO_HOME ""
diff --git a/docs/abi.md b/docs/abi.md
index c233fd071a..08b5ad174e 100644
--- a/docs/abi.md
+++ b/docs/abi.md
@@ -181,9 +181,13 @@ If writing to the specified channel would violate
 
 ### `channel_create`
 
-Creates a new unidirectional Channel assigning the label specified by `param[2]`
-and `param[3]` to the newly created Channel, and returns the Channel handles for
-its read and write halves as output parameters in `param[0]` and `param[1]`.
+Creates a new unidirectional Channel assigning the name specified by `param[2]`
+and `param[3]` and the label specified in `param[4]` and `param[5]` to the newly
+created Channel, and returns the Channel handles for its read and write halves
+as output parameters in `param[0]` and `param[1]`.
+
+The name is a UTF-8 encoded string. It does not have to be unique and can be
+empty. It is used in logs to help with identifying channels during debugging.
 
 The label is a serialized [`Label`](/oak_abi/proto/label.proto) protobuf
 message.
@@ -200,8 +204,10 @@ If creating the specified Channel would violate
   for the write half of the channel (as a little-endian u64).
 - `param[1]: usize`: Address of an 8-byte location that will receive the handle
   for the read half of the channel (as a little-endian u64).
-- `param[2]: usize`: Source buffer holding serialized `Label`
-- `param[3]: usize`: Label size in bytes
+- `param[2]: usize`: Source buffer holding the UTF-8 encoded name
+- `param[3]: usize`: Size in bytes of the UTF-8 encoding of the name
+- `param[4]: usize`: Source buffer holding serialized `Label`
+- `param[5]: usize`: Label size in bytes
 - `result[0]: u32`: Status of operation
 
 ### `channel_close`
diff --git a/docs/programming-oak.md b/docs/programming-oak.md
index 1d88c8b2b6..ae4fae3210 100644
--- a/docs/programming-oak.md
+++ b/docs/programming-oak.md
@@ -64,7 +64,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     };
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(node, grpc_channel);
+    oak::run_command_loop(node, grpc_channel.iter());
 });
 ```
 <!-- prettier-ignore-end -->
@@ -112,7 +112,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     let dispatcher = TranslatorDispatcher::new(Node);
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 }
 ```
 <!-- prettier-ignore-end -->
@@ -144,7 +144,7 @@ pub extern "C" fn frontend_oak_main(_in_handle: u64) {
         let dispatcher = OakAbiTestServiceDispatcher::new(node);
         let grpc_channel =
             oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-        oak::run_command_loop(dispatcher, grpc_channel);
+        oak::run_command_loop(dispatcher, grpc_channel.iter());
     });
 }
 ```
@@ -488,8 +488,8 @@ to the room:
 [embedmd]:# (../examples/chat/module/rust/src/lib.rs Rust /.*self\.rooms\.entry\(/ /\}\);$/)
 ```Rust
                 let channel = self.rooms.entry(label.clone()).or_insert_with(|| {
-                    let (wh, rh) =
-                        oak::io::channel_create(&label).expect("could not create channel");
+                    let (wh, rh) = oak::io::channel_create("Room init", &label)
+                        .expect("could not create channel");
                     oak::node_create(&oak::node_config::wasm("app", "room"), &label, rh.handle)
                         .expect("could not create node");
                     rh.close().expect("could not close channel");
diff --git a/examples/Cargo.lock b/examples/Cargo.lock
index 32f4de800b..87068972a0 100644
--- a/examples/Cargo.lock
+++ b/examples/Cargo.lock
@@ -1143,6 +1143,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/examples/aggregator/client/cpp/aggregator.cc b/examples/aggregator/client/cpp/aggregator.cc
index 2192a0601a..f9a52e2e6c 100644
--- a/examples/aggregator/client/cpp/aggregator.cc
+++ b/examples/aggregator/client/cpp/aggregator.cc
@@ -79,7 +79,7 @@ int main(int argc, char** argv) {
   // The particular value corresponds to the hash on the `aggregator.wasm` line in
   // https://github.com/project-oak/oak/blob/hashes/reproducibility_index.
   oak::label::Label label = oak::WebAssemblyModuleHashLabel(
-      absl::HexStringToBytes("b32f72f574513d824e1c01347f9b39cafa7d08ef0686d5d6b43425aa8fac78de"));
+      absl::HexStringToBytes("9a75540f69287bc579f896416030de0e06ba915aa93279384859fa25c7a384ce"));
   // Connect to the Oak Application.
   auto stub = Aggregator::NewStub(oak::ApplicationClient::CreateChannel(
       address, oak::ApplicationClient::GetTlsChannelCredentials(ca_cert), label));
diff --git a/examples/aggregator/module/rust/src/lib.rs b/examples/aggregator/module/rust/src/lib.rs
index 88f254e056..0cf736ca92 100644
--- a/examples/aggregator/module/rust/src/lib.rs
+++ b/examples/aggregator/module/rust/src/lib.rs
@@ -174,7 +174,7 @@ oak::entrypoint!(aggregator<AggregatorInit> => |init_receiver: Receiver<Aggregat
         }
     );
     let dispatcher = AggregatorDispatcher::new(node);
-    oak::run_command_loop(dispatcher, grpc_server_invocation_receiver);
+    oak::run_command_loop(dispatcher, grpc_server_invocation_receiver.iter());
 });
 
 #[derive(Debug, serde::Deserialize)]
@@ -209,7 +209,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |receiver: Receiver<ConfigMap>| {
         oak::grpc::server::init(&config.grpc_server_listen_address).expect("Couldn't create gRPC server pseudo-Node");
 
     // Create an Aggregator Node.
-    let (init_sender, init_receiver) = oak::io::channel_create::<AggregatorInit>(&Label::public_untrusted())
+    let (init_sender, init_receiver) = oak::io::channel_create::<AggregatorInit>("Aggregator init", &Label::public_untrusted())
         .expect("Couldn't create initialization channel");
     oak::node_create(&oak::node_config::wasm("app", "aggregator"), &Label::public_untrusted(), init_receiver.handle)
         .expect("Couldn't create gRPC worker node");
@@ -238,7 +238,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |receiver: Receiver<ConfigMap>| {
     // Send the initialization message to Aggregator Node containing a gRPC server invocation
     // receiver and a gRPC client invocation sender.
     debug!("Sending the initialization message to handler Node");
-    let (invocation_sender, invocation_receiver) = oak::io::channel_create::<grpc::Invocation>(&Label::public_untrusted())
+    let (invocation_sender, invocation_receiver) = oak::io::channel_create::<grpc::Invocation>("gRPC invocation", &Label::public_untrusted())
         .expect("Couldn't create gRPC invocation channel");
     let init_message = create_init_message(&invocation_receiver, &grpc_client.invocation_sender);
     init_sender.send(&init_message).expect("Couldn't send the initialization message to Aggregator Node");
diff --git a/examples/aggregator/oak_app_manifest.toml b/examples/aggregator/oak_app_manifest.toml
index 4d1b8640de..ac8cf21090 100644
--- a/examples/aggregator/oak_app_manifest.toml
+++ b/examples/aggregator/oak_app_manifest.toml
@@ -1,4 +1,4 @@
 name = "aggregator"
 
 [modules]
-app = { external = { url = "https://storage.googleapis.com/oak-modules/aggregator/b32f72f574513d824e1c01347f9b39cafa7d08ef0686d5d6b43425aa8fac78de", sha256 = "b32f72f574513d824e1c01347f9b39cafa7d08ef0686d5d6b43425aa8fac78de" } }
+app = { external = { url = "https://storage.googleapis.com/oak-modules/aggregator/9a75540f69287bc579f896416030de0e06ba915aa93279384859fa25c7a384ce", sha256 = "9a75540f69287bc579f896416030de0e06ba915aa93279384859fa25c7a384ce" } }
diff --git a/examples/chat/module/rust/src/lib.rs b/examples/chat/module/rust/src/lib.rs
index 07455a67f5..d7419fedb0 100644
--- a/examples/chat/module/rust/src/lib.rs
+++ b/examples/chat/module/rust/src/lib.rs
@@ -18,7 +18,7 @@ use anyhow::Context;
 use log::{info, warn};
 use oak::{
     grpc,
-    io::{ReceiverExt, Sender, SenderExt},
+    io::{Receiver, ReceiverExt, Sender, SenderExt},
     Label,
 };
 use oak_abi::proto::oak::application::ConfigMap;
@@ -85,8 +85,8 @@ impl oak::CommandHandler<oak::grpc::Invocation> for Router {
                 // Check if there is a channel to a room with the desired label already, or create
                 // it if not.
                 let channel = self.rooms.entry(label.clone()).or_insert_with(|| {
-                    let (wh, rh) =
-                        oak::io::channel_create(&label).expect("could not create channel");
+                    let (wh, rh) = oak::io::channel_create("Room init", &label)
+                        .expect("could not create channel");
                     oak::node_create(&oak::node_config::wasm("app", "room"), &label, rh.handle)
                         .expect("could not create node");
                     rh.close().expect("could not close channel");
@@ -109,17 +109,17 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     let router = Router::default();
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(router, grpc_channel);
+    oak::run_command_loop(router, grpc_channel.iter());
 });
 
 // A node that receives gRPC invocations for an individual label.
 //
 // Multiple instances of nodes with this entrypoint may be created at runtime, according to the
 // variety of labels of incoming requests.
-oak::entrypoint!(room<oak::grpc::Invocation> => |receiver| {
+oak::entrypoint!(room<oak::grpc::Invocation> => |receiver: Receiver<oak::grpc::Invocation>| {
     oak::logger::init_default();
     let dispatcher = ChatDispatcher::new(Room::default());
-    oak::run_command_loop(dispatcher, receiver);
+    oak::run_command_loop(dispatcher, receiver.iter());
 });
 
 /// A worker node implementation for an individual label.
diff --git a/examples/hello_world/module/rust/src/lib.rs b/examples/hello_world/module/rust/src/lib.rs
index 0ad1fd0c63..7ba05e0b99 100644
--- a/examples/hello_world/module/rust/src/lib.rs
+++ b/examples/hello_world/module/rust/src/lib.rs
@@ -19,7 +19,7 @@ pub mod proto {
 }
 
 use log::info;
-use oak::grpc;
+use oak::{grpc, io::ReceiverExt};
 use oak_abi::proto::oak::application::ConfigMap;
 use proto::{HelloRequest, HelloResponse, HelloWorld, HelloWorldDispatcher};
 
@@ -32,7 +32,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let dispatcher = HelloWorldDispatcher::new(node);
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 struct Node {
diff --git a/examples/http_server/module/src/lib.rs b/examples/http_server/module/src/lib.rs
index db76fb6874..a0528e80f2 100644
--- a/examples/http_server/module/src/lib.rs
+++ b/examples/http_server/module/src/lib.rs
@@ -17,7 +17,7 @@
 //! Simple example starting an Oak Application serving a static page over HTTP.
 
 use log::{info, warn};
-use oak::{http::Invocation, CommandHandler, OakError, OakStatus};
+use oak::{http::Invocation, io::ReceiverExt, CommandHandler, OakError, OakStatus};
 use oak_abi::proto::oak::application::ConfigMap;
 
 oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
@@ -27,7 +27,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let http_channel =
         oak::http::init("[::]:8080").expect("Could not create HTTP server pseudo-Node!");
 
-    oak::run_command_loop(node, http_channel);
+    oak::run_command_loop(node, http_channel.iter());
 });
 
 /// A simple HTTP server that responds with `OK` (200) to every request sent to `/`, and with
diff --git a/examples/injection/module/rust/src/lib.rs b/examples/injection/module/rust/src/lib.rs
index 8eedaadc78..7b58f065f5 100644
--- a/examples/injection/module/rust/src/lib.rs
+++ b/examples/injection/module/rust/src/lib.rs
@@ -79,8 +79,8 @@ use proto::{
 
 oak::entrypoint!(grpc_fe<ConfigMap> => |_receiver| {
     oak::logger::init_default();
-    let (to_provider_write_handle, to_provider_read_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
-    let (from_provider_write_handle, from_provider_read_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (to_provider_write_handle, to_provider_read_handle) = oak::channel_create("To-provider", &Label::public_untrusted()).unwrap();
+    let (from_provider_write_handle, from_provider_read_handle) = oak::channel_create("From-provider", &Label::public_untrusted()).unwrap();
 
     oak::node_create(&oak::node_config::wasm("app", "provider"), &Label::public_untrusted(), to_provider_read_handle)
         .expect("Failed to create provider");
@@ -98,7 +98,7 @@ oak::entrypoint!(grpc_fe<ConfigMap> => |_receiver| {
     let grpc_channel = oak::grpc::server::init("[::]:8080")
         .expect("could not create gRPC server pseudo-Node");
 
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 oak::entrypoint!(provider<BlobStoreRequest> => |receiver: Receiver<BlobStoreRequest>| {
@@ -112,7 +112,7 @@ oak::entrypoint!(provider<BlobStoreRequest> => |receiver: Receiver<BlobStoreRequ
             .expect("Did not receive a decodable message")
             .sender
             .expect("No sender in received message");
-    oak::run_command_loop(BlobStoreProvider::new(frontend_sender), receiver);
+    oak::run_command_loop(BlobStoreProvider::new(frontend_sender), receiver.iter());
 });
 
 oak::entrypoint!(store<BlobRequest> => |receiver: Receiver<BlobRequest>| {
@@ -126,7 +126,7 @@ oak::entrypoint!(store<BlobRequest> => |receiver: Receiver<BlobRequest>| {
             .expect("Did not receive a write handle")
             .sender
             .expect("No write handle in received message");
-    oak::run_command_loop(BlobStoreImpl::new(sender), receiver);
+    oak::run_command_loop(BlobStoreImpl::new(sender), receiver.iter());
 });
 
 enum BlobStoreAccess {
@@ -218,9 +218,11 @@ impl oak::CommandHandler<BlobStoreRequest> for BlobStoreProvider {
     fn handle_command(&mut self, _command: BlobStoreRequest) -> anyhow::Result<()> {
         // Create new BlobStore
         let (to_store_write_handle, to_store_read_handle) =
-            oak::channel_create(&Label::public_untrusted()).context("Could not create channel")?;
+            oak::channel_create("To-store", &Label::public_untrusted())
+                .context("Could not create channel")?;
         let (from_store_write_handle, from_store_read_handle) =
-            oak::channel_create(&Label::public_untrusted()).context("Could not create channel")?;
+            oak::channel_create("From-store", &Label::public_untrusted())
+                .context("Could not create channel")?;
         oak::node_create(
             &oak::node_config::wasm("app", "store"),
             &Label::public_untrusted(),
diff --git a/examples/machine_learning/module/rust/src/lib.rs b/examples/machine_learning/module/rust/src/lib.rs
index ce647c7c47..1d719facaa 100644
--- a/examples/machine_learning/module/rust/src/lib.rs
+++ b/examples/machine_learning/module/rust/src/lib.rs
@@ -18,7 +18,10 @@
 // https://github.com/AtheMathmo/rusty-machine/blob/master/examples/naive_bayes_dogs.rs .
 
 use log::{error, info, warn};
-use oak::grpc;
+use oak::{
+    grpc,
+    io::{Receiver, ReceiverExt},
+};
 use oak_abi::proto::oak::application::ConfigMap;
 use rand::prelude::*;
 use rand_distr::{Distribution, Normal, Standard};
@@ -147,7 +150,7 @@ struct Config {
     test_animals: Vec<Animal>,
 }
 
-oak::entrypoint!(oak_main<grpc::Invocation> => |receiver| {
+oak::entrypoint!(oak_main<grpc::Invocation> => |receiver: Receiver<grpc::Invocation>| {
     oak::logger::init_default();
     let node = Node {
         training_set_size: 1000,
@@ -155,7 +158,7 @@ oak::entrypoint!(oak_main<grpc::Invocation> => |receiver| {
         config: None,
         model: NaiveBayes::new(),
     };
-    oak::run_command_loop(node, receiver);
+    oak::run_command_loop(node, receiver.iter());
 });
 
 oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
@@ -168,7 +171,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     };
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(node, grpc_channel);
+    oak::run_command_loop(node, grpc_channel.iter());
 });
 
 struct Node {
diff --git a/examples/private_set_intersection/module/rust/src/lib.rs b/examples/private_set_intersection/module/rust/src/lib.rs
index 95d04e598a..bcc151463f 100644
--- a/examples/private_set_intersection/module/rust/src/lib.rs
+++ b/examples/private_set_intersection/module/rust/src/lib.rs
@@ -38,7 +38,7 @@ pub mod proto {
     ));
 }
 
-use oak::grpc;
+use oak::{grpc, io::ReceiverExt};
 use oak_abi::proto::oak::application::ConfigMap;
 use proto::{
     GetIntersectionRequest, GetIntersectionResponse, PrivateSetIntersection,
@@ -50,7 +50,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let dispatcher = PrivateSetIntersectionDispatcher::new(Node::default());
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 /// Maximum number of contributed private sets.
diff --git a/examples/private_set_intersection/oak_app_manifest.toml b/examples/private_set_intersection/oak_app_manifest.toml
index bf4b094d1b..d9935c6692 100644
--- a/examples/private_set_intersection/oak_app_manifest.toml
+++ b/examples/private_set_intersection/oak_app_manifest.toml
@@ -2,4 +2,4 @@ name = "private_set_intersection"
 
 [modules]
 # TODO(865): Use locally built module once reproducibility is fixed.
-app = { external = { url = "https://storage.googleapis.com/oak-modules/private_set_intersection/b86267f3aabbc2d3fffbc463d3c23409d786f3577abe1f66ea52d092f2d72365", sha256 = "b86267f3aabbc2d3fffbc463d3c23409d786f3577abe1f66ea52d092f2d72365" } }
+app = { external = { url = "https://storage.googleapis.com/oak-modules/private_set_intersection/21963350a129dd79db7952b2e5ea9b1ef31e855becac22d6a980efede61da06f", sha256 = "21963350a129dd79db7952b2e5ea9b1ef31e855becac22d6a980efede61da06f" } }
diff --git a/examples/private_set_intersection/private_set_intersection.sign b/examples/private_set_intersection/private_set_intersection.sign
index ff38263faa..22d54a8241 100644
--- a/examples/private_set_intersection/private_set_intersection.sign
+++ b/examples/private_set_intersection/private_set_intersection.sign
@@ -3,10 +3,10 @@ f41SClNtR4i46v2Tuh1fQLbt/ZqRr1lENajCW92jyP4=
 -----END PUBLIC KEY-----
 
 -----BEGIN SIGNATURE-----
-XOKfIiD/krp2WcCPkZ+9T6fcAk5J9xuXM394VZW40V/Z6TXKaA/4e/O79iCTLLZG
-O2tPi/Tpie8XFwHgDm3VBg==
+6pSC4WodR+brN9OkhSX/mudNriLNzPvzftAP4MO0rybe9YxPDRC/vbBSB2ewAIjo
++UR86oYG657laC3bGX0tCw==
 -----END SIGNATURE-----
 
 -----BEGIN HASH-----
-uGJn86q7wtP/+8Rj08I0CdeG81d6vh9m6lLQkvLXI2U=
+IZYzUKEp3XnbeVKy5eqbHvMehVvsrCLWqYDv7eYdoG8=
 -----END HASH-----
diff --git a/examples/running_average/module/rust/src/lib.rs b/examples/running_average/module/rust/src/lib.rs
index a71e60fdb0..908eaa11d4 100644
--- a/examples/running_average/module/rust/src/lib.rs
+++ b/examples/running_average/module/rust/src/lib.rs
@@ -26,7 +26,7 @@ pub mod proto {
     include!(concat!(env!("OUT_DIR"), "/oak.examples.running_average.rs"));
 }
 
-use oak::grpc;
+use oak::{grpc, io::ReceiverExt};
 use oak_abi::proto::oak::application::ConfigMap;
 use proto::{GetAverageResponse, RunningAverage, RunningAverageDispatcher, SubmitSampleRequest};
 
@@ -34,7 +34,7 @@ oak::entrypoint!(oak_main<ConfigMap> => |_receiver| {
     let dispatcher = RunningAverageDispatcher::new(Node::default());
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 #[derive(Default)]
diff --git a/examples/translator/module/rust/src/lib.rs b/examples/translator/module/rust/src/lib.rs
index fc28691c74..46b3cab474 100644
--- a/examples/translator/module/rust/src/lib.rs
+++ b/examples/translator/module/rust/src/lib.rs
@@ -15,7 +15,10 @@
 //
 
 use log::info;
-use oak::grpc;
+use oak::{
+    grpc,
+    io::{Receiver, ReceiverExt},
+};
 use oak_abi::proto::oak::application::ConfigMap;
 use translator_common::proto::{
     TranslateRequest, TranslateResponse, Translator, TranslatorDispatcher,
@@ -24,10 +27,10 @@ use translator_common::proto::{
 // The `oak_main` entrypoint is used when the Translator acts as a library Node for a
 // wider Application. In this case, invocations arrive directly over the channel received
 // at start-of-day.
-oak::entrypoint!(oak_main<grpc::Invocation> => |receiver| {
+oak::entrypoint!(oak_main<grpc::Invocation> => |receiver: Receiver<grpc::Invocation>| {
     oak::logger::init_default();
     let dispatcher = TranslatorDispatcher::new(Node);
-    oak::run_command_loop(dispatcher, receiver);
+    oak::run_command_loop(dispatcher, receiver.iter());
 });
 
 // The `grpc_oak_main` entrypoint is used when the Translator acts as a standalone Oak
@@ -39,7 +42,7 @@ oak::entrypoint!(grpc_oak_main<ConfigMap> => |_receiver| {
     let dispatcher = TranslatorDispatcher::new(Node);
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-    oak::run_command_loop(dispatcher, grpc_channel);
+    oak::run_command_loop(dispatcher, grpc_channel.iter());
 });
 
 struct Node;
diff --git a/examples/trusted_database/module/rust/src/handler.rs b/examples/trusted_database/module/rust/src/handler.rs
index 7046ef8656..e53485664d 100644
--- a/examples/trusted_database/module/rust/src/handler.rs
+++ b/examples/trusted_database/module/rust/src/handler.rs
@@ -149,5 +149,5 @@ oak::entrypoint!(handler_oak_main<TrustedDatabaseCommand> => |command_receiver:
     let dispatcher = TrustedDatabaseDispatcher::new(node);
     let invocation_receiver = receiver.receiver.expect("Empty gRPC invocation receiver");
     // The event loop only runs once because the `Main` Node sends a single invocation.
-    oak::run_command_loop(dispatcher, invocation_receiver);
+    oak::run_command_loop(dispatcher, invocation_receiver.iter());
 });
diff --git a/examples/trusted_database/module/rust/src/lib.rs b/examples/trusted_database/module/rust/src/lib.rs
index 833bf6bcca..a9f8addfe4 100644
--- a/examples/trusted_database/module/rust/src/lib.rs
+++ b/examples/trusted_database/module/rust/src/lib.rs
@@ -76,9 +76,11 @@ impl CommandHandler<grpc::Invocation> for TrustedDatabaseNode {
     fn handle_command(&mut self, invocation: grpc::Invocation) -> anyhow::Result<()> {
         // Create a client request handler Node.
         debug!("Creating handler Node");
-        let (sender, receiver) =
-            oak::io::channel_create::<TrustedDatabaseCommand>(&Label::public_untrusted())
-                .context("Couldn't create command channel")?;
+        let (sender, receiver) = oak::io::channel_create::<TrustedDatabaseCommand>(
+            "Command",
+            &Label::public_untrusted(),
+        )
+        .context("Couldn't create command channel")?;
         // TODO(#1406): Use client assigned label for creating a new handler Node.
         oak::node_create(
             &oak::node_config::wasm("app", "handler_oak_main"),
@@ -90,9 +92,11 @@ impl CommandHandler<grpc::Invocation> for TrustedDatabaseNode {
 
         // Create a gRPC invocation channel for forwarding requests to the
         // `TrustedDatabaseHandlerNode`.
-        let (invocation_sender, invocation_receiver) =
-            oak::io::channel_create::<grpc::Invocation>(&Label::public_untrusted())
-                .context("Couldn't create gRPC invocation channel")?;
+        let (invocation_sender, invocation_receiver) = oak::io::channel_create::<grpc::Invocation>(
+            "gRPC invocation",
+            &Label::public_untrusted(),
+        )
+        .context("Couldn't create gRPC invocation channel")?;
 
         // Create a command message that contains a copy of the database.
         let command = TrustedDatabaseCommand {
@@ -127,5 +131,5 @@ oak::entrypoint!(oak_main<ConfigMap> => |receiver: Receiver<ConfigMap>| {
     let points_of_interest = load_database(config_map).expect("Couldn't load database");
     let grpc_channel =
         oak::grpc::server::init("[::]:8080").expect("Couldn't create gRPC server pseudo-Node");
-    oak::run_command_loop(TrustedDatabaseNode { points_of_interest }, grpc_channel);
+    oak::run_command_loop(TrustedDatabaseNode { points_of_interest }, grpc_channel.iter());
 });
diff --git a/experimental/Cargo.lock b/experimental/Cargo.lock
index 8c6b0fb1fd..e5526e4978 100644
--- a/experimental/Cargo.lock
+++ b/experimental/Cargo.lock
@@ -821,6 +821,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/experimental/web/index.html b/experimental/web/index.html
index c2fcfdaed1..f39e5f1b18 100644
--- a/experimental/web/index.html
+++ b/experimental/web/index.html
@@ -131,29 +131,37 @@
                       handleBuf,
                       handleCount,
                     ].join(', ')}) -> ${status}
-    bytes: [${bytes}]
-    bytes(string): "${bytesString}"
-    handles: [${handles}]`;
+      bytes: [${bytes}]
+      bytes(string): "${bytesString}"
+      handles: [${handles}]`;
                     this.trace.push(entry);
                     return status;
                   },
                   channel_create: (
                     writeHandle,
                     readHandle,
+                    nameBuf,
+                    nameSize,
                     labelBuf,
                     labelSize
                   ) => {
                     const status = OakStatus.values.OK;
+                    const name = new TextDecoder().decode(
+                      this.readMemory(nameBuf, nameSize)
+                    );
                     const label = Label.decode(
                       this.readMemory(labelBuf, labelSize)
                     );
                     const entry = `${new Date().toISOString()}: channel_create(${[
                       writeHandle,
                       readHandle,
+                      nameBuf,
+                      nameSize,
                       labelBuf,
                       labelSize,
                     ].join(', ')}) -> ${status}
-    label: ${JSON.stringify(label)}`;
+      name: ${JSON.stringify(name)}
+      label: ${JSON.stringify(label)}`;
                     this.trace.push(entry);
                     return status;
                   },
@@ -178,8 +186,8 @@
                       labelSize,
                       handle,
                     ].join(', ')}) -> ${status}
-    config: ${JSON.stringify(config)}
-    label: ${JSON.stringify(label)}`;
+      config: ${JSON.stringify(config)}
+      label: ${JSON.stringify(label)}`;
                     this.trace.push(entry);
                     this.createNode(config);
                     return status;
diff --git a/oak/module/oak_abi.h b/oak/module/oak_abi.h
index 0bfb0fb99e..3f3b31dbae 100644
--- a/oak/module/oak_abi.h
+++ b/oak/module/oak_abi.h
@@ -52,7 +52,8 @@ oak_abi::OakStatus channel_write(oak_abi::Handle handle, uint8_t* buff, size_t u
 WASM_IMPORT("oak") oak_abi::OakStatus channel_close(oak_abi::Handle handle);
 WASM_IMPORT("oak")
 oak_abi::OakStatus channel_create(oak_abi::Handle* write_handle, oak_abi::Handle* read_handle,
-                                  uint8_t* label_buf, size_t label_size);
+                                  uint8_t* name_buf, size_t name_size, uint8_t* label_buf,
+                                  size_t label_size);
 WASM_IMPORT("oak")
 oak_abi::OakStatus channel_label_read(oak_abi::Handle handle, uint8_t* label_buf, size_t label_size,
                                       uint32_t* actual_size);
diff --git a/oak/module/oak_main.h b/oak/module/oak_main.h
index e57e63916a..2c488311ad 100644
--- a/oak/module/oak_main.h
+++ b/oak/module/oak_main.h
@@ -38,7 +38,7 @@ WASM_EXPORT void oak_main(oak_abi::Handle _handle) {
   // Create a channel to the gRPC server pseudo-Node.
   oak_abi::Handle write_handle;
   oak_abi::Handle read_handle;
-  oak_abi::OakStatus result = channel_create(&write_handle, &read_handle, nullptr, 0);
+  oak_abi::OakStatus result = channel_create(&write_handle, &read_handle, nullptr, 0, nullptr, 0);
   if (result != oak_abi::OakStatus::OK) {
     return;
   }
@@ -65,7 +65,7 @@ WASM_EXPORT void oak_main(oak_abi::Handle _handle) {
   // Create a separate channel for receiving invocations and pass it to the gRPC pseudo-Node.
   oak_abi::Handle grpc_out_handle;
   oak_abi::Handle grpc_in_handle;
-  result = channel_create(&grpc_out_handle, &grpc_in_handle, nullptr, 0);
+  result = channel_create(&grpc_out_handle, &grpc_in_handle, nullptr, 0, nullptr, 0);
   if (result != oak_abi::OakStatus::OK) {
     return;
   }
diff --git a/oak_abi/src/lib.rs b/oak_abi/src/lib.rs
index 1ccd5e429f..eaa3db3550 100644
--- a/oak_abi/src/lib.rs
+++ b/oak_abi/src/lib.rs
@@ -135,6 +135,9 @@ extern "C" {
     /// Returns handles for the the write and read halves of the Channel in the spaces given by
     /// `write` and `read`.
     ///
+    /// The name to assign to the newly created Channel is provided in the memory area given by
+    /// `name_buf` and `name_len` as a UTF-8 encoded string.
+    ///
     /// The label to assign to the newly created Channel is provided in the memory area given by
     /// `label_buf` and `label_len`.
     ///
@@ -144,6 +147,8 @@ extern "C" {
     pub fn channel_create(
         write: *mut u64,
         read: *mut u64,
+        name_buf: *const u8,
+        name_len: usize,
         label_buf: *const u8,
         label_len: usize,
     ) -> u32;
diff --git a/oak_derive/Cargo.lock b/oak_derive/Cargo.lock
index c5fe777b89..ab203c86e5 100644
--- a/oak_derive/Cargo.lock
+++ b/oak_derive/Cargo.lock
@@ -130,6 +130,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/oak_io/Cargo.lock b/oak_io/Cargo.lock
index d0b47a4053..85d1e87faa 100644
--- a/oak_io/Cargo.lock
+++ b/oak_io/Cargo.lock
@@ -6,6 +6,12 @@ version = "1.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
 
+[[package]]
+name = "assert_matches"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695579f0f2520f3774bb40461e5adb066459d4e0af4d59d20175484fb8e9edf1"
+
 [[package]]
 name = "autocfg"
 version = "1.0.0"
@@ -129,6 +135,8 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "assert_matches",
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/oak_io/Cargo.toml b/oak_io/Cargo.toml
index 0a79d9c346..259d80d122 100644
--- a/oak_io/Cargo.toml
+++ b/oak_io/Cargo.toml
@@ -12,6 +12,10 @@ oak_abi = { path = "../oak_abi" }
 oak_derive = { path = "../oak_derive" }
 prost = "*"
 prost-types = "*"
+either = "*"
+
+[dev-dependencies]
+assert_matches = "*"
 
 [build-dependencies]
 oak_utils = { path = "../oak_utils" }
diff --git a/oak_io/src/lib.rs b/oak_io/src/lib.rs
index df1bfc69d8..13756eb556 100644
--- a/oak_io/src/lib.rs
+++ b/oak_io/src/lib.rs
@@ -24,10 +24,11 @@ mod receiver;
 mod sender;
 
 pub use decodable::Decodable;
+use either::Either;
 pub use encodable::Encodable;
 pub use error::OakError;
 use handle::{ReadHandle, WriteHandle};
-pub use oak_abi::Handle;
+pub use oak_abi::{Handle, OakStatus};
 pub use receiver::Receiver;
 pub use sender::Sender;
 
@@ -37,3 +38,116 @@ pub struct Message {
     pub bytes: Vec<u8>,
     pub handles: Vec<Handle>,
 }
+
+/// Implementation of [`Encodable`] for [`Either`] that encodes the variant (0 or 1) in the first
+/// byte of the resulting [`Message`].
+impl<L: Encodable, R: Encodable> Encodable for Either<L, R> {
+    fn encode(&self) -> Result<Message, OakError> {
+        let (variant, mut inner) = match self {
+            // Left variant == 0.
+            Either::Left(m) => (0, m.encode()?),
+            // Right variant == 1.
+            Either::Right(m) => (1, m.encode()?),
+        };
+        // Insert the variant byte as the beginning of the message bytes, and leave handles as they
+        // are.
+        inner.bytes.insert(0, variant);
+        Ok(inner)
+    }
+}
+
+/// Implementation of [`Decodable`] for [`Either`] that decodes the variant (0 or 1) from the first
+/// byte of the provided [`Message`].
+impl<L: Decodable, R: Decodable> Decodable for Either<L, R> {
+    fn decode(message: &Message) -> Result<Self, OakError> {
+        match message.bytes.get(0) {
+            // Left variant == 0.
+            Some(0) => {
+                let inner_message = Message {
+                    bytes: message.bytes[1..].to_vec(),
+                    handles: message.handles.clone(),
+                };
+                Ok(Either::Left(L::decode(&inner_message)?))
+            }
+            // Right variant == 1.
+            Some(1) => {
+                let inner_message = Message {
+                    bytes: message.bytes[1..].to_vec(),
+                    handles: message.handles.clone(),
+                };
+                Ok(Either::Right(R::decode(&inner_message)?))
+            }
+            // Invalid variant, or not enough bytes.
+            _ => Err(OakStatus::ErrInvalidArgs.into()),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use assert_matches::assert_matches;
+
+    #[test]
+    fn either_round_trip() {
+        type T = Either<u32, u32>;
+
+        {
+            // This test case is just a baseline reference for the ones below, to spell out the
+            // protobuf encoding of the inner object.
+            let original = 1988;
+            let encoded = original.encode().unwrap();
+            assert_eq!(
+                Message {
+                    bytes: vec![8, 196, 15],
+                    handles: vec![]
+                },
+                encoded
+            );
+            let decoded = u32::decode(&encoded).unwrap();
+            assert_eq!(original, decoded);
+        }
+
+        {
+            let original = T::Left(1988);
+            let encoded = original.encode().unwrap();
+            // Note the first byte corresponds to the variant == 0.
+            assert_eq!(
+                Message {
+                    bytes: vec![0, 8, 196, 15],
+                    handles: vec![]
+                },
+                encoded
+            );
+            let decoded = T::decode(&encoded).unwrap();
+            assert_eq!(original, decoded);
+        }
+
+        {
+            let original = T::Right(1988);
+            let encoded = original.encode().unwrap();
+            // Note the first byte corresponds to the variant == 1.
+            assert_eq!(
+                Message {
+                    bytes: vec![1, 8, 196, 15],
+                    handles: vec![]
+                },
+                encoded
+            );
+            let decoded = T::decode(&encoded).unwrap();
+            assert_eq!(original, decoded);
+        }
+
+        {
+            let invalid_variant = 42;
+            let encoded_invalid = Message {
+                bytes: vec![invalid_variant, 8, 196, 15],
+                handles: vec![],
+            };
+            assert_matches!(
+                T::decode(&encoded_invalid),
+                Err(OakError::OakStatus(OakStatus::ErrInvalidArgs))
+            );
+        }
+    }
+}
diff --git a/oak_loader/Cargo.lock b/oak_loader/Cargo.lock
index f41c685efe..0c515947e8 100644
--- a/oak_loader/Cargo.lock
+++ b/oak_loader/Cargo.lock
@@ -710,6 +710,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/oak_runtime/Cargo.lock b/oak_runtime/Cargo.lock
index 9c2f9c982e..e2962f3d59 100644
--- a/oak_runtime/Cargo.lock
+++ b/oak_runtime/Cargo.lock
@@ -716,6 +716,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/oak_runtime/introspection_browser_client/components/Event/index.tsx b/oak_runtime/introspection_browser_client/components/Event/index.tsx
index 1cb9cd39e7..dae66c9ae4 100644
--- a/oak_runtime/introspection_browser_client/components/Event/index.tsx
+++ b/oak_runtime/introspection_browser_client/components/Event/index.tsx
@@ -148,6 +148,7 @@ function renderEventDescription(
         <ObjectAsDescriptionList
           object={{
             id: event.getChannelCreated()!.getChannelId(),
+            name: event.getChannelCreated()!.getName(),
             label: event.getChannelCreated()!.getLabel()!.toObject(),
           }}
           dlComponent={InlineDl}
diff --git a/oak_runtime/introspection_browser_client/components/Root/index.tsx b/oak_runtime/introspection_browser_client/components/Root/index.tsx
index c6e83c8916..bf1d03db73 100644
--- a/oak_runtime/introspection_browser_client/components/Root/index.tsx
+++ b/oak_runtime/introspection_browser_client/components/Root/index.tsx
@@ -86,6 +86,7 @@ interface Message {
 interface Channel {
   id: ChannelID;
   messages: Message[];
+  name: string;
   label: Label;
 }
 type Channels = Map<ChannelID, Channel>;
@@ -140,6 +141,7 @@ function eventReducer(
         applicationState.channels.set(channelId, {
           id: channelId,
           messages: [],
+          name: channel.getName(),
           label: channel.getLabel()!.toObject(),
         });
       }
diff --git a/oak_runtime/src/channel.rs b/oak_runtime/src/channel.rs
index 63606295d3..762c753512 100644
--- a/oak_runtime/src/channel.rs
+++ b/oak_runtime/src/channel.rs
@@ -74,6 +74,12 @@ pub struct Channel {
     /// See https://github.com/project-oak/oak/blob/main/docs/concepts.md#labels
     pub label: oak_abi::label::Label,
 
+    /// The name for the channel.
+    ///
+    /// The name does not have to be unique and can be empty. It is used in logs to help with
+    /// identifying channels during debugging.
+    pub name: String,
+
     /// Weak reference to the Runtime used for sending introspection events.
     runtime_weak: Weak<Runtime>,
 }
@@ -82,10 +88,11 @@ impl std::fmt::Debug for Channel {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(
             f,
-            "Channel {{ id={}, #readers={}, #writers={}, label={:?} }}",
+            "Channel {{ id={}, #readers={}, #writers={}, name={:?}, label={:?} }}",
             self.id,
             self.reader_count.load(SeqCst),
             self.writer_count.load(SeqCst),
+            self.name,
             self.label,
         )
     }
@@ -269,6 +276,7 @@ impl Drop for Channel {
 impl Channel {
     pub fn new(
         id: ChannelId,
+        name: &str,
         label: &oak_abi::label::Label,
         runtime_weak: Weak<Runtime>,
     ) -> Arc<Channel> {
@@ -280,6 +288,7 @@ impl Channel {
             reader_count: AtomicU64::new(0),
             waiting_threads: Mutex::new(HashMap::new()),
             label: label.clone(),
+            name: format!("{}({})", name, id),
             runtime_weak,
         })
     }
diff --git a/oak_runtime/src/lib.rs b/oak_runtime/src/lib.rs
index b1a47b2d24..f12665be89 100644
--- a/oak_runtime/src/lib.rs
+++ b/oak_runtime/src/lib.rs
@@ -746,6 +746,7 @@ impl Runtime {
     fn channel_create(
         self: &Arc<Self>,
         node_id: NodeId,
+        name: &str,
         label: &Label,
     ) -> Result<(oak_abi::Handle, oak_abi::Handle), OakStatus> {
         if self.is_terminating() {
@@ -762,7 +763,7 @@ impl Runtime {
 
         // First get a pair of `ChannelHalf` objects.
         let channel_id = self.next_channel_id.fetch_add(1, SeqCst);
-        let channel = Channel::new(channel_id, label, Arc::downgrade(&self));
+        let channel = Channel::new(channel_id, name, label, Arc::downgrade(&self));
         let write_half = ChannelHalf::new(channel.clone(), ChannelHalfDirection::Write);
         let read_half = ChannelHalf::new(channel, ChannelHalfDirection::Read);
         trace!(
@@ -777,6 +778,7 @@ impl Runtime {
         // channel.
         self.introspection_event(EventDetails::ChannelCreated(ChannelCreated {
             channel_id,
+            name: name.to_owned(),
             label: Some(label.clone()),
         }));
 
diff --git a/oak_runtime/src/node/grpc/server/mod.rs b/oak_runtime/src/node/grpc/server/mod.rs
index 4a482e6730..d36ddceaad 100644
--- a/oak_runtime/src/node/grpc/server/mod.rs
+++ b/oak_runtime/src/node/grpc/server/mod.rs
@@ -425,13 +425,15 @@ impl GrpcInvocationHandler {
 
         // The channel containing the request is created with the label specified by the caller.
         // This will fail if the label has a non-empty integrity component.
-        let (request_writer, request_reader) =
-            self.runtime.channel_create(&label).map_err(|err| {
+        let (request_writer, request_reader) = self
+            .runtime
+            .channel_create("gRPC request", &label)
+            .map_err(|err| {
                 warn!("could not create gRPC request channel: {:?}", err);
             })?;
         let (response_writer, response_reader) = self
             .runtime
-            .channel_create(&Label::public_untrusted())
+            .channel_create("gRPC response", &Label::public_untrusted())
             .map_err(|err| {
                 warn!("could not create gRPC response channel: {:?}", err);
             })?;
diff --git a/oak_runtime/src/node/http/mod.rs b/oak_runtime/src/node/http/mod.rs
index e6212fc1aa..aadc52d153 100644
--- a/oak_runtime/src/node/http/mod.rs
+++ b/oak_runtime/src/node/http/mod.rs
@@ -358,11 +358,13 @@ impl Pipe {
         // Create a channel for passing HTTP requests to the Oak node. This channel is created with
         // the label specified by the caller. This will fail if the label has a non-empty
         // integrity component.
-        let (request_writer, request_reader) = runtime.channel_create(&label).map_err(|err| {
-            warn!("could not create HTTP request channel: {:?}", err);
-        })?;
+        let (request_writer, request_reader) = runtime
+            .channel_create("HTTP request", &label)
+            .map_err(|err| {
+                warn!("could not create HTTP request channel: {:?}", err);
+            })?;
         let (response_writer, response_reader) = runtime
-            .channel_create(&Label::public_untrusted())
+            .channel_create("HTTP response", &Label::public_untrusted())
             .map_err(|err| {
                 warn!("could not create HTTP response channel: {:?}", err);
             })?;
diff --git a/oak_runtime/src/node/wasm/mod.rs b/oak_runtime/src/node/wasm/mod.rs
index d6f4bcf657..d38cc1772a 100644
--- a/oak_runtime/src/node/wasm/mod.rs
+++ b/oak_runtime/src/node/wasm/mod.rs
@@ -207,14 +207,18 @@ impl WasmInterface {
         &mut self,
         write_addr: AbiPointer,
         read_addr: AbiPointer,
+        name_ptr: AbiPointer,
+        name_length: AbiPointerOffset,
         label_ptr: AbiPointer,
         label_length: AbiPointerOffset,
     ) -> Result<(), OakStatus> {
         trace!(
-            "{}: channel_create({}, {}, {}, {})",
+            "{}: channel_create({}, {}, {}, {}, {}, {})",
             self.pretty_name,
             write_addr,
             read_addr,
+            name_ptr,
+            name_length,
             label_ptr,
             label_length
         );
@@ -227,6 +231,25 @@ impl WasmInterface {
         self.validate_ptr(write_addr, 8)?;
         self.validate_ptr(read_addr, 8)?;
 
+        let name_bytes = self
+            .get_memory()
+            .get(name_ptr, name_length as usize)
+            .map_err(|err| {
+                error!(
+                    "{}: channel_create(): Unable to read name from guest memory: {:?}",
+                    self.pretty_name, err
+                );
+                OakStatus::ErrInvalidArgs
+            })?;
+
+        let name = String::from_utf8(name_bytes).map_err(|err| {
+            error!(
+                "{}: channel_create(): Unabel to decode name as a string: {:?}",
+                self.pretty_name, err
+            );
+            OakStatus::ErrInvalidArgs
+        })?;
+
         let label_bytes = self
             .get_memory()
             .get(label_ptr, label_length as usize)
@@ -239,13 +262,13 @@ impl WasmInterface {
             })?;
         let label = Label::deserialize(&label_bytes).ok_or_else(|| {
             error!(
-                "{}: channel_create: could not deserialize label",
+                "{}: channel_create(): could not deserialize label",
                 self.pretty_name
             );
             OakStatus::ErrInvalidArgs
         })?;
 
-        let (write_handle, read_handle) = self.runtime.channel_create(&label)?;
+        let (write_handle, read_handle) = self.runtime.channel_create(&name, &label)?;
 
         self.get_memory()
             .set_value(write_addr, write_handle as i64)
@@ -629,6 +652,8 @@ impl wasmi::Externals for WasmInterface {
                 args.nth_checked(1)?,
                 args.nth_checked(2)?,
                 args.nth_checked(3)?,
+                args.nth_checked(4)?,
+                args.nth_checked(5)?,
             )),
             CHANNEL_WRITE => map_host_errors(self.channel_write(
                 args.nth_checked(0)?,
@@ -727,6 +752,8 @@ fn oak_resolve_func(
                 &[
                     ABI_USIZE, // write handle (out)
                     ABI_USIZE, // read handle (out)
+                    ABI_USIZE, // name_buf
+                    ABI_USIZE, // name_len
                     ABI_USIZE, // label_buf
                     ABI_USIZE, // label_len
                 ][..],
diff --git a/oak_runtime/src/proxy.rs b/oak_runtime/src/proxy.rs
index 16051f6e1a..6bcc90912a 100644
--- a/oak_runtime/src/proxy.rs
+++ b/oak_runtime/src/proxy.rs
@@ -141,7 +141,8 @@ impl RuntimeProxy {
 
         // When first starting, we assign the least privileged label to the channel connecting the
         // outside world to the entry point Node.
-        let (write_handle, read_handle) = self.channel_create(&Label::public_untrusted())?;
+        let (write_handle, read_handle) =
+            self.channel_create("Initial", &Label::public_untrusted())?;
         debug!(
             "{:?}: created initial channel ({}, {})",
             self.node_id, write_handle, read_handle,
@@ -215,13 +216,17 @@ impl RuntimeProxy {
     /// See [`Runtime::channel_create`].
     pub fn channel_create(
         &self,
+        name: &str,
         label: &Label,
     ) -> Result<(oak_abi::Handle, oak_abi::Handle), OakStatus> {
-        debug!("{:?}: channel_create({:?})", self.node_id, label);
-        let result = self.runtime.channel_create(self.node_id, label);
         debug!(
-            "{:?}: channel_create({:?}) -> {:?}",
-            self.node_id, label, result
+            "{:?}: channel_create({:?}, {:?})",
+            self.node_id, name, label
+        );
+        let result = self.runtime.channel_create(self.node_id, name, label);
+        debug!(
+            "{:?}: channel_create({:?}, {:?}) -> {:?}",
+            self.node_id, name, label, result
         );
         result
     }
diff --git a/oak_services/Cargo.lock b/oak_services/Cargo.lock
index e1220c7936..1898e23e38 100644
--- a/oak_services/Cargo.lock
+++ b/oak_services/Cargo.lock
@@ -158,6 +158,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/proto/introspection_events.proto b/proto/introspection_events.proto
index 9f03072eb4..bc2a9459c7 100644
--- a/proto/introspection_events.proto
+++ b/proto/introspection_events.proto
@@ -63,7 +63,9 @@ message NodeDestroyed {
 message ChannelCreated {
   uint64 channel_id = 1 [jstype = JS_STRING];
 
-  oak.label.Label label = 2;
+  string name = 2;
+
+  oak.label.Label label = 3;
 }
 
 message ChannelDestroyed {
diff --git a/sdk/Cargo.lock b/sdk/Cargo.lock
index d53646fd70..88bd440440 100644
--- a/sdk/Cargo.lock
+++ b/sdk/Cargo.lock
@@ -814,6 +814,7 @@ dependencies = [
 name = "oak_io"
 version = "0.1.0"
 dependencies = [
+ "either",
  "oak_abi",
  "oak_derive",
  "oak_utils",
diff --git a/sdk/rust/oak/src/grpc/client.rs b/sdk/rust/oak/src/grpc/client.rs
index 461cab06ee..53ebe98386 100644
--- a/sdk/rust/oak/src/grpc/client.rs
+++ b/sdk/rust/oak/src/grpc/client.rs
@@ -40,7 +40,7 @@ impl Client {
     /// The provided [`Label`] specifies the label for the newly created Node and Channel.
     pub fn new_with_label(config: &NodeConfiguration, label: &Label) -> Option<Client> {
         let (invocation_sender, invocation_receiver) =
-            crate::io::channel_create(label).expect("failed to create channel");
+            crate::io::channel_create("gRPC invocation", label).expect("failed to create channel");
         let status = crate::node_create(config, label, invocation_receiver.handle);
         invocation_receiver
             .close()
diff --git a/sdk/rust/oak/src/grpc/mod.rs b/sdk/rust/oak/src/grpc/mod.rs
index 78b8167088..923e29a080 100644
--- a/sdk/rust/oak/src/grpc/mod.rs
+++ b/sdk/rust/oak/src/grpc/mod.rs
@@ -236,7 +236,7 @@ where
 {
     // Create a new channel for request message delivery.
     let (req_sender, req_receiver) =
-        crate::io::channel_create::<GrpcRequest>(&Label::public_untrusted())
+        crate::io::channel_create::<GrpcRequest>("gRPC request", &Label::public_untrusted())
             .expect("failed to create channel");
 
     // Put the request in a GrpcRequest wrapper and send it into the request
@@ -248,7 +248,7 @@ where
 
     // Create a new channel for responses to arrive on.
     let (rsp_sender, rsp_receiver) =
-        crate::io::channel_create::<GrpcResponse>(&Label::public_untrusted())
+        crate::io::channel_create::<GrpcResponse>("gRPC response", &Label::public_untrusted())
             .expect("failed to create channel");
 
     // Build an Invocation holding the two channels and send it down the
diff --git a/sdk/rust/oak/src/grpc/server.rs b/sdk/rust/oak/src/grpc/server.rs
index 920528ae38..f9773ff3b3 100644
--- a/sdk/rust/oak/src/grpc/server.rs
+++ b/sdk/rust/oak/src/grpc/server.rs
@@ -32,7 +32,7 @@ use oak_abi::label::Label;
 pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
     // Create a channel and pass the read half to a new gRPC server pseudo-Node.
     let (init_sender, init_receiver) =
-        crate::io::channel_create::<GrpcInvocationSender>(&Label::public_untrusted())
+        crate::io::channel_create::<GrpcInvocationSender>("gRPC init", &Label::public_untrusted())
             .expect("Couldn't create init channel to gRPC server pseudo-node");
     let config = crate::node_config::grpc_server(address);
     crate::node_create(&config, &Label::public_untrusted(), init_receiver.handle)?;
@@ -42,7 +42,7 @@ pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
 
     // Create a separate channel for receiving invocations and pass it to a gRPC pseudo-Node.
     let (invocation_sender, invocation_receiver) =
-        crate::io::channel_create::<Invocation>(&Label::public_untrusted())
+        crate::io::channel_create::<Invocation>("gRPC invocation", &Label::public_untrusted())
             .expect("Couldn't create gRPC invocation channel");
     let local_invocation_sender = invocation_sender.handle;
 
diff --git a/sdk/rust/oak/src/http/mod.rs b/sdk/rust/oak/src/http/mod.rs
index b52c7c0283..f82685da1e 100644
--- a/sdk/rust/oak/src/http/mod.rs
+++ b/sdk/rust/oak/src/http/mod.rs
@@ -81,7 +81,7 @@ pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
 
     // Create a channel and pass the read half to a new http server pseudo-Node.
     let (init_sender, init_receiver) =
-        crate::io::channel_create::<HttpInvocationSender>(&Label::public_untrusted())
+        crate::io::channel_create::<HttpInvocationSender>("HTTP init", &Label::public_untrusted())
             .expect("Couldn't create init channel to HTTP server pseudo-node");
     crate::node_create(&config, &Label::public_untrusted(), init_receiver.handle)?;
     init_receiver
@@ -90,7 +90,7 @@ pub fn init(address: &str) -> Result<Receiver<Invocation>, OakStatus> {
 
     // Create a separate channel for receiving invocations and pass it to an HTTP pseudo-Node.
     let (invocation_sender, invocation_receiver) =
-        crate::io::channel_create::<Invocation>(&Label::public_untrusted())
+        crate::io::channel_create::<Invocation>("HTTP invocation", &Label::public_untrusted())
             .expect("Couldn't create HTTP invocation channel");
 
     let http_server_init = HttpInvocationSender {
diff --git a/sdk/rust/oak/src/io/mod.rs b/sdk/rust/oak/src/io/mod.rs
index 6c6de3fb94..bd445a9262 100644
--- a/sdk/rust/oak/src/io/mod.rs
+++ b/sdk/rust/oak/src/io/mod.rs
@@ -29,9 +29,10 @@ pub use sender::SenderExt;
 
 /// Create a new channel for transmission of `Encodable` and `Decodable` types.
 pub fn channel_create<T: Encodable + Decodable>(
+    name: &str,
     label: &Label,
 ) -> Result<(Sender<T>, Receiver<T>), OakStatus> {
-    let (wh, rh) = crate::channel_create(label)?;
+    let (wh, rh) = crate::channel_create(name, label)?;
     Ok((Sender::<T>::new(wh), Receiver::<T>::new(rh)))
 }
 
diff --git a/sdk/rust/oak/src/io/receiver.rs b/sdk/rust/oak/src/io/receiver.rs
index a40c0c3573..d22bec0a35 100644
--- a/sdk/rust/oak/src/io/receiver.rs
+++ b/sdk/rust/oak/src/io/receiver.rs
@@ -18,10 +18,10 @@ use crate::{
     io::{Decodable, Receiver},
     ChannelReadStatus, Label, OakError, OakStatus,
 };
-use log::error;
+use log::{debug, error, info, trace, warn};
 
 /// SDK-specific functionality provided by a receiver.
-pub trait ReceiverExt<T> {
+pub trait ReceiverExt<T: Decodable> {
     /// Closes the underlying channel used by the receiver.
     fn close(&self) -> Result<(), OakStatus>;
 
@@ -39,6 +39,81 @@ pub trait ReceiverExt<T> {
     /// Returns [`ChannelReadStatus`] of the wrapped handle, or `Err(OakStatus::ErrTerminated)` if
     /// the Oak Runtime is terminating.
     fn wait(&self) -> Result<ChannelReadStatus, OakStatus>;
+
+    /// Consumes the receiver and returns an iterator that will block waiting for messages. It will
+    /// return None when the node terminates, or a non-transient error occurs when reading from the
+    /// channel. Transient errors will be silently ignored by this iterator, including cases in
+    /// which the incoming message cannot be decoded correctly.
+    ///
+    /// Also see [`std::sync::mpsc::Receiver::iter`].
+    fn iter(self) -> ReceiverIterator<T>;
+}
+
+/// Blocking iterator over incoming messages, created via [`ReceiverExt::iter`].
+pub struct ReceiverIterator<T: Decodable> {
+    inner: Receiver<T>,
+}
+
+impl<T: Decodable> Iterator for ReceiverIterator<T> {
+    type Item = T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let receiver = &self.inner;
+        loop {
+            match receiver.wait() {
+                Err(status) => {
+                    use crate::OakStatus::*;
+                    match status {
+                        ErrTerminated => {
+                            info!(
+                            "{:?}: received node termination status: {:?}; terminating iterator",
+                            receiver, status
+                        );
+                            return None;
+                        }
+                        _ => {
+                            error!(
+                            "{:?}: received status: {:?}; but `Receiver::wait` should never return an error other than `ErrTerminated`",
+                            receiver, status
+                        );
+                            return None;
+                        }
+                    }
+                }
+                Ok(status) => match status {
+                    ChannelReadStatus::ReadReady => trace!("{:?}: wait over", receiver),
+                    ChannelReadStatus::Orphaned
+                    | ChannelReadStatus::PermissionDenied
+                    | ChannelReadStatus::InvalidChannel => {
+                        warn!(
+                        "{:?}: received invalid channel read status: {:?}; terminating iterator",
+                        receiver, status
+                    );
+                        return None;
+                    }
+                    ChannelReadStatus::NotReady => {
+                        error!(
+                        "{:?}: received `ChannelReadStatus::NotReady`, which should never be returned from `Receiver::wait`.",
+                        receiver);
+                        return None;
+                    }
+                },
+            }
+            match receiver.try_receive() {
+                Ok(m) => {
+                    debug!("{:?}: received message", receiver);
+                    return Some(m);
+                }
+                Err(err) => {
+                    // Log error and continue with the next iteration.
+                    // We do this because the error may have been caused by an invalid or malformed
+                    // message, and we don't want to terminate the iterator early if that's the
+                    // case.
+                    error!("{:?}: error receiving message: {}", receiver, err);
+                }
+            }
+        }
+    }
 }
 
 impl<T: Decodable> ReceiverExt<T> for Receiver<T> {
@@ -96,4 +171,8 @@ impl<T: Decodable> ReceiverExt<T> for Receiver<T> {
             }
         }
     }
+
+    fn iter(self) -> ReceiverIterator<T> {
+        ReceiverIterator { inner: self }
+    }
 }
diff --git a/sdk/rust/oak/src/lib.rs b/sdk/rust/oak/src/lib.rs
index 915b305f5f..c5a5667e9e 100644
--- a/sdk/rust/oak/src/lib.rs
+++ b/sdk/rust/oak/src/lib.rs
@@ -18,8 +18,7 @@
 //! underlying Oak platform functionality.
 
 use byteorder::WriteBytesExt;
-use io::ReceiverExt;
-use log::{debug, error, info, trace, warn};
+use log::{debug, error, warn};
 use oak_abi::proto::oak::application::NodeConfiguration;
 use prost::Message;
 
@@ -231,7 +230,7 @@ pub fn channel_write(half: WriteHandle, buf: &[u8], handles: &[Handle]) -> Resul
 ///
 /// On success, returns [`WriteHandle`] and a [`ReadHandle`] values for the
 /// write and read halves (respectively).
-pub fn channel_create(label: &Label) -> Result<(WriteHandle, ReadHandle), OakStatus> {
+pub fn channel_create(name: &str, label: &Label) -> Result<(WriteHandle, ReadHandle), OakStatus> {
     let mut write = WriteHandle {
         handle: crate::handle::invalid(),
     };
@@ -239,10 +238,13 @@ pub fn channel_create(label: &Label) -> Result<(WriteHandle, ReadHandle), OakSta
         handle: crate::handle::invalid(),
     };
     let label_bytes = label.serialize();
+    let name_bytes = name.as_bytes();
     let status = unsafe {
         oak_abi::channel_create(
             &mut write.handle as *mut u64,
             &mut read.handle as *mut u64,
+            name_bytes.as_ptr(),
+            name_bytes.len(),
             label_bytes.as_ptr(),
             label_bytes.len(),
         )
@@ -411,92 +413,31 @@ pub fn set_panic_hook() {
 
 /// Trait implemented by Oak Nodes (or parts thereof) that operate on commands.
 ///
-/// It has a single method for handling commands, which are [`Decodable`](crate::io::Decodable)
-/// objects that are received via the single incoming channel handle which is passed in at Node
-/// creation time. The return value is only used for logging in case of error.
-pub trait CommandHandler<T: crate::io::Decodable> {
+/// It has a single method for handling commands, which are usually received via the single incoming
+/// channel handle which is passed in at Node creation time, or derived from such stream.
+pub trait CommandHandler<T> {
+    /// Handles a single command instance.
+    ///
+    /// The return value is only used for logging in case of error.
     fn handle_command(&mut self, command: T) -> anyhow::Result<()>;
 }
 
-/// Run a command loop on the provided [`CommandHandler`]:
+/// Runs a command loop on the provided [`CommandHandler`]:
 ///
-/// - wait for new messages on the provided channel `in_channel`
-/// - if the runtime signals that the Node was terminated while waiting, then exit the event loop
-/// - otherwise, read the available message via the provided channel handle
-/// - decode the message from (bytes + handles) to the specified command type `T`
-/// - pass the typed command object to the `Node::handle_command` method of the `node`, which
+/// - waits for new messages from the provided iterator
+/// - passes the typed command object to the `Node::handle_command` method of the `node`, which
 ///   executes a single iteration of the loop
 ///
 /// Note the loop is only interrupted if the Node is terminated while waiting. Other errors are just
 /// logged, and the loop continues with the next iteration.
-pub fn run_command_loop<
-    T: crate::io::Decodable,
-    N: CommandHandler<T>,
-    R: Into<crate::io::Receiver<T>>,
->(
+pub fn run_command_loop<T, N: CommandHandler<T>, R: Iterator<Item = T>>(
     mut command_handler: N,
-    receiver: R,
+    command_iterator: R,
 ) {
-    let receiver = receiver.into();
-    if !crate::handle::is_valid(receiver.handle.handle) {
-        error!("{:?}: invalid input handle", receiver);
-        return;
-    }
-    info!("{:?}: starting command loop", receiver);
-    loop {
-        // First wait until a message is available. If the Node was terminated while waiting, this
-        // will return `ErrTerminated`, which indicates that the event loop should be terminated.
-        // For any other error raised while waiting is logged, we try and determine whether it is
-        // transient or not, and then continue or terminate the event loop, respectively.
-        match receiver.wait() {
-            Err(status) => {
-                use crate::OakStatus::*;
-                match status {
-                    ErrTerminated => {
-                        info!(
-                            "{:?}: received termination status: {:?}; terminating event loop",
-                            receiver, status
-                        );
-                        return;
-                    }
-                    _ => {
-                        error!(
-                            "{:?}: received status: {:?}; but `Receiver::wait` never returns an error other than `ErrTerminated`",
-                            receiver, status
-                        );
-                        return;
-                    }
-                }
-            }
-            Ok(status) => match status {
-                ChannelReadStatus::ReadReady => trace!("{:?}: wait over", receiver),
-                ChannelReadStatus::Orphaned
-                | ChannelReadStatus::PermissionDenied
-                | ChannelReadStatus::InvalidChannel => {
-                    warn!(
-                        "{:?}: received invalid channel read status: {:?}; terminating event loop",
-                        receiver, status
-                    );
-                    return;
-                }
-                ChannelReadStatus::NotReady => {
-                    error!(
-                        "{:?}: received `ChannelReadStatus::NotReady`, which should never be returned from `Receiver::wait`.",
-                        receiver);
-                    return;
-                }
-            },
-        }
-        match receiver.try_receive() {
-            Ok(command) => {
-                info!("{:?}: received command", receiver);
-                if let Err(err) = command_handler.handle_command(command) {
-                    error!("{:?}: error handling command: {}", receiver, err);
-                }
-            }
-            Err(err) => {
-                error!("{:?}: error receiving command: {}", receiver, err);
-            }
+    for command in command_iterator {
+        debug!("received command");
+        if let Err(err) = command_handler.handle_command(command) {
+            error!("error handling command: {}", err);
         }
     }
 }
@@ -536,7 +477,7 @@ pub fn run_command_loop<
 ///     let grpc_channel = oak::grpc::server::init(&grpc_server_listen_address)
 ///         .expect("could not create gRPC server pseudo-node");
 ///     let dispatcher = DummyNode::default();
-///     oak::run_command_loop(dispatcher, grpc_channel);
+///     oak::run_command_loop(dispatcher, grpc_channel.iter());
 /// });
 ///
 /// # fn main() {}
@@ -550,6 +491,7 @@ pub fn run_command_loop<
 /// expression too:
 ///
 /// ```
+/// use oak::io::ReceiverExt;
 /// use oak_abi::proto::oak::application::ConfigMap;
 ///
 /// # fn init_all_the_things() {}
@@ -568,7 +510,7 @@ pub fn run_command_loop<
 ///     let dispatcher = DummyNode::default();
 ///     let grpc_channel = oak::grpc::server::init("[::]:8080")
 ///         .expect("could not create gRPC server pseudo-node");
-///     oak::run_command_loop(dispatcher, grpc_channel);
+///     oak::run_command_loop(dispatcher, grpc_channel.iter());
 /// });
 /// #
 /// # fn main() {}
diff --git a/sdk/rust/oak/src/logger/mod.rs b/sdk/rust/oak/src/logger/mod.rs
index 77e4bfb8ff..073c262af1 100644
--- a/sdk/rust/oak/src/logger/mod.rs
+++ b/sdk/rust/oak/src/logger/mod.rs
@@ -84,8 +84,8 @@ pub fn init_default() {
 /// An error is returned if a logger has already been set.
 pub fn init(level: Level) -> Result<(), SetLoggerError> {
     // Create a channel and pass the read half to a fresh logging Node.
-    let (write_handle, read_handle) =
-        crate::channel_create(&Label::public_untrusted()).expect("could not create channel");
+    let (write_handle, read_handle) = crate::channel_create("Logger", &Label::public_untrusted())
+        .expect("could not create channel");
     crate::node_create(
         &crate::node_config::log(),
         &Label::public_untrusted(),
diff --git a/sdk/rust/oak/src/stubs.rs b/sdk/rust/oak/src/stubs.rs
index 686429dd9f..e84ae81d17 100644
--- a/sdk/rust/oak/src/stubs.rs
+++ b/sdk/rust/oak/src/stubs.rs
@@ -64,7 +64,14 @@ pub extern "C" fn channel_write(
     panic!("stub function invoked!");
 }
 #[no_mangle]
-pub extern "C" fn channel_create(_write: *mut u64, _read: *mut u64) -> u32 {
+pub extern "C" fn channel_create(
+    _write: *mut u64,
+    _read: *mut u64,
+    _name_buf: *const u8,
+    _name_size: usize,
+    _label_buf: *const u8,
+    _label_size: usize,
+) -> u32 {
     panic!("stub function invoked!");
 }
 #[no_mangle]


Test patch:
diff --git a/examples/abitest/module_0/rust/src/lib.rs b/examples/abitest/module_0/rust/src/lib.rs
index 69b62a8533..f220a2b9a1 100644
--- a/examples/abitest/module_0/rust/src/lib.rs
+++ b/examples/abitest/module_0/rust/src/lib.rs
@@ -25,7 +25,7 @@ use log::{debug, error, info, trace, warn};
 use oak::{
     grpc,
     handle::is_valid,
-    io::{ReceiverExt, SenderExt},
+    io::{Receiver, ReceiverExt, SenderExt},
     ChannelReadStatus, OakError, OakStatus,
 };
 use oak_abi::{
@@ -74,7 +74,7 @@ impl FrontendNode {
             lose_channels();
 
             // Second, start an ephemeral Node which also loses channels.
-            let (wh, rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+            let (wh, rh) = oak::channel_create("Initial", &Label::public_untrusted()).unwrap();
             oak::node_create(
                 &oak::node_config::wasm(FRONTEND_MODULE_NAME, "channel_loser"),
                 &Label::public_untrusted(),
@@ -105,7 +105,8 @@ impl FrontendNode {
         let mut backend_in = Vec::with_capacity(BACKEND_COUNT);
         for i in 0..BACKEND_COUNT {
             let (write_handle, read_handle) =
-                oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+                oak::channel_create("Initial", &Label::public_untrusted())
+                    .expect("could not create channel");
             oak::node_create(
                 &oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME),
                 &Label::public_untrusted(),
@@ -118,7 +119,8 @@ impl FrontendNode {
             // Create a back channel, and pass the write half to the backend
             // as the first message on the outbound channel.
             let (write_handle, read_handle) =
-                oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+                oak::channel_create("Back", &Label::public_untrusted())
+                    .expect("could not create channel");
             oak::channel_write(backend_out[i], &[], &[write_handle.handle])
                 .expect("could not write to channel");
             oak::channel_close(write_handle.handle).expect("could not close channel");
@@ -162,7 +164,7 @@ pub extern "C" fn frontend_oak_main(_in_handle: u64) {
         let dispatcher = OakAbiTestServiceDispatcher::new(node);
         let grpc_channel =
             oak::grpc::server::init("[::]:8080").expect("could not create gRPC server pseudo-Node");
-        oak::run_command_loop(dispatcher, grpc_channel);
+        oak::run_command_loop(dispatcher, grpc_channel.iter());
     });
 }
 
@@ -574,11 +576,14 @@ unsafe fn invalid_raw_offset() -> *mut u64 {
 fn channel_create_raw() -> (u64, u64, u32) {
     let mut w = 0u64;
     let mut r = 0u64;
+    let name_bytes = "Raw channel".as_bytes();
     let label_bytes = Label::public_untrusted().serialize();
     let result = unsafe {
         oak_abi::channel_create(
             &mut w as *mut u64,
             &mut r as *mut u64,
+            name_bytes.as_ptr(),
+            name_bytes.len(),
             label_bytes.as_ptr(),
             label_bytes.len(),
         )
@@ -590,11 +595,17 @@ impl FrontendNode {
     fn test_channel_create_raw(&mut self) -> TestResult {
         let mut write = 0u64;
         let mut read = 0u64;
+        let name_bytes = "Raw channel".as_bytes();
         let label_bytes = Label::public_untrusted().serialize();
+        let invalid_string_bytes = vec![240];
+        expect_eq!(false, std::str::from_utf8(&invalid_string_bytes).is_ok());
+
         expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe {
             oak_abi::channel_create(
                 invalid_raw_offset(),
                 &mut read as *mut u64,
+                name_bytes.as_ptr(),
+                name_bytes.len(),
                 label_bytes.as_ptr(),
                 label_bytes.len(),
             )
@@ -603,6 +614,28 @@ impl FrontendNode {
             oak_abi::channel_create(
                 &mut write as *mut u64,
                 invalid_raw_offset(),
+                name_bytes.as_ptr(),
+                name_bytes.len(),
+                label_bytes.as_ptr(),
+                label_bytes.len(),
+            )
+        });
+        expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe {
+            oak_abi::channel_create(
+                &mut write as *mut u64,
+                &mut read as *mut u64,
+                invalid_raw_offset() as *const u8,
+                1,
+                label_bytes.as_ptr(),
+                label_bytes.len(),
+            )
+        });
+        expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe {
+            oak_abi::channel_create(
+                &mut write as *mut u64,
+                &mut read as *mut u64,
+                invalid_string_bytes.as_ptr(),
+                invalid_string_bytes.len(),
                 label_bytes.as_ptr(),
                 label_bytes.len(),
             )
@@ -614,7 +647,7 @@ impl FrontendNode {
         let mut handles = Vec::<(oak::WriteHandle, oak::ReadHandle)>::new();
         const CHANNEL_COUNT: usize = 50;
         for _ in 0..CHANNEL_COUNT {
-            match oak::channel_create(&Label::public_untrusted()) {
+            match oak::channel_create("Test", &Label::public_untrusted()) {
                 Ok(pair) => handles.push(pair),
                 Err(status) => {
                     return Err(Box::new(OakError::OakStatus(status)));
@@ -644,14 +677,14 @@ impl FrontendNode {
     }
 
     fn test_channel_close(&mut self) -> TestResult {
-        let (w, r) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (w, r) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(Ok(()), oak::channel_close(w.handle));
         expect_eq!(Ok(()), oak::channel_close(r.handle));
         expect_eq!(Err(OakStatus::ErrBadHandle), oak::channel_close(w.handle));
         expect_eq!(Err(OakStatus::ErrBadHandle), oak::channel_close(9_999_999));
 
         // Can close ends in either order.
-        let (w, r) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (w, r) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(Ok(()), oak::channel_close(r.handle));
         expect_eq!(Ok(()), oak::channel_close(w.handle));
         Ok(())
@@ -741,7 +774,8 @@ impl FrontendNode {
     }
 
     fn test_channel_read(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // No message pending.
         let mut buffer = Vec::<u8>::with_capacity(5);
@@ -839,7 +873,8 @@ impl FrontendNode {
     }
 
     fn test_channel_read_orphan(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Write a message to the channel.
         let buf = vec![0x01];
@@ -909,7 +944,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Empty message.
         let empty = vec![];
@@ -936,7 +972,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write_handle(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Send a single handle.
         let empty = vec![];
@@ -963,7 +1000,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write_orphan_empty(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Close the only read handle for the channel.
         expect_eq!(Ok(()), oak::channel_close(in_channel.handle));
@@ -980,7 +1018,8 @@ impl FrontendNode {
     }
 
     fn test_channel_write_orphan_full(&mut self) -> TestResult {
-        let (out_channel, in_channel) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_channel, in_channel) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Ensure that there are messages pending on the channel.
         let data = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
@@ -1000,12 +1039,13 @@ impl FrontendNode {
 
     fn test_channel_handle_recovered(&mut self) -> TestResult {
         // Set up a channel with a message in it.
-        let (lost_wh, lost_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (lost_wh, lost_rh) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         let data = vec![0x08, 0x0c];
         expect_eq!(Ok(()), oak::channel_write(lost_wh, &data, &[]));
 
         // Put a message with handle to the first channel into a second channel.
-        let (holder_wh, holder_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (holder_wh, holder_rh) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(
             Ok(()),
             oak::channel_write(holder_wh, &[], &[lost_rh.handle])
@@ -1243,8 +1283,8 @@ impl FrontendNode {
     }
 
     fn test_channel_wait(&mut self) -> TestResult {
-        let (out1, in1) = oak::channel_create(&Label::public_untrusted()).unwrap();
-        let (out2, in2) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out1, in1) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
+        let (out2, in2) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Waiting on (just) non-read channel handles should fail immediately.
         expect_eq!(
@@ -1337,8 +1377,8 @@ impl FrontendNode {
     fn test_channel_wait_orphan(&mut self) -> TestResult {
         // Use 2 channels so there's always a ready channel to prevent
         // wait_on_channels blocking.
-        let (out1, in1) = oak::channel_create(&Label::public_untrusted()).unwrap();
-        let (out2, in2) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out1, in1) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
+        let (out2, in2) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
 
         // Set up pending messages so each channel is read-ready.
         let data = vec![0x01, 0x02, 0x03];
@@ -1489,7 +1529,8 @@ impl FrontendNode {
                 }
             )
         );
-        let (out_handle, in_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_handle, in_handle) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         expect_eq!(
             Ok(()),
             oak::node_create(
@@ -1520,7 +1561,7 @@ impl FrontendNode {
         };
 
         // Expect valid handles to return correct label.
-        let (out_handle, in_handle) = oak::channel_create(&label).unwrap();
+        let (out_handle, in_handle) = oak::channel_create("Test", &label).unwrap();
         let expected = Ok(label);
         expect_eq!(expected, oak::channel_label_read(in_handle.handle));
         expect_eq!(expected, oak::channel_label_read(out_handle.handle));
@@ -1682,7 +1723,8 @@ impl FrontendNode {
     }
 
     fn test_node_panic(&mut self) -> TestResult {
-        let (out_handle, in_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_handle, in_handle) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         // Create a Node that panics. We can't see the panic, but the rest
         // of the Application should continue.
         expect_eq!(
@@ -1729,7 +1771,8 @@ impl FrontendNode {
     fn test_channel_handle_reuse(&mut self) -> TestResult {
         // Set up a fresh channel with a pending message so wait_on_channels
         // doesn't block.
-        let (out_handle, in_handle) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (out_handle, in_handle) =
+            oak::channel_create("Test", &Label::public_untrusted()).unwrap();
         let data = vec![0x01, 0x02, 0x03];
         expect_eq!(Ok(()), oak::channel_write(out_handle, &data, &[]));
 
@@ -1775,8 +1818,8 @@ impl FrontendNode {
     fn test_direct_log(&mut self) -> TestResult {
         // Send a message directly to a fresh logging Node (not via the log facade).
         // Include some handles which will be ignored.
-        let (logging_handle, read_handle) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (logging_handle, read_handle) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         oak::node_create(
             &oak::node_config::log(),
             &Label::public_untrusted(),
@@ -1786,8 +1829,8 @@ impl FrontendNode {
         oak::channel_close(read_handle.handle).expect("could not close channel");
 
         expect!(is_valid(logging_handle.handle));
-        let (out_handle, in_handle) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (out_handle, in_handle) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
 
         oak::channel_write(
             logging_handle,
@@ -1826,7 +1869,8 @@ impl FrontendNode {
         let mut read_handles = Vec::with_capacity(CHANNEL_COUNT);
         let mut write_handles = Vec::with_capacity(CHANNEL_COUNT);
         for _ in 0..CHANNEL_COUNT {
-            let (new_write, new_read) = oak::channel_create(&Label::public_untrusted()).unwrap();
+            let (new_write, new_read) =
+                oak::channel_create("Test", &Label::public_untrusted()).unwrap();
             write_handles.push(new_write);
             read_handles.push(new_read.handle);
         }
@@ -1996,8 +2040,8 @@ impl FrontendNode {
         let config = oak::node_config::grpc_server(ADDITIONAL_TEST_SERVER_ADDR);
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with data.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2013,8 +2057,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a single
         // read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2030,8 +2074,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a write
         // handle and a read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2258,8 +2302,8 @@ impl FrontendNode {
 
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with data.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2275,8 +2319,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a single
         // read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2292,8 +2336,8 @@ impl FrontendNode {
         // Rather than passing the newly-created Node a message with a write handle
         // for an invocation channel in it, instead pass it a message with a write
         // handle and a read handle.
-        let (wh, rh) =
-            oak::channel_create(&Label::public_untrusted()).expect("could not create channel");
+        let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted())
+            .expect("could not create channel");
         expect_eq!(
             Ok(()),
             oak::node_create(&config, &Label::public_untrusted(), rh)
@@ -2359,7 +2403,7 @@ fn from_proto(status: oak::grpc::Status) -> Box<dyn std::error::Error> {
 
 fn lose_channels() {
     // Create a channel holding a message that holds references to itself.
-    let (wh, rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
     let data = vec![0x01, 0x02, 0x03];
     oak::channel_write(wh, &data, &[wh.handle, rh.handle]).unwrap();
     // Close both handles so this channel is immediately lost.
@@ -2367,7 +2411,7 @@ fn lose_channels() {
     oak::channel_close(rh.handle).unwrap();
 
     // Create a channel holding a message that holds references to itself.
-    let (wh, rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh, rh) = oak::channel_create("Test", &Label::public_untrusted()).unwrap();
     let data = vec![0x01, 0x02, 0x03];
     oak::channel_write(wh, &data, &[wh.handle, rh.handle]).unwrap();
     // Keep the write handle open, so this channel will be lost when
@@ -2375,8 +2419,8 @@ fn lose_channels() {
     oak::channel_close(rh.handle).unwrap();
 
     // Create a pair of channels, each holding a message that holds references to the other
-    let (wh_a, rh_a) = oak::channel_create(&Label::public_untrusted()).unwrap();
-    let (wh_b, rh_b) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh_a, rh_a) = oak::channel_create("Test channel a", &Label::public_untrusted()).unwrap();
+    let (wh_b, rh_b) = oak::channel_create("Test channel b", &Label::public_untrusted()).unwrap();
     oak::channel_write(wh_a, &data, &[wh_b.handle, rh_b.handle]).unwrap();
     oak::channel_write(wh_b, &data, &[wh_a.handle, rh_a.handle]).unwrap();
     // Close all handles so these channels are immediately lost.
@@ -2386,8 +2430,8 @@ fn lose_channels() {
     oak::channel_close(rh_b.handle).unwrap();
 
     // Create a pair of channels, each holding a message that holds references to the other
-    let (wh_a, rh_a) = oak::channel_create(&Label::public_untrusted()).unwrap();
-    let (wh_b, rh_b) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (wh_a, rh_a) = oak::channel_create("Test channel a", &Label::public_untrusted()).unwrap();
+    let (wh_b, rh_b) = oak::channel_create("Test channel b", &Label::public_untrusted()).unwrap();
     oak::channel_write(wh_a, &data, &[wh_b.handle, rh_b.handle]).unwrap();
     oak::channel_write(wh_b, &data, &[wh_a.handle, rh_a.handle]).unwrap();
     // Keep the write handles open.
@@ -2411,12 +2455,13 @@ pub extern "C" fn channel_loser(_handle: u64) {
 #[no_mangle]
 pub extern "C" fn http_oak_main(http_channel: u64) {
     oak::logger::init_default();
-    let node = http_server::StaticHttpServer {};
+    let node = http_server::StaticHttpServer;
     oak::run_command_loop(
         node,
-        oak_io::handle::ReadHandle {
+        Receiver::new(oak_io::handle::ReadHandle {
             handle: http_channel,
-        },
+        })
+        .iter(),
     );
 }
 
@@ -2434,7 +2479,8 @@ pub extern "C" fn http_oak_main(http_channel: u64) {
 // the runtime when the final handle is closed.
 fn new_channel_chain(nest_count: u32) -> Result<oak::ReadHandle, Box<dyn std::error::Error>> {
     // Set up a channel with a message in it.
-    let (innermost_wh, innermost_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+    let (innermost_wh, innermost_rh) =
+        oak::channel_create("Innermost", &Label::public_untrusted()).unwrap();
     let data = vec![0x08, 0x0c];
     expect_eq!(Ok(()), oak::channel_write(innermost_wh, &data, &[]));
     expect_eq!(Ok(()), oak::channel_close(innermost_wh.handle));
@@ -2442,7 +2488,8 @@ fn new_channel_chain(nest_count: u32) -> Result<oak::ReadHandle, Box<dyn std::er
     let mut inner_rh = innermost_rh;
     for _ in 0..nest_count {
         // Put a message with a handle to the previous channel into a new outer channel.
-        let (outer_wh, outer_rh) = oak::channel_create(&Label::public_untrusted()).unwrap();
+        let (outer_wh, outer_rh) =
+            oak::channel_create("Outer", &Label::public_untrusted()).unwrap();
         expect_eq!(
             Ok(()),
             oak::channel_write(outer_wh, &[], &[inner_rh.handle])
diff --git a/examples/abitest/module_1/rust/src/lib.rs b/examples/abitest/module_1/rust/src/lib.rs
index e3b86c4c27..3c9144cbb4 100644
--- a/examples/abitest/module_1/rust/src/lib.rs
+++ b/examples/abitest/module_1/rust/src/lib.rs
@@ -117,7 +117,7 @@ fn inner_main(in_handle: u64) -> Result<(), oak::OakStatus> {
             info!("received frontend request: {:?}", internal_req);
 
             // Create a new channel and write the response into it.
-            let (new_write, new_read) = oak::channel_create(&Label::public_untrusted())?;
+            let (new_write, new_read) = oak::channel_create("Reply", &Label::public_untrusted())?;
             let internal_rsp = InternalMessage {
                 msg: internal_req.msg + "xxx",
             };
diff --git a/oak_runtime/src/node/http/tests.rs b/oak_runtime/src/node/http/tests.rs
index 6fc0628ab9..3f5798e665 100644
--- a/oak_runtime/src/node/http/tests.rs
+++ b/oak_runtime/src/node/http/tests.rs
@@ -206,7 +206,7 @@ fn create_server_node(runtime: &RuntimeProxy, port: u32) -> oak_abi::Handle {
 fn create_communication_channel(runtime: &RuntimeProxy) -> (oak_abi::Handle, oak_abi::Handle) {
     // create channel: one end to server_node::run; the other to the Oak node.
     let (init_sender, init_receiver) = runtime
-        .channel_create(&Label::public_untrusted())
+        .channel_create("HTTP init", &Label::public_untrusted())
         .expect("Could not create channel");
 
     // At the start the HTTP server pseudo-Node expects to receive an invocation channel, with
@@ -214,7 +214,7 @@ fn create_communication_channel(runtime: &RuntimeProxy) -> (oak_abi::Handle, oak
     //
     // Create a channel for receiving invocations to pass to the HTTP server pseudo-Node.
     let (invocation_sender, invocation_receiver) = runtime
-        .channel_create(&Label::public_untrusted())
+        .channel_create("HTTP invocation", &Label::public_untrusted())
         .expect("Could not create channel");
     let invocation_sender = HttpInvocationSender {
         sender: Some(Sender::<HttpInvocation>::new(WriteHandle {
diff --git a/oak_runtime/src/node/wasm/tests.rs b/oak_runtime/src/node/wasm/tests.rs
index 519e475e2e..b775c4b2ed 100644
--- a/oak_runtime/src/node/wasm/tests.rs
+++ b/oak_runtime/src/node/wasm/tests.rs
@@ -57,7 +57,7 @@ fn start_node(
         &SecureServerConfiguration::default(),
         &signature_table,
     );
-    let (_write_handle, read_handle) = proxy.channel_create(&Label::public_untrusted())?;
+    let (_write_handle, read_handle) = proxy.channel_create("", &Label::public_untrusted())?;
 
     let result = proxy.node_create(
         &NodeConfiguration {
diff --git a/oak_runtime/src/tests.rs b/oak_runtime/src/tests.rs
index d5f9052964..a36d7fe6ab 100644
--- a/oak_runtime/src/tests.rs
+++ b/oak_runtime/src/tests.rs
@@ -71,7 +71,7 @@ fn run_node_body(node_label: &Label, node_privilege: &NodePrivilege, node_body:
         node_privilege: node_privilege.clone(),
     };
     let (_write_handle, read_handle) = proxy
-        .channel_create(&Label::public_untrusted())
+        .channel_create("Initial", &Label::public_untrusted())
         .expect("Could not create init channel");
 
     proxy
@@ -123,7 +123,7 @@ fn create_channel_same_label_err() {
         Box::new(move |runtime| {
             // Attempt to perform an operation that requires the [`Runtime`] to have created an
             // appropriate [`NodeInfo`] instance.
-            let result = runtime.channel_create(&label_clone);
+            let result = runtime.channel_create("", &label_clone);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -150,7 +150,7 @@ fn create_channel_less_confidential_label_err() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&less_confidential_label);
+            let result = runtime.channel_create("", &less_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -183,7 +183,7 @@ fn create_channel_less_confidential_label_declassification_err() {
             can_endorse_integrity_tags: hashset! { other_tag },
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&less_confidential_label);
+            let result = runtime.channel_create("", &less_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -215,7 +215,7 @@ fn create_channel_less_confidential_label_no_privilege_err() {
             can_endorse_integrity_tags: hashset! { tag_0 },
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&less_confidential_label);
+            let result = runtime.channel_create("", &less_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -241,7 +241,7 @@ fn create_channel_with_more_confidential_label_from_public_untrusted_node_ok() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&more_confidential_label);
+            let result = runtime.channel_create("", &more_confidential_label);
             assert_eq!(true, result.is_ok());
 
             let (write_handle, read_handle) = result.unwrap();
@@ -289,7 +289,7 @@ fn create_channel_with_more_confidential_label_from_public_node_with_privilege_o
             can_endorse_integrity_tags: hashset! {},
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&more_confidential_label);
+            let result = runtime.channel_create("", &more_confidential_label);
             assert_eq!(true, result.is_ok());
 
             let (write_handle, read_handle) = result.unwrap();
@@ -335,7 +335,7 @@ fn create_channel_with_more_confidential_label_from_non_public_node_with_privile
             can_endorse_integrity_tags: hashset! {},
         },
         Box::new(move |runtime| {
-            let result = runtime.channel_create(&more_confidential_label);
+            let result = runtime.channel_create("", &more_confidential_label);
             assert_eq!(Err(OakStatus::ErrPermissionDenied), result);
             Ok(())
         }),
@@ -351,7 +351,7 @@ fn create_node_same_label_ok() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
                 config_type: Some(ConfigType::LogConfig(LogConfiguration {})),
@@ -372,7 +372,7 @@ fn create_node_invalid_configuration_err() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
             // Node configuration without config type.
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
@@ -406,7 +406,7 @@ fn create_node_less_confidential_label_err() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&initial_label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &initial_label_clone)?;
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
                 config_type: Some(ConfigType::LogConfig(LogConfiguration {})),
@@ -437,7 +437,7 @@ fn create_node_more_confidential_label_ok() {
         &initial_label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (_write_handle, read_handle) = runtime.channel_create(&initial_label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &initial_label_clone)?;
             let node_configuration = NodeConfiguration {
                 name: "test".to_string(),
                 config_type: Some(ConfigType::LogConfig(LogConfiguration {})),
@@ -458,8 +458,8 @@ fn wait_on_channels_immediately_returns_if_any_channel_is_orphaned() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (write_handle_0, read_handle_0) = runtime.channel_create(&label_clone)?;
-            let (_write_handle_1, read_handle_1) = runtime.channel_create(&label_clone)?;
+            let (write_handle_0, read_handle_0) = runtime.channel_create("", &label_clone)?;
+            let (_write_handle_1, read_handle_1) = runtime.channel_create("", &label_clone)?;
 
             // Close the write_handle; this should make the channel Orphaned
             let result = runtime.channel_close(write_handle_0);
@@ -486,7 +486,7 @@ fn wait_on_channels_blocks_if_all_channels_have_status_not_ready() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
 
             // Change the status of the channel concurrently, to unpark the waiting thread.
             let runtime_copy = runtime.clone();
@@ -516,8 +516,8 @@ fn wait_on_channels_immediately_returns_if_any_channel_is_invalid() {
         &label,
         &NodePrivilege::default(),
         Box::new(move |runtime| {
-            let (write_handle, _read_handle) = runtime.channel_create(&label_clone)?;
-            let (_write_handle, read_handle) = runtime.channel_create(&label_clone)?;
+            let (write_handle, _read_handle) = runtime.channel_create("", &label_clone)?;
+            let (_write_handle, read_handle) = runtime.channel_create("", &label_clone)?;
 
             let result = runtime.wait_on_channels(&[write_handle, read_handle]);
             assert_eq!(