/* * Copyright 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "model/devices/scripted_beacon.h" #include #include #include #include "log.h" #include "model/devices/scripted_beacon_ble_payload.pb.h" #include "model/setup/device_boutique.h" #ifdef _WIN32 #define F_OK 00 #define R_OK 04 #endif using std::vector; using std::chrono::steady_clock; using std::chrono::system_clock; namespace rootcanal { using namespace model::packets; using namespace std::chrono_literals; bool ScriptedBeacon::registered_ = DeviceBoutique::Register("scripted_beacon", &ScriptedBeacon::Create); ScriptedBeacon::ScriptedBeacon(const vector& args) : Beacon(args) { advertising_interval_ = 1280ms; advertising_type_ = LegacyAdvertisingType::ADV_SCAN_IND; advertising_data_ = { 0x18 /* Length */, 0x09 /* TYPE_NAME_CMPL */, 'g', 'D', 'e', 'v', 'i', 'c', 'e', '-', 's', 'c', 'r', 'i', 'p', 't', 'e', 'd', '-', 'b', 'e', 'a', 'c', 'o', 'n', 0x02 /* Length */, 0x01 /* TYPE_FLAG */, 0x4 /* BREDR_NOT_SPT */ | 0x2 /* GEN_DISC_FLAG */, }; scan_response_data_ = { 0x05 /* Length */, 0x08 /* TYPE_NAME_SHORT */, 'g', 'b', 'e', 'a'}; INFO("Scripted_beacon registered {}", registered_); if (args.size() >= 4) { config_file_ = args[2]; events_file_ = args[3]; set_state(PlaybackEvent::INITIALIZED); } else { ERROR( "Initialization failed, need playback and playback events file " "arguments"); } } bool has_time_elapsed(steady_clock::time_point time_point) { return steady_clock::now() > time_point; } static void populate_event(PlaybackEvent* event, PlaybackEvent::PlaybackEventType type) { INFO("Adding event: {}", PlaybackEvent::PlaybackEventType_Name(type)); event->set_type(type); event->set_secs_since_epoch(system_clock::now().time_since_epoch().count()); } // Adds events to events file; we won't be able to post anything to the file // until we set to permissive mode in tests. No events are posted until then. void ScriptedBeacon::set_state(PlaybackEvent::PlaybackEventType state) { PlaybackEvent event; current_state_ = state; if (!events_ostream_.is_open()) { events_ostream_.open(events_file_, std::ios::out | std::ios::binary | std::ios::trunc); if (!events_ostream_.is_open()) { INFO("Events file not opened yet, for event: {}", PlaybackEvent::PlaybackEventType_Name(state)); return; } } populate_event(&event, state); event.SerializeToOstream(&events_ostream_); events_ostream_.flush(); } void ScriptedBeacon::Tick() { switch (current_state_) { case PlaybackEvent::INITIALIZED: Beacon::Tick(); break; case PlaybackEvent::SCANNED_ONCE: next_check_time_ = steady_clock::now() + steady_clock::duration(std::chrono::seconds(1)); set_state(PlaybackEvent::WAITING_FOR_FILE); break; case PlaybackEvent::WAITING_FOR_FILE: if (!has_time_elapsed(next_check_time_)) { return; } next_check_time_ = steady_clock::now() + steady_clock::duration(std::chrono::seconds(1)); if (access(config_file_.c_str(), F_OK) == -1) { return; } set_state(PlaybackEvent::WAITING_FOR_FILE_TO_BE_READABLE); break; case PlaybackEvent::WAITING_FOR_FILE_TO_BE_READABLE: if (access(config_file_.c_str(), R_OK) == -1) { return; } set_state(PlaybackEvent::PARSING_FILE); break; case PlaybackEvent::PARSING_FILE: { if (!has_time_elapsed(next_check_time_)) { return; } std::fstream input(config_file_, std::ios::in | std::ios::binary); if (!ble_ad_list_.ParseFromIstream(&input)) { ERROR("Cannot parse playback file {}", config_file_); set_state(PlaybackEvent::FILE_PARSING_FAILED); return; } set_state(PlaybackEvent::PLAYBACK_STARTED); INFO("Starting Ble advertisement playback from file: {}", config_file_); next_ad_.ad_time = steady_clock::now(); get_next_advertisement(); input.close(); break; } case PlaybackEvent::PLAYBACK_STARTED: { while (has_time_elapsed(next_ad_.ad_time)) { auto ad = model::packets::LeLegacyAdvertisingPduBuilder::Create( next_ad_.address, Address::kEmpty /* Destination */, AddressType::RANDOM, AddressType::PUBLIC, LegacyAdvertisingType::ADV_NONCONN_IND, next_ad_.ad); SendLinkLayerPacket(std::move(ad), Phy::Type::LOW_ENERGY); if (packet_num_ < ble_ad_list_.advertisements().size()) { get_next_advertisement(); } else { set_state(PlaybackEvent::PLAYBACK_ENDED); if (events_ostream_.is_open()) { events_ostream_.close(); } INFO( "Completed Ble advertisement playback from file: {} with {} " "packets", config_file_, packet_num_); break; } } } break; case PlaybackEvent::FILE_PARSING_FAILED: case PlaybackEvent::PLAYBACK_ENDED: case PlaybackEvent::UNKNOWN: return; } } void ScriptedBeacon::ReceiveLinkLayerPacket( model::packets::LinkLayerPacketView packet, Phy::Type /*type*/, int8_t /*rssi*/) { if (current_state_ == PlaybackEvent::INITIALIZED) { if (packet.GetDestinationAddress() == address_ && packet.GetType() == PacketType::LE_SCAN) { set_state(PlaybackEvent::SCANNED_ONCE); SendLinkLayerPacket( std::move(model::packets::LeScanResponseBuilder::Create( address_, packet.GetSourceAddress(), AddressType::PUBLIC, std::vector(scan_response_data_.begin(), scan_response_data_.end()))), Phy::Type::LOW_ENERGY); } } } void ScriptedBeacon::get_next_advertisement() { std::string payload = ble_ad_list_.advertisements(packet_num_).payload(); std::string mac_address = ble_ad_list_.advertisements(packet_num_).mac_address(); uint32_t delay_before_send_ms = ble_ad_list_.advertisements(packet_num_).delay_before_send_ms(); next_ad_.ad.assign(payload.begin(), payload.end()); if (Address::IsValidAddress(mac_address)) { // formatted string with colons like "12:34:56:78:9a:bc" Address::FromString(mac_address, next_ad_.address); } else if (mac_address.size() == Address::kLength) { // six-byte binary address std::vector mac_vector(mac_address.cbegin(), mac_address.cend()); next_ad_.address.Address::FromOctets(mac_vector.data()); } else { Address::FromString("BA:D0:AD:BA:D0:AD", next_ad_.address); } next_ad_.ad_time += steady_clock::duration(std::chrono::milliseconds(delay_before_send_ms)); packet_num_++; } } // namespace rootcanal