Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : /**
19 : * @file
20 : * This file implements a QRCode Setup Payload generator in accordance
21 : * with the CHIP specification.
22 : *
23 : */
24 :
25 : #include "QRCodeSetupPayloadGenerator.h"
26 : #include "Base38Encode.h"
27 :
28 : #include <lib/core/CHIPCore.h>
29 : #include <lib/core/TLV.h>
30 : #include <lib/core/TLVData.h>
31 : #include <lib/core/TLVDebug.h>
32 : #include <lib/core/TLVUtilities.h>
33 : #include <lib/support/CodeUtils.h>
34 : #include <lib/support/SafeInt.h>
35 : #include <lib/support/ScopedBuffer.h>
36 : #include <protocols/Protocols.h>
37 :
38 : #include <stdlib.h>
39 : #include <string.h>
40 : #include <string>
41 :
42 : namespace chip {
43 :
44 : // Populates numberOfBits starting from LSB of input into bits, which is assumed to be zero-initialized
45 392 : static CHIP_ERROR populateBits(uint8_t * bits, size_t & offset, uint64_t input, size_t numberOfBits,
46 : size_t totalPayloadDataSizeInBits)
47 : {
48 392 : VerifyOrReturnError(offset + numberOfBits <= totalPayloadDataSizeInBits, CHIP_ERROR_INVALID_ARGUMENT);
49 392 : VerifyOrReturnError(input < 1u << numberOfBits, CHIP_ERROR_INVALID_ARGUMENT);
50 :
51 392 : size_t index = offset;
52 392 : offset += numberOfBits;
53 2030 : while (input != 0)
54 : {
55 1638 : if (input & 1)
56 : {
57 652 : const uint8_t mask = static_cast<uint8_t>(1 << index % 8);
58 652 : bits[index / 8] = static_cast<uint8_t>(bits[index / 8] | mask);
59 : }
60 1638 : index++;
61 1638 : input >>= 1;
62 : }
63 392 : return CHIP_NO_ERROR;
64 : }
65 :
66 33 : static CHIP_ERROR populateTLVBits(uint8_t * bits, size_t & offset, const uint8_t * tlvBuf, size_t tlvBufSizeInBytes,
67 : size_t totalPayloadDataSizeInBits)
68 : {
69 161 : for (size_t i = 0; i < tlvBufSizeInBytes; i++)
70 : {
71 128 : const uint8_t value = tlvBuf[i];
72 128 : ReturnErrorOnFailure(populateBits(bits, offset, value, 8, totalPayloadDataSizeInBits));
73 : }
74 33 : return CHIP_NO_ERROR;
75 : }
76 :
77 14 : CHIP_ERROR writeTag(TLV::TLVWriter & writer, TLV::Tag tag, OptionalQRCodeInfo & info)
78 : {
79 14 : CHIP_ERROR err = CHIP_NO_ERROR;
80 :
81 14 : if (info.type == optionalQRCodeInfoTypeString)
82 : {
83 10 : err = writer.PutString(tag, info.data.c_str());
84 : }
85 4 : else if (info.type == optionalQRCodeInfoTypeInt32)
86 : {
87 4 : err = writer.Put(tag, info.int32);
88 : }
89 : else
90 : {
91 0 : err = CHIP_ERROR_INVALID_ARGUMENT;
92 : }
93 :
94 14 : return err;
95 : }
96 :
97 7 : CHIP_ERROR writeTag(TLV::TLVWriter & writer, TLV::Tag tag, OptionalQRCodeInfoExtension & info)
98 : {
99 7 : CHIP_ERROR err = CHIP_NO_ERROR;
100 :
101 7 : if (info.type == optionalQRCodeInfoTypeString || info.type == optionalQRCodeInfoTypeInt32)
102 : {
103 6 : err = writeTag(writer, tag, static_cast<OptionalQRCodeInfo &>(info));
104 : }
105 1 : else if (info.type == optionalQRCodeInfoTypeInt64)
106 : {
107 0 : err = writer.Put(tag, info.int64);
108 : }
109 1 : else if (info.type == optionalQRCodeInfoTypeUInt32)
110 : {
111 1 : err = writer.Put(tag, info.uint32);
112 : }
113 0 : else if (info.type == optionalQRCodeInfoTypeUInt64)
114 : {
115 0 : err = writer.Put(tag, info.uint64);
116 : }
117 : else
118 : {
119 0 : err = CHIP_ERROR_INVALID_ARGUMENT;
120 : }
121 :
122 7 : return err;
123 : }
124 :
125 36 : CHIP_ERROR QRCodeSetupPayloadGenerator::generateTLVFromOptionalData(SetupPayload & outPayload, uint8_t * tlvDataStart,
126 : uint32_t maxLen, size_t & tlvDataLengthInBytes)
127 : {
128 36 : std::vector<OptionalQRCodeInfo> optionalData = outPayload.getAllOptionalVendorData();
129 36 : std::vector<OptionalQRCodeInfoExtension> optionalExtensionData = outPayload.getAllOptionalExtensionData();
130 36 : VerifyOrReturnError(!optionalData.empty() || !optionalExtensionData.empty(), CHIP_NO_ERROR);
131 :
132 12 : TLV::TLVWriter rootWriter;
133 12 : rootWriter.Init(tlvDataStart, maxLen);
134 :
135 12 : TLV::TLVWriter innerStructureWriter;
136 :
137 12 : ReturnErrorOnFailure(rootWriter.OpenContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, innerStructureWriter));
138 :
139 17 : for (OptionalQRCodeInfo info : optionalData)
140 : {
141 8 : ReturnErrorOnFailure(writeTag(innerStructureWriter, TLV::ContextTag(info.tag), info));
142 8 : }
143 :
144 16 : for (OptionalQRCodeInfoExtension info : optionalExtensionData)
145 : {
146 7 : ReturnErrorOnFailure(writeTag(innerStructureWriter, TLV::ContextTag(info.tag), info));
147 7 : }
148 :
149 9 : ReturnErrorOnFailure(rootWriter.CloseContainer(innerStructureWriter));
150 :
151 9 : ReturnErrorOnFailure(rootWriter.Finalize());
152 :
153 9 : tlvDataLengthInBytes = rootWriter.GetLengthWritten();
154 :
155 9 : return CHIP_NO_ERROR;
156 36 : }
157 :
158 33 : static CHIP_ERROR generateBitSet(PayloadContents & payload, MutableByteSpan & bits, uint8_t * tlvDataStart,
159 : size_t tlvDataLengthInBytes)
160 : {
161 33 : size_t offset = 0;
162 33 : size_t totalPayloadSizeInBits = kTotalPayloadDataSizeInBits + (tlvDataLengthInBytes * 8);
163 33 : VerifyOrReturnError(bits.size() * 8 >= totalPayloadSizeInBits, CHIP_ERROR_BUFFER_TOO_SMALL);
164 :
165 : // isValidQRCodePayload() has already performed all relevant checks (including that we have a
166 : // long discriminator and rendevouz information). But if AllowInvalidPayload is set these
167 : // requirements might be violated; in that case simply encode 0 for the relevant fields.
168 : // Encoding an invalid (or partially valid) payload is useful for clients that need to be able
169 : // to serialize and deserialize partially populated or invalid payloads.
170 33 : ReturnErrorOnFailure(
171 : populateBits(bits.data(), offset, payload.version, kVersionFieldLengthInBits, kTotalPayloadDataSizeInBits));
172 33 : ReturnErrorOnFailure(
173 : populateBits(bits.data(), offset, payload.vendorID, kVendorIDFieldLengthInBits, kTotalPayloadDataSizeInBits));
174 33 : ReturnErrorOnFailure(
175 : populateBits(bits.data(), offset, payload.productID, kProductIDFieldLengthInBits, kTotalPayloadDataSizeInBits));
176 33 : ReturnErrorOnFailure(populateBits(bits.data(), offset, static_cast<uint64_t>(payload.commissioningFlow),
177 : kCommissioningFlowFieldLengthInBits, kTotalPayloadDataSizeInBits));
178 33 : ReturnErrorOnFailure(populateBits(bits.data(), offset,
179 : payload.rendezvousInformation.ValueOr(RendezvousInformationFlag::kNone).Raw(),
180 : kRendezvousInfoFieldLengthInBits, kTotalPayloadDataSizeInBits));
181 33 : auto const & pd = payload.discriminator;
182 33 : ReturnErrorOnFailure(populateBits(bits.data(), offset, (!pd.IsShortDiscriminator() ? pd.GetLongValue() : 0),
183 : kPayloadDiscriminatorFieldLengthInBits, kTotalPayloadDataSizeInBits));
184 33 : ReturnErrorOnFailure(
185 : populateBits(bits.data(), offset, payload.setUpPINCode, kSetupPINCodeFieldLengthInBits, kTotalPayloadDataSizeInBits));
186 33 : ReturnErrorOnFailure(populateBits(bits.data(), offset, 0, kPaddingFieldLengthInBits, kTotalPayloadDataSizeInBits));
187 33 : ReturnErrorOnFailure(populateTLVBits(bits.data(), offset, tlvDataStart, tlvDataLengthInBytes, totalPayloadSizeInBits));
188 :
189 33 : return CHIP_NO_ERROR;
190 : }
191 :
192 33 : static CHIP_ERROR payloadBase38RepresentationWithTLV(PayloadContents & payload, MutableCharSpan & outBuffer, MutableByteSpan bits,
193 : uint8_t * tlvDataStart, size_t tlvDataLengthInBytes)
194 : {
195 33 : memset(bits.data(), 0, bits.size());
196 33 : ReturnErrorOnFailure(generateBitSet(payload, bits, tlvDataStart, tlvDataLengthInBytes));
197 :
198 33 : CHIP_ERROR err = CHIP_NO_ERROR;
199 33 : size_t prefixLen = strlen(kQRCodePrefix);
200 :
201 33 : if (outBuffer.size() < prefixLen + 1)
202 : {
203 0 : err = CHIP_ERROR_BUFFER_TOO_SMALL;
204 : }
205 : else
206 : {
207 33 : MutableCharSpan subSpan = outBuffer.SubSpan(prefixLen, outBuffer.size() - prefixLen);
208 33 : memcpy(outBuffer.data(), kQRCodePrefix, prefixLen);
209 33 : err = base38Encode(bits, subSpan);
210 : // Reduce output span size to be the size of written data
211 33 : outBuffer.reduce_size(subSpan.size() + prefixLen);
212 : }
213 :
214 33 : return err;
215 : }
216 :
217 10 : CHIP_ERROR QRCodeSetupPayloadGenerator::payloadBase38Representation(std::string & base38Representation)
218 : {
219 : // 6.1.2.2. Table: Packed Binary Data Structure
220 : // The TLV Data should be 0 length if TLV is not included.
221 10 : return payloadBase38Representation(base38Representation, nullptr, 0);
222 : }
223 :
224 3 : CHIP_ERROR QRCodeSetupPayloadGenerator::payloadBase38RepresentationWithAutoTLVBuffer(std::string & base38Representation)
225 : {
226 : // Estimate the size of the needed buffer.
227 3 : size_t estimate = 0;
228 :
229 4 : auto dataItemSizeEstimate = [](const OptionalQRCodeInfo & item) {
230 : // Each data item needs a control byte and a context tag.
231 4 : size_t size = 2;
232 :
233 4 : if (item.type == optionalQRCodeInfoTypeString)
234 : {
235 : // We'll need to encode the string length and then the string data.
236 : // Length is at most 8 bytes.
237 3 : size += 8;
238 3 : size += item.data.size();
239 : }
240 : else
241 : {
242 : // Integer. Assume it might need up to 8 bytes, for simplicity.
243 1 : size += 8;
244 : }
245 4 : return size;
246 : };
247 :
248 3 : auto vendorData = mPayload.getAllOptionalVendorData();
249 5 : for (auto & data : vendorData)
250 : {
251 2 : estimate += dataItemSizeEstimate(data);
252 : }
253 :
254 3 : auto extensionData = mPayload.getAllOptionalExtensionData();
255 5 : for (auto & data : extensionData)
256 : {
257 2 : estimate += dataItemSizeEstimate(data);
258 : }
259 :
260 3 : estimate = TLV::EstimateStructOverhead(estimate);
261 :
262 3 : VerifyOrReturnError(CanCastTo<uint32_t>(estimate), CHIP_ERROR_NO_MEMORY);
263 :
264 3 : Platform::ScopedMemoryBuffer<uint8_t> buf;
265 3 : VerifyOrReturnError(buf.Alloc(estimate), CHIP_ERROR_NO_MEMORY);
266 :
267 3 : return payloadBase38Representation(base38Representation, buf.Get(), static_cast<uint32_t>(estimate));
268 3 : }
269 :
270 38 : CHIP_ERROR QRCodeSetupPayloadGenerator::payloadBase38Representation(std::string & base38Representation, uint8_t * tlvDataStart,
271 : uint32_t tlvDataStartSize)
272 : {
273 38 : size_t tlvDataLengthInBytes = 0;
274 :
275 38 : VerifyOrReturnError(mAllowInvalidPayload || mPayload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT);
276 36 : ReturnErrorOnFailure(generateTLVFromOptionalData(mPayload, tlvDataStart, tlvDataStartSize, tlvDataLengthInBytes));
277 :
278 33 : std::vector<uint8_t> bits(kTotalPayloadDataSizeInBytes + tlvDataLengthInBytes);
279 33 : MutableByteSpan bitsSpan(bits.data(), bits.capacity());
280 33 : std::vector<char> buffer(base38EncodedLength(bits.capacity()) + strlen(kQRCodePrefix));
281 33 : MutableCharSpan bufferSpan(buffer.data(), buffer.capacity());
282 :
283 33 : ReturnErrorOnFailure(payloadBase38RepresentationWithTLV(mPayload, bufferSpan, bitsSpan, tlvDataStart, tlvDataLengthInBytes));
284 :
285 33 : base38Representation.assign(bufferSpan.data());
286 33 : return CHIP_NO_ERROR;
287 33 : }
288 :
289 0 : CHIP_ERROR QRCodeBasicSetupPayloadGenerator::payloadBase38Representation(MutableCharSpan & outBuffer)
290 : {
291 : uint8_t bits[kTotalPayloadDataSizeInBytes];
292 0 : VerifyOrReturnError(mPayload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT);
293 :
294 0 : return payloadBase38RepresentationWithTLV(mPayload, outBuffer, MutableByteSpan(bits), nullptr, 0);
295 : }
296 :
297 : } // namespace chip
|