Keyple Card Calypso C++ Library - 3.2.2
Component of the Keyple C++ middleware
CalypsoCardSelectionExtensionAdapter.cpp
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ *
3 * *
4 * See the NOTICE file(s) distributed with this work for additional *
5 * information regarding copyright ownership. *
6 * *
7 * This program and the accompanying materials are made available under the *
8 * terms of the Eclipse Public License 2.0 which is available at *
9 * http://www.eclipse.org/legal/epl-2.0 *
10 * *
11 * SPDX-License-Identifier: EPL-2.0 *
12 ******************************************************************************/
13
14#include "keyple/card/calypso/CalypsoCardSelectionExtensionAdapter.hpp"
15
16#include <memory>
17#include <string>
18#include <vector>
19
20#include "keyple/card/calypso/CalypsoCardAdapter.hpp"
21#include "keyple/card/calypso/CalypsoCardConstant.hpp"
22#include "keyple/card/calypso/CardDataAccessException.hpp"
23#include "keyple/card/calypso/CommandGetDataEfList.hpp"
24#include "keyple/card/calypso/CommandGetDataFci.hpp"
25#include "keyple/card/calypso/CommandGetDataFcp.hpp"
26#include "keyple/card/calypso/CommandGetDataTraceabilityInformation.hpp"
27#include "keyple/card/calypso/CommandOpenSecureSession.hpp"
28#include "keyple/card/calypso/CommandReadBinary.hpp"
29#include "keyple/card/calypso/CommandReadRecords.hpp"
30#include "keyple/card/calypso/CommandSelectFile.hpp"
31#include "keyple/core/util/HexUtil.hpp"
32#include "keyple/core/util/KeypleAssert.hpp"
33#include "keyple/core/util/cpp/exception/IllegalArgumentException.hpp"
34#include "keyple/core/util/cpp/exception/IllegalStateException.hpp"
35#include "keyple/core/util/cpp/exception/UnsupportedOperationException.hpp"
36#include "keypop/calypso/card/transaction/InconsistentDataException.hpp"
37#include "keypop/calypso/card/transaction/SelectFileException.hpp"
38#include "keypop/card/CardResponseApi.hpp"
39#include "keypop/card/ParseException.hpp"
40#include "keypop/reader/selection/InvalidCardResponseException.hpp"
41
42namespace keyple {
43namespace card {
44namespace calypso {
45
46using keyple::core::util::Assert;
47using keyple::core::util::HexUtil;
48using keyple::core::util::cpp::exception::IllegalArgumentException;
49using keyple::core::util::cpp::exception::IllegalStateException;
50using keyple::core::util::cpp::exception::UnsupportedOperationException;
51using keypop::calypso::card::transaction::InconsistentDataException;
52using keypop::calypso::card::transaction::SelectFileException;
53using keypop::card::CardResponseApi;
54using keypop::card::ParseException;
55using keypop::reader::selection::InvalidCardResponseException;
56
57const int CalypsoCardSelectionExtensionAdapter::SW_CARD_INVALIDATED = 0x6283;
58
59CalypsoCardSelectionExtensionAdapter::CalypsoCardSelectionExtensionAdapter()
60: mTransactionContext(
61 std::unique_ptr<DtoAdapters::TransactionContextDto>(
62 new DtoAdapters::TransactionContextDto()))
63, mCommandContext(
64 std::unique_ptr<DtoAdapters::CommandContextDto>(
65 new DtoAdapters::CommandContextDto(false, false)))
66, mIsPreOpenPrepared(false)
67, mIsInvalidatedCardAccepted(false)
68{
69}
70
71CalypsoCardSelectionExtension&
72CalypsoCardSelectionExtensionAdapter::acceptInvalidatedCard()
73{
74 mIsInvalidatedCardAccepted = true;
75
76 return *this;
77}
78
79CalypsoCardSelectionExtension&
80CalypsoCardSelectionExtensionAdapter::prepareReadRecord(
81 std::uint8_t sfi, int recordNumber)
82{
83 Assert::getInstance()
84 .isInRange(
85 sfi,
86 CalypsoCardConstant::SFI_MIN,
87 CalypsoCardConstant::SFI_MAX,
88 "sfi")
89 .isInRange(
90 recordNumber,
91 CalypsoCardConstant::NB_REC_MIN,
92 CalypsoCardConstant::NB_REC_MAX,
93 "recordNumber");
94
95 mCommands.push_back(
96 std::make_shared<CommandReadRecords>(
97 mTransactionContext,
98 mCommandContext,
99 sfi,
100 recordNumber,
101 CommandReadRecords::ReadMode::ONE_RECORD,
102 nullptr,
103 0));
104
105 return *this;
106}
107
108CalypsoCardSelectionExtension&
109CalypsoCardSelectionExtensionAdapter::prepareSelectFile(std::uint16_t lid)
110{
111 mCommands.push_back(
112 std::make_shared<CommandSelectFile>(
113 mTransactionContext, mCommandContext, lid));
114
115 return *this;
116}
117
118CalypsoCardSelectionExtension&
119CalypsoCardSelectionExtensionAdapter::prepareSelectFile(
120 SelectFileControl selectControl)
121{
122 mCommands.push_back(
123 std::make_shared<CommandSelectFile>(
124 mTransactionContext, mCommandContext, selectControl));
125
126 return *this;
127}
128
129std::unique_ptr<CardSelectionRequestSpi>
130CalypsoCardSelectionExtensionAdapter::getCardSelectionRequest()
131{
132 std::unique_ptr<DtoAdapters::CardSelectionRequestAdapter>
133 cardSelectionRequest;
134
135 if (mCommands.empty()) {
136 cardSelectionRequest
137 = std::unique_ptr<DtoAdapters::CardSelectionRequestAdapter>(
138 new DtoAdapters::CardSelectionRequestAdapter(nullptr));
139
140 } else {
141 std::vector<std::shared_ptr<ApduRequestSpi>> cardSelectionApduRequests;
142 for (const auto& command : mCommands) {
143 cardSelectionApduRequests.push_back(command->getApduRequest());
144 }
145 cardSelectionRequest
146 = std::unique_ptr<DtoAdapters::CardSelectionRequestAdapter>(
147 new DtoAdapters::CardSelectionRequestAdapter(
148 std::unique_ptr<DtoAdapters::CardRequestAdapter>(
149 new DtoAdapters::CardRequestAdapter(
150 cardSelectionApduRequests, false))));
151 }
152
153 if (mIsInvalidatedCardAccepted) {
154 cardSelectionRequest->addSuccessfulSelectionStatusWord(
155 SW_CARD_INVALIDATED);
156 }
157
158 return cardSelectionRequest;
159}
160
161CalypsoCardSelectionExtension&
162CalypsoCardSelectionExtensionAdapter::prepareReadBinary(
163 std::uint8_t sfi, int offset, int nbBytesToRead)
164{
165 Assert::getInstance()
166 .isInRange(
167 static_cast<int>(sfi),
168 CalypsoCardConstant::SFI_MIN,
169 CalypsoCardConstant::SFI_MAX,
170 "sfi")
171 .isInRange(
172 offset,
173 CalypsoCardConstant::OFFSET_MIN,
174 CalypsoCardConstant::OFFSET_BINARY_MAX,
175 "offset")
176 .greaterOrEqual(nbBytesToRead, 1, "nbBytesToRead");
177
178 if (sfi > 0 && offset > 255) { // FFh
179 /*
180 * Tips to select the file: add a "Read Binary" command (read one byte
181 * at offset 0).
182 */
183 mCommands.push_back(
184 std::make_shared<CommandReadBinary>(
185 mTransactionContext, mCommandContext, sfi, 0, 1));
186 }
187
188 int currentOffset = offset;
189 int nbBytesRemainingToRead = nbBytesToRead;
190
191 do {
192 int currentLength = std::min(
193 nbBytesRemainingToRead,
194 CalypsoCardConstant::DEFAULT_PAYLOAD_CAPACITY);
195
196 mCommands.push_back(
197 std::make_shared<CommandReadBinary>(
198 mTransactionContext,
199 mCommandContext,
200 sfi,
201 currentOffset,
202 currentLength));
203
204 currentOffset += currentLength;
205 nbBytesRemainingToRead -= currentLength;
206
207 } while (nbBytesRemainingToRead > 0);
208
209 return *this;
210}
211
212CalypsoCardSelectionExtension&
213CalypsoCardSelectionExtensionAdapter::prepareReadCounter(
214 std::uint8_t sfi, int nbCountersToRead)
215{
216 Assert::getInstance()
217 .isInRange(
218 static_cast<int>(sfi),
219 CalypsoCardConstant::SFI_MIN,
220 CalypsoCardConstant::SFI_MAX,
221 "sfi")
222 .isInRange(
223 nbCountersToRead,
224 0,
225 CalypsoCardConstant::DEFAULT_PAYLOAD_CAPACITY / 3,
226 "nbCountersToRead");
227
228 mCommands.push_back(
229 std::make_shared<CommandReadRecords>(
230 mTransactionContext,
231 mCommandContext,
232 sfi,
233 1,
234 CommandReadRecords::ReadMode::ONE_RECORD,
235 std::unique_ptr<int>(new int(nbCountersToRead * 3)),
236 0));
237
238 return *this;
239}
240
241CalypsoCardSelectionExtension&
242CalypsoCardSelectionExtensionAdapter::preparePreOpenSecureSession(
243 WriteAccessLevel writeAccessLevel)
244{
245 if (mIsPreOpenPrepared) {
246 throw IllegalStateException(
247 "'Pre-Open Secure Session' command is already prepared");
248 }
249
250 mCommands.push_back(
251 std::make_shared<CommandOpenSecureSession>(
252 mTransactionContext, mCommandContext, writeAccessLevel));
253
254 mIsPreOpenPrepared = true;
255
256 return *this;
257}
258
259CalypsoCardSelectionExtension&
260CalypsoCardSelectionExtensionAdapter::prepareGetData(GetDataTag tag)
261{
262 switch (tag) {
263 case GetDataTag::FCI_FOR_CURRENT_DF:
264 mCommands.push_back(
265 std::make_shared<CommandGetDataFci>(
266 mTransactionContext, mCommandContext));
267 break;
268 case GetDataTag::FCP_FOR_CURRENT_FILE:
269 mCommands.push_back(
270 std::make_shared<CommandGetDataFcp>(
271 mTransactionContext, mCommandContext));
272 break;
273 case GetDataTag::EF_LIST:
274 mCommands.push_back(
275 std::make_shared<CommandGetDataEfList>(
276 mTransactionContext, mCommandContext));
277 break;
278 case GetDataTag::TRACEABILITY_INFORMATION:
279 mCommands.push_back(
280 std::make_shared<CommandGetDataTraceabilityInformation>(
281 mTransactionContext, mCommandContext));
282 break;
283 default:
284 throw UnsupportedOperationException(
285 "Unsupported GetDataTag: " + std::to_string(static_cast<int>(tag)));
286 }
287
288 return *this;
289}
290
291std::shared_ptr<SmartCardSpi>
292CalypsoCardSelectionExtensionAdapter::parse(
293 const std::shared_ptr<CardSelectionResponseApi>& cardSelectionResponse)
294{
295 std::shared_ptr<CardResponseApi> cardResponse
296 = cardSelectionResponse->getCardResponse();
297
298 std::vector<std::shared_ptr<ApduResponseApi>> apduResponses
299 = cardResponse != nullptr
300 ? cardResponse->getApduResponses()
301 : std::vector<std::shared_ptr<ApduResponseApi>> {};
302
303 if (mCommands.size() != apduResponses.size()) {
304 throw ParseException(
305 "The number of commands/responses does not match. Expected "
306 + std::to_string(mCommands.size()) + " responses, got "
307 + std::to_string(apduResponses.size()));
308 }
309
310 std::shared_ptr<CalypsoCardAdapter> calypsoCard;
311
312 try {
313 calypsoCard = std::make_shared<CalypsoCardAdapter>();
314 calypsoCard->initialize(cardSelectionResponse);
315 if (!mCommands.empty()) {
316 parseApduResponses(calypsoCard, mCommands, apduResponses);
317 }
318
319 } catch (const Exception& e) {
320 throw ParseException(
321 "Invalid card response", std::make_shared<Exception>(e));
322 }
323
324 if (calypsoCard->getProductType() == CalypsoCard::ProductType::UNKNOWN
325 && cardSelectionResponse->getSelectApplicationResponse() == nullptr
326 && cardSelectionResponse->getPowerOnData() == "") {
327 throw ParseException(
328 std::string("No power-on data and no FCI provided. Unable to ")
329 + "create a CalypsoCard");
330 }
331
332 return calypsoCard;
333}
334
335void
336CalypsoCardSelectionExtensionAdapter::parseApduResponses(
337 const std::shared_ptr<CalypsoCardAdapter>& calypsoCard,
338 const std::vector<std::shared_ptr<Command>>& commands,
339 const std::vector<std::shared_ptr<ApduResponseApi>>& apduResponses)
340{
341 /*
342 * If there are more responses than requests, then we are unable to fill the
343 * card image. In this case we stop processing immediately because it may be
344 * a case of fraud, and we throw a desynchronized exception.
345 */
346 if (apduResponses.size() > commands.size()) {
347 throw InconsistentDataException(
348 std::string("The number of commands/responses does not match. ")
349 + "Expected " + std::to_string(commands.size()) + " responses, "
350 + "got " + std::to_string(apduResponses.size()));
351 }
352
353 /*
354 * We go through all the responses (and not the requests) because there may
355 * be fewer in the case of an error that occurred in strict mode. In this
356 * case the last response will raise an exception.
357 */
358 for (int i = 0; i < static_cast<int>(apduResponses.size()); i++) {
359 try {
360 commands[i]->parseResponseForSelection(
361 apduResponses[i], calypsoCard);
362
363 } catch (const CardCommandException& e) {
364 CardCommandRef commandRef = commands[i]->getCommandRef();
365 if (commandRef == CardCommandRef::READ_RECORDS
366 || commandRef == CardCommandRef::READ_BINARY
367 || commandRef == CardCommandRef::OPEN_SECURE_SESSION) {
368 continue;
369 }
370
371 try {
372 auto cardDataAccessException
373 = dynamic_cast<const CardDataAccessException&>(e);
374 if (commandRef == CardCommandRef::SELECT_FILE) {
375 throw SelectFileException("File not found", e);
376
377 } else {
378 /* Go to the catch section to avoid code duplication. */
379 throw std::bad_cast();
380 }
381
382 } catch (const std::bad_cast&) {
383 const std::string sw = commands[i]->getApduResponse() != nullptr
384 ? HexUtil::toHex(
385 static_cast<std::uint16_t>(
386 commands[i]
387 ->getApduResponse()
388 ->getStatusWord()))
389 : "null";
390 throw InvalidCardResponseException(
391 std::string("Failed to process SAM response. ")
392 + "Command: " + commandRef.getName() + ", "
393 + "SW: " + sw,
394 e);
395 }
396 }
397 }
398
399 /*
400 * Finally, if no error has occurred and there are fewer responses than
401 * requests, then we throw a desynchronized exception.
402 */
403 if (apduResponses.size() < commands.size()) {
404 throw InconsistentDataException(
405 std::string("The number of commands/responses does not match. ")
406 + "Expected " + std::to_string(commands.size()) + " responses, "
407 + "got " + std::to_string(apduResponses.size()));
408 }
409}
410
411} /* namespace calypso */
412} /* namespace card */
413} /* namespace keyple */