Keyple Card Calypso C++ Library - 3.2.2
Component of the Keyple C++ middleware
CommandReadRecords.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/CommandReadRecords.hpp"
15
16#include <map>
17#include <memory>
18#include <string>
19#include <utility>
20#include <vector>
21
22#include "keyple/card/calypso/CalypsoCardAdapter.hpp"
23#include "keyple/card/calypso/CalypsoCardClass.hpp"
24#include "keyple/card/calypso/CardAccessForbiddenException.hpp"
25#include "keyple/card/calypso/CardCommandRef.hpp"
26#include "keyple/card/calypso/CardDataAccessException.hpp"
27#include "keyple/card/calypso/CardIllegalParameterException.hpp"
28#include "keyple/card/calypso/CardSecurityContextException.hpp"
29#include "keyple/core/util/ApduUtil.hpp"
30#include "keyple/core/util/HexUtil.hpp"
31#include "keyple/core/util/cpp/Arrays.hpp"
32#include "keyple/core/util/cpp/System.hpp"
33
34namespace keyple {
35namespace card {
36namespace calypso {
37
38using keyple::core::util::ApduUtil;
39using keyple::core::util::HexUtil;
40using keyple::core::util::cpp::Arrays;
41using keyple::core::util::cpp::System;
42
43const std::map<int, const std::shared_ptr<Command::StatusProperties>>
44 CommandReadRecords::STATUS_TABLE = [] {
45 std::map<int, const std::shared_ptr<Command::StatusProperties>> m(
46 Command::STATUS_TABLE);
47
48 m.insert(
49 {{0x6981,
50 std::make_shared<Command::StatusProperties>(
51 "Command forbidden on binary files",
52 typeid(CardDataAccessException))},
53 {0x6982,
54 std::make_shared<Command::StatusProperties>(
55 "Security conditions not fulfilled (PIN code not presented, "
56 "encryption required)",
57 typeid(CardSecurityContextException))},
58 {0x6985,
59 std::make_shared<Command::StatusProperties>(
60 "Access forbidden (Never access mode, stored value log file "
61 "and a stored value "
62 "operation was done during the current session)",
63 typeid(CardAccessForbiddenException))},
64 {0x6986,
65 std::make_shared<Command::StatusProperties>(
66 "Command not allowed (no current EF)",
67 typeid(CardDataAccessException))},
68 {0x6A82,
69 std::make_shared<Command::StatusProperties>(
70 "File not found", typeid(CardDataAccessException))},
71 {0x6A83,
72 std::make_shared<Command::StatusProperties>(
73 "Record not found (record index is 0, or above NumRec",
74 typeid(CardDataAccessException))},
75 {0x6B00,
76 std::make_shared<Command::StatusProperties>(
77 "P2 value not supported",
78 typeid(CardIllegalParameterException))}});
79 return m;
80 }();
81
82CommandReadRecords::CommandReadRecords(
83 const std::shared_ptr<DtoAdapters::TransactionContextDto>&
84 transactionContext,
85 const std::shared_ptr<DtoAdapters::CommandContextDto>& commandContext,
86 int sfi,
87 int firstRecordNumber,
88 ReadMode readMode,
89 std::unique_ptr<int> expectedLength,
90 int recordSize)
91: Command(
92 CardCommandRef::READ_RECORDS,
93 std::move(expectedLength),
94 transactionContext,
95 commandContext)
96, mSfi(sfi)
97, mFirstRecordNumber(firstRecordNumber)
98, mRecordSize(recordSize)
99, mReadMode(readMode)
100, mIsPreOpenMode(
101 transactionContext->getCard() != nullptr
102 && transactionContext->getCard()->getPreOpenWriteAccessLevel()
103 != WriteAccessLevel::UNKOWN)
104{
105 const std::uint8_t cardClass
106 = transactionContext->getCard() != nullptr
107 ? transactionContext->getCard()->getCardClass().getValue()
108 : CalypsoCardClass::ISO.getValue();
109
110 const std::uint8_t p1 = static_cast<std::uint8_t>(firstRecordNumber);
111 std::uint8_t p2
112 = static_cast<std::uint8_t>((sfi == 0x00) ? 0x05 : ((sfi * 8) + 5));
113 if (readMode == ReadMode::ONE_RECORD) {
114 p2 -= 1;
115 }
116 /*
117 * Careful, 'expectedLength' is already std::move()'d into the Command base
118 * class subobject above, so it is unconditionally null here. Read the
119 * value back through the base class accessor instead.
120 */
121 const int* expectedResponseLength = getExpectedResponseLength();
122 const std::uint8_t le = static_cast<std::uint8_t>(
123 expectedResponseLength != nullptr ? *expectedResponseLength : 0x00);
124
125 /* APDU Case 2 */
126 setApduRequest(
127 std::make_shared<DtoAdapters::ApduRequestAdapter>(ApduUtil::build(
128 cardClass, getCommandRef().getInstructionByte(), p1, p2, le)));
129
130 const std::string subName
131 = std::string("SFI: ") + HexUtil::toHex(static_cast<std::uint32_t>(sfi))
132 + "h, Rec: " + std::to_string(firstRecordNumber) + ", Read mode: "
133 + (readMode == CommandReadRecords::ReadMode::ONE_RECORD
134 ? "ONE_RECORD"
135 : "MULTIPLE_RECORD")
136 + ", Expected length: "
137 + (expectedResponseLength != nullptr
138 ? std::to_string(*expectedResponseLength)
139 : "null");
140 addSubName(subName);
141}
142
143void
144CommandReadRecords::finalizeRequest()
145{
146 encryptRequestAndUpdateTerminalSessionMacIfNeeded();
147}
148
149bool
150CommandReadRecords::isCryptoServiceRequiredToFinalizeRequest() const
151{
152 return getCommandContext()->isEncryptionActive();
153}
154
155bool
156CommandReadRecords::synchronizeCryptoServiceBeforeCardProcessing()
157{
158 if (!getCommandContext()->isSecureSessionOpen()) {
159 return true; /* Nothing to synchronize */
160 }
161
162 if (getCommandContext()->isEncryptionActive()) {
163 return false;
164 }
165
166 if (!mIsPreOpenMode) {
167 return false;
168 }
169
170 /* Pre-open mode without encryption in secure session */
171 if (!isCryptoServiceSynchronized()) {
172 const std::vector<std::uint8_t> anticipatedApduResponse(
173 buildAnticipatedResponse());
174 if (anticipatedApduResponse.empty()) {
175 const std::string sfiHex
176 = HexUtil::toHex(static_cast<std::uint32_t>(mSfi));
177 mLogger->warn(
178 std::string("Unable to determine anticipated APDU response ")
179 + "because the record or some records have not been read "
180 + "beforehand [command=%, sfi=%, record=%]\n",
181 getName(),
182 sfiHex,
183 mFirstRecordNumber);
184
185 return false;
186 }
187
188 mAnticipatedDataOut = Arrays::copyOf(
189 anticipatedApduResponse, anticipatedApduResponse.size() - 2);
190
191 updateTerminalSessionIfNeeded(anticipatedApduResponse);
192 }
193
194 return true;
195}
196
197void
198CommandReadRecords::parseResponse(std::shared_ptr<ApduResponseApi> apduResponse)
199{
200 decryptResponseAndUpdateTerminalSessionMacIfNeeded(apduResponse);
201 if (!setApduResponseAndCheckStatusInBestEffortMode(apduResponse)) {
202 return;
203 }
204
205 const std::vector<std::uint8_t> dataOut = apduResponse->getDataOut();
206 if (mReadMode == CommandReadRecords::ReadMode::ONE_RECORD) {
207 getTransactionContext()->getCard()->setContent(
208 static_cast<std::uint8_t>(mSfi),
209 static_cast<std::uint8_t>(mFirstRecordNumber),
210 dataOut);
211 } else {
212 int apduLen = static_cast<int>(dataOut.size());
213 int index = 0;
214 while (apduLen > 0) {
215 const std::uint8_t recordNb = dataOut[index++];
216 const std::uint8_t len = dataOut[index++];
217 getTransactionContext()->getCard()->setContent(
218 static_cast<std::uint8_t>(mSfi),
219 recordNb,
220 Arrays::copyOfRange(dataOut, index, index + len));
221 index += len;
222 apduLen -= (2 + len);
223 }
224 }
225 if (!isCryptoServiceSynchronized()) {
226 updateTerminalSessionIfNeeded();
227 } else if (
228 getCommandContext()->isSecureSessionOpen() && mIsPreOpenMode
229 && !Arrays::equals(dataOut, mAnticipatedDataOut)) {
230 throw CardSecurityContextException(
231 "Data out does not match the anticipated data out",
232 CardCommandRef::READ_RECORDS);
233 }
234}
235
236const std::map<int, const std::shared_ptr<Command::StatusProperties>>&
237CommandReadRecords::getStatusTable() const
238{
239 return STATUS_TABLE;
240}
241
242std::vector<std::uint8_t>
243CommandReadRecords::buildAnticipatedResponse()
244{
245 std::shared_ptr<ElementaryFile> ef
246 = getTransactionContext()->getCard()->getFileBySfi(
247 static_cast<std::uint8_t>(mSfi));
248 if (ef == nullptr) {
249 return {};
250 }
251
252 return mReadMode == CommandReadRecords::ReadMode::ONE_RECORD
253 ? buildAnticipatedResponseForOneRecordMode(ef)
254 : buildAnticipatedResponseForMultipleRecordsMode(ef);
255}
256
257std::vector<std::uint8_t>
258CommandReadRecords::buildAnticipatedResponseForOneRecordMode(
259 const std::shared_ptr<ElementaryFile>& ef)
260{
261 const std::vector<std::uint8_t> content = ef->getData()->getContent(
262 static_cast<std::uint8_t>(mFirstRecordNumber));
263
264 const int expectedResponseLength = *getExpectedResponseLength();
265 if (content.size() > 0
266 && static_cast<int>(content.size()) >= expectedResponseLength) {
267 const int length = expectedResponseLength != 0
268 ? expectedResponseLength
269 : static_cast<int>(content.size());
270 std::vector<std::uint8_t> apdu(length + 2);
271 /* Record content */
272 System::arraycopy(content, 0, apdu, 0, length);
273 /* SW 9000 */
274 apdu[length] = 0x90;
275
276 return apdu;
277 }
278
279 return {};
280}
281
282std::vector<std::uint8_t>
283CommandReadRecords::buildAnticipatedResponseForMultipleRecordsMode(
284 const std::shared_ptr<ElementaryFile>& ef)
285{
286 std::vector<std::uint8_t> apdu(*getExpectedResponseLength() + 2);
287 const int nbRecords = *getExpectedResponseLength() / (mRecordSize + 2);
288 const int lastRecordNumber = mFirstRecordNumber + nbRecords - 1;
289 int index = 0;
290
291 for (int i = mFirstRecordNumber; i <= lastRecordNumber; i++) {
292 const std::vector<std::uint8_t> content
293 = ef->getData()->getContent(static_cast<std::uint8_t>(i));
294 if (static_cast<int>(content.size()) >= mRecordSize) {
295 /* Record number */
296 apdu[index++] = static_cast<std::uint8_t>(i);
297 /* Record size */
298 apdu[index++] = static_cast<std::uint8_t>(mRecordSize);
299 /* Record content */
300 System::arraycopy(content, 0, apdu, index, mRecordSize);
301 index += mRecordSize;
302 } else {
303 return {};
304 }
305 }
306
307 /* SW 9000 */
308 apdu[index] = 0x90;
309
310 return apdu;
311}
312
313} /* namespace calypso */
314} /* namespace card */
315} /* namespace keyple */