Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 36 Next »

What are pico channels?


A pico receives events and respond to queries on its channels (Event-Query API). A channel on a pico can be viewed as a unique stream of communication with that pico. A pico can have any number of channels. Through the use of policies, type of communication through the channel can be controlled (e.g. only allows events of a certain type/domain to be received on a channel). Pico channels are a type of Event Channels.

A pico channel is represented by an ECI (Event Channel Identifier). It is a unique token, a string, such as "ckiz680qq000a7u2r8myeezof". To use a channel, a programmer only needs the ECI itself and the location of the engine the pico is located. Then, following the Sky Event API or Sky Cloud API queries and events can be sent. Mechanisms within KRL have this built in, such as the event library and Wrangler's skyQuery

Wrangler's Subscriptions use channels to create unique relationships between picos.

Purpose of channels

As a quick illustration of the purpose of channels, a pico representing a real-life lamp may wish to make the ability to query whether it is "lit" or "off" to some entities, but reserve the ability to actually change its state to "lit" or "off" to other actors:

For each actor that the lamp pico will give control to, the lamp pico may create a channel that is able to receive "lamp:on" and "lamp:off" events, and then share the created channel with the desired actor. Conversely, for actors that are only allowed to query for the state of the lamp pico, the lamp pico will create channels that only allow an "IsLampOn" query and share that.

Finally, because the lamp pico created a channel for each actor, if any actor misbehaves (e.g. the lamp pico is programmed to stop actors from rapidly flashing the light), the lamp pico can revoke or restrict just that channel, without affecting its communication with any other actor.

Channels in the developer UI

A pico's channels as shown in the developer UI, for a pico named "Alpha" which has both a parent pico and one child pico:

Types of channels

There are channels for use inside the pico-engine, and those which can be used by outside systems (such as browsers) to communicate a query or event to a pico.

Besides have a unique identifier (the ECI), each channel also has one or more tags and a policy. The tags can be used to search for a channel, and the policy determines how the channel can be used. For detailed discussion about the attributes of a channel, see below.

Family channels

Family channels are usable only within a pico-engine. They have a "system" tag.

Family channels do not have policies. Any event or query is allowed on a family channel, but has to come from the right pico.

Self channel

Each pico has such a channel, with a "self" tag. Since a pico cannot send itself a query or an event, this channel serves only to identify it. And in fact its ECI is the same as the pico's identifier.

Child channel

Each pico except the root pico has one such channel, with a "child" tag. This channel can be used by code running in its parent to send this pico events and queries.

Parent channels

Each pico which has children has one such channel for each child, with a "parent" tag. This channel can be used by code running in the child to send this pico events and queries.

External channels

All other channels to a pico can be used either by other picos in the same pico-engine, other picos hosted in external pico-engines, or by systems anywhere on the Internet. Each external channel you give to a pico defines an API for the pico. That API is determined by the rulesets which you install in the pico and shaped by the policies you bake into these channels.

Policies, discussed in more detail below, allow or deny certain specified events and queries. It is through these policies that the lamp pico discussed above implements the restrictions spoken of.

Creating a Channel


Manual creation of a channel using the developer UI

The "Channels" tab of the developer UI, besides listing the channels currently in the pico, has a form that you can use to create a new channel for the pico.

Here is a screenshot showing the creation of channel giving full access to the lamp pico, just before the developer clicks on the "Add" button:

Here is the same tab after the developer has created both a full-access channel and a read-only channel for the Lamp pico:

The channel identifiers (ECIs) are assigned by the pico-engine and are globally unique.

Notice that checking the box beside a channel's ECI shows the policies of the channel.

The ECI "ckj080ny8008ffi2r1n1b2kr0" can be given to trustworthy actors, giving them the ability to use the full capabilities of the lamp_ruleset to control the lamp.

On the other hand the ECI "ckj081v5a0093fi2r9es49i77" could be more widely published so that less-trusted actors can check the lamp's state. Such actors would not be able to send any events to the Lamp pico.

