blob: a4d9d8a1ba1f0fc8c6a5ea07cf7b4079d4096f94 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/apdu/apdu_command.h"
namespace apdu {
namespace {
// APDU command data length is 2 bytes encoded in big endian order.
uint16_t ParseMessageLength(base::span<const uint8_t> message, size_t offset) {
DCHECK_GE(message.size(), offset + 2);
return (message[offset] << 8) | message[offset + 1];
}
} // namespace
base::Optional<ApduCommand> ApduCommand::CreateFromMessage(
base::span<const uint8_t> message) {
if (message.size() < kApduMinHeader || message.size() > kApduMaxLength)
return base::nullopt;
uint8_t cla = message[0];
uint8_t ins = message[1];
uint8_t p1 = message[2];
uint8_t p2 = message[3];
size_t response_length = 0;
std::vector<uint8_t> data;
switch (message.size()) {
// No data present; no expected response.
case kApduMinHeader:
break;
// Invalid encoding sizes.
case kApduMinHeader + 1:
case kApduMinHeader + 2:
return base::nullopt;
// No data present; response expected.
case kApduMinHeader + 3:
// Fifth byte must be 0.
if (message[4] != 0)
return base::nullopt;
response_length = ParseMessageLength(message, kApduCommandLengthOffset);
// Special case where response length of 0x0000 corresponds to 65536
// as defined in ISO7816-4.
if (response_length == 0)
response_length = kApduMaxResponseLength;
break;
default:
// Fifth byte must be 0.
if (message[4] != 0)
return base::nullopt;
auto data_length = ParseMessageLength(message, kApduCommandLengthOffset);
if (message.size() == data_length + kApduCommandDataOffset) {
// No response expected.
data.insert(data.end(), message.begin() + kApduCommandDataOffset,
message.end());
} else if (message.size() == data_length + kApduCommandDataOffset + 2) {
// Maximum response size is stored in final 2 bytes.
data.insert(data.end(), message.begin() + kApduCommandDataOffset,
message.end() - 2);
auto response_length_offset = kApduCommandDataOffset + data_length;
response_length = ParseMessageLength(message, response_length_offset);
// Special case where response length of 0x0000 corresponds to 65536
// as defined in ISO7816-4.
if (response_length == 0)
response_length = kApduMaxResponseLength;
} else {
return base::nullopt;
}
break;
}
return ApduCommand(cla, ins, p1, p2, response_length, std::move(data));
}
ApduCommand::ApduCommand() = default;
ApduCommand::ApduCommand(uint8_t cla,
uint8_t ins,
uint8_t p1,
uint8_t p2,
size_t response_length,
std::vector<uint8_t> data)
: cla_(cla),
ins_(ins),
p1_(p1),
p2_(p2),
response_length_(response_length),
data_(std::move(data)) {}
ApduCommand::ApduCommand(ApduCommand&& that) = default;
ApduCommand& ApduCommand::operator=(ApduCommand&& that) = default;
ApduCommand::~ApduCommand() = default;
std::vector<uint8_t> ApduCommand::GetEncodedCommand() const {
std::vector<uint8_t> encoded = {cla_, ins_, p1_, p2_};
// If data exists, request size (Lc) is encoded in 3 bytes, with the first
// byte always being null, and the other two bytes being a big-endian
// representation of the request size. If data length is 0, response size (Le)
// will be prepended with a null byte.
if (!data_.empty()) {
size_t data_length = data_.size();
encoded.push_back(0x0);
if (data_length > kApduMaxDataLength)
data_length = kApduMaxDataLength;
encoded.push_back((data_length >> 8) & 0xff);
encoded.push_back(data_length & 0xff);
encoded.insert(encoded.end(), data_.begin(), data_.begin() + data_length);
} else if (response_length_ > 0) {
encoded.push_back(0x0);
}
if (response_length_ > 0) {
size_t response_length = response_length_;
if (response_length > kApduMaxResponseLength)
response_length = kApduMaxResponseLength;
// A zero value represents a response length of 65,536 bytes.
encoded.push_back((response_length >> 8) & 0xff);
encoded.push_back(response_length & 0xff);
}
return encoded;
}
} // namespace apdu