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_ = {0x05 /* Length */, 0x08 /* TYPE_NAME_SHORT */, 'g', 'b', 'e', 'a'};
79
80 INFO("Scripted_beacon registered {}", registered_);
81
82 if (args.size() >= 4) {
83 config_file_ = args[2];
84 events_file_ = args[3];
85 set_state(PlaybackEvent::INITIALIZED);
86 } else {
87 ERROR("Initialization failed, need playback and playback events file "
88 "arguments");
89 }
90 }
91
has_time_elapsed(steady_clock::time_point time_point)92 bool has_time_elapsed(steady_clock::time_point time_point) {
93 return steady_clock::now() > time_point;
94 }
95
populate_event(PlaybackEvent * event,PlaybackEvent::PlaybackEventType type)96 static void populate_event(PlaybackEvent* event, PlaybackEvent::PlaybackEventType type) {
97 INFO("Adding event: {}", PlaybackEvent::PlaybackEventType_Name(type));
98 event->set_type(type);
99 event->set_secs_since_epoch(system_clock::now().time_since_epoch().count());
100 }
101
102 // Adds events to events file; we won't be able to post anything to the file
103 // until we set to permissive mode in tests. No events are posted until then.
set_state(PlaybackEvent::PlaybackEventType state)104 void ScriptedBeacon::set_state(PlaybackEvent::PlaybackEventType state) {
105 PlaybackEvent event;
106 current_state_ = state;
107 if (!events_ostream_.is_open()) {
108 events_ostream_.open(events_file_, std::ios::out | std::ios::binary | std::ios::trunc);
109 if (!events_ostream_.is_open()) {
110 INFO("Events file not opened yet, for event: {}",
111 PlaybackEvent::PlaybackEventType_Name(state));
112 return;
113 }
114 }
115 populate_event(&event, state);
116 event.SerializeToOstream(&events_ostream_);
117 events_ostream_.flush();
118 }
119
Tick()120 void ScriptedBeacon::Tick() {
121 switch (current_state_) {
122 case PlaybackEvent::INITIALIZED:
123 Beacon::Tick();
124 break;
125 case PlaybackEvent::SCANNED_ONCE:
126 next_check_time_ = steady_clock::now() + steady_clock::duration(std::chrono::seconds(1));
127 set_state(PlaybackEvent::WAITING_FOR_FILE);
128 break;
129 case PlaybackEvent::WAITING_FOR_FILE:
130 if (!has_time_elapsed(next_check_time_)) {
131 return;
132 }
133 next_check_time_ = steady_clock::now() + steady_clock::duration(std::chrono::seconds(1));
134 if (access(config_file_.c_str(), F_OK) == -1) {
135 return;
136 }
137 set_state(PlaybackEvent::WAITING_FOR_FILE_TO_BE_READABLE);
138 break;
139 case PlaybackEvent::WAITING_FOR_FILE_TO_BE_READABLE:
140 if (access(config_file_.c_str(), R_OK) == -1) {
141 return;
142 }
143 set_state(PlaybackEvent::PARSING_FILE);
144 break;
145 case PlaybackEvent::PARSING_FILE: {
146 if (!has_time_elapsed(next_check_time_)) {
147 return;
148 }
149 std::fstream input(config_file_, std::ios::in | std::ios::binary);
150 if (!ble_ad_list_.ParseFromIstream(&input)) {
151 ERROR("Cannot parse playback file {}", config_file_);
152 set_state(PlaybackEvent::FILE_PARSING_FAILED);
153 return;
154 }
155 set_state(PlaybackEvent::PLAYBACK_STARTED);
156 INFO("Starting Ble advertisement playback from file: {}", config_file_);
157 next_ad_.ad_time = steady_clock::now();
158 get_next_advertisement();
159 input.close();
160 break;
161 }
162 case PlaybackEvent::PLAYBACK_STARTED: {
163 while (has_time_elapsed(next_ad_.ad_time)) {
164 auto ad = model::packets::LeLegacyAdvertisingPduBuilder::Create(
165 next_ad_.address, Address::kEmpty /* Destination */, AddressType::RANDOM,
166 AddressType::PUBLIC, LegacyAdvertisingType::ADV_NONCONN_IND, next_ad_.ad);
167 SendLinkLayerPacket(std::move(ad), Phy::Type::LOW_ENERGY);
168 if (packet_num_ < ble_ad_list_.advertisements().size()) {
169 get_next_advertisement();
170 } else {
171 set_state(PlaybackEvent::PLAYBACK_ENDED);
172 if (events_ostream_.is_open()) {
173 events_ostream_.close();
174 }
175 INFO("Completed Ble advertisement playback from file: {} with {} "
176 "packets",
177 config_file_, packet_num_);
178 break;
179 }
180 }
181 } break;
182 case PlaybackEvent::FILE_PARSING_FAILED:
183 case PlaybackEvent::PLAYBACK_ENDED:
184 case PlaybackEvent::UNKNOWN:
185 return;
186 }
187 }
188
ReceiveLinkLayerPacket(model::packets::LinkLayerPacketView packet,Phy::Type,int8_t)189 void ScriptedBeacon::ReceiveLinkLayerPacket(model::packets::LinkLayerPacketView packet,
190 Phy::Type /*type*/, int8_t /*rssi*/) {
191 if (current_state_ == PlaybackEvent::INITIALIZED) {
192 if (packet.GetDestinationAddress() == address_ && packet.GetType() == PacketType::LE_SCAN) {
193 set_state(PlaybackEvent::SCANNED_ONCE);
194 SendLinkLayerPacket(
195 model::packets::LeScanResponseBuilder::Create(
196 address_, packet.GetSourceAddress(), AddressType::PUBLIC,
197 std::vector(scan_response_data_.begin(), scan_response_data_.end())),
198 Phy::Type::LOW_ENERGY);
199 }
200 }
201 }
202
get_next_advertisement()203 void ScriptedBeacon::get_next_advertisement() {
204 std::string payload = ble_ad_list_.advertisements(packet_num_).payload();
205 std::string mac_address = ble_ad_list_.advertisements(packet_num_).mac_address();
206 uint32_t delay_before_send_ms = ble_ad_list_.advertisements(packet_num_).delay_before_send_ms();
207 next_ad_.ad.assign(payload.begin(), payload.end());
208 if (Address::IsValidAddress(mac_address)) {
209 // formatted string with colons like "12:34:56:78:9a:bc"
210 Address::FromString(mac_address, next_ad_.address);
211 } else if (mac_address.size() == Address::kLength) {
212 // six-byte binary address
213 std::vector<uint8_t> mac_vector(mac_address.cbegin(), mac_address.cend());
214 next_ad_.address.Address::FromOctets(mac_vector.data());
215 } else {
216 Address::FromString("BA:D0:AD:BA:D0:AD", next_ad_.address);
217 }
218 next_ad_.ad_time += steady_clock::duration(std::chrono::milliseconds(delay_before_send_ms));
219 packet_num_++;
220 }
221 } // namespace rootcanal
222