If any actor misbehaves, you can simply delete the channel and recreate it, with a different ECI. Even though the read-only ECI gives access only to a single query, an actor might misbehave by mounted a distributed denial of service attack on the pico-engine. Again, you can eliminate the attack simply by deleting that channel. After recreating it, you can give the new ECI to good actors, and they can resume their legitimate work.

Programmatic creation of a channel

Synchronous creation

Channels can be created synchronously by your KRL code using the wrangler:createChannel defined action which is provided to any ruleset that uses Wrangler as a module. See the Pico-Based Systems lesson for more examples. This code shows how to use the Wrangler ruleset as a module:

meta {
  use module io.picolabs.wrangler alias wrangler
  shares IsLampOn
}

Line 2 makes the ruleset available within this ruleset using the internal name wrangler. Line 3 shares the IsLampOn function so that it can be used by external systems over an appropriate channel.

This is code to create one of the lamp channel within KRL code:

rule create_limited_use_channel {
  select when lamp read_only_channel_needed
  pre {
    tags = ["lamp","read-only"]
    eventPolicy = {"allow": [], "deny": [{"domain": "*", "name": "*"}]}
    queryPolicy = {"allow":[{"rid": meta:rid, "name": "IsLampOn"}], "deny": []}
  }
  every {
    wrangler:createChannel(tags,eventPolicy,queryPolicy) setting(channel)
    send_directive("new channel",{"eci": channel{"id"}})
  }
}

Lines 3-7 bind local names to the tags, the eventPolicy and the queryPolicy for the new channel. The rule will unconditionally fire executing both of the actions specified (in order).

In line 9 the io.picolabs.wrangler module will run the createChannel defaction, creating the new channel and returning it, where it will be bound to the internal name channel by the setting clause.

Finally, line 10 will return a directive to the event generator, giving it the channel's ECI.

The event generator might be a trusted web page (given an ECI with a policy allowing the event lamp:read_only_channel_needed) which could send the event to the Lamp pico and use the response to set up a URL that could be given to the user to check on the lamp's state.

Asynchronous creation

Channels can be created asynchronously by raising the wrangler:new_channel_request event. Wrangler enforces that all channels with the same type must have a unique name. On a successful channel creation, a wrangler:channel_created event will be raised by Wrangler.

Creation Example

Channels can be created asynchronously by raising thewrangler:new_channel_requestevent. Wrangler enforces that all channels with the same type must have a unique name. On a successful channel creation, a wrangler:channel_created event will be raised by Wrangler.

raise wrangler event "new_channel_request" attributes {
  "tags":["lamp","read-only"],
  "eventPolicy":{"allow": [], "deny": [{"domain": "*", "name": "*"}]},
  "queryPolicy":{"allow":[{"rid": meta:rid, "name": "IsLampOn"}], "deny": []},
  "co_id":meta:rid // Added attributes get passed through
}

Responding to Creation

  rule onNewTestChannel {
    select when wrangler channel_created where event:attr("co_id") == meta:rid
    pre {
      newChannel = event:attr("channel").klog("New channel")
      channelID = newChannel{"id"}
    }
  }
/*
[KLOG] New channel
    {
      "id": "ckj081v5a0093fi2r9es49i77",
      "tags": [
        "lamp",
        "read-only"
      ],
      "eventPolicy": {
        "allow": [],
        "deny": [
          {
            "domain": "*",
            "name": "*"
          }
        ]
      },
      "queryPolicy": {
        "allow": [
          {
            "rid": "lamp_ruleset",
            "name": "IsLampOn"
          }
        ],
        "deny": []
      },
      "familyChannelPicoID": null
    }
*/

Deleting a Channel

Deleting a channel occurs in much the same way that creating one does. The client raises a wrangler:channel_deletion_request event with attributes that are either the tags, or the ECI of the channel they wish to delete. If invalid attributes are provided a wrangler:channel_deletion_failed event will be raised by Wrangler.

