1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <libminradio/sim/apps/FilesystemApp.h>
18
19 #include "tlv.h"
20
21 #include <android-base/logging.h>
22 #include <libminradio/binder_printing.h>
23 #include <libminradio/sim/IccConstants.h>
24 #include <libminradio/sim/IccUtils.h>
25
26 #include <unordered_set>
27
28 namespace android::hardware::radio::minimal::sim::apps {
29
30 using namespace ::android::hardware::radio::minimal::binder_printing;
31 using namespace ::android::hardware::radio::minimal::sim::constants;
32 namespace aidl = ::aidl::android::hardware::radio::sim;
33
34 // ETSI TS 102 221 11.1.1.2 Table 11.1: Coding of P1 for SELECT
35 static constexpr uint8_t SELECT_BY_FILE_ID = 0x00;
36
37 // ETSI TS 102 221 11.1.1.2 Table 11.2: Coding of P2 for SELECT
38 static constexpr uint8_t SELECT_RETURN_FCP_TEMPLATE = 0x04;
39 static constexpr uint8_t SELECT_RETURN_NOTHING = 0x0C;
40
41 // From android.carrierapi.cts.FcpTemplate
42 static constexpr uint8_t BER_TAG_FCP_TEMPLATE = 0x62;
43 static constexpr uint8_t FILE_IDENTIFIER = 0x83;
44
45 static const std::unordered_set<int32_t> kLinearFixedFiles{EF_MSISDN};
46
47 class FilesystemApp::FilesystemChannel : public App::Channel {
48 public:
49 FilesystemChannel(int32_t channelId, std::shared_ptr<Filesystem> filesystem);
50
51 void select(Filesystem::Path path);
52 aidl::IccIoResult transmit(const aidl::SimApdu& message) override;
53
54 private:
55 std::shared_ptr<Filesystem> mFilesystem;
56 Filesystem::Path mSelectedFile = paths::mf;
57
58 aidl::IccIoResult commandSelect(int32_t p1, int32_t p2, int32_t p3, const std::string& data);
59 aidl::IccIoResult commandStatus(int32_t p1) const;
60 aidl::IccIoResult commandReadBinary(int32_t p1, int32_t p2) const;
61 aidl::IccIoResult commandUpdateBinary(int32_t p1, int32_t p2, std::string_view data);
62 aidl::IccIoResult commandReadRecord(int32_t p1, int32_t p2, int32_t p3);
63 aidl::IccIoResult commandGetResponse() const;
64 };
65
FilesystemApp(const std::shared_ptr<Filesystem> & filesystem)66 FilesystemApp::FilesystemApp(const std::shared_ptr<Filesystem>& filesystem)
67 : App(AID), mFilesystem(filesystem) {}
68
newChannel(int32_t id)69 std::shared_ptr<App::Channel> FilesystemApp::newChannel(int32_t id) {
70 auto channel = std::make_shared<FilesystemApp::FilesystemChannel>(id, mFilesystem);
71 if (id == 0) mBasicChannel = channel;
72 return channel;
73 }
74
FilesystemChannel(int32_t channelId,std::shared_ptr<Filesystem> filesystem)75 FilesystemApp::FilesystemChannel::FilesystemChannel( //
76 int32_t channelId, std::shared_ptr<Filesystem> filesystem)
77 : App::Channel(channelId), mFilesystem(filesystem) {}
78
select(Filesystem::Path path)79 void FilesystemApp::FilesystemChannel::select(Filesystem::Path path) {
80 mSelectedFile = path;
81 }
82
83 // android.carrierapi.cts.FcpTemplate.parseFcpTemplate (inversion)
makeFcpTemplate(const Filesystem::Path & path)84 static std::vector<uint8_t> makeFcpTemplate(const Filesystem::Path& path) {
85 // clang-format off
86 return makeTlv(BER_TAG_FCP_TEMPLATE,
87 makeTlv(FILE_IDENTIFIER, uint16ToBytes(path.fileId))
88 );
89 // clang-format on
90 }
91
92 // ETSI TS 102 221 11.1.1
commandSelect(int32_t p1,int32_t p2,int32_t length,const std::string & data)93 aidl::IccIoResult FilesystemApp::FilesystemChannel::commandSelect( //
94 int32_t p1, int32_t p2, int32_t length, const std::string& data) {
95 if (p1 != SELECT_BY_FILE_ID ||
96 (p2 != SELECT_RETURN_FCP_TEMPLATE && p2 != SELECT_RETURN_NOTHING)) {
97 return toIccIoResult(IO_RESULT_INCORRECT_P1_P2);
98 }
99 if (length != 2) { // file ids are 2 byte long
100 return toIccIoResult(IO_RESULT_INCORRECT_LENGTH | 2);
101 }
102
103 auto fileId = strtol(data.c_str(), nullptr, 16);
104 if (fileId <= 0 || fileId > 0xFFFF) {
105 LOG(WARNING) << "Incorrect file ID: " << data;
106 return toIccIoResult(IO_RESULT_INCORRECT_DATA);
107 }
108
109 auto path = mFilesystem->find(fileId);
110 if (!path.has_value()) {
111 LOG(WARNING) << "FilesystemChannel: file " << std::hex << fileId << " not found";
112 return toIccIoResult(IO_RESULT_FILE_NOT_FOUND);
113 }
114 select(*path);
115
116 if (p2 == SELECT_RETURN_FCP_TEMPLATE) {
117 return toIccIoResult(bytesToHexString(makeFcpTemplate(mSelectedFile)));
118 }
119 return toIccIoResult("");
120 }
121
122 // ETSI TS 102 221 11.1.2
commandStatus(int32_t p1) const123 aidl::IccIoResult FilesystemApp::FilesystemChannel::commandStatus(int32_t p1) const {
124 if (p1 != 0x00 && p1 != 0x01) { // 0x02 (termination) not implemented
125 return toIccIoResult(IO_RESULT_INCORRECT_P1_P2);
126 }
127 return toIccIoResult(bytesToHexString(makeFcpTemplate(mSelectedFile)));
128 }
129
130 // ETSI TS 102 221 11.1.3
commandReadBinary(int32_t offsetHi,int32_t offsetLo) const131 aidl::IccIoResult FilesystemApp::FilesystemChannel::commandReadBinary( //
132 int32_t offsetHi, int32_t offsetLo) const {
133 CHECK(offsetHi == 0 && offsetLo == 0) << "Offset not supported";
134 if (auto contents = mFilesystem->read(mSelectedFile); contents.has_value()) {
135 return toIccIoResult(*contents);
136 }
137 LOG(DEBUG) << "Missing ICC file (READ_BINARY): " << mSelectedFile.toString();
138 return toIccIoResult(IO_RESULT_FILE_NOT_FOUND);
139 }
140
141 // ETSI TS 102 221 11.1.4
commandUpdateBinary(int32_t offsetHi,int32_t offsetLo,std::string_view data)142 aidl::IccIoResult FilesystemApp::FilesystemChannel::commandUpdateBinary( //
143 int32_t offsetHi, int32_t offsetLo, std::string_view data) {
144 CHECK(offsetHi == 0 && offsetLo == 0) << "Offset not supported";
145 mFilesystem->write(mSelectedFile, hexStringToBytes(data));
146 return toIccIoResult("");
147 }
148
149 // ETSI TS 102 221 11.1.5
commandReadRecord(int32_t recordId,int32_t mode,int32_t length)150 aidl::IccIoResult FilesystemApp::FilesystemChannel::commandReadRecord( //
151 int32_t recordId, int32_t mode, int32_t length) {
152 CHECK(recordId == 1) << "Records other than no 1 are not supported";
153 CHECK(mode == 4) << "Unsupported record mode"; // absolute is the only currently supported mode
154 CHECK(length >= 0);
155 if (auto contents = mFilesystem->read(mSelectedFile); contents.has_value()) {
156 CHECK(static_cast<size_t>(length) == contents->size())
157 << "Partial reads not supported (" << length << " != " << contents->size() << ")";
158 return toIccIoResult(*contents);
159 }
160 LOG(DEBUG) << "Missing ICC file (READ_RECORD): " << mSelectedFile.toString();
161 return toIccIoResult(IO_RESULT_FILE_NOT_FOUND);
162 }
163
164 // com.android.internal.telephony.uicc.IccFileHandler (inversion)
165 // ETSI TS 102 221 12.1.1
commandGetResponse() const166 aidl::IccIoResult FilesystemApp::FilesystemChannel::commandGetResponse() const {
167 auto file = mSelectedFile;
168 auto contents = mFilesystem->read(file);
169 if (!contents.has_value()) {
170 LOG(DEBUG) << "Missing ICC file (GET_RESPONSE): " << file.toString();
171 return toIccIoResult(IO_RESULT_FILE_NOT_FOUND);
172 }
173 auto fileSize = contents->size();
174 CHECK(fileSize <= 0xFFFF) << "File size won't fit in GET_RESPONSE";
175
176 // 3GPP TS 51.011 9.2.1
177 std::vector<uint8_t> response(GET_RESPONSE_EF_SIZE_BYTES, 0);
178 response[RESPONSE_DATA_FILE_SIZE_1] = fileSize >> 8;
179 response[RESPONSE_DATA_FILE_SIZE_2] = 0xFF & fileSize;
180 response[RESPONSE_DATA_FILE_ID_1] = file.fileId >> 8;
181 response[RESPONSE_DATA_FILE_ID_2] = 0xFF & file.fileId;
182 response[RESPONSE_DATA_FILE_TYPE] = TYPE_EF;
183 response[RESPONSE_DATA_LENGTH] = GET_RESPONSE_EF_SIZE_BYTES - RESPONSE_DATA_STRUCTURE;
184 if (kLinearFixedFiles.contains(file.fileId)) {
185 response[RESPONSE_DATA_STRUCTURE] = EF_TYPE_LINEAR_FIXED;
186 response[RESPONSE_DATA_RECORD_LENGTH] = fileSize; // single record support only
187 } else {
188 response[RESPONSE_DATA_STRUCTURE] = EF_TYPE_TRANSPARENT;
189 }
190
191 return toIccIoResult(response);
192 }
193
transmit(const aidl::SimApdu & message)194 aidl::IccIoResult FilesystemApp::FilesystemChannel::transmit(const aidl::SimApdu& message) {
195 switch (message.instruction) {
196 case COMMAND_SELECT:
197 return commandSelect(message.p1, message.p2, message.p3, message.data);
198 case COMMAND_STATUS:
199 return commandStatus(message.p1);
200 case COMMAND_READ_BINARY:
201 return commandReadBinary(message.p1, message.p2);
202 case COMMAND_UPDATE_BINARY:
203 return commandUpdateBinary(message.p1, message.p2, message.data);
204 case COMMAND_READ_RECORD:
205 return commandReadRecord(message.p1, message.p2, message.p3);
206 case COMMAND_GET_RESPONSE:
207 return commandGetResponse();
208 default:
209 LOG(ERROR) << "Unsupported filesystem instruction: " << message;
210 return toIccIoResult(IO_RESULT_NOT_SUPPORTED);
211 }
212 }
213
iccIo(const aidl::IccIo & iccIo)214 aidl::IccIoResult FilesystemApp::iccIo(const aidl::IccIo& iccIo) {
215 CHECK(mBasicChannel) << "Basic channel must always be present";
216
217 if (iccIo.fileId != 0) {
218 mBasicChannel->select({iccIo.fileId, iccIo.path});
219 }
220
221 aidl::SimApdu message = {
222 .instruction = iccIo.command,
223 .p1 = iccIo.p1,
224 .p2 = iccIo.p2,
225 .p3 = iccIo.p3,
226 .data = iccIo.data,
227 };
228 return mBasicChannel->transmit(message);
229 }
230
231 } // namespace android::hardware::radio::minimal::sim::apps
232