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