Deletion Example

raise wrangler event "channel_deletion_request" attributes {
  "tags":["lamp","read-only"],
  "co_id":meta:rid
}

Responding to Deletion

rule onChannelDeleted {
  select when wrangler channel_deleted where event:attr("co_id") == meta:rid
  pre {
    deletedChannel = event:attr("channel").klog("Deleted channel")
    channelID = deletedChannel{"id"}.klog("Channel ID")
  }
}

Channel Attributes


A pico channel has certain attributes that each serve a unique purpose in identifying and using that channel. A map containing these attributes is often given to the client by Wrangler, such as when creating and deleting channels.

ID

The channel ID is the globally unique identifier for a channel. It is also known by the initialism "ECI" (Event Channel Identifier). Anyone who knows the ECI and has network access to the engine that the pico with the channel lives on can send events and queries to the channel.

Tags

The channel has one or more tags as a way to identify the channel without knowing its ECI. This allows easier manipulation of the channels that the KRL programmer cares about.

Event Policy

This is a map that encodes the "policy" that the channel uses for events . The policy determines what events are allowed on that channel.

Query Policy

This is a map that encodes the "policy" that the channel uses for queries. The policy determines what queries are allowed on that channel.

Policies


Every channel has a policy attached to it. Policies limit which events or queries a channel will accept. They are defined using JSON maps. For events you define which domains and names are allowed and which are denied. Queries use "rid" and "name" instead of "domain" and "name".

For example, a policy that allows all events with domain `foo` and name `bar`. An event `foo:hello` will be rejected.

"eventPolicy": {
    "allow": [
        // This is a list of rules.
        // If the event matches one of these rules, the event will be allowed
        {"domain": "foo", "name": "bar"}
    ],
    "deny": []
}

Now let's try a policy that will allow all events with domain `foo`, but reject `foo/bar`.

    "eventPolicy": {
        "allow": [
            {"domain": "foo", "name": "*"},// "*" means match all
            {"domain": "aaa", "name": "bbb"},
        ],
        "deny": [
            // deny creates a black list of events
            {"domain": "foo", "name": "bar"}
        ],
    }

// foo:foo - allowed
// foo:wat - allowed
// foo:bar - denied b/c it matches a rule in the `deny` list
// zzz:wat - denied b/c domain zzz is not allowed
// aaa:bbb - allowed b/c it matched an `allow` rule
// aaa:ccc - denied b/c it didn't match an `allow` rule

Now let's allow all events except `system:*` and `danger:nuke`

    "event": {
        "allow": [
            {"domain": "*", "name": "*"}// using "*" for `domain` and `type` means match all events
        ],
        "deny": [
            {"domain": "system"},
            {"domain": "danger", "name": "nuke"},
        ],
    }

// foo:bar - allowed
// hello:system - allowed
// system:secret - denied
// system:foobar - denied
// danger:gun - allowed
// danger:nuke - denied

Queries work the same. But use `rid`+`name` instead of `domain`+`name`

The allow all policy looks like this

{
  "id": "ckj0a2o12009efi2r576qhe5q",
  "tags": [
    "allow_all"
  ],
  "eventPolicy": {
    "allow": [
      {
        "domain": "*",
        "name": "*"
      }
    ],
    "deny": []
  },
  "queryPolicy": {
    "allow": [
      {
        "rid": "*",
        "name": "*"
      }
    ],
    "deny": []
  },
  "familyChannelPicoID": null
}

Using Channels


List of ways to interact with channels:

Channels API


A list of the functions, actions, received events (that Wrangler responds to) and raised events (that Wrangler raises) and in-depth descriptions of when to use them.

API is undergoing development. Incremental changes will be made.

  • For all functions, enter the parameters in the order listed.
  • Italicized parameters are optional.
  • Received Events are events of the "wrangler" domain that Wrangler will respond to
  • Raised Events are events that can be selected on within any ruleset in the pico when they occur
  • Sent Events are events that the pico will send elsewhere as a result of triggering a Wrangler event.

