blob: ccb6164df4df8524dd1b65080662f0efd8990c24 [file] [log] [blame]
/*
*
* Copyright (c) 2020-2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Import helpers from zap core
const zapPath = '../../../../../third_party/zap/repo/dist/src-electron/';
const templateUtil = require(zapPath + 'generator/template-util.js');
const zclHelper = require(zapPath + 'generator/helper-zcl.js');
const iteratorUtil = require(zapPath + 'util/iterator-util.js');
const queryAccess = require(zapPath + 'db/query-access')
const queryZcl = require(zapPath + 'db/query-zcl');
const { asBlocks, ensureClusters } = require('../../common/ClustersHelper.js');
const StringHelper = require('../../common/StringHelper.js');
const ChipTypesHelper = require('../../common/ChipTypesHelper.js');
function throwErrorIfUndefined(item, errorMsg, conditions)
{
conditions.forEach(condition => {
if (condition == undefined) {
console.log(item);
console.log(errorMsg);
throw error;
}
});
}
function checkIsInsideClusterBlock(context, name)
{
const clusterName = context.name ? context.name : context.clusterName;
const clusterSide = context.side ? context.side : context.clusterSide;
const errorMsg = name + ': Not inside a ({#chip_server_clusters}} block.';
throwErrorIfUndefined(context, errorMsg, [ clusterName, clusterSide ]);
return { clusterName, clusterSide };
}
function checkIsInsideCommandBlock(context, name)
{
const clusterName = context.clusterName;
const clusterSide = context.clusterSide;
const commandId = context.id;
const errorMsg = name + ': Not inside a ({#chip_cluster_commands}} block.';
throwErrorIfUndefined(context, errorMsg, [ commandId, clusterName, clusterSide ]);
return commandId;
}
function checkIsInsideAttributeBlock(context, name)
{
const code = context.code;
const errorMsg = name + ': Not inside a ({#chip_server_attributes}} block.';
throwErrorIfUndefined(context, errorMsg, [ code ]);
}
function checkIsChipType(context, name)
{
const type = context.chipType;
const errorMsg = name + ': Could not find chipType';
throwErrorIfUndefined(context, errorMsg, [ type ]);
return type;
}
function getCommands(methodName)
{
const { clusterName, clusterSide } = checkIsInsideClusterBlock(this, methodName);
return clusterSide == 'client' ? ensureClusters(this).getClientCommands(clusterName)
: ensureClusters(this).getServerCommands(clusterName);
}
function getAttributes(methodName)
{
const { clusterName, clusterSide } = checkIsInsideClusterBlock(this, methodName);
return ensureClusters(this).getAttributesByClusterName(clusterName);
}
function getResponses(methodName)
{
const { clusterName, clusterSide } = checkIsInsideClusterBlock(this, methodName);
return clusterSide == 'client' ? ensureClusters(this).getClientResponses(clusterName)
: ensureClusters(this).getServerResponses(clusterName);
}
/**
* Creates block iterator over the enabled server side clusters
*
* @param {*} options
*/
function chip_server_clusters(options)
{
return asBlocks.call(this, ensureClusters(this, options.hash.includeAll).getServerClusters(), options);
}
/**
* Check if there is any enabled server clusters
*
*/
function chip_has_server_clusters(options)
{
return ensureClusters(this).getServerClusters().then(clusters => !!clusters.length);
}
/**
* Creates block iterator over client side enabled clusters
*
* @param {*} options
*/
function chip_client_clusters(options)
{
return asBlocks.call(this, ensureClusters(this, options.hash.includeAll).getClientClusters(), options);
}
/**
* Check if there is any enabled client clusters
*
*/
function chip_has_client_clusters(options)
{
return ensureClusters(this).getClientClusters().then(clusters => !!clusters.length);
}
/**
* Creates block iterator over enabled clusters
*
* @param {*} options
*/
function chip_clusters(options)
{
return asBlocks.call(this, ensureClusters(this, options.hash.includeAll).getClusters(), options);
}
/**
* Check if there is any enabled clusters
*
*/
function chip_has_clusters(options)
{
return ensureClusters(this).getClusters().then(clusters => !!clusters.length);
}
/**
* Creates block iterator over the server global responses
*
* @param {*} options
*/
function chip_server_global_responses(options)
{
return asBlocks.call(this, getServerGlobalAttributeResponses(this), options);
}
async function if_basic_global_response(options)
{
const attribute = this.response.arguments[0];
const globalResponses = await getServerGlobalAttributeResponses(this);
const complexType = attribute.isNullable || attribute.isOptional || attribute.isStruct || attribute.isArray;
const responseTypeExists = globalResponses.find(item => item.chipType == attribute.chipType);
if (!complexType && responseTypeExists) {
return options.fn(this);
} else {
return options.inverse(this);
}
}
function getServerGlobalAttributeResponses(context)
{
const sorter = (a, b) => a.chipCallback.name.localeCompare(b.chipCallback.name, 'en', { numeric : true });
const reducer = (unique, item) => {
const { type, size, isArray, isOptional, isNullable, chipCallback, chipType } = item.response.arguments[0];
// List-typed elements have a dedicated callback
if (isArray) {
return unique;
}
if (unique.find(item => item.chipCallback.name == chipCallback.name)) {
return unique;
}
return [...unique, { chipCallback, chipType, size, isOptional, isNullable } ];
};
const filter = attributes => attributes.reduce(reducer, []).sort(sorter);
return ensureClusters(context).getAttributesByClusterSide('server').then(filter);
}
/**
* Creates block iterator over the cluster commands for a given cluster/side.
*
* This function is meant to be used inside a {{#chip_*_clusters}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_cluster_commands(options)
{
const commands = getCommands.call(this, 'chip_cluster_commands');
return asBlocks.call(this, commands, options);
}
/**
* Creates block iterator over the cluster responses for a given cluster/side.
*
* This function is meant to be used inside a {{#chip_*_clusters}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_cluster_responses(options)
{
const responses = getResponses.call(this, 'chip_cluster_responses');
return asBlocks.call(this, responses, options);
}
/**
* Creates block iterator over the current command arguments for a given cluster/side.
*
* This function is meant to be used inside a {{#chip_cluster_commands}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_cluster_command_arguments(options)
{
const commandId = checkIsInsideCommandBlock(this, 'chip_cluster_command_arguments');
const commands = getCommands.call(this.parent, 'chip_cluster_commands_argments');
const filter = command => command.id == commandId;
return asBlocks.call(this, commands.then(items => items.find(filter).arguments), options);
}
/**
* Creates block iterator over the current command arguments for a given cluster/side.
*
* This function is meant to be used inside a {{#chip_cluster_commands}}
* block. It will throw otherwise.
*
* The arguments list built by this function differs from {{chip_cluster_command_arguments}}.
* For example, if a command contains a single struct argument "SomeStruct", with the following type:
*
* struct SomeStruct {
* uint8_t a;
* uint16_t b;
* uint32_t c;
* }
*
* then that argument will be expanded into 3 arguments (uint8_t a, uint16_t b, uint32_t c).
*
* @param {*} options
*/
function chip_cluster_command_arguments_with_structs_expanded(options)
{
const commandId = checkIsInsideCommandBlock(this, 'chip_cluster_command_arguments');
const commands = getCommands.call(this.parent, 'chip_cluster_command_arguments_with_structs_expanded');
const filter = command => command.id == commandId;
return asBlocks.call(this, commands.then(items => {
const item = items.find(filter);
if (item === undefined) {
return [];
}
return item.expandedArguments || item.arguments;
}),
options);
}
/**
* Creates block iterator over the current response arguments for a given cluster/side.
*
* This function is meant to be used inside a {{#chip_cluster_responses}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_cluster_response_arguments(options)
{
const commandId = checkIsInsideCommandBlock(this, 'chip_cluster_response_arguments');
const responses = getResponses.call(this.parent, 'chip_cluster_responses_argments');
const filter = command => command.id == commandId;
return asBlocks.call(this, responses.then(items => items.find(filter).arguments), options);
}
/**
* Returns if a given server cluster has any attributes of type List[T]
*
* This function is meant to be used inside a {{#chip_server_clusters}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_server_has_list_attributes(options)
{
const { clusterName } = checkIsInsideClusterBlock(this, 'chip_server_has_list_attributes');
const attributes = ensureClusters(this).getServerAttributes(clusterName);
const filter = attribute => attribute.isArray;
return attributes.then(items => items.find(filter));
}
/**
* Returns if a given client cluster has any attributes of type List[T]
*
* This function is meant to be used inside a {{#chip_client_clusters}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_client_has_list_attributes(options)
{
const { clusterName } = checkIsInsideClusterBlock(this, 'chip_client_has_list_attributes');
const attributes = ensureClusters(this).getClientAttributes(clusterName);
const filter = attribute => attribute.isArray;
return attributes.then(items => items.find(filter));
}
/**
* Returns if a given server cluster has any reportable attribute
*
* This function is meant to be used inside a {{#chip_server_clusters}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_server_has_reportable_attributes(options)
{
const { clusterName } = checkIsInsideClusterBlock(this, 'chip_server_has_reportable_attributes');
const attributes = ensureClusters(this).getServerAttributes(clusterName);
const filter = attribute => attribute.isReportableAttribute;
return attributes.then(items => items.find(filter));
}
/**
* Creates block iterator over the server side cluster attributes
* for a given cluster.
*
* This function is meant to be used inside a {{#chip_server_clusters}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_server_cluster_attributes(options)
{
const { clusterName } = checkIsInsideClusterBlock(this, 'chip_server_cluster_attributes');
const attributes = ensureClusters(this).getServerAttributes(clusterName);
return asBlocks.call(this, attributes, options);
}
/**
* Creates block iterator over the server side cluster attributes
* for a given cluster.
*
* This function is meant to be used inside a {{#chip_server_clusters}}
* block. It will throw otherwise.
*
* @param {*} options
*/
function chip_server_cluster_events(options)
{
const { clusterName } = checkIsInsideClusterBlock(this, 'chip_server_cluster_events');
const events = ensureClusters(this).getServerEvents(clusterName);
return asBlocks.call(this, events, options);
}
function chip_attribute_list_entryTypes(options)
{
checkIsInsideAttributeBlock(this, 'chip_attribute_list_entry_types');
return templateUtil.collectBlocks(this.items, options, this);
}
/**
* Creates block iterator over commands for a given cluster that have the
* following properties:
*
* 1) Are not manufacturer-specific (to exclude MfgSpecificPing)
* 2) Are available in the isCommandAvailable sense.
*/
function chip_available_cluster_commands(options)
{
const { clusterName, clusterSide } = checkIsInsideClusterBlock(this, 'chip_available_cluster_commands');
let promise = iteratorUtil.all_user_cluster_commands_helper.call(this, options)
.then(endpointCommands => endpointCommands.filter(command => {
return command.clusterName == clusterName
&& zclHelper.isCommandAvailable(
clusterSide, command.incoming, command.outgoing, command.commandSource, command.name)
&& /* exclude MfgSpecificPing */ !command.mfgCode;
}))
.then(filteredCommands => templateUtil.collectBlocks(filteredCommands, options, this));
return promise;
}
/**
* Creates block iterator over structures belonging to the current cluster
*/
async function chip_cluster_specific_structs(options)
{
const { clusterName, clusterSide } = checkIsInsideClusterBlock(this, 'chip_cluster_specific_structs');
const structs = await ensureClusters(this).getStructuresByClusterName(clusterName);
return templateUtil.collectBlocks(structs, options, this);
}
/**
* Creates block iterator over structures that are shared between clusters
*/
async function chip_shared_structs(options)
{
const structs = await ensureClusters(this).getSharedStructs();
return templateUtil.collectBlocks(structs, options, this);
}
async function chip_endpoints(options)
{
const endpoints = await ensureClusters(this).getEndPoints();
return templateUtil.collectBlocks(endpoints, options, this);
}
async function chip_endpoint_clusters(options)
{
const clusters = this.clusters;
return templateUtil.collectBlocks(clusters, options, this);
}
/**
* Helper checks if the type for the bitmap is BitFlags. This generally includes
* all bitmaps apart from
* bitmap8/16/32 (generally defined in types.xml)
* example:
* {{#if_is_strongly_typed_bitmap type}}
* strongly typed bitmap
* {{else}}
* not a strongly typed bitmap
* {{/if_is_strongly_typed_bitmap}}
*
* @param {*} type
* @returns Promise of content.
*/
async function if_is_strongly_typed_bitmap(type, options)
{
let packageId = await templateUtil.ensureZclPackageId(this);
let bitmap;
if (type && typeof type === 'string') {
bitmap = await queryZcl.selectBitmapByName(this.global.db, packageId, type);
} else {
bitmap = await queryZcl.selectBitmapById(this.global.db, type);
}
if (bitmap) {
let a = await queryZcl.selectAtomicType(this.global.db, packageId, bitmap.name);
if (a) {
// If this is an atomic type, it's a generic, weakly typed, bitmap.
return options.inverse(this);
} else {
return options.fn(this);
}
}
return options.inverse(this);
}
/**
* Handlebar helper function which checks if an enum is a strongly typed enum or
* not. This generally includes all enums apart from
* enum8/16/32 (generally defined in types.xml)
* example for if_is_strongly_typed_chip_enum:
* {{#if_is_strongly_typed_chip_enum type}}
* strongly typed enum
* {{else}}
* not a strongly typed enum
* {{/if_is_strongly_typed_chip_enum}}
*
* @param {*} type
* @param {*} options
* @returns Promise of content.
*/
async function if_is_strongly_typed_chip_enum(type, options)
{
// There are certain exceptions.
if (type.toLowerCase() == 'vendor_id') {
return options.fn(this);
} else {
let packageId = await templateUtil.ensureZclPackageId(this);
let enumRes;
// Retrieving the enum from the enum table
if (type && typeof type === 'string') {
enumRes = await queryZcl.selectEnumByName(this.global.db, type, packageId);
} else {
enumRes = await queryZcl.selectEnumById(this.global.db, type);
}
// Checking if an enum is atomic. If an enum is not atomic then the enum
// is a strongly typed enum
if (enumRes) {
let a = await queryZcl.selectAtomicType(this.global.db, packageId, enumRes.name);
if (a) {
// if an enum has an atomic type that means it's a weakly-typed enum.
return options.inverse(this);
} else {
return options.fn(this);
}
}
return options.inverse(this);
}
}
/**
* Checks whether a type is an enum for purposes of its chipType. That includes
* both spec-defined enum types and types that we map to enum types in our code.
*/
async function if_chip_enum(type, options)
{
if (type.toLowerCase() == 'vendor_id') {
return options.fn(this);
}
let pkgId = await templateUtil.ensureZclPackageId(this);
let checkResult = await zclHelper.isEnum(this.global.db, type, pkgId);
let result;
if (checkResult != 'unknown') {
result = options.fn(this);
} else {
result = options.inverse(this);
}
return templateUtil.templatePromise(this.global, result);
}
async function if_chip_complex(options)
{
// `zcl_command_arguments` has an `isArray` property and `type`
// contains the array element type.
if (this.isArray) {
return options.fn(this);
}
// zcl_attributes iterators does not expose an `isArray` property
// and `entryType` contains the array element type, while `type`
// contains the atomic type, which is array in this case.
// https://github.com/project-chip/zap/issues/412
if (this.type == 'array') {
return options.fn(this);
}
let pkgId = await templateUtil.ensureZclPackageId(this);
let checkResult = await zclHelper.isStruct(this.global.db, this.type, pkgId);
let result;
if (checkResult != 'unknown') {
result = options.fn(this);
} else {
result = options.inverse(this);
}
return templateUtil.templatePromise(this.global, result);
}
async function chip_access_elements(options)
{
// console.log(options);
let entityType = options.hash.entity
if (entityType == null)
{
throw new Error('Access helper requires entityType, either from context, or from the entity="<entityType>" option.')
}
let accessList = null
// Exaples of operations:
// { operation: null, role: null, accessModifier: 'fabric-scoped' },
// { operation: 'read', role: 'administer', accessModifier: null },
// { operation: 'write', role: 'administer', accessModifier: null }
//
// Note the existence of a null operation with a modifier of fabric-scoped
// accessDefaults contains acceptable operations
// together with their default value
let accessDefaults = new Map();
switch (entityType) {
case 'attribute':
accessList = await queryAccess.selectAttributeAccess(this.global.db, this.id);
accessDefaults.set('read', 'view');
accessDefaults.set('write', 'operate');
break;
case 'command':
accessList = await queryAccess.selectCommandAccess(this.global.db, this.id);
accessDefaults.set('invoke', 'operate');
break;
case 'event':
accessList = await queryAccess.selectEventAccess(this.global.db, this.id);
accessDefaults.set('read', 'view');
break;
default:
throw new Error(`Entity type ${entityType} not supported. Requires: attribute/command/event.`)
}
let accessEntries = [];
for (element of accessList) {
if (!element.operation) {
continue; // not a valid operation (likely null)
}
const operation = element.operation.toLowerCase();
if (!accessDefaults.has(operation)) {
continue; // not a valid operation (may be a bug or non-matter operation)
}
const role = element.role.toLowerCase();
if (role === accessDefaults.get(operation)) {
continue; // already set as a default
}
accessEntries.push({ operation, role })
}
let p = templateUtil.collectBlocks(accessEntries, options, this)
return templateUtil.templatePromise(this.global, p)
}
//
// Module exports
//
exports.chip_clusters = chip_clusters;
exports.chip_has_clusters = chip_has_clusters;
exports.chip_client_clusters = chip_client_clusters;
exports.chip_has_client_clusters = chip_has_client_clusters;
exports.chip_server_clusters = chip_server_clusters;
exports.chip_has_server_clusters = chip_has_server_clusters;
exports.chip_cluster_commands = chip_cluster_commands;
exports.chip_cluster_command_arguments = chip_cluster_command_arguments;
exports.chip_cluster_command_arguments_with_structs_expanded = chip_cluster_command_arguments_with_structs_expanded;
exports.chip_server_global_responses = chip_server_global_responses;
exports.chip_cluster_responses = chip_cluster_responses;
exports.chip_cluster_response_arguments = chip_cluster_response_arguments
exports.chip_attribute_list_entryTypes = chip_attribute_list_entryTypes;
exports.chip_server_cluster_attributes = chip_server_cluster_attributes;
exports.chip_server_cluster_events = chip_server_cluster_events;
exports.chip_server_has_list_attributes = chip_server_has_list_attributes;
exports.chip_server_has_reportable_attributes = chip_server_has_reportable_attributes;
exports.chip_available_cluster_commands = chip_available_cluster_commands;
exports.chip_endpoints = chip_endpoints;
exports.chip_endpoint_clusters = chip_endpoint_clusters;
exports.if_chip_enum = if_chip_enum;
exports.if_chip_complex = if_chip_complex;
exports.if_basic_global_response = if_basic_global_response;
exports.chip_cluster_specific_structs = chip_cluster_specific_structs;
exports.chip_shared_structs = chip_shared_structs;
exports.chip_access_elements = chip_access_elements
exports.if_is_strongly_typed_chip_enum = if_is_strongly_typed_chip_enum
exports.if_is_strongly_typed_bitmap = if_is_strongly_typed_bitmap