1 /*
2 * Copyright 2016 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 "scripted_beacon.h"
18
19 #include <fstream>
20 #include <cstdint>
21 #include <unistd.h>
22
23 #include "model/devices/scripted_beacon_ble_payload.pb.h"
24 #include "model/setup/device_boutique.h"
25 #include "os/log.h"
26
27 using std::vector;
28 using std::chrono::steady_clock;
29 using std::chrono::system_clock;
30
31 namespace test_vendor_lib {
32 bool ScriptedBeacon::registered_ =
33 DeviceBoutique::Register("scripted_beacon", &ScriptedBeacon::Create);
ScriptedBeacon()34 ScriptedBeacon::ScriptedBeacon() {
35 advertising_interval_ms_ = std::chrono::milliseconds(1280);
36 properties_.SetLeAdvertisementType(0x02 /* SCANNABLE */);
37 properties_.SetLeAdvertisement({
38 0x18, // Length
39 0x09 /* TYPE_NAME_CMPL */,
40 'g',
41 'D',
42 'e',
43 'v',
44 'i',
45 'c',
46 'e',
47 '-',
48 's',
49 'c',
50 'r',
51 'i',
52 'p',
53 't',
54 'e',
55 'd',
56 '-',
57 'b',
58 'e',
59 'a',
60 'c',
61 'o',
62 'n',
63 0x02, // Length
64 0x01 /* TYPE_FLAG */,
65 0x4 /* BREDR_NOT_SPT */ | 0x2 /* GEN_DISC_FLAG */,
66 });
67
68 properties_.SetLeScanResponse({0x05, // Length
69 0x08, // TYPE_NAME_SHORT
70 'g', 'b', 'e', 'a'});
71 LOG_INFO("Scripted_beacon registered %s", registered_ ? "true" : "false");
72 }
73
has_time_elapsed(steady_clock::time_point time_point)74 bool has_time_elapsed(steady_clock::time_point time_point) {
75 return steady_clock::now() > time_point;
76 }
77
Initialize(const vector<std::string> & args)78 void ScriptedBeacon::Initialize(const vector<std::string>& args) {
79 if (args.size() < 2) {
80 LOG_ERROR(
81 "Initialization failed, need mac address, playback and playback events "
82 "file arguments");
83 return;
84 }
85
86 Address addr{};
87 if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
88
89 if (args.size() < 4) {
90 LOG_ERROR(
91 "Initialization failed, need playback and playback events file "
92 "arguments");
93 }
94 config_file_ = args[2];
95 events_file_ = args[3];
96 set_state(PlaybackEvent::INITIALIZED);
97 }
98
populate_event(PlaybackEvent * event,PlaybackEvent::PlaybackEventType type)99 void ScriptedBeacon::populate_event(PlaybackEvent * event, PlaybackEvent::PlaybackEventType type) {
100 LOG_INFO("Adding event: %d", type);
101 event->set_type(type);
102 event->set_secs_since_epoch(system_clock::now().time_since_epoch().count());
103 }
104
105 // Adds events to events file; we won't be able to post anything to the file
106 // until we set to permissive mode in tests. No events are posted until then.
set_state(PlaybackEvent::PlaybackEventType state)107 void ScriptedBeacon::set_state(PlaybackEvent::PlaybackEventType state) {
108 PlaybackEvent event;
109 current_state_ = state;
110 if (!events_ostream_.is_open()) {
111 events_ostream_.open(events_file_, std::ios::out | std::ios::binary | std::ios::trunc);
112 if (!events_ostream_.is_open()) {
113 LOG_INFO("Events file not opened yet, for event: %d", state);
114 return;
115 }
116 }
117 populate_event(&event, state);
118 event.SerializeToOstream(&events_ostream_);
119 events_ostream_.flush();
120 }
121
TimerTick()122 void ScriptedBeacon::TimerTick() {
123 switch (current_state_) {
124 case PlaybackEvent::INITIALIZED:
125 Beacon::TimerTick();
126 break;
127 case PlaybackEvent::SCANNED_ONCE:
128 next_check_time_ =
129 steady_clock::now() + steady_clock::duration(std::chrono::seconds(1));
130 set_state(PlaybackEvent::WAITING_FOR_FILE);
131 break;
132 case PlaybackEvent::WAITING_FOR_FILE:
133 if (!has_time_elapsed(next_check_time_)) {
134 return;
135 }
136 next_check_time_ =
137 steady_clock::now() + steady_clock::duration(std::chrono::seconds(1));
138 if (access(config_file_.c_str(), F_OK) == -1) {
139 return;
140 }
141 set_state(PlaybackEvent::WAITING_FOR_FILE_TO_BE_READABLE);
142 break;
143 case PlaybackEvent::WAITING_FOR_FILE_TO_BE_READABLE:
144 if (access(config_file_.c_str(), R_OK) == -1) {
145 return;
146 }
147 set_state(PlaybackEvent::PARSING_FILE);
148 break;
149 case PlaybackEvent::PARSING_FILE: {
150 if (!has_time_elapsed(next_check_time_)) {
151 return;
152 }
153 std::fstream input(config_file_, std::ios::in | std::ios::binary);
154 if (!ble_ad_list_.ParseFromIstream(&input)) {
155 LOG_ERROR("Cannot parse playback file %s", config_file_.c_str());
156 set_state(PlaybackEvent::FILE_PARSING_FAILED);
157 return;
158 } else {
159 set_state(PlaybackEvent::PLAYBACK_STARTED);
160 LOG_INFO("Starting Ble advertisement playback from file: %s",
161 config_file_.c_str());
162 next_ad_.ad_time = steady_clock::now();
163 get_next_advertisement();
164 input.close();
165 }
166 } break;
167 case PlaybackEvent::PLAYBACK_STARTED: {
168 while (has_time_elapsed(next_ad_.ad_time)) {
169 auto ad = model::packets::LeAdvertisementBuilder::Create(
170 next_ad_.address, Address::kEmpty /* Destination */,
171 model::packets::AddressType::RANDOM,
172 model::packets::AdvertisementType::ADV_NONCONN_IND, next_ad_.ad);
173 SendLinkLayerPacket(std::move(ad), Phy::Type::LOW_ENERGY);
174 if (packet_num_ < ble_ad_list_.advertisements().size()) {
175 get_next_advertisement();
176 } else {
177 set_state(PlaybackEvent::PLAYBACK_ENDED);
178 if (events_ostream_.is_open()) {
179 events_ostream_.close();
180 }
181 LOG_INFO(
182 "Completed Ble advertisement playback from file: %s with %d "
183 "packets",
184 config_file_.c_str(), packet_num_);
185 break;
186 }
187 }
188 } break;
189 case PlaybackEvent::FILE_PARSING_FAILED:
190 case PlaybackEvent::PLAYBACK_ENDED:
191 case PlaybackEvent::UNKNOWN:
192 return;
193 }
194 }
195
IncomingPacket(model::packets::LinkLayerPacketView packet)196 void ScriptedBeacon::IncomingPacket(
197 model::packets::LinkLayerPacketView packet) {
198 if (current_state_ == PlaybackEvent::INITIALIZED) {
199 if (packet.GetDestinationAddress() == properties_.GetLeAddress() &&
200 packet.GetType() == model::packets::PacketType::LE_SCAN) {
201 auto scan_response = model::packets::LeScanResponseBuilder::Create(
202 properties_.GetLeAddress(), packet.GetSourceAddress(),
203 static_cast<model::packets::AddressType>(
204 properties_.GetLeAddressType()),
205 model::packets::AdvertisementType::SCAN_RESPONSE,
206 properties_.GetLeScanResponse());
207 set_state(PlaybackEvent::SCANNED_ONCE);
208 SendLinkLayerPacket(std::move(scan_response), Phy::Type::LOW_ENERGY);
209 }
210 }
211 }
212
get_next_advertisement()213 void ScriptedBeacon::get_next_advertisement() {
214 std::string payload = ble_ad_list_.advertisements(packet_num_).payload();
215 std::string mac_address =
216 ble_ad_list_.advertisements(packet_num_).mac_address();
217 uint32_t delay_before_send_ms =
218 ble_ad_list_.advertisements(packet_num_).delay_before_send_ms();
219 next_ad_.ad.assign(payload.begin(), payload.end());
220 if (Address::IsValidAddress(mac_address)) {
221 // formatted string with colons like "12:34:56:78:9a:bc"
222 Address::FromString(mac_address, next_ad_.address);
223 } else if (mac_address.size() == Address::kLength) {
224 // six-byte binary address
225 std::vector<uint8_t> mac_vector(mac_address.cbegin(), mac_address.cend());
226 next_ad_.address.Address::FromOctets(mac_vector.data());
227 } else {
228 Address::FromString("BA:D0:AD:BA:D0:AD", next_ad_.address);
229 }
230 next_ad_.ad_time +=
231 steady_clock::duration(std::chrono::milliseconds(delay_before_send_ms));
232 packet_num_++;
233 }
234 } // namespace test_vendor_lib
235