| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/api/gcd_private/gcd_private_api.h" | 5 #include "chrome/browser/extensions/api/gcd_private/gcd_private_api.h" |
| 6 | 6 |
| 7 #include "base/lazy_instance.h" | 7 #include "base/lazy_instance.h" |
| 8 #include "base/memory/linked_ptr.h" | 8 #include "base/memory/linked_ptr.h" |
| 9 #include "base/memory/scoped_ptr.h" | 9 #include "base/memory/scoped_ptr.h" |
| 10 #include "base/memory/scoped_vector.h" | 10 #include "base/memory/scoped_vector.h" |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 94 | 94 |
| 95 class GcdPrivateSessionHolder; | 95 class GcdPrivateSessionHolder; |
| 96 | 96 |
| 97 class GcdPrivateAPIImpl : public EventRouter::Observer, | 97 class GcdPrivateAPIImpl : public EventRouter::Observer, |
| 98 public local_discovery::PrivetDeviceLister::Delegate { | 98 public local_discovery::PrivetDeviceLister::Delegate { |
| 99 public: | 99 public: |
| 100 typedef base::Callback<void(bool success)> SuccessCallback; | 100 typedef base::Callback<void(bool success)> SuccessCallback; |
| 101 | 101 |
| 102 typedef base::Callback<void(int session_id, | 102 typedef base::Callback<void(int session_id, |
| 103 api::gcd_private::Status status, | 103 api::gcd_private::Status status, |
| 104 const std::vector<api::gcd_private::PairingType>& | 104 const base::DictionaryValue& info)> |
| 105 pairing_types)> EstablishSessionCallback; | 105 CreateSessionCallback; |
| 106 | 106 |
| 107 typedef base::Callback<void(api::gcd_private::Status status)> SessionCallback; | 107 typedef base::Callback<void(api::gcd_private::Status status)> SessionCallback; |
| 108 | 108 |
| 109 typedef base::Callback<void(api::gcd_private::Status status, | 109 typedef base::Callback<void(api::gcd_private::Status status, |
| 110 const base::DictionaryValue& response)> | 110 const base::DictionaryValue& response)> |
| 111 MessageResponseCallback; | 111 MessageResponseCallback; |
| 112 | 112 |
| 113 explicit GcdPrivateAPIImpl(content::BrowserContext* context); | 113 explicit GcdPrivateAPIImpl(content::BrowserContext* context); |
| 114 virtual ~GcdPrivateAPIImpl(); | 114 virtual ~GcdPrivateAPIImpl(); |
| 115 | 115 |
| 116 static GcdPrivateAPIImpl* Get(content::BrowserContext* context); | 116 static GcdPrivateAPIImpl* Get(content::BrowserContext* context); |
| 117 | 117 |
| 118 bool QueryForDevices(); | 118 bool QueryForDevices(); |
| 119 | 119 |
| 120 void EstablishSession(const std::string& ip_address, | |
| 121 int port, | |
| 122 const EstablishSessionCallback& callback); | |
| 123 | |
| 124 void CreateSession(const std::string& service_name, | 120 void CreateSession(const std::string& service_name, |
| 125 const EstablishSessionCallback& callback); | 121 const CreateSessionCallback& callback); |
| 126 | 122 |
| 127 void StartPairing(int session_id, | 123 void StartPairing(int session_id, |
| 128 api::gcd_private::PairingType pairing_type, | 124 api::gcd_private::PairingType pairing_type, |
| 129 const SessionCallback& callback); | 125 const SessionCallback& callback); |
| 130 | 126 |
| 131 void ConfirmCode(int session_id, | 127 void ConfirmCode(int session_id, |
| 132 const std::string& code, | 128 const std::string& code, |
| 133 const SessionCallback& callback); | 129 const SessionCallback& callback); |
| 134 | 130 |
| 135 void SendMessage(int session_id, | 131 void SendMessage(int session_id, |
| 136 const std::string& api, | 132 const std::string& api, |
| 137 const base::DictionaryValue& input, | 133 const base::DictionaryValue& input, |
| 138 const MessageResponseCallback& callback); | 134 const MessageResponseCallback& callback); |
| 139 | 135 |
| 140 void RequestWifiPassword(const std::string& ssid, | 136 void RequestWifiPassword(const std::string& ssid, |
| 141 const SuccessCallback& callback); | 137 const SuccessCallback& callback); |
| 142 | 138 |
| 143 void RemoveSession(int session_id); | 139 void RemoveSession(int session_id); |
| 140 void RemoveSessionDelayed(int session_id); |
| 144 | 141 |
| 145 scoped_ptr<base::ListValue> GetPrefetchedSSIDList(); | 142 scoped_ptr<base::ListValue> GetPrefetchedSSIDList(); |
| 146 | 143 |
| 147 private: | 144 private: |
| 148 typedef std::map<std::string /* id_string */, | 145 typedef std::map<std::string /* id_string */, |
| 149 linked_ptr<api::gcd_private::GCDDevice> > GCDDeviceMap; | 146 linked_ptr<api::gcd_private::GCDDevice> > GCDDeviceMap; |
| 150 | 147 |
| 151 typedef std::map<std::string /* ssid */, std::string /* password */> | 148 typedef std::map<std::string /* ssid */, std::string /* password */> |
| 152 PasswordMap; | 149 PasswordMap; |
| 153 | 150 |
| 154 // EventRouter::Observer implementation. | 151 // EventRouter::Observer implementation. |
| 155 void OnListenerAdded(const EventListenerInfo& details) override; | 152 void OnListenerAdded(const EventListenerInfo& details) override; |
| 156 void OnListenerRemoved(const EventListenerInfo& details) override; | 153 void OnListenerRemoved(const EventListenerInfo& details) override; |
| 157 | 154 |
| 158 // local_discovery::PrivetDeviceLister implementation. | 155 // local_discovery::PrivetDeviceLister implementation. |
| 159 void DeviceChanged( | 156 void DeviceChanged( |
| 160 bool added, | 157 bool added, |
| 161 const std::string& name, | 158 const std::string& name, |
| 162 const local_discovery::DeviceDescription& description) override; | 159 const local_discovery::DeviceDescription& description) override; |
| 163 void DeviceRemoved(const std::string& name) override; | 160 void DeviceRemoved(const std::string& name) override; |
| 164 void DeviceCacheFlushed() override; | 161 void DeviceCacheFlushed() override; |
| 165 | 162 |
| 166 void SendMessageInternal(int session_id, | 163 void SendMessageInternal(int session_id, |
| 167 const std::string& api, | 164 const std::string& api, |
| 168 const base::DictionaryValue& input, | 165 const base::DictionaryValue& input, |
| 169 const MessageResponseCallback& callback); | 166 const MessageResponseCallback& callback); |
| 170 | 167 |
| 171 void OnServiceResolved(int session_id, | 168 void OnServiceResolved(int session_id, |
| 172 const EstablishSessionCallback& callback, | 169 const CreateSessionCallback& callback, |
| 173 scoped_ptr<local_discovery::PrivetHTTPClient> client); | 170 scoped_ptr<local_discovery::PrivetHTTPClient> client); |
| 174 | 171 |
| 175 #if defined(ENABLE_WIFI_BOOTSTRAPPING) | 172 #if defined(ENABLE_WIFI_BOOTSTRAPPING) |
| 176 void OnWifiPassword(const SuccessCallback& callback, | 173 void OnWifiPassword(const SuccessCallback& callback, |
| 177 bool success, | 174 bool success, |
| 178 const std::string& ssid, | 175 const std::string& ssid, |
| 179 const std::string& password); | 176 const std::string& password); |
| 180 void StartWifiIfNotStarted(); | 177 void StartWifiIfNotStarted(); |
| 181 #endif | 178 #endif |
| 182 | 179 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 193 | 190 |
| 194 std::map<int, SessionInfo> sessions_; | 191 std::map<int, SessionInfo> sessions_; |
| 195 int last_session_id_; | 192 int last_session_id_; |
| 196 | 193 |
| 197 content::BrowserContext* const browser_context_; | 194 content::BrowserContext* const browser_context_; |
| 198 | 195 |
| 199 #if defined(ENABLE_WIFI_BOOTSTRAPPING) | 196 #if defined(ENABLE_WIFI_BOOTSTRAPPING) |
| 200 scoped_ptr<local_discovery::wifi::WifiManager> wifi_manager_; | 197 scoped_ptr<local_discovery::wifi::WifiManager> wifi_manager_; |
| 201 #endif | 198 #endif |
| 202 PasswordMap wifi_passwords_; | 199 PasswordMap wifi_passwords_; |
| 200 |
| 201 base::WeakPtrFactory<GcdPrivateAPIImpl> weak_ptr_factory_{this}; |
| 202 |
| 203 DISALLOW_COPY_AND_ASSIGN(GcdPrivateAPIImpl); |
| 203 }; | 204 }; |
| 204 | 205 |
| 205 | 206 |
| 206 GcdPrivateAPIImpl::GcdPrivateAPIImpl(content::BrowserContext* context) | 207 GcdPrivateAPIImpl::GcdPrivateAPIImpl(content::BrowserContext* context) |
| 207 : num_device_listeners_(0), last_session_id_(0), browser_context_(context) { | 208 : num_device_listeners_(0), last_session_id_(0), browser_context_(context) { |
| 208 DCHECK(browser_context_); | 209 DCHECK(browser_context_); |
| 209 if (EventRouter::Get(context)) { | 210 if (EventRouter::Get(context)) { |
| 210 EventRouter::Get(context) | 211 EventRouter::Get(context) |
| 211 ->RegisterObserver(this, gcd_private::OnDeviceStateChanged::kEventName); | 212 ->RegisterObserver(this, gcd_private::OnDeviceStateChanged::kEventName); |
| 212 EventRouter::Get(context) | 213 EventRouter::Get(context) |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 302 | 303 |
| 303 bool GcdPrivateAPIImpl::QueryForDevices() { | 304 bool GcdPrivateAPIImpl::QueryForDevices() { |
| 304 if (!privet_device_lister_) | 305 if (!privet_device_lister_) |
| 305 return false; | 306 return false; |
| 306 | 307 |
| 307 privet_device_lister_->DiscoverNewDevices(true); | 308 privet_device_lister_->DiscoverNewDevices(true); |
| 308 | 309 |
| 309 return true; | 310 return true; |
| 310 } | 311 } |
| 311 | 312 |
| 312 void GcdPrivateAPIImpl::EstablishSession( | 313 void GcdPrivateAPIImpl::CreateSession(const std::string& service_name, |
| 313 const std::string& ip_address, | 314 const CreateSessionCallback& callback) { |
| 314 int port, | |
| 315 const EstablishSessionCallback& callback) { | |
| 316 std::string host_string; | |
| 317 net::IPAddressNumber address_number; | |
| 318 | |
| 319 if (net::ParseIPLiteralToNumber(ip_address, &address_number) && | |
| 320 address_number.size() == net::kIPv6AddressSize) { | |
| 321 host_string = base::StringPrintf("[%s]", ip_address.c_str()); | |
| 322 } else { | |
| 323 host_string = ip_address; | |
| 324 } | |
| 325 | |
| 326 scoped_ptr<local_discovery::PrivetHTTPClient> http_client( | |
| 327 new local_discovery::PrivetHTTPClientImpl( | |
| 328 "", net::HostPortPair(host_string, port), | |
| 329 browser_context_->GetRequestContext())); | |
| 330 | |
| 331 int session_id = last_session_id_++; | 315 int session_id = last_session_id_++; |
| 332 auto& session_data = sessions_[session_id]; | |
| 333 session_data.session.reset( | |
| 334 new local_discovery::PrivetV3Session(http_client.Pass())); | |
| 335 session_data.session->Init(base::Bind(callback, session_id)); | |
| 336 } | |
| 337 | |
| 338 void GcdPrivateAPIImpl::CreateSession( | |
| 339 const std::string& service_name, | |
| 340 const EstablishSessionCallback& callback) { | |
| 341 int session_id = last_session_id_++; | |
| 342 | |
| 343 scoped_ptr<local_discovery::PrivetHTTPAsynchronousFactory> factory( | 316 scoped_ptr<local_discovery::PrivetHTTPAsynchronousFactory> factory( |
| 344 local_discovery::PrivetHTTPAsynchronousFactory::CreateInstance( | 317 local_discovery::PrivetHTTPAsynchronousFactory::CreateInstance( |
| 345 browser_context_->GetRequestContext())); | 318 browser_context_->GetRequestContext())); |
| 346 auto& session_data = sessions_[session_id]; | 319 auto& session_data = sessions_[session_id]; |
| 347 session_data.http_resolution.reset( | 320 session_data.http_resolution.reset( |
| 348 factory->CreatePrivetHTTP(service_name).release()); | 321 factory->CreatePrivetHTTP(service_name).release()); |
| 349 session_data.http_resolution->Start( | 322 session_data.http_resolution->Start( |
| 350 base::Bind(&GcdPrivateAPIImpl::OnServiceResolved, base::Unretained(this), | 323 base::Bind(&GcdPrivateAPIImpl::OnServiceResolved, base::Unretained(this), |
| 351 session_id, callback)); | 324 session_id, callback)); |
| 352 } | 325 } |
| 353 | 326 |
| 354 void GcdPrivateAPIImpl::OnServiceResolved( | 327 void GcdPrivateAPIImpl::OnServiceResolved( |
| 355 int session_id, | 328 int session_id, |
| 356 const EstablishSessionCallback& callback, | 329 const CreateSessionCallback& callback, |
| 357 scoped_ptr<local_discovery::PrivetHTTPClient> client) { | 330 scoped_ptr<local_discovery::PrivetHTTPClient> client) { |
| 358 if (!client) { | 331 if (!client) { |
| 359 return callback.Run(session_id, gcd_private::STATUS_SERVICERESOLUTIONERROR, | 332 return callback.Run(session_id, gcd_private::STATUS_SERVICERESOLUTIONERROR, |
| 360 std::vector<gcd_private::PairingType>()); | 333 base::DictionaryValue()); |
| 361 } | 334 } |
| 362 auto& session_data = sessions_[session_id]; | 335 auto& session_data = sessions_[session_id]; |
| 363 session_data.session.reset( | 336 session_data.session.reset( |
| 364 new local_discovery::PrivetV3Session(client.Pass())); | 337 new local_discovery::PrivetV3Session(client.Pass())); |
| 365 session_data.session->Init(base::Bind(callback, session_id)); | 338 session_data.session->Init(base::Bind(callback, session_id)); |
| 366 } | 339 } |
| 367 | 340 |
| 368 void GcdPrivateAPIImpl::StartPairing(int session_id, | 341 void GcdPrivateAPIImpl::StartPairing(int session_id, |
| 369 api::gcd_private::PairingType pairing_type, | 342 api::gcd_private::PairingType pairing_type, |
| 370 const SessionCallback& callback) { | 343 const SessionCallback& callback) { |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 466 wifi_manager_->Start(); | 439 wifi_manager_->Start(); |
| 467 } | 440 } |
| 468 } | 441 } |
| 469 | 442 |
| 470 #endif | 443 #endif |
| 471 | 444 |
| 472 void GcdPrivateAPIImpl::RemoveSession(int session_id) { | 445 void GcdPrivateAPIImpl::RemoveSession(int session_id) { |
| 473 sessions_.erase(session_id); | 446 sessions_.erase(session_id); |
| 474 } | 447 } |
| 475 | 448 |
| 449 void GcdPrivateAPIImpl::RemoveSessionDelayed(int session_id) { |
| 450 base::MessageLoop::current()->PostTask(FROM_HERE, |
| 451 base::Bind(&GcdPrivateAPIImpl::RemoveSession, |
| 452 weak_ptr_factory_.GetWeakPtr(), session_id)); |
| 453 } |
| 454 |
| 476 scoped_ptr<base::ListValue> GcdPrivateAPIImpl::GetPrefetchedSSIDList() { | 455 scoped_ptr<base::ListValue> GcdPrivateAPIImpl::GetPrefetchedSSIDList() { |
| 477 scoped_ptr<base::ListValue> retval(new base::ListValue); | 456 scoped_ptr<base::ListValue> retval(new base::ListValue); |
| 478 | 457 |
| 479 #if defined(ENABLE_WIFI_BOOTSTRAPPING) | 458 #if defined(ENABLE_WIFI_BOOTSTRAPPING) |
| 480 for (PasswordMap::iterator i = wifi_passwords_.begin(); | 459 for (PasswordMap::iterator i = wifi_passwords_.begin(); |
| 481 i != wifi_passwords_.end(); | 460 i != wifi_passwords_.end(); |
| 482 i++) { | 461 i++) { |
| 483 retval->AppendString(i->first); | 462 retval->AppendString(i->first); |
| 484 } | 463 } |
| 485 #endif | 464 #endif |
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 629 return true; | 608 return true; |
| 630 } | 609 } |
| 631 | 610 |
| 632 void GcdPrivatePrefetchWifiPasswordFunction::OnResponse(bool response) { | 611 void GcdPrivatePrefetchWifiPasswordFunction::OnResponse(bool response) { |
| 633 scoped_ptr<base::FundamentalValue> response_value( | 612 scoped_ptr<base::FundamentalValue> response_value( |
| 634 new base::FundamentalValue(response)); | 613 new base::FundamentalValue(response)); |
| 635 SetResult(response_value.release()); | 614 SetResult(response_value.release()); |
| 636 SendResponse(true); | 615 SendResponse(true); |
| 637 } | 616 } |
| 638 | 617 |
| 618 GcdPrivateGetDeviceInfoFunction::GcdPrivateGetDeviceInfoFunction() { |
| 619 } |
| 620 |
| 621 GcdPrivateGetDeviceInfoFunction::~GcdPrivateGetDeviceInfoFunction() { |
| 622 } |
| 623 |
| 624 bool GcdPrivateGetDeviceInfoFunction::RunAsync() { |
| 625 scoped_ptr<gcd_private::CreateSession::Params> params = |
| 626 gcd_private::CreateSession::Params::Create(*args_); |
| 627 |
| 628 if (!params) |
| 629 return false; |
| 630 |
| 631 GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile()); |
| 632 |
| 633 GcdPrivateAPIImpl::CreateSessionCallback callback = |
| 634 base::Bind(&GcdPrivateGetDeviceInfoFunction::OnSessionInitialized, this); |
| 635 gcd_api->CreateSession(params->service_name, callback); |
| 636 |
| 637 return true; |
| 638 } |
| 639 |
| 640 void GcdPrivateGetDeviceInfoFunction::OnSessionInitialized( |
| 641 int session_id, |
| 642 api::gcd_private::Status status, |
| 643 const base::DictionaryValue& info) { |
| 644 gcd_private::GetDeviceInfo::Results::DeviceInfo device_info; |
| 645 device_info.additional_properties.MergeDictionary(&info); |
| 646 |
| 647 results_ = gcd_private::GetDeviceInfo::Results::Create(status, device_info); |
| 648 SendResponse(true); |
| 649 |
| 650 GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile()); |
| 651 gcd_api->RemoveSessionDelayed(session_id); |
| 652 } |
| 653 |
| 639 GcdPrivateCreateSessionFunction::GcdPrivateCreateSessionFunction() { | 654 GcdPrivateCreateSessionFunction::GcdPrivateCreateSessionFunction() { |
| 640 } | 655 } |
| 641 | 656 |
| 642 GcdPrivateCreateSessionFunction::~GcdPrivateCreateSessionFunction() { | 657 GcdPrivateCreateSessionFunction::~GcdPrivateCreateSessionFunction() { |
| 643 } | 658 } |
| 644 | 659 |
| 645 bool GcdPrivateCreateSessionFunction::RunAsync() { | 660 bool GcdPrivateCreateSessionFunction::RunAsync() { |
| 646 scoped_ptr<gcd_private::CreateSession::Params> params = | 661 scoped_ptr<gcd_private::CreateSession::Params> params = |
| 647 gcd_private::CreateSession::Params::Create(*args_); | 662 gcd_private::CreateSession::Params::Create(*args_); |
| 648 | 663 |
| 649 if (!params) | 664 if (!params) |
| 650 return false; | 665 return false; |
| 651 | 666 |
| 652 GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile()); | 667 GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile()); |
| 653 | 668 |
| 654 GcdPrivateAPIImpl::EstablishSessionCallback callback = | 669 GcdPrivateAPIImpl::CreateSessionCallback callback = |
| 655 base::Bind(&GcdPrivateCreateSessionFunction::OnSessionInitialized, this); | 670 base::Bind(&GcdPrivateCreateSessionFunction::OnSessionInitialized, this); |
| 656 gcd_api->CreateSession(params->service_name, callback); | 671 gcd_api->CreateSession(params->service_name, callback); |
| 657 | 672 |
| 658 return true; | 673 return true; |
| 659 } | 674 } |
| 660 | 675 |
| 661 void GcdPrivateCreateSessionFunction::OnSessionInitialized( | 676 void GcdPrivateCreateSessionFunction::OnSessionInitialized( |
| 662 int session_id, | 677 int session_id, |
| 663 api::gcd_private::Status status, | 678 api::gcd_private::Status status, |
| 664 const std::vector<api::gcd_private::PairingType>& pairing_types) { | 679 const base::DictionaryValue& info) { |
| 680 std::vector<api::gcd_private::PairingType> pairing_types; |
| 681 |
| 682 // TODO(vitalybuka): Remove this parsing and |pairing_types| from callback. |
| 683 if (status == gcd_private::STATUS_SUCCESS) { |
| 684 const base::ListValue* pairing = nullptr; |
| 685 if (info.GetList("authentication.pairing", &pairing)) { |
| 686 for (const base::Value* value : *pairing) { |
| 687 std::string pairing_string; |
| 688 if (value->GetAsString(&pairing_string)) { |
| 689 api::gcd_private::PairingType pairing_type = |
| 690 api::gcd_private::ParsePairingType(pairing_string); |
| 691 if (pairing_type != api::gcd_private::PAIRING_TYPE_NONE) |
| 692 pairing_types.push_back(pairing_type); |
| 693 } |
| 694 } |
| 695 } else { |
| 696 status = gcd_private::STATUS_SESSIONERROR; |
| 697 } |
| 698 } |
| 699 |
| 665 results_ = gcd_private::CreateSession::Results::Create(session_id, status, | 700 results_ = gcd_private::CreateSession::Results::Create(session_id, status, |
| 666 pairing_types); | 701 pairing_types); |
| 667 SendResponse(true); | 702 SendResponse(true); |
| 668 } | 703 } |
| 669 | 704 |
| 670 GcdPrivateStartPairingFunction::GcdPrivateStartPairingFunction() { | 705 GcdPrivateStartPairingFunction::GcdPrivateStartPairingFunction() { |
| 671 } | 706 } |
| 672 | 707 |
| 673 GcdPrivateStartPairingFunction::~GcdPrivateStartPairingFunction() { | 708 GcdPrivateStartPairingFunction::~GcdPrivateStartPairingFunction() { |
| 674 } | 709 } |
| (...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 843 } | 878 } |
| 844 | 879 |
| 845 GcdPrivateGetCommandsListFunction::~GcdPrivateGetCommandsListFunction() { | 880 GcdPrivateGetCommandsListFunction::~GcdPrivateGetCommandsListFunction() { |
| 846 } | 881 } |
| 847 | 882 |
| 848 bool GcdPrivateGetCommandsListFunction::RunAsync() { | 883 bool GcdPrivateGetCommandsListFunction::RunAsync() { |
| 849 return false; | 884 return false; |
| 850 } | 885 } |
| 851 | 886 |
| 852 } // namespace extensions | 887 } // namespace extensions |
| OLD | NEW |