Functions


channel

This is a "one-stop" function for getting desired channel info. Allows grouping of channels by their attributes, or getting specific channels by their ECI/name/etc.

List a set of channels depending on what parameters are past.

If all parameters are passed as null, it will return all channels for that pico.

If the id parameter is passed then the function will always return a single channel based upon the passed value.

If the collection parameter is passed, the function will return a map with channels grouped in arrays keyed to the channel attribute passed as the parameter. That is, whatever is passed to collection will group the pico channels according to the value they have as that attribute.

If the filtered parameter is passed, the collection parameter must have been passed. The filtered parameter will return an array of channels where the value passed to filtered matches the value of the channel attribute passed to collection

Returns

Either

A single channel map containing the information for the value passed to id parameter.

A map with values that are arrays of channel maps keyed to the channel attribute passed to the collection parameter

An array of channel maps whose value for the attribute passed to the collection parameter is the same as the value passed to filtered.

NULL if no channels found for any operation.

Parameters

ParameterDatatypeDescription
id

String

Can either be the name of a single channel or the ECI of a channel.
collectionString

VALID INPUTS: "id", "pico_id", "name", "type", "policy_id"

The channel attribute that you want the value of to be mapped to arrays of channels whose value for that attribute matches

filteredStringThe collection parameter must also be passed! This is the channel attribute value that you want to select for
Example
response = wrangler:channel(null,null,null); // all channels
response = wrangler:channel("flipper",null,null); // single channel with "flipper" name
response = wrangler:channel(null,"type",null); // collection of channels by type
response = wrangler:channel(null,"type","OAUTH"); // collection of channels with "OAUTH" as type

A "channel map" looks like this

Example
{
  "id": "4PkMF8CYq5dsh7ksn6fDKh",
  "pico_id": "cjqr0of8e003p88rq7hric28i",
  "name": "admin",
  "type": "secret",
  "policy_id": "allow-all-policy",
  "sovrin": {
    "did": "4PkMF8CYq6dsh7ksn6fDKh",
    "verifyKey": "2rEaErex4S9CD8y6a1wZuHYxbH5DTpVw7SyBVhCU2gxa",
    "encryptionPublicKey": "AmHEeV8dYHZQ69b4wqtdVDUj97KJU4dfuHVoFcZzuvmg"
  }
}



alwaysEci

Returns the ECI of the channel whose ECI or name is given to the function. Is identical to only passing one parameter to the channel function. It can be used to check if a channel with that ECI exists.

Parameters

ParameterDatatypeDescription
value

String

Can either be the name of a channel or the ECI of a channel.

Returns 

Either

A string containing the ECI of the channel prompted for

NULL if no channel is found for the given parameter 

Example
channelECI = wrangler:alwaysEci("wovyn_channel")

nameFromEci

Returns the name of the channel with the given name or ECI.

Parameters

ParameterDatatypeDescription
eci

String

Can either be the ECI of a channel or the name of a channel

Returns 

Either

A string containing the name of the channel prompted for

NULL if no channel is found for the given parameter 

Example
channelName = wrangler:nameFromEci("4PkMF8CYq5dsh7ksn6fDKh")

Actions


createChannel

Takes a pico ID, a channel name, a channel type, and optional parameter policy ID to create a new channel for that pico ID with those attributes.

Parameters

ParameterDatatypeDescription
pico_idStringID of the pico to add the channel to. Will default to the currently running pico.
nameStringThe name for the new channel
typeString

The type of new channel

This is an arbitrary string typically used to give a domain to channels.

policy_idString

The policy ID to apply to the channel

Currently defaults to an "allow-all-policy" such that any event can be received on this channel.

Returns

A map with the following contents

