Skip to content

Commit

Permalink
Add a basic message reader for the data model.
Browse files Browse the repository at this point in the history
The idea is to expose a semantic API where consumers can read things
like "a cluster id" or "an endpoint id", without necessarily making
assumptions about the sizes/representations of these things.

The API does still expose facilities for reading raw unsigned integers
of specific sizes.

More types will be added to this API as needed.
  • Loading branch information
bzbarsky-apple committed Nov 2, 2020
1 parent b4e37f2 commit 2b4202a
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 49 deletions.
70 changes: 21 additions & 49 deletions src/app/decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,13 @@
// InterPanHeader *interPanHeader)

#include <app/chip-zcl-zpro-codec.h>
#include <core/CHIPEncoding.h>
#include <app/message-reader.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
#include <stdio.h>
#include <string.h>
#include <support/logging/CHIPLogging.h>

template <int N>
struct Reader
{
};

template <>
struct Reader<1>
{
static uint8_t read(const uint8_t *& p) { return chip::Encoding::Read8(p); }
};

template <>
struct Reader<2>
{
static uint16_t read(const uint8_t *& p) { return chip::Encoding::LittleEndian::Read16(p); }
};

extern "C" {

uint16_t extractApsFrame(uint8_t * buffer, uint16_t buf_length, EmberApsFrame * outApsFrame)
Expand All @@ -55,39 +40,26 @@ uint16_t extractApsFrame(uint8_t * buffer, uint16_t buf_length, EmberApsFrame *
return 0;
}

const uint8_t * read_ptr = buffer;
chip::DataModelReader reader(buffer, buf_length);

// Skip first byte, because that's the always-0 frame control.
++read_ptr;
--buf_length;

#define TRY_READ(fieldName, fieldSize) \
do \
{ \
static_assert(sizeof(outApsFrame->fieldName) == fieldSize, "incorrect size for " #fieldName); \
if (buf_length < fieldSize) \
{ \
ChipLogError(Zcl, "Missing " #fieldName " when extracting APS frame"); \
return 0; \
} \
outApsFrame->fieldName = Reader<fieldSize>::read(read_ptr); \
buf_length = static_cast<uint16_t>(buf_length - fieldSize); \
} while (0)

TRY_READ(profileId, 2);
TRY_READ(clusterId, 2);
TRY_READ(sourceEndpoint, 1);
TRY_READ(destinationEndpoint, 1);
TRY_READ(options, 2);
TRY_READ(groupId, 2);
TRY_READ(sequence, 1);
TRY_READ(radius, 1);
CHIP_ERROR err = CHIP_NO_ERROR;

#undef TRY_READ

// Cast is safe because buf_length is uint16_t, so we can't have moved too
// far along.
return static_cast<uint16_t>(read_ptr - buffer);
// Skip first byte, because that's the always-0 frame control.
uint8_t ignored;
err = reader.ReadOctet(&ignored)
.Read16(&outApsFrame->profileId)
.ReadClusterId(&outApsFrame->clusterId)
.ReadEndpointId(&outApsFrame->sourceEndpoint)
.ReadEndpointId(&outApsFrame->destinationEndpoint)
.Read16(&outApsFrame->options)
.ReadGroupId(&outApsFrame->groupId)
.ReadOctet(&outApsFrame->sequence)
.ReadOctet(&outApsFrame->radius)
.StatusCode();
SuccessOrExit(err);

exit:
return err == CHIP_NO_ERROR ? reader.OctetsRead() : 0;
}

void printApsFrame(EmberApsFrame * frame)
Expand Down
138 changes: 138 additions & 0 deletions src/app/message-reader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
*
* Copyright (c) 2020 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.
*/

/**
* @file
* Contains the API definition for a message buffer reader for the data
* model. This reader does the necessary bounds-checks before reading
* and updates its own state as the buffer is read.
*/

#pragma once

#include <app/util/basic-types.h>
#include <lib/core/CHIPError.h>
#include <lib/support/BufferReader.h>
#include <lib/support/CodeUtils.h>
#include <stdint.h>

namespace chip {
// Private inheritance because we don't necessarily want to expose all the
// methods our superclass has.
class DataModelReader : private Encoding::LittleEndian::Reader
{
typedef Encoding::LittleEndian::Reader Super;

public:
/**
* Create a data model reader from a given buffer and length.
*
* @param buffer The octet buffer to read from. The caller must ensure
* (most simply by allocating the reader on the stack) that
* the buffer outlives the reader. The buffer is allowed to
* be null if buf_len is 0.
* @param buf_len The number of octets in the buffer.
*/
DataModelReader(const uint8_t * buffer, uint16_t buf_len) : Super(buffer, buf_len) {}

/**
* Number of octets we have read so far. This might be able to go away once
* we do less switching back and forth between DataModelReader and raw
* buffers.
*/
uint16_t OctetsRead() const { return Super::OctetsRead(); }

/**
* The reader status.
*/
CHIP_ERROR StatusCode() const { return Super::StatusCode(); }

/**
* Read a cluster id.
*
* @param [out] cluster_id Where the cluster id goes.
*
* @return Whether the read succeeded. The read can fail if there are not
* enough octets available.
*/
CHECK_RETURN_VALUE DataModelReader & ReadClusterId(ClusterId * cluster_id)
{
Read(cluster_id);
return *this;
}

/**
* Read an endpoint id.
*
* @param [out] endpoint_id Where the endpoint id goes.
*
* @return Whether the read succeeded. The read can fail if there are not
* enough octets available.
*/
CHECK_RETURN_VALUE DataModelReader & ReadEndpointId(EndpointId * endpoint_id)
{
Read(endpoint_id);
return *this;
}

/**
* Read a group id.
*
* @param [out] group_id Where the group id goes.
*
* @return Whether the read succeeded. The read can fail if there are not
* enough octets available.
*/
CHECK_RETURN_VALUE DataModelReader & ReadGroupId(GroupId * group_id)
{
Read(group_id);
return *this;
}

/**
* Read a single octet.
*
* @param [out] octet Where the octet goes.
*
* @return Whether the read succeeded. The read can fail if there are not
* enough octets available.
*
* @note Use of APIs that read some semantically-meaningful type is preferred.
*/
CHECK_RETURN_VALUE DataModelReader & ReadOctet(uint8_t * octet)
{
Read(octet);
return *this;
}

/**
* Read a single 16-bit unsigned integer.
*
* @param [out] dest Where the 16-bit integer goes.
*
* @return Whether the read succeeded. The read can fail if there are not
* enough octets available.
*
* @note Use of APIs that read some semantically-meaningful type is preferred.
*/
CHECK_RETURN_VALUE DataModelReader & Read16(uint16_t * dest)
{
Read(dest);
return *this;
}
};
} // namespace chip
4 changes: 4 additions & 0 deletions src/app/util/basic-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@
typedef uint8_t CHIPEndpointId;
typedef uint16_t CHIPClusterId;
typedef uint16_t CHIPAttributeId;
typedef uint16_t CHIPGroupId;

/**
* Types for use by generated Silicon Labs code until we convert the generator
* to using CHIP types.
*/
typedef CHIPClusterId EmberAfClusterId;
typedef CHIPAttributeId EmberAfAttributeId;
typedef CHIPGroupId EmberMulticastId;

/**
* @brief Type for referring to zigbee application profile id
* TODO: This is probably not needed for CHIP and should be removed.
Expand All @@ -50,5 +53,6 @@ namespace chip {
typedef CHIPEndpointId EndpointId;
typedef CHIPClusterId ClusterId;
typedef CHIPAttributeId AttributeId;
typedef CHIPGroupId GroupId;
} // namespace chip
#endif // __cplusplus

0 comments on commit 2b4202a

Please sign in to comment.