id: The new ECI for the created channel
pico_id: The pico ID of the pico the channel has been created on
name: The name of the new channel 
type: The type of the channel created
policy_id: The policy that describes the channel that has been created

Example
//Returns:
{
	"id" : <new_eci>,
	"pico_id": <pico_id>,
	"name": "channel_name",
	"type": "channel_type",
	"policy_id": "1234"
}

deleteChannel

Takes a name or ECI of a channel and removes that channel. If a name is given it removes the channel from the pico it is run within. The engine will error if the channel does not exist or you attempt to delete the pico's admin channel.

Parameters

ParameterDatatypeDescription
valueStringID of the pico to add the channel to. Will default to the currently running pico.

Returns

The channel that was removed in a channel map form:

Example
{
  "id": "4PkMF8CYq5dsh7ksn6fDKh",
  "pico_id": "cjqr0of8e003p88rq7hric28i",
  "name": "admin",
  "type": "secret",
  "policy_id": "allow-all-policy",
  "sovrin": {
    "did": "4PkMF8CYq6dsh7ksn6fDKh",
    "verifyKey": "2rEaErex4S9CD8y6a1wZuHYxbH5DTpVw7SyBVhCU2gxa",
    "encryptionPublicKey": "AmHEeV8dYHZQ69b4wqtdVDUj97KJU4dfuHVoFcZzuvmg"
  }
}



newPolicy

Creates a new policy based on a given map argument and returns the created policy. See this pico engine pull for a more in-depth explanation.

Parameters

ParameterDatatypeDescription
policyMap

Example map:
{
   name: "only allow foo/bar events",
   event: {
   allow: [{domain: "foo", type: "bar"}],
   deny: [],
},
     query: {allow: [], deny: []}
}

Returns

The map passed to the action but with an additional "id" key mapped to the policy ID of the newly created policy.

Example
{
    id: "1234",
    name: "only allow foo/bar events",
    event: {
        allow: [{domain: "foo", type: "bar"}],
        deny: [],
    },
    query: {allow: [], deny: []}
}

Received Events


channel_creation_requested

wrangler:channel_creation_requested

Creates a new channel through an event. It first checks that the name passed to it does not exist as a current channel on the pico. If it does not, then it creates the channel. The channel is created with the type and policy given in the event attributes. If no name is passed then an error event is raised.

Attributes

AttributeDatatypeDescription
name

String

The name of the channel you want to create. Must be a unique name from any other channel on the pico.
typeStringThe type of channel you want to create. This is an arbitrary string typically used to give a domain to channels. It will default to _wrangler if not given.
policy_id

String

The policy ID of the policy you want this channel to follow. Currently defaults to the allow-all-policy. 

Directives Returned

Will return empty directives if the name passed already exists as a channel or no name is passed.

Example
send_directive("channel_Created", channel); 
// Channel is a channel map of the channel with its attributes keyed to their values:
/*
{
  "directives": [
    {
      "options": {
        "id": "8nHCdus2sqVmF34SshhtsF",
        "pico_id": "cjqszhxzp015fb0rq1istxqhl",
        "name": "test",
        "type": "test",
        "policy_id": "allow-all-policy",
        "sovrin": {
          "did": "8nHCdus2sqVmF34SshhtsF",
          "verifyKey": "5Exq5iXBUQ8XSctTx1156g4kQCSbK62Su2NXHSX8HFkw",
          "encryptionPublicKey": "AQwwUCGh9Ke9vKRaGhUj6XkZxfvVS8JdTYyzuaiHiqAF"
        }
      },
      "name": "channel_Created",
      "meta": {
        "rid": "io.picolabs.wrangler",
        "rule_name": "createChannel",
        "txn_id": "cjrb9ttiu0023nkrquliin4mj",
        "eid": "__testing"
      }
    }
  ]
}
*/

Corresponding Raised Events:


channel_deletion_requested

wrangler:channel_deletion_requested

Deletes the channel given as either an ECI or channel name. The engine errors and nothing happens if the channel does not exist.

Attributes

AttributeDatatypeDescription
eci

String

The ECI of the channel you want to delete on this pico.
nameStringThe name of the channel you want to delete on this pico.

Directives Returned

Will return empty directives if no channel was found to be deleted.

Example
send_directive("channel_deleted", channel);
// Channel is a channel map of the channel with its attributes keyed to their values:
/*
{
  "directives": [
    {
      "options": {
        "id": "8nHCdus2sqVmF34SshhtsF",
        "pico_id": "cjqszhxzp015fb0rq1istxqhl",
        "name": "test",
        "type": "test",
        "policy_id": "allow-all-policy",
        "sovrin": {
          "did": "8nHCdus2sqVmF34SshhtsF",
          "verifyKey": "5Exq5iXBUQ8XSctTx1156g4kQCSbK62Su2NXHSX8HFkw",
          "encryptionPublicKey": "AQwwUCGh9Ke9vKRaGhUj6XkZxfvVS8JdTYyzuaiHiqAF"
        }
      },
      "name": "channel_deleted",
      "meta": {
        "rid": "io.picolabs.wrangler",
        "rule_name": "deleteChannel",
        "txn_id": "cjrba64ud0024nkrqm4wp8w4v",
        "eid": "__testing"
      }
    }
  ]
}
*/

Corresponding Raised Events:

Raised Events



channel_created

wrangler:channel_created

Raised when a new channel has been created on this pico by using the wrangler:channel_creation_requested event.

Attributes Added

AttributeDatatypeDescription
channelMap

Channel Map describing the created channel's attributes.

A channel map has the structure:

Example
{
  "id": "4PkMF8CYq5dsh7ksn6fDKh",
  "pico_id": "cjqr0of8e003p88rq7hric28i",
  "name": "admin",
  "type": "secret",
  "policy_id": "allow-all-policy",
  "sovrin": {
    "did": "4PkMF8CYq6dsh7ksn6fDKh",
    "verifyKey": "2rEaErex4S9CD8y6a1wZuHYxbH5DTpVw7SyBVhCU2gxa",
    "encryptionPublicKey": "AmHEeV8dYHZQ69b4wqtdVDUj97KJU4dfuHVoFcZzuvmg"
  }
}

An example of a rule selecting on a new channel creation.

Example
rule on_new_channel{
  select when wrangler channel_created where event:attr("channel"){["type"]} == "test" // If a channel of type test has been created
  pre {
  }
  noop() // Do some operation
  fired{
    klog("my channel has been created!");
  }
}

channel_deleted

wrangler:channel_deleted

Raised when a channel has been deleted from this pico using the wrangler:channel_deletion_requested event.

Attributes Added

AttributeDatatypeDescription
channelMap

Channel Map describing the created channel's attributes.

A channel map has the structure:

Example
{
  "id": "4PkMF8CYq5dsh7ksn6fDKh",
  "pico_id": "cjqr0of8e003p88rq7hric28i",
  "name": "admin",
  "type": "secret",
  "policy_id": "allow-all-policy",
  "sovrin": {
    "did": "4PkMF8CYq6dsh7ksn6fDKh",
    "verifyKey": "2rEaErex4S9CD8y6a1wZuHYxbH5DTpVw7SyBVhCU2gxa",
    "encryptionPublicKey": "AmHEeV8dYHZQ69b4wqtdVDUj97KJU4dfuHVoFcZzuvmg"
  }
}

An example of a rule selecting on a channel deletion.

Example
rule on_channel_deletion{
  select when wrangler channel_deleted where event:attr("channel"){["type"]} == "test" // If a channel of type test has been deleted
  pre {
  	channelName = event:attr("channel"){["name"]};
  }
  doCleanup() // Do some operation
  fired{
    klog("my channel has been deleted!");
	ent:channels := ent:channels.delete([channelName]);
  }
}




  • No labels