From 9ed0cab99f18acb3570a35e9408f24355f6b8324 Mon Sep 17 00:00:00 2001 From: Alex Vakulenko Date: Mon, 12 Oct 2015 15:21:28 -0700 Subject: Move chromeos symbols into brillo namespace And move the include files into "brillo" directory instead of "chromeos" BUG: 24872993 TEST=built aosp and brillo and unit tests pass on dragonoboard Change-Id: Ieb979d1ebd3152921d36cd15acbd6247f02aae69 --- Android.mk | 172 +-- brillo/any.cc | 80 ++ brillo/any.h | 205 ++++ brillo/any_internal_impl.h | 373 +++++++ brillo/any_internal_impl_unittest.cc | 141 +++ brillo/any_unittest.cc | 306 ++++++ brillo/asynchronous_signal_handler.cc | 108 ++ brillo/asynchronous_signal_handler.h | 75 ++ brillo/asynchronous_signal_handler_interface.h | 42 + brillo/asynchronous_signal_handler_unittest.cc | 138 +++ brillo/backoff_entry.cc | 167 +++ brillo/backoff_entry.h | 115 ++ brillo/backoff_entry_unittest.cc | 311 ++++++ brillo/bind_lambda.h | 63 ++ brillo/binder_watcher.cc | 58 + brillo/binder_watcher.h | 47 + brillo/brillo_export.h | 60 ++ brillo/cryptohome.cc | 142 +++ brillo/cryptohome.h | 75 ++ brillo/daemons/daemon.cc | 92 ++ brillo/daemons/daemon.h | 116 ++ brillo/daemons/dbus_daemon.cc | 90 ++ brillo/daemons/dbus_daemon.h | 88 ++ brillo/data_encoding.cc | 154 +++ brillo/data_encoding.h | 82 ++ brillo/data_encoding_unittest.cc | 147 +++ brillo/dbus/async_event_sequencer.cc | 131 +++ brillo/dbus/async_event_sequencer.h | 113 ++ brillo/dbus/async_event_sequencer_unittest.cc | 96 ++ brillo/dbus/data_serialization.cc | 321 ++++++ brillo/dbus/data_serialization.h | 886 ++++++++++++++++ brillo/dbus/data_serialization_unittest.cc | 786 ++++++++++++++ brillo/dbus/dbus_method_invoker.cc | 23 + brillo/dbus/dbus_method_invoker.h | 324 ++++++ brillo/dbus/dbus_method_invoker_unittest.cc | 338 ++++++ brillo/dbus/dbus_method_response.cc | 66 ++ brillo/dbus/dbus_method_response.h | 98 ++ brillo/dbus/dbus_object.cc | 279 +++++ brillo/dbus/dbus_object.h | 579 ++++++++++ brillo/dbus/dbus_object_internal_impl.h | 363 +++++++ brillo/dbus/dbus_object_test_helpers.h | 143 +++ brillo/dbus/dbus_object_unittest.cc | 392 +++++++ brillo/dbus/dbus_param_reader.h | 165 +++ brillo/dbus/dbus_param_reader_unittest.cc | 253 +++++ brillo/dbus/dbus_param_writer.h | 80 ++ brillo/dbus/dbus_param_writer_unittest.cc | 189 ++++ brillo/dbus/dbus_property.h | 94 ++ brillo/dbus/dbus_service_watcher.cc | 39 + brillo/dbus/dbus_service_watcher.h | 53 + brillo/dbus/dbus_signal.cc | 28 + brillo/dbus/dbus_signal.h | 68 ++ brillo/dbus/dbus_signal_handler.h | 68 ++ brillo/dbus/dbus_signal_handler_unittest.cc | 145 +++ brillo/dbus/exported_object_manager.cc | 105 ++ brillo/dbus/exported_object_manager.h | 135 +++ brillo/dbus/exported_object_manager_unittest.cc | 194 ++++ brillo/dbus/exported_property_set.cc | 174 +++ brillo/dbus/exported_property_set.h | 226 ++++ brillo/dbus/exported_property_set_unittest.cc | 592 +++++++++++ brillo/dbus/mock_dbus_object.h | 32 + brillo/dbus/mock_exported_object_manager.h | 42 + brillo/dbus/test.proto | 8 + brillo/dbus/utils.cc | 95 ++ brillo/dbus/utils.h | 44 + brillo/errors/error.cc | 138 +++ brillo/errors/error.h | 129 +++ brillo/errors/error_codes.cc | 225 ++++ brillo/errors/error_codes.h | 43 + brillo/errors/error_codes_unittest.cc | 33 + brillo/errors/error_unittest.cc | 83 ++ brillo/file_utils.cc | 165 +++ brillo/file_utils.h | 35 + brillo/file_utils_unittest.cc | 135 +++ brillo/flag_helper.cc | 267 +++++ brillo/flag_helper.h | 274 +++++ brillo/flag_helper_unittest.cc | 362 +++++++ brillo/glib/abstract_dbus_service.cc | 36 + brillo/glib/abstract_dbus_service.h | 50 + brillo/glib/dbus.cc | 356 +++++++ brillo/glib/dbus.h | 468 ++++++++ brillo/glib/object.h | 508 +++++++++ brillo/glib/object_unittest.cc | 134 +++ brillo/http/curl_api.cc | 184 ++++ brillo/http/curl_api.h | 210 ++++ brillo/http/http_connection.h | 108 ++ brillo/http/http_connection_curl.cc | 267 +++++ brillo/http/http_connection_curl.h | 103 ++ brillo/http/http_connection_curl_unittest.cc | 324 ++++++ brillo/http/http_connection_fake.cc | 113 ++ brillo/http/http_connection_fake.h | 62 ++ brillo/http/http_form_data.cc | 221 ++++ brillo/http/http_form_data.h | 233 ++++ brillo/http/http_form_data_unittest.cc | 202 ++++ brillo/http/http_request.cc | 357 +++++++ brillo/http/http_request.h | 381 +++++++ brillo/http/http_request_unittest.cc | 202 ++++ brillo/http/http_transport.cc | 19 + brillo/http/http_transport.h | 95 ++ brillo/http/http_transport_curl.cc | 513 +++++++++ brillo/http/http_transport_curl.h | 140 +++ brillo/http/http_transport_curl_unittest.cc | 305 ++++++ brillo/http/http_transport_fake.cc | 332 ++++++ brillo/http/http_transport_fake.h | 265 +++++ brillo/http/http_utils.cc | 440 ++++++++ brillo/http/http_utils.h | 319 ++++++ brillo/http/http_utils_unittest.cc | 497 +++++++++ brillo/http/mock_connection.h | 51 + brillo/http/mock_curl_api.h | 56 + brillo/http/mock_transport.h | 44 + brillo/key_value_store.cc | 127 +++ brillo/key_value_store.h | 76 ++ brillo/key_value_store_unittest.cc | 192 ++++ brillo/location_logging.h | 24 + brillo/make_unique_ptr.h | 25 + brillo/map_utils.h | 71 ++ brillo/map_utils_unittest.cc | 59 ++ brillo/message_loops/base_message_loop.cc | 342 ++++++ brillo/message_loops/base_message_loop.h | 155 +++ brillo/message_loops/fake_message_loop.cc | 141 +++ brillo/message_loops/fake_message_loop.h | 99 ++ brillo/message_loops/fake_message_loop_unittest.cc | 116 ++ brillo/message_loops/glib_message_loop.cc | 194 ++++ brillo/message_loops/glib_message_loop.h | 83 ++ brillo/message_loops/glib_message_loop_unittest.cc | 69 ++ brillo/message_loops/message_loop.cc | 63 ++ brillo/message_loops/message_loop.h | 135 +++ brillo/message_loops/message_loop_unittest.cc | 422 ++++++++ brillo/message_loops/message_loop_utils.cc | 34 + brillo/message_loops/message_loop_utils.h | 30 + brillo/message_loops/mock_message_loop.h | 89 ++ brillo/mime_utils.cc | 163 +++ brillo/mime_utils.h | 126 +++ brillo/mime_utils_unittest.cc | 66 ++ brillo/minijail/minijail.cc | 135 +++ brillo/minijail/minijail.h | 115 ++ brillo/minijail/mock_minijail.h | 67 ++ brillo/osrelease_reader.cc | 56 + brillo/osrelease_reader.h | 54 + brillo/osrelease_reader_unittest.cc | 95 ++ brillo/pointer_utils.h | 24 + brillo/process.cc | 358 +++++++ brillo/process.h | 200 ++++ brillo/process_information.cc | 27 + brillo/process_information.h | 65 ++ brillo/process_mock.h | 43 + brillo/process_reaper.cc | 86 ++ brillo/process_reaper.h | 67 ++ brillo/process_reaper_unittest.cc | 121 +++ brillo/process_unittest.cc | 337 ++++++ brillo/secure_blob.cc | 75 ++ brillo/secure_blob.h | 58 + brillo/secure_blob_unittest.cc | 120 +++ brillo/streams/fake_stream.cc | 404 +++++++ brillo/streams/fake_stream.h | 171 +++ brillo/streams/fake_stream_unittest.cc | 510 +++++++++ brillo/streams/file_stream.cc | 548 ++++++++++ brillo/streams/file_stream.h | 175 +++ brillo/streams/file_stream_unittest.cc | 1116 +++++++++++++++++++ brillo/streams/input_stream_set.cc | 205 ++++ brillo/streams/input_stream_set.h | 132 +++ brillo/streams/input_stream_set_unittest.cc | 170 +++ brillo/streams/memory_containers.cc | 129 +++ brillo/streams/memory_containers.h | 284 +++++ brillo/streams/memory_containers_unittest.cc | 214 ++++ brillo/streams/memory_stream.cc | 201 ++++ brillo/streams/memory_stream.h | 212 ++++ brillo/streams/memory_stream_unittest.cc | 382 +++++++ brillo/streams/mock_stream.h | 75 ++ brillo/streams/openssl_stream_bio.cc | 101 ++ brillo/streams/openssl_stream_bio.h | 27 + brillo/streams/openssl_stream_bio_unittests.cc | 125 +++ brillo/streams/stream.cc | 392 +++++++ brillo/streams/stream.h | 506 +++++++++ brillo/streams/stream_errors.cc | 21 + brillo/streams/stream_errors.h | 27 + brillo/streams/stream_unittest.cc | 481 +++++++++ brillo/streams/stream_utils.cc | 216 ++++ brillo/streams/stream_utils.h | 114 ++ brillo/streams/stream_utils_unittest.cc | 300 ++++++ brillo/streams/tls_stream.cc | 545 ++++++++++ brillo/streams/tls_stream.h | 84 ++ brillo/strings/string_utils.cc | 89 ++ brillo/strings/string_utils.h | 131 +++ brillo/strings/string_utils_unittest.cc | 163 +++ brillo/syslog_logging.cc | 120 +++ brillo/syslog_logging.h | 48 + brillo/syslog_logging_unittest.cc | 31 + brillo/test_helpers.h | 32 + brillo/type_name_undecorate.cc | 33 + brillo/type_name_undecorate.h | 27 + brillo/url_utils.cc | 166 +++ brillo/url_utils.h | 85 ++ brillo/url_utils_unittest.cc | 146 +++ brillo/userdb_utils.cc | 56 + brillo/userdb_utils.h | 32 + brillo/variant_dictionary.h | 33 + brillo/variant_dictionary_unittest.cc | 26 + chromeos/any.cc | 80 -- chromeos/any.h | 205 ---- chromeos/any_internal_impl.h | 373 ------- chromeos/any_internal_impl_unittest.cc | 141 --- chromeos/any_unittest.cc | 306 ------ chromeos/asynchronous_signal_handler.cc | 108 -- chromeos/asynchronous_signal_handler.h | 75 -- chromeos/asynchronous_signal_handler_interface.h | 42 - chromeos/asynchronous_signal_handler_unittest.cc | 138 --- chromeos/backoff_entry.cc | 167 --- chromeos/backoff_entry.h | 115 -- chromeos/backoff_entry_unittest.cc | 311 ------ chromeos/bind_lambda.h | 63 -- chromeos/binder_watcher.cc | 58 - chromeos/binder_watcher.h | 47 - chromeos/chromeos_export.h | 60 -- chromeos/cryptohome.cc | 142 --- chromeos/cryptohome.h | 75 -- chromeos/daemons/daemon.cc | 92 -- chromeos/daemons/daemon.h | 116 -- chromeos/daemons/dbus_daemon.cc | 90 -- chromeos/daemons/dbus_daemon.h | 88 -- chromeos/data_encoding.cc | 154 --- chromeos/data_encoding.h | 84 -- chromeos/data_encoding_unittest.cc | 147 --- chromeos/dbus/async_event_sequencer.cc | 131 --- chromeos/dbus/async_event_sequencer.h | 113 -- chromeos/dbus/async_event_sequencer_unittest.cc | 96 -- chromeos/dbus/data_serialization.cc | 321 ------ chromeos/dbus/data_serialization.h | 886 ---------------- chromeos/dbus/data_serialization_unittest.cc | 786 -------------- chromeos/dbus/dbus_method_invoker.cc | 23 - chromeos/dbus/dbus_method_invoker.h | 324 ------ chromeos/dbus/dbus_method_invoker_unittest.cc | 342 ------ chromeos/dbus/dbus_method_response.cc | 66 -- chromeos/dbus/dbus_method_response.h | 98 -- chromeos/dbus/dbus_object.cc | 279 ----- chromeos/dbus/dbus_object.h | 579 ---------- chromeos/dbus/dbus_object_internal_impl.h | 363 ------- chromeos/dbus/dbus_object_test_helpers.h | 143 --- chromeos/dbus/dbus_object_unittest.cc | 392 ------- chromeos/dbus/dbus_param_reader.h | 165 --- chromeos/dbus/dbus_param_reader_unittest.cc | 253 ----- chromeos/dbus/dbus_param_writer.h | 80 -- chromeos/dbus/dbus_param_writer_unittest.cc | 189 ---- chromeos/dbus/dbus_property.h | 94 -- chromeos/dbus/dbus_service_watcher.cc | 39 - chromeos/dbus/dbus_service_watcher.h | 53 - chromeos/dbus/dbus_signal.cc | 28 - chromeos/dbus/dbus_signal.h | 68 -- chromeos/dbus/dbus_signal_handler.h | 68 -- chromeos/dbus/dbus_signal_handler_unittest.cc | 145 --- chromeos/dbus/exported_object_manager.cc | 105 -- chromeos/dbus/exported_object_manager.h | 135 --- chromeos/dbus/exported_object_manager_unittest.cc | 194 ---- chromeos/dbus/exported_property_set.cc | 182 ---- chromeos/dbus/exported_property_set.h | 230 ---- chromeos/dbus/exported_property_set_unittest.cc | 595 ----------- chromeos/dbus/mock_dbus_object.h | 32 - chromeos/dbus/mock_exported_object_manager.h | 42 - chromeos/dbus/test.proto | 8 - chromeos/dbus/utils.cc | 95 -- chromeos/dbus/utils.h | 44 - chromeos/errors/error.cc | 138 --- chromeos/errors/error.h | 129 --- chromeos/errors/error_codes.cc | 225 ---- chromeos/errors/error_codes.h | 43 - chromeos/errors/error_codes_unittest.cc | 33 - chromeos/errors/error_unittest.cc | 83 -- chromeos/file_utils.cc | 165 --- chromeos/file_utils.h | 35 - chromeos/file_utils_unittest.cc | 135 --- chromeos/flag_helper.cc | 267 ----- chromeos/flag_helper.h | 275 ----- chromeos/flag_helper_unittest.cc | 364 ------- chromeos/glib/abstract_dbus_service.cc | 36 - chromeos/glib/abstract_dbus_service.h | 50 - chromeos/glib/dbus.cc | 356 ------- chromeos/glib/dbus.h | 468 -------- chromeos/glib/object.h | 508 --------- chromeos/glib/object_unittest.cc | 134 --- chromeos/http/curl_api.cc | 184 ---- chromeos/http/curl_api.h | 210 ---- chromeos/http/http_connection.h | 109 -- chromeos/http/http_connection_curl.cc | 267 ----- chromeos/http/http_connection_curl.h | 104 -- chromeos/http/http_connection_curl_unittest.cc | 324 ------ chromeos/http/http_connection_fake.cc | 113 -- chromeos/http/http_connection_fake.h | 63 -- chromeos/http/http_form_data.cc | 221 ---- chromeos/http/http_form_data.h | 233 ---- chromeos/http/http_form_data_unittest.cc | 202 ---- chromeos/http/http_request.cc | 357 ------- chromeos/http/http_request.h | 381 ------- chromeos/http/http_request_unittest.cc | 202 ---- chromeos/http/http_transport.cc | 19 - chromeos/http/http_transport.h | 96 -- chromeos/http/http_transport_curl.cc | 523 --------- chromeos/http/http_transport_curl.h | 140 --- chromeos/http/http_transport_curl_unittest.cc | 305 ------ chromeos/http/http_transport_fake.cc | 332 ------ chromeos/http/http_transport_fake.h | 265 ----- chromeos/http/http_utils.cc | 452 -------- chromeos/http/http_utils.h | 317 ------ chromeos/http/http_utils_unittest.cc | 497 --------- chromeos/http/mock_connection.h | 51 - chromeos/http/mock_curl_api.h | 56 - chromeos/http/mock_transport.h | 44 - chromeos/key_value_store.cc | 127 --- chromeos/key_value_store.h | 76 -- chromeos/key_value_store_unittest.cc | 192 ---- chromeos/location_logging.h | 24 - chromeos/make_unique_ptr.h | 25 - chromeos/map_utils.h | 71 -- chromeos/map_utils_unittest.cc | 59 -- chromeos/message_loops/base_message_loop.cc | 342 ------ chromeos/message_loops/base_message_loop.h | 155 --- chromeos/message_loops/fake_message_loop.cc | 141 --- chromeos/message_loops/fake_message_loop.h | 99 -- .../message_loops/fake_message_loop_unittest.cc | 116 -- chromeos/message_loops/glib_message_loop.cc | 194 ---- chromeos/message_loops/glib_message_loop.h | 83 -- .../message_loops/glib_message_loop_unittest.cc | 69 -- chromeos/message_loops/message_loop.cc | 63 -- chromeos/message_loops/message_loop.h | 136 --- chromeos/message_loops/message_loop_unittest.cc | 422 -------- chromeos/message_loops/message_loop_utils.cc | 34 - chromeos/message_loops/message_loop_utils.h | 30 - chromeos/message_loops/mock_message_loop.h | 89 -- chromeos/mime_utils.cc | 163 --- chromeos/mime_utils.h | 126 --- chromeos/mime_utils_unittest.cc | 66 -- chromeos/minijail/minijail.cc | 135 --- chromeos/minijail/minijail.h | 115 -- chromeos/minijail/mock_minijail.h | 67 -- chromeos/osrelease_reader.cc | 56 - chromeos/osrelease_reader.h | 54 - chromeos/osrelease_reader_unittest.cc | 95 -- chromeos/pointer_utils.h | 24 - chromeos/process.cc | 358 ------- chromeos/process.h | 200 ---- chromeos/process_information.cc | 27 - chromeos/process_information.h | 65 -- chromeos/process_mock.h | 43 - chromeos/process_reaper.cc | 86 -- chromeos/process_reaper.h | 67 -- chromeos/process_reaper_unittest.cc | 121 --- chromeos/process_unittest.cc | 337 ------ chromeos/secure_blob.cc | 75 -- chromeos/secure_blob.h | 58 - chromeos/secure_blob_unittest.cc | 120 --- chromeos/streams/fake_stream.cc | 404 ------- chromeos/streams/fake_stream.h | 171 --- chromeos/streams/fake_stream_unittest.cc | 510 --------- chromeos/streams/file_stream.cc | 549 ---------- chromeos/streams/file_stream.h | 175 --- chromeos/streams/file_stream_unittest.cc | 1117 -------------------- chromeos/streams/input_stream_set.cc | 205 ---- chromeos/streams/input_stream_set.h | 133 --- chromeos/streams/input_stream_set_unittest.cc | 170 --- chromeos/streams/memory_containers.cc | 129 --- chromeos/streams/memory_containers.h | 285 ----- chromeos/streams/memory_containers_unittest.cc | 214 ---- chromeos/streams/memory_stream.cc | 203 ---- chromeos/streams/memory_stream.h | 213 ---- chromeos/streams/memory_stream_unittest.cc | 382 ------- chromeos/streams/mock_stream.h | 75 -- chromeos/streams/openssl_stream_bio.cc | 101 -- chromeos/streams/openssl_stream_bio.h | 27 - chromeos/streams/openssl_stream_bio_unittests.cc | 125 --- chromeos/streams/stream.cc | 392 ------- chromeos/streams/stream.h | 506 --------- chromeos/streams/stream_errors.cc | 21 - chromeos/streams/stream_errors.h | 27 - chromeos/streams/stream_unittest.cc | 481 --------- chromeos/streams/stream_utils.cc | 217 ---- chromeos/streams/stream_utils.h | 114 -- chromeos/streams/stream_utils_unittest.cc | 300 ------ chromeos/streams/tls_stream.cc | 546 ---------- chromeos/streams/tls_stream.h | 84 -- chromeos/strings/string_utils.cc | 89 -- chromeos/strings/string_utils.h | 131 --- chromeos/strings/string_utils_unittest.cc | 163 --- chromeos/syslog_logging.cc | 120 --- chromeos/syslog_logging.h | 48 - chromeos/syslog_logging_unittest.cc | 31 - chromeos/test_helpers.h | 32 - chromeos/type_name_undecorate.cc | 33 - chromeos/type_name_undecorate.h | 27 - chromeos/url_utils.cc | 166 --- chromeos/url_utils.h | 85 -- chromeos/url_utils_unittest.cc | 146 --- chromeos/userdb_utils.cc | 56 - chromeos/userdb_utils.h | 32 - chromeos/variant_dictionary.h | 33 - chromeos/variant_dictionary_unittest.cc | 26 - libchromeos.gypi | 210 ++-- policy/device_policy.h | 6 +- policy/device_policy_impl.h | 6 +- policy/libpolicy.h | 6 +- policy/mock_device_policy.h | 6 +- policy/mock_libpolicy.h | 6 +- testrunner.cc | 2 +- testrunner_android.cc | 2 +- 401 files changed, 34823 insertions(+), 34881 deletions(-) create mode 100644 brillo/any.cc create mode 100644 brillo/any.h create mode 100644 brillo/any_internal_impl.h create mode 100644 brillo/any_internal_impl_unittest.cc create mode 100644 brillo/any_unittest.cc create mode 100644 brillo/asynchronous_signal_handler.cc create mode 100644 brillo/asynchronous_signal_handler.h create mode 100644 brillo/asynchronous_signal_handler_interface.h create mode 100644 brillo/asynchronous_signal_handler_unittest.cc create mode 100644 brillo/backoff_entry.cc create mode 100644 brillo/backoff_entry.h create mode 100644 brillo/backoff_entry_unittest.cc create mode 100644 brillo/bind_lambda.h create mode 100644 brillo/binder_watcher.cc create mode 100644 brillo/binder_watcher.h create mode 100644 brillo/brillo_export.h create mode 100644 brillo/cryptohome.cc create mode 100644 brillo/cryptohome.h create mode 100644 brillo/daemons/daemon.cc create mode 100644 brillo/daemons/daemon.h create mode 100644 brillo/daemons/dbus_daemon.cc create mode 100644 brillo/daemons/dbus_daemon.h create mode 100644 brillo/data_encoding.cc create mode 100644 brillo/data_encoding.h create mode 100644 brillo/data_encoding_unittest.cc create mode 100644 brillo/dbus/async_event_sequencer.cc create mode 100644 brillo/dbus/async_event_sequencer.h create mode 100644 brillo/dbus/async_event_sequencer_unittest.cc create mode 100644 brillo/dbus/data_serialization.cc create mode 100644 brillo/dbus/data_serialization.h create mode 100644 brillo/dbus/data_serialization_unittest.cc create mode 100644 brillo/dbus/dbus_method_invoker.cc create mode 100644 brillo/dbus/dbus_method_invoker.h create mode 100644 brillo/dbus/dbus_method_invoker_unittest.cc create mode 100644 brillo/dbus/dbus_method_response.cc create mode 100644 brillo/dbus/dbus_method_response.h create mode 100644 brillo/dbus/dbus_object.cc create mode 100644 brillo/dbus/dbus_object.h create mode 100644 brillo/dbus/dbus_object_internal_impl.h create mode 100644 brillo/dbus/dbus_object_test_helpers.h create mode 100644 brillo/dbus/dbus_object_unittest.cc create mode 100644 brillo/dbus/dbus_param_reader.h create mode 100644 brillo/dbus/dbus_param_reader_unittest.cc create mode 100644 brillo/dbus/dbus_param_writer.h create mode 100644 brillo/dbus/dbus_param_writer_unittest.cc create mode 100644 brillo/dbus/dbus_property.h create mode 100644 brillo/dbus/dbus_service_watcher.cc create mode 100644 brillo/dbus/dbus_service_watcher.h create mode 100644 brillo/dbus/dbus_signal.cc create mode 100644 brillo/dbus/dbus_signal.h create mode 100644 brillo/dbus/dbus_signal_handler.h create mode 100644 brillo/dbus/dbus_signal_handler_unittest.cc create mode 100644 brillo/dbus/exported_object_manager.cc create mode 100644 brillo/dbus/exported_object_manager.h create mode 100644 brillo/dbus/exported_object_manager_unittest.cc create mode 100644 brillo/dbus/exported_property_set.cc create mode 100644 brillo/dbus/exported_property_set.h create mode 100644 brillo/dbus/exported_property_set_unittest.cc create mode 100644 brillo/dbus/mock_dbus_object.h create mode 100644 brillo/dbus/mock_exported_object_manager.h create mode 100644 brillo/dbus/test.proto create mode 100644 brillo/dbus/utils.cc create mode 100644 brillo/dbus/utils.h create mode 100644 brillo/errors/error.cc create mode 100644 brillo/errors/error.h create mode 100644 brillo/errors/error_codes.cc create mode 100644 brillo/errors/error_codes.h create mode 100644 brillo/errors/error_codes_unittest.cc create mode 100644 brillo/errors/error_unittest.cc create mode 100644 brillo/file_utils.cc create mode 100644 brillo/file_utils.h create mode 100644 brillo/file_utils_unittest.cc create mode 100644 brillo/flag_helper.cc create mode 100644 brillo/flag_helper.h create mode 100644 brillo/flag_helper_unittest.cc create mode 100644 brillo/glib/abstract_dbus_service.cc create mode 100644 brillo/glib/abstract_dbus_service.h create mode 100644 brillo/glib/dbus.cc create mode 100644 brillo/glib/dbus.h create mode 100644 brillo/glib/object.h create mode 100644 brillo/glib/object_unittest.cc create mode 100644 brillo/http/curl_api.cc create mode 100644 brillo/http/curl_api.h create mode 100644 brillo/http/http_connection.h create mode 100644 brillo/http/http_connection_curl.cc create mode 100644 brillo/http/http_connection_curl.h create mode 100644 brillo/http/http_connection_curl_unittest.cc create mode 100644 brillo/http/http_connection_fake.cc create mode 100644 brillo/http/http_connection_fake.h create mode 100644 brillo/http/http_form_data.cc create mode 100644 brillo/http/http_form_data.h create mode 100644 brillo/http/http_form_data_unittest.cc create mode 100644 brillo/http/http_request.cc create mode 100644 brillo/http/http_request.h create mode 100644 brillo/http/http_request_unittest.cc create mode 100644 brillo/http/http_transport.cc create mode 100644 brillo/http/http_transport.h create mode 100644 brillo/http/http_transport_curl.cc create mode 100644 brillo/http/http_transport_curl.h create mode 100644 brillo/http/http_transport_curl_unittest.cc create mode 100644 brillo/http/http_transport_fake.cc create mode 100644 brillo/http/http_transport_fake.h create mode 100644 brillo/http/http_utils.cc create mode 100644 brillo/http/http_utils.h create mode 100644 brillo/http/http_utils_unittest.cc create mode 100644 brillo/http/mock_connection.h create mode 100644 brillo/http/mock_curl_api.h create mode 100644 brillo/http/mock_transport.h create mode 100644 brillo/key_value_store.cc create mode 100644 brillo/key_value_store.h create mode 100644 brillo/key_value_store_unittest.cc create mode 100644 brillo/location_logging.h create mode 100644 brillo/make_unique_ptr.h create mode 100644 brillo/map_utils.h create mode 100644 brillo/map_utils_unittest.cc create mode 100644 brillo/message_loops/base_message_loop.cc create mode 100644 brillo/message_loops/base_message_loop.h create mode 100644 brillo/message_loops/fake_message_loop.cc create mode 100644 brillo/message_loops/fake_message_loop.h create mode 100644 brillo/message_loops/fake_message_loop_unittest.cc create mode 100644 brillo/message_loops/glib_message_loop.cc create mode 100644 brillo/message_loops/glib_message_loop.h create mode 100644 brillo/message_loops/glib_message_loop_unittest.cc create mode 100644 brillo/message_loops/message_loop.cc create mode 100644 brillo/message_loops/message_loop.h create mode 100644 brillo/message_loops/message_loop_unittest.cc create mode 100644 brillo/message_loops/message_loop_utils.cc create mode 100644 brillo/message_loops/message_loop_utils.h create mode 100644 brillo/message_loops/mock_message_loop.h create mode 100644 brillo/mime_utils.cc create mode 100644 brillo/mime_utils.h create mode 100644 brillo/mime_utils_unittest.cc create mode 100644 brillo/minijail/minijail.cc create mode 100644 brillo/minijail/minijail.h create mode 100644 brillo/minijail/mock_minijail.h create mode 100644 brillo/osrelease_reader.cc create mode 100644 brillo/osrelease_reader.h create mode 100644 brillo/osrelease_reader_unittest.cc create mode 100644 brillo/pointer_utils.h create mode 100644 brillo/process.cc create mode 100644 brillo/process.h create mode 100644 brillo/process_information.cc create mode 100644 brillo/process_information.h create mode 100644 brillo/process_mock.h create mode 100644 brillo/process_reaper.cc create mode 100644 brillo/process_reaper.h create mode 100644 brillo/process_reaper_unittest.cc create mode 100644 brillo/process_unittest.cc create mode 100644 brillo/secure_blob.cc create mode 100644 brillo/secure_blob.h create mode 100644 brillo/secure_blob_unittest.cc create mode 100644 brillo/streams/fake_stream.cc create mode 100644 brillo/streams/fake_stream.h create mode 100644 brillo/streams/fake_stream_unittest.cc create mode 100644 brillo/streams/file_stream.cc create mode 100644 brillo/streams/file_stream.h create mode 100644 brillo/streams/file_stream_unittest.cc create mode 100644 brillo/streams/input_stream_set.cc create mode 100644 brillo/streams/input_stream_set.h create mode 100644 brillo/streams/input_stream_set_unittest.cc create mode 100644 brillo/streams/memory_containers.cc create mode 100644 brillo/streams/memory_containers.h create mode 100644 brillo/streams/memory_containers_unittest.cc create mode 100644 brillo/streams/memory_stream.cc create mode 100644 brillo/streams/memory_stream.h create mode 100644 brillo/streams/memory_stream_unittest.cc create mode 100644 brillo/streams/mock_stream.h create mode 100644 brillo/streams/openssl_stream_bio.cc create mode 100644 brillo/streams/openssl_stream_bio.h create mode 100644 brillo/streams/openssl_stream_bio_unittests.cc create mode 100644 brillo/streams/stream.cc create mode 100644 brillo/streams/stream.h create mode 100644 brillo/streams/stream_errors.cc create mode 100644 brillo/streams/stream_errors.h create mode 100644 brillo/streams/stream_unittest.cc create mode 100644 brillo/streams/stream_utils.cc create mode 100644 brillo/streams/stream_utils.h create mode 100644 brillo/streams/stream_utils_unittest.cc create mode 100644 brillo/streams/tls_stream.cc create mode 100644 brillo/streams/tls_stream.h create mode 100644 brillo/strings/string_utils.cc create mode 100644 brillo/strings/string_utils.h create mode 100644 brillo/strings/string_utils_unittest.cc create mode 100644 brillo/syslog_logging.cc create mode 100644 brillo/syslog_logging.h create mode 100644 brillo/syslog_logging_unittest.cc create mode 100644 brillo/test_helpers.h create mode 100644 brillo/type_name_undecorate.cc create mode 100644 brillo/type_name_undecorate.h create mode 100644 brillo/url_utils.cc create mode 100644 brillo/url_utils.h create mode 100644 brillo/url_utils_unittest.cc create mode 100644 brillo/userdb_utils.cc create mode 100644 brillo/userdb_utils.h create mode 100644 brillo/variant_dictionary.h create mode 100644 brillo/variant_dictionary_unittest.cc delete mode 100644 chromeos/any.cc delete mode 100644 chromeos/any.h delete mode 100644 chromeos/any_internal_impl.h delete mode 100644 chromeos/any_internal_impl_unittest.cc delete mode 100644 chromeos/any_unittest.cc delete mode 100644 chromeos/asynchronous_signal_handler.cc delete mode 100644 chromeos/asynchronous_signal_handler.h delete mode 100644 chromeos/asynchronous_signal_handler_interface.h delete mode 100644 chromeos/asynchronous_signal_handler_unittest.cc delete mode 100644 chromeos/backoff_entry.cc delete mode 100644 chromeos/backoff_entry.h delete mode 100644 chromeos/backoff_entry_unittest.cc delete mode 100644 chromeos/bind_lambda.h delete mode 100644 chromeos/binder_watcher.cc delete mode 100644 chromeos/binder_watcher.h delete mode 100644 chromeos/chromeos_export.h delete mode 100644 chromeos/cryptohome.cc delete mode 100644 chromeos/cryptohome.h delete mode 100644 chromeos/daemons/daemon.cc delete mode 100644 chromeos/daemons/daemon.h delete mode 100644 chromeos/daemons/dbus_daemon.cc delete mode 100644 chromeos/daemons/dbus_daemon.h delete mode 100644 chromeos/data_encoding.cc delete mode 100644 chromeos/data_encoding.h delete mode 100644 chromeos/data_encoding_unittest.cc delete mode 100644 chromeos/dbus/async_event_sequencer.cc delete mode 100644 chromeos/dbus/async_event_sequencer.h delete mode 100644 chromeos/dbus/async_event_sequencer_unittest.cc delete mode 100644 chromeos/dbus/data_serialization.cc delete mode 100644 chromeos/dbus/data_serialization.h delete mode 100644 chromeos/dbus/data_serialization_unittest.cc delete mode 100644 chromeos/dbus/dbus_method_invoker.cc delete mode 100644 chromeos/dbus/dbus_method_invoker.h delete mode 100644 chromeos/dbus/dbus_method_invoker_unittest.cc delete mode 100644 chromeos/dbus/dbus_method_response.cc delete mode 100644 chromeos/dbus/dbus_method_response.h delete mode 100644 chromeos/dbus/dbus_object.cc delete mode 100644 chromeos/dbus/dbus_object.h delete mode 100644 chromeos/dbus/dbus_object_internal_impl.h delete mode 100644 chromeos/dbus/dbus_object_test_helpers.h delete mode 100644 chromeos/dbus/dbus_object_unittest.cc delete mode 100644 chromeos/dbus/dbus_param_reader.h delete mode 100644 chromeos/dbus/dbus_param_reader_unittest.cc delete mode 100644 chromeos/dbus/dbus_param_writer.h delete mode 100644 chromeos/dbus/dbus_param_writer_unittest.cc delete mode 100644 chromeos/dbus/dbus_property.h delete mode 100644 chromeos/dbus/dbus_service_watcher.cc delete mode 100644 chromeos/dbus/dbus_service_watcher.h delete mode 100644 chromeos/dbus/dbus_signal.cc delete mode 100644 chromeos/dbus/dbus_signal.h delete mode 100644 chromeos/dbus/dbus_signal_handler.h delete mode 100644 chromeos/dbus/dbus_signal_handler_unittest.cc delete mode 100644 chromeos/dbus/exported_object_manager.cc delete mode 100644 chromeos/dbus/exported_object_manager.h delete mode 100644 chromeos/dbus/exported_object_manager_unittest.cc delete mode 100644 chromeos/dbus/exported_property_set.cc delete mode 100644 chromeos/dbus/exported_property_set.h delete mode 100644 chromeos/dbus/exported_property_set_unittest.cc delete mode 100644 chromeos/dbus/mock_dbus_object.h delete mode 100644 chromeos/dbus/mock_exported_object_manager.h delete mode 100644 chromeos/dbus/test.proto delete mode 100644 chromeos/dbus/utils.cc delete mode 100644 chromeos/dbus/utils.h delete mode 100644 chromeos/errors/error.cc delete mode 100644 chromeos/errors/error.h delete mode 100644 chromeos/errors/error_codes.cc delete mode 100644 chromeos/errors/error_codes.h delete mode 100644 chromeos/errors/error_codes_unittest.cc delete mode 100644 chromeos/errors/error_unittest.cc delete mode 100644 chromeos/file_utils.cc delete mode 100644 chromeos/file_utils.h delete mode 100644 chromeos/file_utils_unittest.cc delete mode 100644 chromeos/flag_helper.cc delete mode 100644 chromeos/flag_helper.h delete mode 100644 chromeos/flag_helper_unittest.cc delete mode 100644 chromeos/glib/abstract_dbus_service.cc delete mode 100644 chromeos/glib/abstract_dbus_service.h delete mode 100644 chromeos/glib/dbus.cc delete mode 100644 chromeos/glib/dbus.h delete mode 100644 chromeos/glib/object.h delete mode 100644 chromeos/glib/object_unittest.cc delete mode 100644 chromeos/http/curl_api.cc delete mode 100644 chromeos/http/curl_api.h delete mode 100644 chromeos/http/http_connection.h delete mode 100644 chromeos/http/http_connection_curl.cc delete mode 100644 chromeos/http/http_connection_curl.h delete mode 100644 chromeos/http/http_connection_curl_unittest.cc delete mode 100644 chromeos/http/http_connection_fake.cc delete mode 100644 chromeos/http/http_connection_fake.h delete mode 100644 chromeos/http/http_form_data.cc delete mode 100644 chromeos/http/http_form_data.h delete mode 100644 chromeos/http/http_form_data_unittest.cc delete mode 100644 chromeos/http/http_request.cc delete mode 100644 chromeos/http/http_request.h delete mode 100644 chromeos/http/http_request_unittest.cc delete mode 100644 chromeos/http/http_transport.cc delete mode 100644 chromeos/http/http_transport.h delete mode 100644 chromeos/http/http_transport_curl.cc delete mode 100644 chromeos/http/http_transport_curl.h delete mode 100644 chromeos/http/http_transport_curl_unittest.cc delete mode 100644 chromeos/http/http_transport_fake.cc delete mode 100644 chromeos/http/http_transport_fake.h delete mode 100644 chromeos/http/http_utils.cc delete mode 100644 chromeos/http/http_utils.h delete mode 100644 chromeos/http/http_utils_unittest.cc delete mode 100644 chromeos/http/mock_connection.h delete mode 100644 chromeos/http/mock_curl_api.h delete mode 100644 chromeos/http/mock_transport.h delete mode 100644 chromeos/key_value_store.cc delete mode 100644 chromeos/key_value_store.h delete mode 100644 chromeos/key_value_store_unittest.cc delete mode 100644 chromeos/location_logging.h delete mode 100644 chromeos/make_unique_ptr.h delete mode 100644 chromeos/map_utils.h delete mode 100644 chromeos/map_utils_unittest.cc delete mode 100644 chromeos/message_loops/base_message_loop.cc delete mode 100644 chromeos/message_loops/base_message_loop.h delete mode 100644 chromeos/message_loops/fake_message_loop.cc delete mode 100644 chromeos/message_loops/fake_message_loop.h delete mode 100644 chromeos/message_loops/fake_message_loop_unittest.cc delete mode 100644 chromeos/message_loops/glib_message_loop.cc delete mode 100644 chromeos/message_loops/glib_message_loop.h delete mode 100644 chromeos/message_loops/glib_message_loop_unittest.cc delete mode 100644 chromeos/message_loops/message_loop.cc delete mode 100644 chromeos/message_loops/message_loop.h delete mode 100644 chromeos/message_loops/message_loop_unittest.cc delete mode 100644 chromeos/message_loops/message_loop_utils.cc delete mode 100644 chromeos/message_loops/message_loop_utils.h delete mode 100644 chromeos/message_loops/mock_message_loop.h delete mode 100644 chromeos/mime_utils.cc delete mode 100644 chromeos/mime_utils.h delete mode 100644 chromeos/mime_utils_unittest.cc delete mode 100644 chromeos/minijail/minijail.cc delete mode 100644 chromeos/minijail/minijail.h delete mode 100644 chromeos/minijail/mock_minijail.h delete mode 100644 chromeos/osrelease_reader.cc delete mode 100644 chromeos/osrelease_reader.h delete mode 100644 chromeos/osrelease_reader_unittest.cc delete mode 100644 chromeos/pointer_utils.h delete mode 100644 chromeos/process.cc delete mode 100644 chromeos/process.h delete mode 100644 chromeos/process_information.cc delete mode 100644 chromeos/process_information.h delete mode 100644 chromeos/process_mock.h delete mode 100644 chromeos/process_reaper.cc delete mode 100644 chromeos/process_reaper.h delete mode 100644 chromeos/process_reaper_unittest.cc delete mode 100644 chromeos/process_unittest.cc delete mode 100644 chromeos/secure_blob.cc delete mode 100644 chromeos/secure_blob.h delete mode 100644 chromeos/secure_blob_unittest.cc delete mode 100644 chromeos/streams/fake_stream.cc delete mode 100644 chromeos/streams/fake_stream.h delete mode 100644 chromeos/streams/fake_stream_unittest.cc delete mode 100644 chromeos/streams/file_stream.cc delete mode 100644 chromeos/streams/file_stream.h delete mode 100644 chromeos/streams/file_stream_unittest.cc delete mode 100644 chromeos/streams/input_stream_set.cc delete mode 100644 chromeos/streams/input_stream_set.h delete mode 100644 chromeos/streams/input_stream_set_unittest.cc delete mode 100644 chromeos/streams/memory_containers.cc delete mode 100644 chromeos/streams/memory_containers.h delete mode 100644 chromeos/streams/memory_containers_unittest.cc delete mode 100644 chromeos/streams/memory_stream.cc delete mode 100644 chromeos/streams/memory_stream.h delete mode 100644 chromeos/streams/memory_stream_unittest.cc delete mode 100644 chromeos/streams/mock_stream.h delete mode 100644 chromeos/streams/openssl_stream_bio.cc delete mode 100644 chromeos/streams/openssl_stream_bio.h delete mode 100644 chromeos/streams/openssl_stream_bio_unittests.cc delete mode 100644 chromeos/streams/stream.cc delete mode 100644 chromeos/streams/stream.h delete mode 100644 chromeos/streams/stream_errors.cc delete mode 100644 chromeos/streams/stream_errors.h delete mode 100644 chromeos/streams/stream_unittest.cc delete mode 100644 chromeos/streams/stream_utils.cc delete mode 100644 chromeos/streams/stream_utils.h delete mode 100644 chromeos/streams/stream_utils_unittest.cc delete mode 100644 chromeos/streams/tls_stream.cc delete mode 100644 chromeos/streams/tls_stream.h delete mode 100644 chromeos/strings/string_utils.cc delete mode 100644 chromeos/strings/string_utils.h delete mode 100644 chromeos/strings/string_utils_unittest.cc delete mode 100644 chromeos/syslog_logging.cc delete mode 100644 chromeos/syslog_logging.h delete mode 100644 chromeos/syslog_logging_unittest.cc delete mode 100644 chromeos/test_helpers.h delete mode 100644 chromeos/type_name_undecorate.cc delete mode 100644 chromeos/type_name_undecorate.h delete mode 100644 chromeos/url_utils.cc delete mode 100644 chromeos/url_utils.h delete mode 100644 chromeos/url_utils_unittest.cc delete mode 100644 chromeos/userdb_utils.cc delete mode 100644 chromeos/userdb_utils.h delete mode 100644 chromeos/variant_dictionary.h delete mode 100644 chromeos/variant_dictionary_unittest.cc diff --git a/Android.mk b/Android.mk index c6dc921..9531923 100644 --- a/Android.mk +++ b/Android.mk @@ -16,109 +16,109 @@ LOCAL_PATH := $(call my-dir) libchromeos_cpp_extension := .cc libchromeos_core_sources := \ - chromeos/backoff_entry.cc \ - chromeos/data_encoding.cc \ - chromeos/errors/error.cc \ - chromeos/errors/error_codes.cc \ - chromeos/flag_helper.cc \ - chromeos/key_value_store.cc \ - chromeos/message_loops/base_message_loop.cc \ - chromeos/message_loops/message_loop.cc \ - chromeos/message_loops/message_loop_utils.cc \ - chromeos/mime_utils.cc \ - chromeos/osrelease_reader.cc \ - chromeos/process.cc \ - chromeos/process_information.cc \ - chromeos/secure_blob.cc \ - chromeos/strings/string_utils.cc \ - chromeos/syslog_logging.cc \ - chromeos/type_name_undecorate.cc \ - chromeos/url_utils.cc \ + brillo/backoff_entry.cc \ + brillo/data_encoding.cc \ + brillo/errors/error.cc \ + brillo/errors/error_codes.cc \ + brillo/flag_helper.cc \ + brillo/key_value_store.cc \ + brillo/message_loops/base_message_loop.cc \ + brillo/message_loops/message_loop.cc \ + brillo/message_loops/message_loop_utils.cc \ + brillo/mime_utils.cc \ + brillo/osrelease_reader.cc \ + brillo/process.cc \ + brillo/process_information.cc \ + brillo/secure_blob.cc \ + brillo/strings/string_utils.cc \ + brillo/syslog_logging.cc \ + brillo/type_name_undecorate.cc \ + brillo/url_utils.cc \ libchromeos_linux_sources := \ - chromeos/asynchronous_signal_handler.cc \ - chromeos/daemons/daemon.cc \ - chromeos/file_utils.cc \ - chromeos/process_reaper.cc \ + brillo/asynchronous_signal_handler.cc \ + brillo/daemons/daemon.cc \ + brillo/file_utils.cc \ + brillo/process_reaper.cc \ libchromeos_binder_sources := \ - chromeos/binder_watcher.cc \ + brillo/binder_watcher.cc \ libchromeos_dbus_sources := \ - chromeos/any.cc \ - chromeos/daemons/dbus_daemon.cc \ - chromeos/dbus/async_event_sequencer.cc \ - chromeos/dbus/data_serialization.cc \ - chromeos/dbus/dbus_method_invoker.cc \ - chromeos/dbus/dbus_method_response.cc \ - chromeos/dbus/dbus_object.cc \ - chromeos/dbus/dbus_service_watcher.cc \ - chromeos/dbus/dbus_signal.cc \ - chromeos/dbus/exported_object_manager.cc \ - chromeos/dbus/exported_property_set.cc \ - chromeos/dbus/utils.cc \ + brillo/any.cc \ + brillo/daemons/dbus_daemon.cc \ + brillo/dbus/async_event_sequencer.cc \ + brillo/dbus/data_serialization.cc \ + brillo/dbus/dbus_method_invoker.cc \ + brillo/dbus/dbus_method_response.cc \ + brillo/dbus/dbus_object.cc \ + brillo/dbus/dbus_service_watcher.cc \ + brillo/dbus/dbus_signal.cc \ + brillo/dbus/exported_object_manager.cc \ + brillo/dbus/exported_property_set.cc \ + brillo/dbus/utils.cc \ libchromeos_http_sources := \ - chromeos/http/curl_api.cc \ - chromeos/http/http_connection_curl.cc \ - chromeos/http/http_form_data.cc \ - chromeos/http/http_request.cc \ - chromeos/http/http_transport.cc \ - chromeos/http/http_transport_curl.cc \ - chromeos/http/http_utils.cc \ + brillo/http/curl_api.cc \ + brillo/http/http_connection_curl.cc \ + brillo/http/http_form_data.cc \ + brillo/http/http_request.cc \ + brillo/http/http_transport.cc \ + brillo/http/http_transport_curl.cc \ + brillo/http/http_utils.cc \ libchromeos_policy_sources := \ policy/device_policy.cc \ policy/libpolicy.cc \ libchromeos_stream_sources := \ - chromeos/streams/file_stream.cc \ - chromeos/streams/input_stream_set.cc \ - chromeos/streams/memory_containers.cc \ - chromeos/streams/memory_stream.cc \ - chromeos/streams/openssl_stream_bio.cc \ - chromeos/streams/stream.cc \ - chromeos/streams/stream_errors.cc \ - chromeos/streams/stream_utils.cc \ - chromeos/streams/tls_stream.cc \ + brillo/streams/file_stream.cc \ + brillo/streams/input_stream_set.cc \ + brillo/streams/memory_containers.cc \ + brillo/streams/memory_stream.cc \ + brillo/streams/openssl_stream_bio.cc \ + brillo/streams/stream.cc \ + brillo/streams/stream_errors.cc \ + brillo/streams/stream_utils.cc \ + brillo/streams/tls_stream.cc \ libchromeos_test_helpers_sources := \ - chromeos/http/http_connection_fake.cc \ - chromeos/http/http_transport_fake.cc \ - chromeos/message_loops/fake_message_loop.cc \ - chromeos/streams/fake_stream.cc \ + brillo/http/http_connection_fake.cc \ + brillo/http/http_transport_fake.cc \ + brillo/message_loops/fake_message_loop.cc \ + brillo/streams/fake_stream.cc \ libchromeos_test_sources := \ - chromeos/asynchronous_signal_handler_unittest.cc \ - chromeos/backoff_entry_unittest.cc \ - chromeos/data_encoding_unittest.cc \ - chromeos/errors/error_codes_unittest.cc \ - chromeos/errors/error_unittest.cc \ - chromeos/file_utils_unittest.cc \ - chromeos/flag_helper_unittest.cc \ - chromeos/http/http_connection_curl_unittest.cc \ - chromeos/http/http_form_data_unittest.cc \ - chromeos/http/http_request_unittest.cc \ - chromeos/http/http_transport_curl_unittest.cc \ - chromeos/http/http_utils_unittest.cc \ - chromeos/key_value_store_unittest.cc \ - chromeos/map_utils_unittest.cc \ - chromeos/message_loops/fake_message_loop_unittest.cc \ - chromeos/mime_utils_unittest.cc \ - chromeos/osrelease_reader_unittest.cc \ - chromeos/process_reaper_unittest.cc \ - chromeos/process_unittest.cc \ - chromeos/secure_blob_unittest.cc \ - chromeos/streams/fake_stream_unittest.cc \ - chromeos/streams/file_stream_unittest.cc \ - chromeos/streams/input_stream_set_unittest.cc \ - chromeos/streams/memory_containers_unittest.cc \ - chromeos/streams/memory_stream_unittest.cc \ - chromeos/streams/openssl_stream_bio_unittests.cc \ - chromeos/streams/stream_unittest.cc \ - chromeos/streams/stream_utils_unittest.cc \ - chromeos/strings/string_utils_unittest.cc \ - chromeos/url_utils_unittest.cc \ + brillo/asynchronous_signal_handler_unittest.cc \ + brillo/backoff_entry_unittest.cc \ + brillo/data_encoding_unittest.cc \ + brillo/errors/error_codes_unittest.cc \ + brillo/errors/error_unittest.cc \ + brillo/file_utils_unittest.cc \ + brillo/flag_helper_unittest.cc \ + brillo/http/http_connection_curl_unittest.cc \ + brillo/http/http_form_data_unittest.cc \ + brillo/http/http_request_unittest.cc \ + brillo/http/http_transport_curl_unittest.cc \ + brillo/http/http_utils_unittest.cc \ + brillo/key_value_store_unittest.cc \ + brillo/map_utils_unittest.cc \ + brillo/message_loops/fake_message_loop_unittest.cc \ + brillo/mime_utils_unittest.cc \ + brillo/osrelease_reader_unittest.cc \ + brillo/process_reaper_unittest.cc \ + brillo/process_unittest.cc \ + brillo/secure_blob_unittest.cc \ + brillo/streams/fake_stream_unittest.cc \ + brillo/streams/file_stream_unittest.cc \ + brillo/streams/input_stream_set_unittest.cc \ + brillo/streams/memory_containers_unittest.cc \ + brillo/streams/memory_stream_unittest.cc \ + brillo/streams/openssl_stream_bio_unittests.cc \ + brillo/streams/stream_unittest.cc \ + brillo/streams/stream_utils_unittest.cc \ + brillo/strings/string_utils_unittest.cc \ + brillo/url_utils_unittest.cc \ libchromeos_CFLAGS := -Wall \ -Wno-char-subscripts -Wno-missing-field-initializers \ @@ -181,7 +181,7 @@ include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_CPP_EXTENSION := $(libchromeos_cpp_extension) LOCAL_MODULE := libchromeos-minijail -LOCAL_SRC_FILES := chromeos/minijail/minijail.cc \ +LOCAL_SRC_FILES := brillo/minijail/minijail.cc \ LOCAL_C_INCLUDES := $(libchromeos_includes) LOCAL_SHARED_LIBRARIES := $(libchromeos_shared_libraries) libchromeos \ diff --git a/brillo/any.cc b/brillo/any.cc new file mode 100644 index 0000000..30e875b --- /dev/null +++ b/brillo/any.cc @@ -0,0 +1,80 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { + +Any::Any() { +} + +Any::Any(const Any& rhs) : data_buffer_(rhs.data_buffer_) { +} + +// NOLINTNEXTLINE(build/c++11) +Any::Any(Any&& rhs) : data_buffer_(std::move(rhs.data_buffer_)) { +} + +Any::~Any() { +} + +Any& Any::operator=(const Any& rhs) { + data_buffer_ = rhs.data_buffer_; + return *this; +} + +// NOLINTNEXTLINE(build/c++11) +Any& Any::operator=(Any&& rhs) { + data_buffer_ = std::move(rhs.data_buffer_); + return *this; +} + +bool Any::operator==(const Any& rhs) const { + // Make sure both objects contain data of the same type. + if (GetType() != rhs.GetType()) + return false; + + if (IsEmpty()) + return true; + + return data_buffer_.GetDataPtr()->CompareEqual(rhs.data_buffer_.GetDataPtr()); +} + +const std::type_info& Any::GetType() const { + if (!IsEmpty()) + return data_buffer_.GetDataPtr()->GetType(); + + struct NullType {}; // Special helper type representing an empty variant. + return typeid(NullType); +} + +void Any::Swap(Any& other) { + std::swap(data_buffer_, other.data_buffer_); +} + +bool Any::IsEmpty() const { + return data_buffer_.IsEmpty(); +} + +void Any::Clear() { + data_buffer_.Clear(); +} + +bool Any::IsConvertibleToInteger() const { + return !IsEmpty() && data_buffer_.GetDataPtr()->IsConvertibleToInteger(); +} + +intmax_t Any::GetAsInteger() const { + CHECK(!IsEmpty()) << "Must not be called on an empty Any"; + return data_buffer_.GetDataPtr()->GetAsInteger(); +} + +void Any::AppendToDBusMessageWriter(dbus::MessageWriter* writer) const { + CHECK(!IsEmpty()) << "Must not be called on an empty Any"; + data_buffer_.GetDataPtr()->AppendToDBusMessage(writer); +} + +} // namespace brillo diff --git a/brillo/any.h b/brillo/any.h new file mode 100644 index 0000000..b872962 --- /dev/null +++ b/brillo/any.h @@ -0,0 +1,205 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is an implementation of a "true" variant class in C++. +// The brillo::Any class can hold any C++ type, but both the setter and +// getter sites need to know the actual type of data. +// Note that C-style arrays when stored in Any are reduced to simple +// data pointers. Any will not copy a contents of the array. +// const int data[] = [1,2,3]; +// Any v(data); // stores const int*, effectively "Any v(&data[0]);" + +// brillo::Any is a value type. Which means, the data is copied into it +// and Any owns it. The owned object (stored by value) will be destroyed +// when Any is cleared or reassigned. The contained value type must be +// copy-constructible. You can also store pointers and references to objects. +// Storing pointers is trivial. In order to store a reference, you can +// use helper functions std::ref() and std::cref() to create non-const and +// const references respectively. In such a case, the type of contained data +// will be std::reference_wrapper. See 'References' unit tests in +// any_unittest.cc for examples. + +#ifndef LIBCHROMEOS_BRILLO_ANY_H_ +#define LIBCHROMEOS_BRILLO_ANY_H_ + +#include + +#include + +#include +#include + +namespace dbus { +class MessageWriter; +} // namespace dbus + +namespace brillo { + +class BRILLO_EXPORT Any final { + public: + Any(); // Do not inline to hide internal_details::Buffer from export table. + // Standard copy/move constructors. This is a value-class container + // that must be copy-constructible and movable. The copy constructors + // should not be marked as explicit. + Any(const Any& rhs); + Any(Any&& rhs); // NOLINT(build/c++11) + // Typed constructor that stores a value of type T in the Any. + template + inline Any(T value) { // NOLINT(runtime/explicit) + data_buffer_.Assign(std::move(value)); + } + + // Not declaring the destructor as virtual since this is a sealed class + // and there is no need to introduce a virtual table to it. + ~Any(); + + // Assignment operators. + Any& operator=(const Any& rhs); + Any& operator=(Any&& rhs); // NOLINT(build/c++11) + template + inline Any& operator=(T value) { + data_buffer_.Assign(std::move(value)); + return *this; + } + + // Compares the contents of two Any objects for equality. Note that the + // contained type must be equality-comparable (must have operator== defined). + // If operator==() is not available for contained type, comparison operation + // always returns false (as if the data were different). + bool operator==(const Any& rhs) const; + inline bool operator!=(const Any& rhs) const { return !operator==(rhs); } + + // Checks if the given type DestType can be obtained from the Any. + // For example, to check if Any has a 'double' value in it: + // any.IsTypeCompatible() + template + bool IsTypeCompatible() const { + // Make sure the requested type DestType conforms to the storage + // requirements of Any. We always store the data by value, which means we + // strip away any references as well as cv-qualifiers. So, if the user + // stores "const int&", we actually store just an "int". + // When calling IsTypeCompatible, we need to do a similar "type cleansing" + // to make sure the requested type matches the type of data actually stored, + // so this "canonical" type is used for type checking below. + using CanonicalDestType = typename std::decay::type; + const std::type_info& ContainedTypeId = GetType(); + if (typeid(CanonicalDestType) == ContainedTypeId) + return true; + + if (!std::is_pointer::value) + return false; + + // If asking for a const pointer from a variant containing non-const + // pointer, still satisfy the request. So, we need to remove the pointer + // specification first, then strip the const/volatile qualifiers, then + // re-add the pointer back, so "const int*" would become "int*". + using NonPointer = typename std::remove_pointer::type; + using CanonicalDestTypeNoConst = typename std::add_pointer< + typename std::remove_const::type>::type; + using CanonicalDestTypeNoVolatile = typename std::add_pointer< + typename std::remove_volatile::type>::type; + using CanonicalDestTypeNoConstOrVolatile = typename std::add_pointer< + typename std::remove_cv::type>::type; + + return typeid(CanonicalDestTypeNoConst) == ContainedTypeId || + typeid(CanonicalDestTypeNoVolatile) == ContainedTypeId || + typeid(CanonicalDestTypeNoConstOrVolatile) == ContainedTypeId; + } + + // Returns immutable data contained in Any. + // Aborts if Any doesn't contain a value of type T, or trivially + // convertible to/compatible with it. + template + const T& Get() const { + CHECK(IsTypeCompatible()) + << "Requesting value of type '" << GetUndecoratedTypeName() + << "' from variant containing '" << UndecorateTypeName(GetType().name()) + << "'"; + return data_buffer_.GetData(); + } + + // Returns a copy of data in Any and returns true when that data is + // compatible with T. Returns false if contained data is incompatible. + template + bool GetValue(T* value) const { + if (!IsTypeCompatible()) { + return false; + } + *value = Get(); + return true; + } + + // Returns a pointer to mutable value of type T contained within Any. + // No data copying is made, the data pointed to is still owned by Any. + // If Any doesn't contain a value of type T, or trivially + // convertible/compatible to/with it, then it returns nullptr. + template + T* GetPtr() { + if (!IsTypeCompatible()) + return nullptr; + return &(data_buffer_.GetData()); + } + + // Returns a copy of the data contained in Any. + // If the Any doesn't contain a compatible value, the provided default + // |def_val| is returned instead. + template + T TryGet(typename std::decay::type const& def_val) const { + if (!IsTypeCompatible()) + return def_val; + return data_buffer_.GetData(); + } + + // A convenience specialization of the above function where the default + // value of type T is returned in case the underlying Get() fails. + template + T TryGet() const { + return TryGet(typename std::decay::type()); + } + + // Returns the type information about the contained data. For most cases, + // instead of using this function, you should be calling IsTypeCompatible<>(). + const std::type_info& GetType() const; + // Swaps the value of this object with that of |other|. + void Swap(Any& other); + // Checks if Any is empty, that is, not containing a value of any type. + bool IsEmpty() const; + // Clears the Any and destroys any contained object. Makes it empty. + void Clear(); + // Checks if Any contains a type convertible to integer. + // Any type that match std::is_integral and std::is_enum is accepted. + // That includes signed and unsigned char, short, int, long, etc as well as + // 'bool' and enumerated types. + // For 'integer' type, you can call GetAsInteger to do implicit type + // conversion to intmax_t. + bool IsConvertibleToInteger() const; + // For integral types and enums contained in the Any, get the integer value + // of data. This is a useful function to obtain an integer value when + // any can possibly have unspecified integer, such as 'short', 'unsigned long' + // and so on. + intmax_t GetAsInteger() const; + // Writes the contained data to D-Bus message writer, if the appropriate + // serialization method for contained data of the given type is provided + // (an appropriate specialization of AppendValueToWriter() is available). + // Returns false if the Any is empty or if there is no serialization method + // defined for the contained data. + void AppendToDBusMessageWriter(dbus::MessageWriter* writer) const; + + private: + // The data buffer for contained object. + internal_details::Buffer data_buffer_; +}; + +} // namespace brillo + +namespace std { + +// Specialize std::swap() algorithm for brillo::Any class. +inline void swap(brillo::Any& lhs, brillo::Any& rhs) { + lhs.Swap(rhs); +} + +} // namespace std + +#endif // LIBCHROMEOS_BRILLO_ANY_H_ diff --git a/brillo/any_internal_impl.h b/brillo/any_internal_impl.h new file mode 100644 index 0000000..932f0ee --- /dev/null +++ b/brillo/any_internal_impl.h @@ -0,0 +1,373 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Internal implementation of brillo::Any class. + +#ifndef LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_ +#define LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_ + +#include +#include +#include + +#include +#include +#include + +namespace brillo { + +namespace internal_details { + +// An extension to std::is_convertible to allow conversion from an enum to +// an integral type which std::is_convertible does not indicate as supported. +template +struct IsConvertible + : public std::integral_constant< + bool, + std::is_convertible::value || + (std::is_enum::value && std::is_integral::value)> {}; + +// TryConvert is a helper function that does a safe compile-time conditional +// type cast between data types that may not be always convertible. +// From and To are the source and destination types. +// The function returns true if conversion was possible/successful. +template +inline typename std::enable_if::value, bool>::type +TryConvert(const From& in, To* out) { + *out = static_cast(in); + return true; +} +template +inline typename std::enable_if::value, bool>::type +TryConvert(const From& in, To* out) { + return false; +} + +////////////////////////////////////////////////////////////////////////////// +// Provide a way to compare values of unspecified types without compiler errors +// when no operator==() is provided for a given type. This is important to +// allow Any class to have operator==(), yet still allowing arbitrary types +// (not necessarily comparable) to be placed inside Any without resulting in +// compile-time error. +// +// We achieve this in two ways. First, we provide a IsEqualityComparable +// class that can be used in compile-time conditions to determine if there is +// operator==() defined that takes values of type T (or which can be implicitly +// converted to type T). Secondly, this allows us to specialize a helper +// compare function EqCompare(v1, v2) to use operator==() for types that +// are comparable, and just return false for those that are not. +// +// IsEqualityComparableHelper is a helper class for implementing an +// an STL-compatible IsEqualityComparable containing a Boolean member |value| +// which evaluates to true for comparable types and false otherwise. +template +struct IsEqualityComparableHelper { + struct IntWrapper { + // A special structure that provides a constructor that takes an int. + // This way, an int argument passed to a function will be favored over + // IntWrapper when both overloads are provided. + // Also this constructor must NOT be explicit. + // NOLINTNEXTLINE(runtime/explicit) + IntWrapper(int dummy) {} // do nothing + }; + + // Here is an obscure trick to determine if a type U has operator==(). + // We are providing two function prototypes for TriggerFunction. One that + // takes an argument of type IntWrapper (which is implicitly convertible from + // an int), and returns an std::false_type. This is a fall-back mechanism. + template + static std::false_type TriggerFunction(IntWrapper dummy); + + // The second overload of TriggerFunction takes an int (explicitly) and + // returns std::true_type. If both overloads are available, this one will be + // chosen when referencing it as TriggerFunction(0), since it is a better + // (more specific) match. + // + // However this overload is available only for types that support operator==. + // This is achieved by employing SFINAE mechanism inside a template function + // overload that refers to operator==() for two values of types U&. This is + // used inside decltype(), so no actual code is executed. If the types + // are not comparable, reference to "==" would fail and the compiler will + // simply ignore this overload due to SFIANE. + // + // The final little trick used here is the reliance on operator comma inside + // the decltype() expression. The result of the expression is always + // std::true_type(). The expression on the left of comma is just evaluated and + // discarded. If it evaluates successfully (i.e. the type has operator==), the + // return value of the function is set to be std::true_value. If it fails, + // the whole function prototype is discarded and is not available in the + // IsEqualityComparableHelper class. + // + // Here we use std::declval() to make sure we have operator==() that takes + // lvalue references to type U which is not necessarily default-constructible. + template + static decltype((std::declval() == std::declval()), std::true_type()) + TriggerFunction(int dummy); + + // Finally, use the return type of the overload of TriggerFunction that + // matches the argument (int) to be aliased to type |type|. If T is + // comparable, there will be two overloads and the more specific (int) will + // be chosen which returns std::true_value. If the type is non-comparable, + // there will be only one version of TriggerFunction available which + // returns std::false_value. + using type = decltype(TriggerFunction(0)); +}; + +// IsEqualityComparable is simply a class that derives from either +// std::true_value, if type T is comparable, or from std::false_value, if the +// type is non-comparable. We just use |type| alias from +// IsEqualityComparableHelper as the base class. +template +struct IsEqualityComparable : IsEqualityComparableHelper::type {}; + +// EqCompare() overload for non-comparable types. Always returns false. +template +inline typename std::enable_if::value, bool>::type +EqCompare(const T& v1, const T& v2) { + return false; +} + +// EqCompare overload for comparable types. Calls operator==(v1, v2) to compare. +template +inline typename std::enable_if::value, bool>::type +EqCompare(const T& v1, const T& v2) { + return (v1 == v2); +} + +////////////////////////////////////////////////////////////////////////////// + +class Buffer; // Forward declaration of data buffer container. + +// Abstract base class for contained variant data. +struct Data { + virtual ~Data() {} + // Returns the type information for the contained data. + virtual const std::type_info& GetType() const = 0; + // Copies the contained data to the output |buffer|. + virtual void CopyTo(Buffer* buffer) const = 0; + // Moves the contained data to the output |buffer|. + virtual void MoveTo(Buffer* buffer) = 0; + // Checks if the contained data is an integer type (not necessarily an 'int'). + virtual bool IsConvertibleToInteger() const = 0; + // Gets the contained integral value as an integer. + virtual intmax_t GetAsInteger() const = 0; + // Writes the contained value to the D-Bus message buffer. + virtual void AppendToDBusMessage(dbus::MessageWriter* writer) const = 0; + // Compares if the two data containers have objects of the same value. + virtual bool CompareEqual(const Data* other_data) const = 0; +}; + +// Concrete implementation of variant data of type T. +template +struct TypedData : public Data { + explicit TypedData(const T& value) : value_(value) {} + // NOLINTNEXTLINE(build/c++11) + explicit TypedData(T&& value) : value_(std::move(value)) {} + + const std::type_info& GetType() const override { return typeid(T); } + void CopyTo(Buffer* buffer) const override; + void MoveTo(Buffer* buffer) override; + bool IsConvertibleToInteger() const override { + return std::is_integral::value || std::is_enum::value; + } + intmax_t GetAsInteger() const override { + intmax_t int_val = 0; + bool converted = TryConvert(value_, &int_val); + CHECK(converted) << "Unable to convert value of type '" + << GetUndecoratedTypeName() << "' to integer"; + return int_val; + } + + template + static typename std::enable_if::value>::type + AppendValueHelper(dbus::MessageWriter* writer, const U& value) { + brillo::dbus_utils::AppendValueToWriterAsVariant(writer, value); + } + template + static typename std::enable_if::value>::type + AppendValueHelper(dbus::MessageWriter* writer, const U& value) { + LOG(FATAL) << "Type '" << GetUndecoratedTypeName() + << "' is not supported by D-Bus"; + } + + void AppendToDBusMessage(dbus::MessageWriter* writer) const override { + return AppendValueHelper(writer, value_); + } + + bool CompareEqual(const Data* other_data) const override { + return EqCompare(value_, + static_cast*>(other_data)->value_); + } + + // Special methods to copy/move data of the same type + // without reallocating the buffer. + void FastAssign(const T& source) { value_ = source; } + // NOLINTNEXTLINE(build/c++11) + void FastAssign(T&& source) { value_ = std::move(source); } + + T value_; +}; + +// Buffer class that stores the contained variant data. +// To improve performance and reduce memory fragmentation, small variants +// are stored in pre-allocated memory buffers that are part of the Any class. +// If the memory requirements are larger than the set limit or the type is +// non-trivially copyable, then the contained class is allocated in a separate +// memory block and the pointer to that memory is contained within this memory +// buffer class. +class Buffer final { + public: + enum StorageType { kExternal, kContained }; + Buffer() : external_ptr_(nullptr), storage_(kExternal) {} + ~Buffer() { Clear(); } + + Buffer(const Buffer& rhs) : Buffer() { rhs.CopyTo(this); } + // NOLINTNEXTLINE(build/c++11) + Buffer(Buffer&& rhs) : Buffer() { rhs.MoveTo(this); } + Buffer& operator=(const Buffer& rhs) { + rhs.CopyTo(this); + return *this; + } + // NOLINTNEXTLINE(build/c++11) + Buffer& operator=(Buffer&& rhs) { + rhs.MoveTo(this); + return *this; + } + + // Returns the underlying pointer to contained data. Uses either the pointer + // or the raw data depending on |storage_| type. + inline Data* GetDataPtr() { + return (storage_ == kExternal) ? external_ptr_ + : reinterpret_cast(contained_buffer_); + } + inline const Data* GetDataPtr() const { + return (storage_ == kExternal) + ? external_ptr_ + : reinterpret_cast(contained_buffer_); + } + + // Destroys the contained object (and frees memory if needed). + void Clear() { + Data* data = GetDataPtr(); + if (storage_ == kExternal) { + delete data; + } else { + // Call the destructor manually, since the object was constructed inline + // in the pre-allocated buffer. We still need to call the destructor + // to free any associated resources, but we can't call delete |data| here. + data->~Data(); + } + external_ptr_ = nullptr; + storage_ = kExternal; + } + + // Stores a value of type T. + template + void Assign(T&& value) { // NOLINT(build/c++11) + using Type = typename std::decay::type; + using DataType = TypedData; + Data* ptr = GetDataPtr(); + if (ptr && ptr->GetType() == typeid(Type)) { + // We assign the data to the variant container, which already + // has the data of the same type. Do fast copy/move with no memory + // reallocation. + DataType* typed_ptr = static_cast(ptr); + // NOLINTNEXTLINE(build/c++11) + typed_ptr->FastAssign(std::forward(value)); + } else { + Clear(); + // TODO(avakulenko): [see crbug.com/379833] + // Unfortunately, GCC doesn't support std::is_trivially_copyable yet, + // so using std::is_trivial instead, which is a bit more restrictive. + // Once GCC has support for is_trivially_copyable, update the following. + if (!std::is_trivial::value || + sizeof(DataType) > sizeof(contained_buffer_)) { + // If it is too big or not trivially copyable, allocate it separately. + // NOLINTNEXTLINE(build/c++11) + external_ptr_ = new DataType(std::forward(value)); + storage_ = kExternal; + } else { + // Otherwise just use the pre-allocated buffer. + DataType* address = reinterpret_cast(contained_buffer_); + // Make sure we still call the copy/move constructor. + // Call the constructor manually by using placement 'new'. + // NOLINTNEXTLINE(build/c++11) + new (address) DataType(std::forward(value)); + storage_ = kContained; + } + } + } + + // Helper methods to retrieve a reference to contained data. + // These assume that type checking has already been performed by Any + // so the type cast is valid and will succeed. + template + const T& GetData() const { + using DataType = internal_details::TypedData::type>; + return static_cast(GetDataPtr())->value_; + } + template + T& GetData() { + using DataType = internal_details::TypedData::type>; + return static_cast(GetDataPtr())->value_; + } + + // Returns true if the buffer has no contained data. + bool IsEmpty() const { + return (storage_ == kExternal && external_ptr_ == nullptr); + } + + // Copies the data from the current buffer into the |destination|. + void CopyTo(Buffer* destination) const { + if (IsEmpty()) { + destination->Clear(); + } else { + GetDataPtr()->CopyTo(destination); + } + } + + // Moves the data from the current buffer into the |destination|. + void MoveTo(Buffer* destination) { + if (IsEmpty()) { + destination->Clear(); + } else { + if (storage_ == kExternal) { + destination->Clear(); + destination->storage_ = kExternal; + destination->external_ptr_ = external_ptr_; + external_ptr_ = nullptr; + } else { + GetDataPtr()->MoveTo(destination); + } + } + } + + union { + // |external_ptr_| is a pointer to a larger object allocated in + // a separate memory block. + Data* external_ptr_; + // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects. + // Pre-allocate enough memory to store objects as big as "double". + unsigned char contained_buffer_[sizeof(TypedData)]; + }; + // Depending on a value of |storage_|, either |external_ptr_| or + // |contained_buffer_| above is used to get a pointer to memory containing + // the variant data. + StorageType storage_; // Declare after the union to eliminate member padding. +}; + +template +void TypedData::CopyTo(Buffer* buffer) const { + buffer->Assign(value_); +} +template +void TypedData::MoveTo(Buffer* buffer) { + buffer->Assign(std::move(value_)); +} + +} // namespace internal_details + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_ diff --git a/brillo/any_internal_impl_unittest.cc b/brillo/any_internal_impl_unittest.cc new file mode 100644 index 0000000..e782cd0 --- /dev/null +++ b/brillo/any_internal_impl_unittest.cc @@ -0,0 +1,141 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +using brillo::internal_details::Buffer; + +TEST(Buffer, Empty) { + Buffer buffer; + EXPECT_TRUE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kExternal, buffer.storage_); + EXPECT_EQ(nullptr, buffer.GetDataPtr()); +} + +TEST(Buffer, Store_Int) { + Buffer buffer; + buffer.Assign(2); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kContained, buffer.storage_); + EXPECT_EQ(typeid(int), buffer.GetDataPtr()->GetType()); +} + +TEST(Buffer, Store_Double) { + Buffer buffer; + buffer.Assign(2.3); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kContained, buffer.storage_); + EXPECT_EQ(typeid(double), buffer.GetDataPtr()->GetType()); +} + +TEST(Buffer, Store_Pointers) { + Buffer buffer; + // nullptr + buffer.Assign(nullptr); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kContained, buffer.storage_); + EXPECT_EQ(typeid(std::nullptr_t), buffer.GetDataPtr()->GetType()); + + // char * + buffer.Assign("abcd"); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kContained, buffer.storage_); + EXPECT_EQ(typeid(const char*), buffer.GetDataPtr()->GetType()); + + // pointer to non-trivial object + class NonTrivial { + public: + virtual ~NonTrivial() {} + } non_trivial; + buffer.Assign(&non_trivial); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kContained, buffer.storage_); + EXPECT_EQ(typeid(NonTrivial*), buffer.GetDataPtr()->GetType()); +} + +TEST(Buffer, Store_NonTrivialObjects) { + class NonTrivial { + public: + virtual ~NonTrivial() {} + } non_trivial; + Buffer buffer; + buffer.Assign(non_trivial); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kExternal, buffer.storage_); + EXPECT_EQ(typeid(NonTrivial), buffer.GetDataPtr()->GetType()); +} + +TEST(Buffer, Store_Objects) { + Buffer buffer; + + struct Small { + double d; + } small = {}; + buffer.Assign(small); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kContained, buffer.storage_); + EXPECT_EQ(typeid(Small), buffer.GetDataPtr()->GetType()); + + struct Large { + char c[10]; + } large = {}; + buffer.Assign(large); + EXPECT_FALSE(buffer.IsEmpty()); + EXPECT_EQ(Buffer::kExternal, buffer.storage_); + EXPECT_EQ(typeid(Large), buffer.GetDataPtr()->GetType()); +} + +TEST(Buffer, Copy) { + Buffer buffer1; + Buffer buffer2; + + buffer1.Assign(30); + buffer1.CopyTo(&buffer2); + EXPECT_FALSE(buffer1.IsEmpty()); + EXPECT_FALSE(buffer2.IsEmpty()); + EXPECT_EQ(typeid(int), buffer1.GetDataPtr()->GetType()); + EXPECT_EQ(typeid(int), buffer2.GetDataPtr()->GetType()); + EXPECT_EQ(30, buffer1.GetData()); + EXPECT_EQ(30, buffer2.GetData()); + + buffer1.Assign(std::string("abc")); + buffer1.CopyTo(&buffer2); + EXPECT_FALSE(buffer1.IsEmpty()); + EXPECT_FALSE(buffer2.IsEmpty()); + EXPECT_EQ(typeid(std::string), buffer1.GetDataPtr()->GetType()); + EXPECT_EQ(typeid(std::string), buffer2.GetDataPtr()->GetType()); + EXPECT_EQ("abc", buffer1.GetData()); + EXPECT_EQ("abc", buffer2.GetData()); +} + +TEST(Buffer, Move) { + // Move operations essentially leave the source object in a state that is + // guaranteed to be safe for reuse or destruction. There is no other explicit + // guarantees on the exact state of the source after move (e.g. that the + // source Any will be Empty after the move is complete). + Buffer buffer1; + Buffer buffer2; + + buffer1.Assign(30); + buffer1.MoveTo(&buffer2); + // Contained types aren't flushed, so the source Any doesn't become empty. + // The contained value is just moved, but for scalars this just copies + // the data and any retains the actual type. + EXPECT_FALSE(buffer1.IsEmpty()); + EXPECT_FALSE(buffer2.IsEmpty()); + EXPECT_EQ(typeid(int), buffer2.GetDataPtr()->GetType()); + EXPECT_EQ(30, buffer2.GetData()); + + buffer1.Assign(std::string("abc")); + buffer1.MoveTo(&buffer2); + // External types are moved by just moving the pointer value from src to dest. + // This will make the source object effectively "Empty". + EXPECT_TRUE(buffer1.IsEmpty()); + EXPECT_FALSE(buffer2.IsEmpty()); + EXPECT_EQ(typeid(std::string), buffer2.GetDataPtr()->GetType()); + EXPECT_EQ("abc", buffer2.GetData()); +} diff --git a/brillo/any_unittest.cc b/brillo/any_unittest.cc new file mode 100644 index 0000000..4fd23d7 --- /dev/null +++ b/brillo/any_unittest.cc @@ -0,0 +1,306 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include +#include + +using brillo::Any; + +TEST(Any, Empty) { + Any val; + EXPECT_TRUE(val.IsEmpty()); + + Any val2 = val; + EXPECT_TRUE(val.IsEmpty()); + EXPECT_TRUE(val2.IsEmpty()); + + Any val3 = std::move(val); + EXPECT_TRUE(val.IsEmpty()); + EXPECT_TRUE(val3.IsEmpty()); +} + +TEST(Any, SimpleTypes) { + Any val(20); + EXPECT_FALSE(val.IsEmpty()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_EQ(20, val.Get()); + + Any val2(3.1415926); + EXPECT_FALSE(val2.IsEmpty()); + EXPECT_TRUE(val2.IsTypeCompatible()); + EXPECT_FALSE(val2.IsTypeCompatible()); + EXPECT_DOUBLE_EQ(3.1415926, val2.Get()); + + Any val3(std::string("blah")); + EXPECT_TRUE(val3.IsTypeCompatible()); + EXPECT_EQ("blah", val3.Get()); +} + +TEST(Any, Clear) { + Any val('x'); + EXPECT_FALSE(val.IsEmpty()); + EXPECT_EQ('x', val.Get()); + + val.Clear(); + EXPECT_TRUE(val.IsEmpty()); +} + +TEST(Any, Assignments) { + Any val(20); + EXPECT_EQ(20, val.Get()); + + val = 3.1415926; + EXPECT_FALSE(val.IsEmpty()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_DOUBLE_EQ(3.1415926, val.Get()); + + val = std::string("blah"); + EXPECT_EQ("blah", val.Get()); + + Any val2; + EXPECT_TRUE(val2.IsEmpty()); + val2 = val; + EXPECT_FALSE(val.IsEmpty()); + EXPECT_FALSE(val2.IsEmpty()); + EXPECT_EQ("blah", val.Get()); + EXPECT_EQ("blah", val2.Get()); + val.Clear(); + EXPECT_TRUE(val.IsEmpty()); + EXPECT_EQ("blah", val2.Get()); + val2.Clear(); + EXPECT_TRUE(val2.IsEmpty()); + + val = std::vector{100, 20, 3}; + auto v = val.Get>(); + EXPECT_EQ(100, v[0]); + EXPECT_EQ(20, v[1]); + EXPECT_EQ(3, v[2]); + + val2 = std::move(val); + EXPECT_TRUE(val.IsEmpty()); + EXPECT_TRUE(val2.IsTypeCompatible>()); + EXPECT_EQ(3, val2.Get>().size()); + + val = val2; + EXPECT_TRUE(val.IsTypeCompatible>()); + EXPECT_TRUE(val2.IsTypeCompatible>()); + EXPECT_EQ(3, val.Get>().size()); + EXPECT_EQ(3, val2.Get>().size()); +} + +TEST(Any, Enums) { + enum class Dummy { foo, bar, baz }; + Any val(Dummy::bar); + EXPECT_FALSE(val.IsEmpty()); + EXPECT_TRUE(val.IsConvertibleToInteger()); + EXPECT_EQ(Dummy::bar, val.Get()); + EXPECT_EQ(1, val.GetAsInteger()); + + val = Dummy::baz; + EXPECT_EQ(2, val.GetAsInteger()); + + val = Dummy::foo; + EXPECT_EQ(0, val.GetAsInteger()); +} + +TEST(Any, Integers) { + Any val(14); + EXPECT_TRUE(val.IsConvertibleToInteger()); + EXPECT_EQ(14, val.Get()); + EXPECT_EQ(14, val.GetAsInteger()); + + val = '\x40'; + EXPECT_TRUE(val.IsConvertibleToInteger()); + EXPECT_EQ(64, val.Get()); + EXPECT_EQ(64, val.GetAsInteger()); + + val = static_cast(65535); + EXPECT_TRUE(val.IsConvertibleToInteger()); + EXPECT_EQ(65535, val.Get()); + EXPECT_EQ(65535, val.GetAsInteger()); + + val = static_cast(0xFFFFFFFFFFFFFFFFULL); + EXPECT_TRUE(val.IsConvertibleToInteger()); + EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, val.Get()); + EXPECT_EQ(-1, val.GetAsInteger()); + + val = "abc"; + EXPECT_FALSE(val.IsConvertibleToInteger()); + + int a = 5; + val = &a; + EXPECT_FALSE(val.IsConvertibleToInteger()); +} + +TEST(Any, Pointers) { + Any val("abc"); // const char* + EXPECT_FALSE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_FALSE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_STREQ("abc", val.Get()); + + int a = 10; + val = &a; + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_EQ(10, *val.Get()); + *val.Get() = 3; + EXPECT_EQ(3, a); +} + +TEST(Any, Arrays) { + // The following test are here to validate the array-to-pointer decay rules. + // Since Any does not store the contents of a C-style array, just a pointer + // to the data, putting array data into Any could be dangerous. + // Make sure the array's lifetime exceeds that of an Any containing the + // pointer to the array data. + // If you want to store the array with data, use corresponding value types + // such as std::vector or a struct containing C-style array as a member. + + int int_array[] = {1, 2, 3}; // int* + Any val = int_array; + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_EQ(3, val.Get()[2]); + + const int const_int_array[] = {10, 20, 30}; // const int* + val = const_int_array; + EXPECT_FALSE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_FALSE(val.IsTypeCompatible()); + EXPECT_TRUE(val.IsTypeCompatible()); + EXPECT_EQ(30, val.Get()[2]); +} + +TEST(Any, References) { + // Passing references to object via Any might be error-prone or the + // semantics could be unfamiliar to other developers. In many cases, + // using pointers instead of references are more conventional and easier + // to understand. Even though the cases of passing references are quite + // explicit on both storing and retrieving ends, you might want to + // use pointers instead anyway. + + int a = 5; + Any val(std::ref(a)); // int& + EXPECT_EQ(5, val.Get>().get()); + val.Get>().get() = 7; + EXPECT_EQ(7, val.Get>().get()); + EXPECT_EQ(7, a); + + Any val2(std::cref(a)); // const int& + EXPECT_EQ(7, val2.Get>().get()); + + a = 10; + EXPECT_EQ(10, val.Get>().get()); + EXPECT_EQ(10, val2.Get>().get()); +} + +TEST(Any, CustomTypes) { + struct Person { + std::string name; + int age; + }; + Any val(Person{"Jack", 40}); + Any val2 = val; + EXPECT_EQ("Jack", val.Get().name); + val.GetPtr()->name = "Joe"; + val.GetPtr()->age /= 2; + EXPECT_EQ("Joe", val.Get().name); + EXPECT_EQ(20, val.Get().age); + EXPECT_EQ("Jack", val2.Get().name); + EXPECT_EQ(40, val2.Get().age); +} + +TEST(Any, Swap) { + Any val(12); + Any val2(2.7); + EXPECT_EQ(12, val.Get()); + EXPECT_EQ(2.7, val2.Get()); + + val.Swap(val2); + EXPECT_EQ(2.7, val.Get()); + EXPECT_EQ(12, val2.Get()); + + std::swap(val, val2); + EXPECT_EQ(12, val.Get()); + EXPECT_EQ(2.7, val2.Get()); +} + +TEST(Any, TypeMismatch) { + Any val(12); + EXPECT_DEATH(val.Get(), + "Requesting value of type 'double' from variant containing " + "'int'"); + + val = std::string("123"); + EXPECT_DEATH(val.GetAsInteger(), + "Unable to convert value of type 'std::string' to integer"); + + Any empty; + EXPECT_DEATH(empty.GetAsInteger(), "Must not be called on an empty Any"); +} + +TEST(Any, TryGet) { + Any val(12); + Any empty; + EXPECT_EQ("dummy", val.TryGet("dummy")); + EXPECT_EQ(12, val.TryGet(17)); + EXPECT_EQ(17, empty.TryGet(17)); +} + +TEST(Any, Compare_Int) { + Any int1{12}; + Any int2{12}; + Any int3{20}; + EXPECT_EQ(int1, int2); + EXPECT_NE(int2, int3); +} + +TEST(Any, Compare_String) { + Any str1{std::string{"foo"}}; + Any str2{std::string{"foo"}}; + Any str3{std::string{"bar"}}; + EXPECT_EQ(str1, str2); + EXPECT_NE(str2, str3); +} + +TEST(Any, Compare_Array) { + Any vec1{std::vector{1, 2}}; + Any vec2{std::vector{1, 2}}; + Any vec3{std::vector{1, 2, 3}}; + EXPECT_EQ(vec1, vec2); + EXPECT_NE(vec2, vec3); +} + +TEST(Any, Compare_Empty) { + Any empty1; + Any empty2; + Any int1{1}; + EXPECT_EQ(empty1, empty2); + EXPECT_NE(int1, empty1); + EXPECT_NE(empty2, int1); +} + +TEST(Any, Compare_NonComparable) { + struct Person { + std::string name; + int age; + }; + Any person1(Person{"Jack", 40}); + Any person2 = person1; + Any person3(Person{"Jill", 20}); + EXPECT_NE(person1, person2); + EXPECT_NE(person1, person3); + EXPECT_NE(person2, person3); +} diff --git a/brillo/asynchronous_signal_handler.cc b/brillo/asynchronous_signal_handler.cc new file mode 100644 index 0000000..b8ec529 --- /dev/null +++ b/brillo/asynchronous_signal_handler.cc @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/asynchronous_signal_handler.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { +const int kInvalidDescriptor = -1; +} // namespace + +namespace brillo { + +AsynchronousSignalHandler::AsynchronousSignalHandler() + : descriptor_(kInvalidDescriptor) { + CHECK_EQ(sigemptyset(&signal_mask_), 0) << "Failed to initialize signal mask"; + CHECK_EQ(sigemptyset(&saved_signal_mask_), 0) + << "Failed to initialize signal mask"; +} + +AsynchronousSignalHandler::~AsynchronousSignalHandler() { + if (descriptor_ != kInvalidDescriptor) { + MessageLoop::current()->CancelTask(fd_watcher_task_); + + if (IGNORE_EINTR(close(descriptor_)) != 0) + PLOG(WARNING) << "Failed to close file descriptor"; + + descriptor_ = kInvalidDescriptor; + CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, nullptr)); + } +} + +void AsynchronousSignalHandler::Init() { + CHECK_EQ(kInvalidDescriptor, descriptor_); + CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, &saved_signal_mask_)); + descriptor_ = + signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK); + CHECK_NE(kInvalidDescriptor, descriptor_); + fd_watcher_task_ = MessageLoop::current()->WatchFileDescriptor( + FROM_HERE, + descriptor_, + MessageLoop::WatchMode::kWatchRead, + true, + base::Bind(&AsynchronousSignalHandler::OnFileCanReadWithoutBlocking, + base::Unretained(this))); + CHECK(fd_watcher_task_ != MessageLoop::kTaskIdNull) + << "Watching shutdown pipe failed."; +} + +void AsynchronousSignalHandler::RegisterHandler(int signal, + const SignalHandler& callback) { + registered_callbacks_[signal] = callback; + CHECK_EQ(0, sigaddset(&signal_mask_, signal)); + UpdateSignals(); +} + +void AsynchronousSignalHandler::UnregisterHandler(int signal) { + Callbacks::iterator callback_it = registered_callbacks_.find(signal); + if (callback_it != registered_callbacks_.end()) { + registered_callbacks_.erase(callback_it); + ResetSignal(signal); + } +} + +void AsynchronousSignalHandler::OnFileCanReadWithoutBlocking() { + struct signalfd_siginfo info; + while (base::ReadFromFD(descriptor_, + reinterpret_cast(&info), sizeof(info))) { + int signal = info.ssi_signo; + Callbacks::iterator callback_it = registered_callbacks_.find(signal); + if (callback_it == registered_callbacks_.end()) { + LOG(WARNING) << "Unable to find a signal handler for signal: " << signal; + // Can happen if a signal has been called multiple time, and the callback + // asked to be unregistered the first time. + continue; + } + const SignalHandler& callback = callback_it->second; + bool must_unregister = callback.Run(info); + if (must_unregister) { + UnregisterHandler(signal); + } + } +} + +void AsynchronousSignalHandler::ResetSignal(int signal) { + CHECK_EQ(0, sigdelset(&signal_mask_, signal)); + UpdateSignals(); +} + +void AsynchronousSignalHandler::UpdateSignals() { + if (descriptor_ != kInvalidDescriptor) { + CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, nullptr)); + CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, nullptr)); + CHECK_EQ(descriptor_, + signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK)); + } +} + +} // namespace brillo diff --git a/brillo/asynchronous_signal_handler.h b/brillo/asynchronous_signal_handler.h new file mode 100644 index 0000000..8205cd6 --- /dev/null +++ b/brillo/asynchronous_signal_handler.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_H_ +#define LIBCHROMEOS_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_H_ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { +// Sets up signal handlers for registered signals, and converts signal receipt +// into a write on a pipe. Watches that pipe for data and, when some appears, +// execute the associated callback. +class BRILLO_EXPORT AsynchronousSignalHandler final : + public AsynchronousSignalHandlerInterface { + public: + AsynchronousSignalHandler(); + ~AsynchronousSignalHandler() override; + + using AsynchronousSignalHandlerInterface::SignalHandler; + + // Initialize the handler. + void Init(); + + // AsynchronousSignalHandlerInterface overrides. + void RegisterHandler(int signal, const SignalHandler& callback) override; + void UnregisterHandler(int signal) override; + + private: + // Called from the main loop when we can read from |descriptor_|, indicated + // that a signal was processed. + void OnFileCanReadWithoutBlocking(); + + // Controller used to manage watching of signalling pipe. + MessageLoop::TaskId fd_watcher_task_{MessageLoop::kTaskIdNull}; + + // The registered callbacks. + typedef std::map Callbacks; + Callbacks registered_callbacks_; + + // File descriptor for accepting signals indicated by |signal_mask_|. + int descriptor_; + + // A set of signals to be handled after the dispatcher is running. + sigset_t signal_mask_; + + // A copy of the signal mask before the dispatcher starts, which will be + // used to restore to the original state when the dispatcher stops. + sigset_t saved_signal_mask_; + + // Resets the given signal to its default behavior. Doesn't touch + // |registered_callbacks_|. + BRILLO_PRIVATE void ResetSignal(int signal); + + // Updates the set of signals that this handler listens to. + BRILLO_PRIVATE void UpdateSignals(); + + DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandler); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_H_ diff --git a/brillo/asynchronous_signal_handler_interface.h b/brillo/asynchronous_signal_handler_interface.h new file mode 100644 index 0000000..300d975 --- /dev/null +++ b/brillo/asynchronous_signal_handler_interface.h @@ -0,0 +1,42 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_ +#define LIBCHROMEOS_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_ + +#include + +#include +#include + +namespace brillo { + +// Sets up signal handlers for registered signals, and converts signal receipt +// into a write on a pipe. Watches that pipe for data and, when some appears, +// execute the associated callback. +class BRILLO_EXPORT AsynchronousSignalHandlerInterface { + public: + virtual ~AsynchronousSignalHandlerInterface() = default; + + // The callback called when a signal is received. + using SignalHandler = base::Callback; + + // Register a new handler for the given |signal|, replacing any previously + // registered handler. |callback| will be called on the thread the + // |AsynchronousSignalHandlerInterface| implementation is bound to when a + // signal is received. The received |signalfd_siginfo| will be passed to + // |callback|. |callback| must returns |true| if the signal handler must be + // unregistered, and |false| otherwise. Due to an implementation detail, you + // cannot set any sigaction flags you might be accustomed to using. This might + // matter if you hoped to use SA_NOCLDSTOP to avoid getting a SIGCHLD when a + // child process receives a SIGSTOP. + virtual void RegisterHandler(int signal, const SignalHandler& callback) = 0; + + // Unregister a previously registered handler for the given |signal|. + virtual void UnregisterHandler(int signal) = 0; +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_ diff --git a/brillo/asynchronous_signal_handler_unittest.cc b/brillo/asynchronous_signal_handler_unittest.cc new file mode 100644 index 0000000..ec3b061 --- /dev/null +++ b/brillo/asynchronous_signal_handler_unittest.cc @@ -0,0 +1,138 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/asynchronous_signal_handler.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { + +class AsynchronousSignalHandlerTest : public ::testing::Test { + public: + AsynchronousSignalHandlerTest() {} + virtual ~AsynchronousSignalHandlerTest() {} + + virtual void SetUp() { + brillo_loop_.SetAsCurrent(); + handler_.Init(); + } + + virtual void TearDown() {} + + bool RecordInfoAndQuit(bool response, const struct signalfd_siginfo& info) { + infos_.push_back(info); + brillo_loop_.PostTask(FROM_HERE, brillo_loop_.QuitClosure()); + return response; + } + + protected: + base::MessageLoopForIO base_loop_; + BaseMessageLoop brillo_loop_{&base_loop_}; + std::vector infos_; + AsynchronousSignalHandler handler_; + + private: + DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandlerTest); +}; + +TEST_F(AsynchronousSignalHandlerTest, CheckTerm) { + handler_.RegisterHandler( + SIGTERM, + base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, + base::Unretained(this), + true)); + EXPECT_EQ(0, infos_.size()); + EXPECT_EQ(0, kill(getpid(), SIGTERM)); + + // Spin the message loop. + MessageLoop::current()->Run(); + + ASSERT_EQ(1, infos_.size()); + EXPECT_EQ(SIGTERM, infos_[0].ssi_signo); +} + +TEST_F(AsynchronousSignalHandlerTest, CheckSignalUnregistration) { + handler_.RegisterHandler( + SIGCHLD, + base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, + base::Unretained(this), + true)); + EXPECT_EQ(0, infos_.size()); + EXPECT_EQ(0, kill(getpid(), SIGCHLD)); + + // Spin the message loop. + MessageLoop::current()->Run(); + + ASSERT_EQ(1, infos_.size()); + EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo); + + EXPECT_EQ(0, kill(getpid(), SIGCHLD)); + + // Run the loop with a timeout, as no message are expected. + brillo_loop_.PostDelayedTask(FROM_HERE, + base::Bind(&MessageLoop::BreakLoop, + base::Unretained(&brillo_loop_)), + base::TimeDelta::FromMilliseconds(10)); + MessageLoop::current()->Run(); + + // The signal handle should have been unregistered. No new message are + // expected. + EXPECT_EQ(1, infos_.size()); +} + +TEST_F(AsynchronousSignalHandlerTest, CheckMultipleSignal) { + const uint8_t NB_SIGNALS = 5; + handler_.RegisterHandler( + SIGCHLD, + base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, + base::Unretained(this), + false)); + EXPECT_EQ(0, infos_.size()); + for (int i = 0; i < NB_SIGNALS; ++i) { + EXPECT_EQ(0, kill(getpid(), SIGCHLD)); + + // Spin the message loop. + MessageLoop::current()->Run(); + } + + ASSERT_EQ(NB_SIGNALS, infos_.size()); + for (int i = 0; i < NB_SIGNALS; ++i) { + EXPECT_EQ(SIGCHLD, infos_[i].ssi_signo); + } +} + +TEST_F(AsynchronousSignalHandlerTest, CheckChld) { + handler_.RegisterHandler( + SIGCHLD, + base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, + base::Unretained(this), + false)); + pid_t child_pid = fork(); + if (child_pid == 0) { + _Exit(EXIT_SUCCESS); + } + + EXPECT_EQ(0, infos_.size()); + // Spin the message loop. + MessageLoop::current()->Run(); + + ASSERT_EQ(1, infos_.size()); + EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo); + EXPECT_EQ(child_pid, infos_[0].ssi_pid); + EXPECT_EQ(static_cast(CLD_EXITED), infos_[0].ssi_code); + EXPECT_EQ(EXIT_SUCCESS, infos_[0].ssi_status); +} + +} // namespace brillo diff --git a/brillo/backoff_entry.cc b/brillo/backoff_entry.cc new file mode 100644 index 0000000..acef714 --- /dev/null +++ b/brillo/backoff_entry.cc @@ -0,0 +1,167 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include + +namespace brillo { + +BackoffEntry::BackoffEntry(const BackoffEntry::Policy* const policy) + : policy_(policy) { + DCHECK(policy_); + Reset(); +} + +void BackoffEntry::InformOfRequest(bool succeeded) { + if (!succeeded) { + ++failure_count_; + exponential_backoff_release_time_ = CalculateReleaseTime(); + } else { + // We slowly decay the number of times delayed instead of + // resetting it to 0 in order to stay stable if we receive + // successes interleaved between lots of failures. Note that in + // the normal case, the calculated release time (in the next + // statement) will be in the past once the method returns. + if (failure_count_ > 0) + --failure_count_; + + // The reason why we are not just cutting the release time to + // ImplGetTimeNow() is on the one hand, it would unset a release + // time set by SetCustomReleaseTime and on the other we would like + // to push every request up to our "horizon" when dealing with + // multiple in-flight requests. Ex: If we send three requests and + // we receive 2 failures and 1 success. The success that follows + // those failures will not reset the release time, further + // requests will then need to wait the delay caused by the 2 + // failures. + base::TimeDelta delay; + if (policy_->always_use_initial_delay) + delay = base::TimeDelta::FromMilliseconds(policy_->initial_delay_ms); + exponential_backoff_release_time_ = std::max( + ImplGetTimeNow() + delay, exponential_backoff_release_time_); + } +} + +bool BackoffEntry::ShouldRejectRequest() const { + return exponential_backoff_release_time_ > ImplGetTimeNow(); +} + +base::TimeDelta BackoffEntry::GetTimeUntilRelease() const { + base::TimeTicks now = ImplGetTimeNow(); + if (exponential_backoff_release_time_ <= now) + return base::TimeDelta(); + return exponential_backoff_release_time_ - now; +} + +base::TimeTicks BackoffEntry::GetReleaseTime() const { + return exponential_backoff_release_time_; +} + +void BackoffEntry::SetCustomReleaseTime(const base::TimeTicks& release_time) { + exponential_backoff_release_time_ = release_time; +} + +bool BackoffEntry::CanDiscard() const { + if (policy_->entry_lifetime_ms == -1) + return false; + + base::TimeTicks now = ImplGetTimeNow(); + + int64 unused_since_ms = + (now - exponential_backoff_release_time_).InMilliseconds(); + + // Release time is further than now, we are managing it. + if (unused_since_ms < 0) + return false; + + if (failure_count_ > 0) { + // Need to keep track of failures until maximum back-off period + // has passed (since further failures can add to back-off). + return unused_since_ms >= std::max(policy_->maximum_backoff_ms, + policy_->entry_lifetime_ms); + } + + // Otherwise, consider the entry is outdated if it hasn't been used for the + // specified lifetime period. + return unused_since_ms >= policy_->entry_lifetime_ms; +} + +void BackoffEntry::Reset() { + failure_count_ = 0; + + // We leave exponential_backoff_release_time_ unset, meaning 0. We could + // initialize to ImplGetTimeNow() but because it's a virtual method it's + // not safe to call in the constructor (and the constructor calls Reset()). + // The effects are the same, i.e. ShouldRejectRequest() will return false + // right after Reset(). + exponential_backoff_release_time_ = base::TimeTicks(); +} + +base::TimeTicks BackoffEntry::ImplGetTimeNow() const { + return base::TimeTicks::Now(); +} + +base::TimeTicks BackoffEntry::CalculateReleaseTime() const { + int effective_failure_count = + std::max(0, failure_count_ - policy_->num_errors_to_ignore); + + // If always_use_initial_delay is true, it's equivalent to + // the effective_failure_count always being one greater than when it's false. + if (policy_->always_use_initial_delay) + ++effective_failure_count; + + if (effective_failure_count == 0) { + // Never reduce previously set release horizon, e.g. due to Retry-After + // header. + return std::max(ImplGetTimeNow(), exponential_backoff_release_time_); + } + + // The delay is calculated with this formula: + // delay = initial_backoff * multiply_factor^( + // effective_failure_count - 1) * Uniform(1 - jitter_factor, 1] + // Note: if the failure count is too high, |delay_ms| will become infinity + // after the exponential calculation, and then NaN after the jitter is + // accounted for. Both cases are handled by using CheckedNumeric to + // perform the conversion to integers. + double delay_ms = policy_->initial_delay_ms; + delay_ms *= pow(policy_->multiply_factor, effective_failure_count - 1); + delay_ms -= base::RandDouble() * policy_->jitter_factor * delay_ms; + + // Do overflow checking in microseconds, the internal unit of TimeTicks. + const int64_t kTimeTicksNowUs = + (ImplGetTimeNow() - base::TimeTicks()).InMicroseconds(); + base::internal::CheckedNumeric calculated_release_time_us = + delay_ms + 0.5; + calculated_release_time_us *= base::Time::kMicrosecondsPerMillisecond; + calculated_release_time_us += kTimeTicksNowUs; + + const int64_t kMaxTime = std::numeric_limits::max(); + base::internal::CheckedNumeric maximum_release_time_us = kMaxTime; + if (policy_->maximum_backoff_ms >= 0) { + maximum_release_time_us = policy_->maximum_backoff_ms; + maximum_release_time_us *= base::Time::kMicrosecondsPerMillisecond; + maximum_release_time_us += kTimeTicksNowUs; + } + + // Decide between maximum release time and calculated release time, accounting + // for overflow with both. + int64 release_time_us = std::min( + calculated_release_time_us.ValueOrDefault(kMaxTime), + maximum_release_time_us.ValueOrDefault(kMaxTime)); + + // Never reduce previously set release horizon, e.g. due to Retry-After + // header. + return std::max( + base::TimeTicks() + base::TimeDelta::FromMicroseconds(release_time_us), + exponential_backoff_release_time_); +} + +} // namespace brillo diff --git a/brillo/backoff_entry.h b/brillo/backoff_entry.h new file mode 100644 index 0000000..ff9872b --- /dev/null +++ b/brillo/backoff_entry.h @@ -0,0 +1,115 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_BACKOFF_ENTRY_H_ +#define LIBCHROMEOS_BRILLO_BACKOFF_ENTRY_H_ + +#include +#include + +namespace brillo { + +// Provides the core logic needed for randomized exponential back-off +// on requests to a given resource, given a back-off policy. +// +// This class is largely taken from net/base/backoff_entry.h from Chromium. +// TODO(avakulenko): Consider packaging portions of Chrome's //net functionality +// into the current libchrome library. +class BRILLO_EXPORT BackoffEntry { + public: + // The set of parameters that define a back-off policy. + struct Policy { + // Number of initial errors (in sequence) to ignore before applying + // exponential back-off rules. + int num_errors_to_ignore; + + // Initial delay. The interpretation of this value depends on + // always_use_initial_delay. It's either how long we wait between + // requests before backoff starts, or how much we delay the first request + // after backoff starts. + int initial_delay_ms; + + // Factor by which the waiting time will be multiplied. + double multiply_factor; + + // Fuzzing percentage. ex: 10% will spread requests randomly + // between 90%-100% of the calculated time. + double jitter_factor; + + // Maximum amount of time we are willing to delay our request, -1 + // for no maximum. + int64 maximum_backoff_ms; + + // Time to keep an entry from being discarded even when it + // has no significant state, -1 to never discard. + int64 entry_lifetime_ms; + + // If true, we always use a delay of initial_delay_ms, even before + // we've seen num_errors_to_ignore errors. Otherwise, initial_delay_ms + // is the first delay once we start exponential backoff. + // + // So if we're ignoring 1 error, we'll see (N, N, Nm, Nm^2, ...) if true, + // and (0, 0, N, Nm, ...) when false, where N is initial_backoff_ms and + // m is multiply_factor, assuming we've already seen one success. + bool always_use_initial_delay; + }; + + // Lifetime of policy must enclose lifetime of BackoffEntry. The + // pointer must be valid but is not dereferenced during construction. + explicit BackoffEntry(const Policy* const policy); + virtual ~BackoffEntry() = default; + + // Inform this item that a request for the network resource it is + // tracking was made, and whether it failed or succeeded. + void InformOfRequest(bool succeeded); + + // Returns true if a request for the resource this item tracks should + // be rejected at the present time due to exponential back-off policy. + bool ShouldRejectRequest() const; + + // Returns the absolute time after which this entry (given its present + // state) will no longer reject requests. + base::TimeTicks GetReleaseTime() const; + + // Returns the time until a request can be sent. + base::TimeDelta GetTimeUntilRelease() const; + + // Causes this object reject requests until the specified absolute time. + // This can be used to e.g. implement support for a Retry-After header. + void SetCustomReleaseTime(const base::TimeTicks& release_time); + + // Returns true if this object has no significant state (i.e. you could + // just as well start with a fresh BackoffEntry object), and hasn't + // had for Policy::entry_lifetime_ms. + bool CanDiscard() const; + + // Resets this entry to a fresh (as if just constructed) state. + void Reset(); + + // Returns the failure count for this entry. + int failure_count() const { return failure_count_; } + + protected: + // Equivalent to TimeTicks::Now(), virtual so unit tests can override. + virtual base::TimeTicks ImplGetTimeNow() const; + + private: + // Calculates when requests should again be allowed through. + base::TimeTicks CalculateReleaseTime() const; + + // Timestamp calculated by the exponential back-off algorithm at which we are + // allowed to start sending requests again. + base::TimeTicks exponential_backoff_release_time_; + + // Counts request errors; decremented on success. + int failure_count_; + + const Policy* const policy_; + + DISALLOW_COPY_AND_ASSIGN(BackoffEntry); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_BACKOFF_ENTRY_H_ diff --git a/brillo/backoff_entry_unittest.cc b/brillo/backoff_entry_unittest.cc new file mode 100644 index 0000000..dcfa0b2 --- /dev/null +++ b/brillo/backoff_entry_unittest.cc @@ -0,0 +1,311 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +using base::TimeDelta; +using base::TimeTicks; + +namespace brillo { + +BackoffEntry::Policy base_policy = { 0, 1000, 2.0, 0.0, 20000, 2000, false }; + +class TestBackoffEntry : public BackoffEntry { + public: + explicit TestBackoffEntry(const Policy* const policy) + : BackoffEntry(policy), + now_(TimeTicks()) { + // Work around initialization in constructor not picking up + // fake time. + SetCustomReleaseTime(TimeTicks()); + } + + ~TestBackoffEntry() override {} + + TimeTicks ImplGetTimeNow() const override { return now_; } + + void set_now(const TimeTicks& now) { + now_ = now; + } + + private: + TimeTicks now_; + + DISALLOW_COPY_AND_ASSIGN(TestBackoffEntry); +}; + +TEST(BackoffEntryTest, BaseTest) { + TestBackoffEntry entry(&base_policy); + EXPECT_FALSE(entry.ShouldRejectRequest()); + EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease()); + + entry.InformOfRequest(false); + EXPECT_TRUE(entry.ShouldRejectRequest()); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); +} + +TEST(BackoffEntryTest, CanDiscardNeverExpires) { + BackoffEntry::Policy never_expires_policy = base_policy; + never_expires_policy.entry_lifetime_ms = -1; + TestBackoffEntry never_expires(&never_expires_policy); + EXPECT_FALSE(never_expires.CanDiscard()); + never_expires.set_now(TimeTicks() + TimeDelta::FromDays(100)); + EXPECT_FALSE(never_expires.CanDiscard()); +} + +TEST(BackoffEntryTest, CanDiscard) { + TestBackoffEntry entry(&base_policy); + // Because lifetime is non-zero, we shouldn't be able to discard yet. + EXPECT_FALSE(entry.CanDiscard()); + + // Test the "being used" case. + entry.InformOfRequest(false); + EXPECT_FALSE(entry.CanDiscard()); + + // Test the case where there are errors but we can time out. + entry.set_now( + entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1)); + EXPECT_FALSE(entry.CanDiscard()); + entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( + base_policy.maximum_backoff_ms + 1)); + EXPECT_TRUE(entry.CanDiscard()); + + // Test the final case (no errors, dependent only on specified lifetime). + entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( + base_policy.entry_lifetime_ms - 1)); + entry.InformOfRequest(true); + EXPECT_FALSE(entry.CanDiscard()); + entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( + base_policy.entry_lifetime_ms)); + EXPECT_TRUE(entry.CanDiscard()); +} + +TEST(BackoffEntryTest, CanDiscardAlwaysDelay) { + BackoffEntry::Policy always_delay_policy = base_policy; + always_delay_policy.always_use_initial_delay = true; + always_delay_policy.entry_lifetime_ms = 0; + + TestBackoffEntry entry(&always_delay_policy); + + // Because lifetime is non-zero, we shouldn't be able to discard yet. + entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); + EXPECT_TRUE(entry.CanDiscard()); + + // Even with no failures, we wait until the delay before we allow discard. + entry.InformOfRequest(true); + EXPECT_FALSE(entry.CanDiscard()); + + // Wait until the delay expires, and we can discard the entry again. + entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1000)); + EXPECT_TRUE(entry.CanDiscard()); +} + +TEST(BackoffEntryTest, CanDiscardNotStored) { + BackoffEntry::Policy no_store_policy = base_policy; + no_store_policy.entry_lifetime_ms = 0; + TestBackoffEntry not_stored(&no_store_policy); + EXPECT_TRUE(not_stored.CanDiscard()); +} + +TEST(BackoffEntryTest, ShouldIgnoreFirstTwo) { + BackoffEntry::Policy lenient_policy = base_policy; + lenient_policy.num_errors_to_ignore = 2; + + BackoffEntry entry(&lenient_policy); + + entry.InformOfRequest(false); + EXPECT_FALSE(entry.ShouldRejectRequest()); + + entry.InformOfRequest(false); + EXPECT_FALSE(entry.ShouldRejectRequest()); + + entry.InformOfRequest(false); + EXPECT_TRUE(entry.ShouldRejectRequest()); +} + +TEST(BackoffEntryTest, ReleaseTimeCalculation) { + TestBackoffEntry entry(&base_policy); + + // With zero errors, should return "now". + TimeTicks result = entry.GetReleaseTime(); + EXPECT_EQ(entry.ImplGetTimeNow(), result); + + // 1 error. + entry.InformOfRequest(false); + result = entry.GetReleaseTime(); + EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(1000), result); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); + + // 2 errors. + entry.InformOfRequest(false); + result = entry.GetReleaseTime(); + EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(2000), result); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); + + // 3 errors. + entry.InformOfRequest(false); + result = entry.GetReleaseTime(); + EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result); + EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease()); + + // 6 errors (to check it doesn't pass maximum). + entry.InformOfRequest(false); + entry.InformOfRequest(false); + entry.InformOfRequest(false); + result = entry.GetReleaseTime(); + EXPECT_EQ( + entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(20000), result); +} + +TEST(BackoffEntryTest, ReleaseTimeCalculationAlwaysDelay) { + BackoffEntry::Policy always_delay_policy = base_policy; + always_delay_policy.always_use_initial_delay = true; + always_delay_policy.num_errors_to_ignore = 2; + + TestBackoffEntry entry(&always_delay_policy); + + // With previous requests, should return "now". + TimeTicks result = entry.GetReleaseTime(); + EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease()); + + // 1 error. + entry.InformOfRequest(false); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); + + // 2 errors. + entry.InformOfRequest(false); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); + + // 3 errors, exponential backoff starts. + entry.InformOfRequest(false); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); + + // 4 errors. + entry.InformOfRequest(false); + EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease()); + + // 8 errors (to check it doesn't pass maximum). + entry.InformOfRequest(false); + entry.InformOfRequest(false); + entry.InformOfRequest(false); + entry.InformOfRequest(false); + result = entry.GetReleaseTime(); + EXPECT_EQ(TimeDelta::FromMilliseconds(20000), entry.GetTimeUntilRelease()); +} + +TEST(BackoffEntryTest, ReleaseTimeCalculationWithJitter) { + for (int i = 0; i < 10; ++i) { + BackoffEntry::Policy jittery_policy = base_policy; + jittery_policy.jitter_factor = 0.2; + + TestBackoffEntry entry(&jittery_policy); + + entry.InformOfRequest(false); + entry.InformOfRequest(false); + entry.InformOfRequest(false); + TimeTicks result = entry.GetReleaseTime(); + EXPECT_LE( + entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(3200), result); + EXPECT_GE( + entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result); + } +} + +TEST(BackoffEntryTest, FailureThenSuccess) { + TestBackoffEntry entry(&base_policy); + + // Failure count 1, establishes horizon. + entry.InformOfRequest(false); + TimeTicks release_time = entry.GetReleaseTime(); + EXPECT_EQ(TimeTicks() + TimeDelta::FromMilliseconds(1000), release_time); + + // Success, failure count 0, should not advance past + // the horizon that was already set. + entry.set_now(release_time - TimeDelta::FromMilliseconds(200)); + entry.InformOfRequest(true); + EXPECT_EQ(release_time, entry.GetReleaseTime()); + + // Failure, failure count 1. + entry.InformOfRequest(false); + EXPECT_EQ(release_time + TimeDelta::FromMilliseconds(800), + entry.GetReleaseTime()); +} + +TEST(BackoffEntryTest, FailureThenSuccessAlwaysDelay) { + BackoffEntry::Policy always_delay_policy = base_policy; + always_delay_policy.always_use_initial_delay = true; + always_delay_policy.num_errors_to_ignore = 1; + + TestBackoffEntry entry(&always_delay_policy); + + // Failure count 1. + entry.InformOfRequest(false); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); + + // Failure count 2. + entry.InformOfRequest(false); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); + entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); + + // Success. We should go back to the original delay. + entry.InformOfRequest(true); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); + + // Failure count reaches 2 again. We should increase the delay once more. + entry.InformOfRequest(false); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); + entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); +} + +TEST(BackoffEntryTest, RetainCustomHorizon) { + TestBackoffEntry custom(&base_policy); + TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3); + custom.SetCustomReleaseTime(custom_horizon); + custom.InformOfRequest(false); + custom.InformOfRequest(true); + custom.set_now(TimeTicks() + TimeDelta::FromDays(2)); + custom.InformOfRequest(false); + custom.InformOfRequest(true); + EXPECT_EQ(custom_horizon, custom.GetReleaseTime()); + + // Now check that once we are at or past the custom horizon, + // we get normal behavior. + custom.set_now(TimeTicks() + TimeDelta::FromDays(3)); + custom.InformOfRequest(false); + EXPECT_EQ( + TimeTicks() + TimeDelta::FromDays(3) + TimeDelta::FromMilliseconds(1000), + custom.GetReleaseTime()); +} + +TEST(BackoffEntryTest, RetainCustomHorizonWhenInitialErrorsIgnored) { + // Regression test for a bug discovered during code review. + BackoffEntry::Policy lenient_policy = base_policy; + lenient_policy.num_errors_to_ignore = 1; + TestBackoffEntry custom(&lenient_policy); + TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3); + custom.SetCustomReleaseTime(custom_horizon); + custom.InformOfRequest(false); // This must not reset the horizon. + EXPECT_EQ(custom_horizon, custom.GetReleaseTime()); +} + +TEST(BackoffEntryTest, OverflowProtection) { + BackoffEntry::Policy large_multiply_policy = base_policy; + large_multiply_policy.multiply_factor = 256; + TestBackoffEntry custom(&large_multiply_policy); + + // Trigger enough failures such that more than 11 bits of exponent are used + // to represent the exponential backoff intermediate values. Given a multiply + // factor of 256 (2^8), 129 iterations is enough: 2^(8*(129-1)) = 2^1024. + for (int i = 0; i < 129; ++i) { + custom.set_now(custom.ImplGetTimeNow() + custom.GetTimeUntilRelease()); + custom.InformOfRequest(false); + ASSERT_TRUE(custom.ShouldRejectRequest()); + } + + // Max delay should still be respected. + EXPECT_EQ(20000, custom.GetTimeUntilRelease().InMilliseconds()); +} + +} // namespace diff --git a/brillo/bind_lambda.h b/brillo/bind_lambda.h new file mode 100644 index 0000000..7ae692d --- /dev/null +++ b/brillo/bind_lambda.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_BIND_LAMBDA_H_ +#define LIBCHROMEOS_BRILLO_BIND_LAMBDA_H_ + +#include + +//////////////////////////////////////////////////////////////////////////////// +// This file is an extension to base/bind_internal.h and adds a RunnableAdapter +// class specialization that wraps a functor (including lambda objects), so +// they can be used in base::Callback/base::Bind constructs. +// By including this file you will gain the ability to write expressions like: +// base::Callback callback = base::Bind([](int value) { +// return value * value; +// }); +//////////////////////////////////////////////////////////////////////////////// +namespace base { +namespace internal { + +// LambdaAdapter is a helper class that specializes on different function call +// signatures and provides the RunType and Run() method required by +// RunnableAdapter<> class. +template +class LambdaAdapter; + +// R(...) +template +class LambdaAdapter { + public: + typedef R(RunType)(Args...); + LambdaAdapter(Lambda lambda) : lambda_(lambda) {} + R Run(Args... args) { return lambda_(CallbackForward(args)...); } + + private: + Lambda lambda_; +}; + +// R(...) const +template +class LambdaAdapter { + public: + typedef R(RunType)(Args...); + LambdaAdapter(Lambda lambda) : lambda_(lambda) {} + R Run(Args... args) { return lambda_(CallbackForward(args)...); } + + private: + Lambda lambda_; +}; + +template +class RunnableAdapter + : public LambdaAdapter { + public: + explicit RunnableAdapter(Lambda lambda) + : LambdaAdapter(lambda) {} +}; + +} // namespace internal +} // namespace base + +#endif // LIBCHROMEOS_BRILLO_BIND_LAMBDA_H_ diff --git a/brillo/binder_watcher.cc b/brillo/binder_watcher.cc new file mode 100644 index 0000000..53494bb --- /dev/null +++ b/brillo/binder_watcher.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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 + +#include +#include +#include + +using android::IPCThreadState; +using android::ProcessState; + +namespace brillo { + +BinderWatcher::BinderWatcher() = default; + +BinderWatcher::~BinderWatcher() = default; + +bool BinderWatcher::Init() { + int binder_fd = -1; + ProcessState::self()->setThreadPoolMaxThreadCount(0); + IPCThreadState::self()->disableBackgroundScheduling(true); + IPCThreadState::self()->setupPolling(&binder_fd); + LOG(INFO) << "Got binder FD " << binder_fd; + if (binder_fd < 0) + return false; + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + binder_fd, true /* persistent */, base::MessageLoopForIO::WATCH_READ, + &watcher_, this)) { + LOG(ERROR) << "Failed to watch binder FD"; + return false; + } + return true; +} + +void BinderWatcher::OnFileCanReadWithoutBlocking(int fd) { + IPCThreadState::self()->handlePolledCommands(); +} + +void BinderWatcher::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED() << "Unexpected writable notification for FD " << fd; +} + +} // namespace brillo diff --git a/brillo/binder_watcher.h b/brillo/binder_watcher.h new file mode 100644 index 0000000..af74f31 --- /dev/null +++ b/brillo/binder_watcher.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef LIBCHROMEOS_BRILLO_BINDER_WATCHER_H_ +#define LIBCHROMEOS_BRILLO_BINDER_WATCHER_H_ + +#include +#include + +namespace brillo { + +// Bridge between libbinder and base::MessageLoop. Construct at startup to make +// the message loop watch for binder events and pass them to libbinder. +class BinderWatcher : public base::MessageLoopForIO::Watcher { + public: + BinderWatcher(); + ~BinderWatcher() override; + + // Initializes the object, returning true on success. + bool Init(); + + // base::MessageLoopForIO::Watcher: + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override; + + private: + base::MessageLoopForIO::FileDescriptorWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(BinderWatcher); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_BINDER_WATCHER_H_ diff --git a/brillo/brillo_export.h b/brillo/brillo_export.h new file mode 100644 index 0000000..8d42f58 --- /dev/null +++ b/brillo/brillo_export.h @@ -0,0 +1,60 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_CHROMEOS_EXPORT_H_ +#define LIBCHROMEOS_BRILLO_CHROMEOS_EXPORT_H_ + +// Use BRILLO_EXPORT attribute to decorate your classes, methods and variables +// that need to be exported out of libchromeos. By default, any symbol not +// explicitly marked with BRILLO_EXPORT attribute is not exported. + +// Put BRILLO_EXPORT in front of methods or variables and in between the +// class and the tag name: +/* + +BRILLO_EXPORT void foo(); + +class BRILLO_EXPORT Bar { + public: + void baz(); // Exported since it is a member of an exported class. +}; + +*/ + +// Exporting a class automatically exports all of its members. However there are +// no export entries for non-static member variables since they are not accessed +// directly, but rather through "this" pointer. Class methods, type information, +// virtual table (if any), and static member variables are exported. + +// Finally, template functions and template members of a class may not be +// inlined by the compiler automatically and the out-of-line version will not +// be exported and fail to link. Marking those inline explicitly might help. +// Alternatively, exporting specific instantiation of the template could be +// used with "extern template" and combining this with BRILLO_EXPORT. +#define BRILLO_EXPORT __attribute__((__visibility__("default"))) + +// On occasion you might need to disable exporting a particular symbol if +// you don't want the clients to see it. For example, you can explicitly +// hide a member of an exported class: +/* + +class BRILLO_EXPORT Foo { + public: + void bar(); // Exported since it is a member of an exported class. + + private: + BRILLO_PRIVATE void baz(); // Explicitly removed from export table. +}; + +*/ + +// Note that even though a class may have a private member it doesn't mean +// that it must not be exported, since the compiler might still need it. +// For example, an inline public method calling a private method will not link +// if that private method is not exported. +// So be careful with hiding members if you don't want to deal with obscure +// linker errors. +#define BRILLO_PRIVATE __attribute__((__visibility__("hidden"))) + +#endif // LIBCHROMEOS_BRILLO_CHROMEOS_EXPORT_H_ diff --git a/brillo/cryptohome.cc b/brillo/cryptohome.cc new file mode 100644 index 0000000..49a4a88 --- /dev/null +++ b/brillo/cryptohome.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/cryptohome.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +using base::FilePath; + +namespace brillo { +namespace cryptohome { +namespace home { + +const char kGuestUserName[] = "$guest"; + +static char g_user_home_prefix[PATH_MAX] = "/home/user/"; +static char g_root_home_prefix[PATH_MAX] = "/home/root/"; +static char g_system_salt_path[PATH_MAX] = "/home/.shadow/salt"; + +static std::string* salt = nullptr; + +static bool EnsureSystemSaltIsLoaded() { + if (salt && !salt->empty()) + return true; + FilePath salt_path(g_system_salt_path); + int64_t file_size; + if (!base::GetFileSize(salt_path, &file_size)) { + PLOG(ERROR) << "Could not get size of system salt: " << g_system_salt_path; + return false; + } + if (file_size > static_cast(std::numeric_limits::max())) { + LOG(ERROR) << "System salt too large: " << file_size; + return false; + } + std::vector buf; + buf.resize(file_size); + unsigned int data_read = base::ReadFile(salt_path, buf.data(), file_size); + if (data_read != file_size) { + PLOG(ERROR) << "Could not read entire file: " << data_read + << " != " << file_size; + return false; + } + + if (!salt) + salt = new std::string(); + salt->assign(buf.data(), file_size); + return true; +} + +std::string SanitizeUserName(const std::string& username) { + if (!EnsureSystemSaltIsLoaded()) + return std::string(); + + unsigned char binmd[SHA_DIGEST_LENGTH]; + std::string lowercase(username); + std::transform( + lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower); + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, salt->data(), salt->size()); + SHA1_Update(&ctx, lowercase.data(), lowercase.size()); + SHA1_Final(binmd, &ctx); + std::string final = base::HexEncode(binmd, sizeof(binmd)); + // Stay compatible with CryptoLib::HexEncodeToBuffer() + std::transform(final.begin(), final.end(), final.begin(), ::tolower); + return final; +} + +FilePath GetUserPathPrefix() { + return FilePath(g_user_home_prefix); +} + +FilePath GetRootPathPrefix() { + return FilePath(g_root_home_prefix); +} + +FilePath GetHashedUserPath(const std::string& hashed_username) { + return FilePath( + base::StringPrintf("%s%s", g_user_home_prefix, hashed_username.c_str())); +} + +FilePath GetUserPath(const std::string& username) { + if (!EnsureSystemSaltIsLoaded()) + return FilePath(""); + return GetHashedUserPath(SanitizeUserName(username)); +} + +FilePath GetRootPath(const std::string& username) { + if (!EnsureSystemSaltIsLoaded()) + return FilePath(""); + return FilePath(base::StringPrintf( + "%s%s", g_root_home_prefix, SanitizeUserName(username).c_str())); +} + +FilePath GetDaemonPath(const std::string& username, const std::string& daemon) { + if (!EnsureSystemSaltIsLoaded()) + return FilePath(""); + return GetRootPath(username).Append(daemon); +} + +bool IsSanitizedUserName(const std::string& sanitized) { + std::vector bytes; + return (sanitized.length() == 2 * SHA_DIGEST_LENGTH) && + base::HexStringToBytes(sanitized, &bytes); +} + +void SetUserHomePrefix(const std::string& prefix) { + if (prefix.length() < sizeof(g_user_home_prefix)) { + snprintf( + g_user_home_prefix, sizeof(g_user_home_prefix), "%s", prefix.c_str()); + } +} + +void SetRootHomePrefix(const std::string& prefix) { + if (prefix.length() < sizeof(g_root_home_prefix)) { + snprintf( + g_root_home_prefix, sizeof(g_root_home_prefix), "%s", prefix.c_str()); + } +} + +std::string* GetSystemSalt() { + return salt; +} + +void SetSystemSalt(std::string* value) { + salt = value; +} + +} // namespace home +} // namespace cryptohome +} // namespace brillo diff --git a/brillo/cryptohome.h b/brillo/cryptohome.h new file mode 100644 index 0000000..af2097e --- /dev/null +++ b/brillo/cryptohome.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_CRYPTOHOME_H_ +#define LIBCHROMEOS_BRILLO_CRYPTOHOME_H_ + +#include + +#include +#include + +namespace brillo { +namespace cryptohome { +namespace home { + +BRILLO_EXPORT extern const char kGuestUserName[]; + +// Returns the common prefix under which the mount points for user homes are +// created. +BRILLO_EXPORT base::FilePath GetUserPathPrefix(); + +// Returns the common prefix under which the mount points for root homes are +// created. +BRILLO_EXPORT base::FilePath GetRootPathPrefix(); + +// Returns the path at which the user home for |username| will be mounted. +// Returns "" for failures. +BRILLO_EXPORT base::FilePath GetUserPath(const std::string& username); + +// Returns the path at which the user home for |hashed_username| will be +// mounted. Useful when you already have the username hashed. +// Returns "" for failures. +BRILLO_EXPORT base::FilePath GetHashedUserPath( + const std::string& hashed_username); + +// Returns the path at which the root home for |username| will be mounted. +// Returns "" for failures. +BRILLO_EXPORT base::FilePath GetRootPath(const std::string& username); + +// Returns the path at which the daemon |daemon| should store per-user data. +BRILLO_EXPORT base::FilePath GetDaemonPath(const std::string& username, + const std::string& daemon); + +// Checks whether |sanitized| has the format of a sanitized username. +BRILLO_EXPORT bool IsSanitizedUserName(const std::string& sanitized); + +// Returns a sanitized form of |username|. For x != y, SanitizeUserName(x) != +// SanitizeUserName(y). +BRILLO_EXPORT std::string SanitizeUserName(const std::string& username); + +// Overrides the common prefix under which the mount points for user homes are +// created. This is used for testing only. +BRILLO_EXPORT void SetUserHomePrefix(const std::string& prefix); + +// Overrides the common prefix under which the mount points for root homes are +// created. This is used for testing only. +BRILLO_EXPORT void SetRootHomePrefix(const std::string& prefix); + +// Overrides the contents of the system salt. +// salt should be non-NULL and non-empty when attempting to avoid filesystem +// usage in tests. +// Note: +// (1) Never mix usage with SetSystemSaltPath(). +// (2) Ownership of the pointer stays with the caller. +BRILLO_EXPORT void SetSystemSalt(std::string* salt); + +// Returns the system salt. +BRILLO_EXPORT std::string* GetSystemSalt(); + +} // namespace home +} // namespace cryptohome +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_CRYPTOHOME_H_ diff --git a/brillo/daemons/daemon.cc b/brillo/daemons/daemon.cc new file mode 100644 index 0000000..11700f1 --- /dev/null +++ b/brillo/daemons/daemon.cc @@ -0,0 +1,92 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include + +namespace brillo { + +Daemon::Daemon() : exit_code_{EX_OK} { + brillo_message_loop_.SetAsCurrent(); +} + +Daemon::~Daemon() { +} + +int Daemon::Run() { + int exit_code = OnInit(); + if (exit_code != EX_OK) + return exit_code; + + brillo_message_loop_.Run(); + + OnShutdown(&exit_code_); + + // base::RunLoop::QuitClosure() causes the message loop to quit + // immediately, even if pending tasks are still queued. + // Run a secondary loop to make sure all those are processed. + // This becomes important when working with D-Bus since dbus::Bus does + // a bunch of clean-up tasks asynchronously when shutting down. + while (brillo_message_loop_.RunOnce(false /* may_block */)) {} + + return exit_code_; +} + +void Daemon::Quit() { QuitWithExitCode(EX_OK); } + +void Daemon::QuitWithExitCode(int exit_code) { + exit_code_ = exit_code; + message_loop_.PostTask(FROM_HERE, QuitClosure()); +} + +void Daemon::RegisterHandler( + int signal, + const AsynchronousSignalHandlerInterface::SignalHandler& callback) { + async_signal_handler_.RegisterHandler(signal, callback); +} + +void Daemon::UnregisterHandler(int signal) { + async_signal_handler_.UnregisterHandler(signal); +} + +int Daemon::OnInit() { + async_signal_handler_.Init(); + for (int signal : {SIGTERM, SIGINT}) { + async_signal_handler_.RegisterHandler( + signal, base::Bind(&Daemon::Shutdown, base::Unretained(this))); + } + async_signal_handler_.RegisterHandler( + SIGHUP, base::Bind(&Daemon::Restart, base::Unretained(this))); + return EX_OK; +} + +void Daemon::OnShutdown(int* exit_code) { + // Do nothing. +} + +bool Daemon::OnRestart() { + // Not handled. + return false; // Returning false will shut down the daemon instead. +} + +bool Daemon::Shutdown(const signalfd_siginfo& info) { + Quit(); + return true; // Unregister the signal handler. +} + +bool Daemon::Restart(const signalfd_siginfo& info) { + if (OnRestart()) + return false; // Keep listening to the signal. + Quit(); + return true; // Unregister the signal handler. +} + +} // namespace brillo diff --git a/brillo/daemons/daemon.h b/brillo/daemons/daemon.h new file mode 100644 index 0000000..be9c8ca --- /dev/null +++ b/brillo/daemons/daemon.h @@ -0,0 +1,116 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DAEMONS_DAEMON_H_ +#define LIBCHROMEOS_BRILLO_DAEMONS_DAEMON_H_ + +#include + +#include +#include +#include +#include +#include +#include + +struct signalfd_siginfo; + +namespace brillo { + +// Daemon is a simple base class for system daemons. It provides a lot +// of useful facilities such as a message loop, handling of SIGTERM, SIGINT, and +// SIGHUP system signals. +// You can use this class directly to implement your daemon or you can +// specialize it by creating your own class and deriving it from +// brillo::Daemon. Override some of the virtual methods provide to fine-tune +// its behavior to suit your daemon's needs. +class BRILLO_EXPORT Daemon : public AsynchronousSignalHandlerInterface { + public: + Daemon(); + virtual ~Daemon(); + + // Performs proper initialization of the daemon and runs the message loop. + // Blocks until the daemon is finished. The return value is the error + // code that should be returned from daemon's main(). Returns EX_OK (0) on + // success. + virtual int Run(); + + // Can be used by call-backs to trigger shut-down of a running message loop. + // Calls QuiteWithExitCode(EX_OK); + // WARNING: This method (as well as QuitWithExitCode) can only be called when + // the message loop is running (that is, during Daemon::Run() call). Calling + // these methods before (e.g. during OnInit()) or after (e.g in OnShutdown()) + // will lead to abnormal process termination. + void Quit(); + + // |exit_code| is the status code to be returned when the daemon process + // quits. See the warning for Quit() above regarding the allowed scope for + // this method. + void QuitWithExitCode(int exit_code); + + // AsynchronousSignalHandlerInterface overrides. + // Register/unregister custom signal handlers for the daemon. The semantics + // are identical to AsynchronousSignalHandler::RegisterHandler and + // AsynchronousSignalHandler::UnregisterHandler, except that handlers for + // SIGTERM, SIGINT, and SIGHUP cannot be modified. + void RegisterHandler( + int signal, const + AsynchronousSignalHandlerInterface::SignalHandler& callback) override; + void UnregisterHandler(int signal) override; + + protected: + // Overload to provide your own initialization code that should happen just + // before running the message loop. Return EX_OK (0) on success or any other + // non-zero error codes. If an error is returned, the message loop execution + // is aborted and Daemon::Run() exits early. + // When overloading, make sure you call the base implementation of OnInit(). + virtual int OnInit(); + // Called when the message loops exits and before Daemon::Run() returns. + // Overload to clean up the data that was set up during OnInit(). + // |return_code| contains the current error code that will be returned from + // Run(). You can override this value with your own error code if needed. + // When overloading, make sure you call the base implementation of + // OnShutdown(). + virtual void OnShutdown(int* exit_code); + // Called when the SIGHUP signal is received. In response to this call, your + // daemon could reset/reload the configuration and re-initialize its state + // as if the process has been reloaded. + // Return true if the signal was processed successfully and the daemon + // reset its configuration. Returning false will force the daemon to + // quit (and subsequently relaunched by an upstart job, if one is configured). + // The default implementation just returns false (unhandled), which terminates + // the daemon, so do not call the base implementation of OnRestart() from + // your overload. + virtual bool OnRestart(); + + // Returns a delegate to Quit() method in the base::RunLoop instance. + base::Closure QuitClosure() const { + return chromeos_message_loop_.QuitClosure(); + } + + private: + // Called when SIGTERM/SIGINT signals are received. + bool Shutdown(const signalfd_siginfo& info); + // Called when SIGHUP signal is received. + bool Restart(const signalfd_siginfo& info); + + // |at_exit_manager_| must be first to make sure it is initialized before + // other members, especially the |message_loop_|. + base::AtExitManager at_exit_manager_; + // The main message loop for the daemon. + base::MessageLoopForIO message_loop_; + // The brillo wrapper for the main message loop. + BaseMessageLoop chromeos_message_loop_{&message_loop_}; + // A helper to dispatch signal handlers asynchronously, so that the main + // system signal handler returns as soon as possible. + AsynchronousSignalHandler async_signal_handler_; + // Process exit code specified in QuitWithExitCode() method call. + int exit_code_; + + DISALLOW_COPY_AND_ASSIGN(Daemon); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DAEMONS_DAEMON_H_ diff --git a/brillo/daemons/dbus_daemon.cc b/brillo/daemons/dbus_daemon.cc new file mode 100644 index 0000000..ebbfd94 --- /dev/null +++ b/brillo/daemons/dbus_daemon.cc @@ -0,0 +1,90 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include + +using brillo::dbus_utils::AsyncEventSequencer; +using brillo::dbus_utils::ExportedObjectManager; + +namespace brillo { + +DBusDaemon::DBusDaemon() { +} + +DBusDaemon::~DBusDaemon() { + if (bus_) + bus_->ShutdownAndBlock(); +} + +int DBusDaemon::OnInit() { + int exit_code = Daemon::OnInit(); + if (exit_code != EX_OK) + return exit_code; + + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + + bus_ = new dbus::Bus(options); + CHECK(bus_->Connect()); + + return exit_code; +} + +DBusServiceDaemon::DBusServiceDaemon(const std::string& service_name) + : service_name_(service_name) { +} + +DBusServiceDaemon::DBusServiceDaemon( + const std::string& service_name, + const dbus::ObjectPath& object_manager_path) + : service_name_(service_name), object_manager_path_(object_manager_path) { +} + +DBusServiceDaemon::DBusServiceDaemon(const std::string& service_name, + base::StringPiece object_manager_path) + : DBusServiceDaemon(service_name, + dbus::ObjectPath(object_manager_path.as_string())) { +} + +int DBusServiceDaemon::OnInit() { + int exit_code = DBusDaemon::OnInit(); + if (exit_code != EX_OK) + return exit_code; + + scoped_refptr sequencer(new AsyncEventSequencer()); + if (object_manager_path_.IsValid()) { + object_manager_.reset( + new ExportedObjectManager(bus_, object_manager_path_)); + object_manager_->RegisterAsync( + sequencer->GetHandler("ObjectManager.RegisterAsync() failed.", true)); + } + RegisterDBusObjectsAsync(sequencer.get()); + sequencer->OnAllTasksCompletedCall({ + base::Bind(&DBusServiceDaemon::TakeServiceOwnership, + base::Unretained(this)) + }); + return EX_OK; +} + +void DBusServiceDaemon::RegisterDBusObjectsAsync( + dbus_utils::AsyncEventSequencer* sequencer) { + // Do nothing here. + // Overload this method to export custom D-Bus objects at daemon startup. +} + +void DBusServiceDaemon::TakeServiceOwnership(bool success) { + // Success should always be true since we've said that failures are fatal. + CHECK(success) << "Init of one or more objects has failed."; + CHECK(bus_->RequestOwnershipAndBlock(service_name_, + dbus::Bus::REQUIRE_PRIMARY)) + << "Unable to take ownership of " << service_name_; +} + +} // namespace brillo diff --git a/brillo/daemons/dbus_daemon.h b/brillo/daemons/dbus_daemon.h new file mode 100644 index 0000000..c2352c1 --- /dev/null +++ b/brillo/daemons/dbus_daemon.h @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DAEMONS_DBUS_DAEMON_H_ +#define LIBCHROMEOS_BRILLO_DAEMONS_DBUS_DAEMON_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { + +namespace dbus_utils { +class AsyncEventSequencer; +} // namespace dbus_utils + +// DBusDaemon adds D-Bus support to Daemon. +// Derive your daemon from this class if you want D-Bus client services in your +// daemon (consuming other D-Bus objects). Currently uses a SYSTEM bus. +class BRILLO_EXPORT DBusDaemon : public Daemon { + public: + DBusDaemon(); + ~DBusDaemon() override; + + protected: + // Calls the base OnInit() and then instantiates dbus::Bus and establishes + // a D-Bus connection. + int OnInit() override; + + scoped_refptr bus_; + + private: + DISALLOW_COPY_AND_ASSIGN(DBusDaemon); +}; + +// DBusServiceDaemon adds D-Bus service support to DBusDaemon. +// Derive your daemon from this class if your daemon exposes D-Bus objects. +// Provides an ExportedObjectManager to announce your object/interface creation +// and destruction. +class BRILLO_EXPORT DBusServiceDaemon : public DBusDaemon { + public: + // Constructs the daemon. + // |service_name| is the name of D-Bus service provided by the daemon. + // |object_manager_path_| is a well-known D-Bus object path for + // ExportedObjectManager object. + // If |object_manager_path_| is not specified, then ExportedObjectManager is + // not created and is not available as part of the D-Bus service. + explicit DBusServiceDaemon(const std::string& service_name); + DBusServiceDaemon(const std::string& service_name, + const dbus::ObjectPath& object_manager_path); + DBusServiceDaemon(const std::string& service_name, + base::StringPiece object_manager_path); + + protected: + // OnInit() overload exporting D-Bus objects. Exports the contained + // ExportedObjectManager object and calls RegisterDBusObjectsAsync() to let + // you provide additional D-Bus objects. + int OnInit() override; + + // Overload this method to export your custom D-Bus objects at startup. + // Objects exported in this way will finish exporting before we claim the + // daemon's service name on DBus. + virtual void RegisterDBusObjectsAsync( + dbus_utils::AsyncEventSequencer* sequencer); + + std::string service_name_; + dbus::ObjectPath object_manager_path_; + std::unique_ptr object_manager_; + + private: + // A callback that will be called when all the D-Bus objects/interfaces are + // exported successfully and the daemon is ready to claim the D-Bus service + // ownership. + void TakeServiceOwnership(bool success); + + DISALLOW_COPY_AND_ASSIGN(DBusServiceDaemon); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DAEMONS_DBUS_DAEMON_H_ diff --git a/brillo/data_encoding.cc b/brillo/data_encoding.cc new file mode 100644 index 0000000..f3e95f8 --- /dev/null +++ b/brillo/data_encoding.cc @@ -0,0 +1,154 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include + +#include +#include +#include +#include + +namespace { + +inline int HexToDec(int hex) { + int dec = -1; + if (hex >= '0' && hex <= '9') { + dec = hex - '0'; + } else if (hex >= 'A' && hex <= 'F') { + dec = hex - 'A' + 10; + } else if (hex >= 'a' && hex <= 'f') { + dec = hex - 'a' + 10; + } + return dec; +} + +// Helper for Base64Encode() and Base64EncodeWrapLines(). +std::string Base64EncodeHelper(const void* data, size_t size) { + std::vector buffer; + buffer.resize(modp_b64_encode_len(size)); + size_t out_size = modp_b64_encode(buffer.data(), + static_cast(data), + size); + return std::string{buffer.begin(), buffer.begin() + out_size}; +} + +} // namespace + +///////////////////////////////////////////////////////////////////////// +namespace brillo { +namespace data_encoding { + +std::string UrlEncode(const char* data, bool encodeSpaceAsPlus) { + std::string result; + + while (*data) { + char c = *data++; + // According to RFC3986 (http://www.faqs.org/rfcs/rfc3986.html), + // section 2.3. - Unreserved Characters + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || + c == '~') { + result += c; + } else if (c == ' ' && encodeSpaceAsPlus) { + // For historical reasons, some URLs have spaces encoded as '+', + // this also applies to form data encoded as + // 'application/x-www-form-urlencoded' + result += '+'; + } else { + base::StringAppendF(&result, + "%%%02X", + static_cast(c)); // Encode as %NN + } + } + return result; +} + +std::string UrlDecode(const char* data) { + std::string result; + while (*data) { + char c = *data++; + int part1 = 0, part2 = 0; + // HexToDec would return -1 even for character 0 (end of string), + // so it is safe to access data[0] and data[1] without overrunning the buf. + if (c == '%' && (part1 = HexToDec(data[0])) >= 0 && + (part2 = HexToDec(data[1])) >= 0) { + c = static_cast((part1 << 4) | part2); + data += 2; + } else if (c == '+') { + c = ' '; + } + result += c; + } + return result; +} + +std::string WebParamsEncode(const WebParamList& params, + bool encodeSpaceAsPlus) { + std::vector pairs; + pairs.reserve(params.size()); + for (const auto& p : params) { + std::string key = UrlEncode(p.first.c_str(), encodeSpaceAsPlus); + std::string value = UrlEncode(p.second.c_str(), encodeSpaceAsPlus); + pairs.push_back(brillo::string_utils::Join("=", key, value)); + } + + return brillo::string_utils::Join("&", pairs); +} + +WebParamList WebParamsDecode(const std::string& data) { + WebParamList result; + std::vector params = brillo::string_utils::Split(data, "&"); + for (const auto& p : params) { + auto pair = brillo::string_utils::SplitAtFirst(p, "="); + result.emplace_back(UrlDecode(pair.first.c_str()), + UrlDecode(pair.second.c_str())); + } + return result; +} + +std::string Base64Encode(const void* data, size_t size) { + return Base64EncodeHelper(data, size); +} + +std::string Base64EncodeWrapLines(const void* data, size_t size) { + std::string unwrapped = Base64EncodeHelper(data, size); + std::string wrapped; + + for (size_t i = 0; i < unwrapped.size(); i += 64) { + wrapped.append(unwrapped, i, 64); + wrapped.append("\n"); + } + return wrapped; +} + +bool Base64Decode(const std::string& input, brillo::Blob* output) { + std::string temp_buffer; + const std::string* data = &input; + if (input.find_first_of("\r\n") != std::string::npos) { + base::ReplaceChars(input, "\n", "", &temp_buffer); + base::ReplaceChars(temp_buffer, "\r", "", &temp_buffer); + data = &temp_buffer; + } + // base64 decoded data has 25% fewer bytes than the original (since every + // 3 source octets are encoded as 4 characters in base64). + // modp_b64_decode_len provides an upper estimate of the size of the output + // data. + output->resize(modp_b64_decode_len(data->size())); + + size_t size_read = modp_b64_decode(reinterpret_cast(output->data()), + data->data(), data->size()); + if (size_read == MODP_B64_ERROR) { + output->resize(0); + return false; + } + output->resize(size_read); + + return true; +} + +} // namespace data_encoding +} // namespace brillo diff --git a/brillo/data_encoding.h b/brillo/data_encoding.h new file mode 100644 index 0000000..d850f39 --- /dev/null +++ b/brillo/data_encoding.h @@ -0,0 +1,82 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DATA_ENCODING_H_ +#define LIBCHROMEOS_BRILLO_DATA_ENCODING_H_ + +#include +#include +#include + +#include +#include + +namespace brillo { +namespace data_encoding { + +using WebParamList = std::vector>; + +// Encode/escape string to be used in the query portion of a URL. +// If |encodeSpaceAsPlus| is set to true, spaces are encoded as '+' instead +// of "%20" +BRILLO_EXPORT std::string UrlEncode(const char* data, bool encodeSpaceAsPlus); + +inline std::string UrlEncode(const char* data) { + return UrlEncode(data, true); +} + +// Decodes/unescapes a URL. Replaces all %XX sequences with actual characters. +// Also replaces '+' with spaces. +BRILLO_EXPORT std::string UrlDecode(const char* data); + +// Converts a list of key-value pairs into a string compatible with +// 'application/x-www-form-urlencoded' content encoding. +BRILLO_EXPORT std::string WebParamsEncode(const WebParamList& params, + bool encodeSpaceAsPlus); + +inline std::string WebParamsEncode(const WebParamList& params) { + return WebParamsEncode(params, true); +} + +// Parses a string of '&'-delimited key-value pairs (separated by '=') and +// encoded in a way compatible with 'application/x-www-form-urlencoded' +// content encoding. +BRILLO_EXPORT WebParamList WebParamsDecode(const std::string& data); + +// Encodes binary data using base64-encoding. +BRILLO_EXPORT std::string Base64Encode(const void* data, size_t size); + +// Encodes binary data using base64-encoding and wraps lines at 64 character +// boundary using LF as required by PEM (RFC 1421) specification. +BRILLO_EXPORT std::string Base64EncodeWrapLines(const void* data, size_t size); + +// Decodes the input string from Base64. +BRILLO_EXPORT bool Base64Decode(const std::string& input, brillo::Blob* output); + +// Helper wrappers to use std::string and brillo::Blob as binary data +// containers. +inline std::string Base64Encode(const brillo::Blob& input) { + return Base64Encode(input.data(), input.size()); +} +inline std::string Base64EncodeWrapLines(const brillo::Blob& input) { + return Base64EncodeWrapLines(input.data(), input.size()); +} +inline std::string Base64Encode(const std::string& input) { + return Base64Encode(input.data(), input.size()); +} +inline std::string Base64EncodeWrapLines(const std::string& input) { + return Base64EncodeWrapLines(input.data(), input.size()); +} +inline bool Base64Decode(const std::string& input, std::string* output) { + brillo::Blob blob; + if (!Base64Decode(input, &blob)) + return false; + *output = std::string{blob.begin(), blob.end()}; + return true; +} + +} // namespace data_encoding +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DATA_ENCODING_H_ diff --git a/brillo/data_encoding_unittest.cc b/brillo/data_encoding_unittest.cc new file mode 100644 index 0000000..cb73da6 --- /dev/null +++ b/brillo/data_encoding_unittest.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2011 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include + +namespace brillo { +namespace data_encoding { + +TEST(data_encoding, UrlEncoding) { + std::string test = "\"http://sample/path/0014.html \""; + std::string encoded = UrlEncode(test.c_str()); + EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html+%22", encoded); + EXPECT_EQ(test, UrlDecode(encoded.c_str())); + + test = "\"http://sample/path/0014.html \""; + encoded = UrlEncode(test.c_str(), false); + EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html%20%22", encoded); + EXPECT_EQ(test, UrlDecode(encoded.c_str())); +} + +TEST(data_encoding, WebParamsEncoding) { + std::string encoded = + WebParamsEncode({{"q", "test"}, {"path", "/usr/bin"}, {"#", "%"}}); + EXPECT_EQ("q=test&path=%2Fusr%2Fbin&%23=%25", encoded); + + auto params = WebParamsDecode(encoded); + EXPECT_EQ(3, params.size()); + EXPECT_EQ("q", params[0].first); + EXPECT_EQ("test", params[0].second); + EXPECT_EQ("path", params[1].first); + EXPECT_EQ("/usr/bin", params[1].second); + EXPECT_EQ("#", params[2].first); + EXPECT_EQ("%", params[2].second); +} + +TEST(data_encoding, Base64Encode) { + const std::string text1 = "hello world"; + const std::string encoded1 = "aGVsbG8gd29ybGQ="; + + const std::string text2 = + "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque " + "molestie commodo. Viverra tincidunt integer erat ipsum, integer " + "molestie, arcu in, sit mauris ac a sed sit etiam."; + const std::string encoded2 = + "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBhbGlxdWF" + "tLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRpbmNpZHVudCBpbn" + "RlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFyY3UgaW4sIHNpdCBtYXVya" + "XMgYWMgYSBzZWQgc2l0IGV0aWFtLg=="; + + brillo::Blob data3(256); + std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF. + const std::string encoded3 = + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ" + "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW" + "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en" + "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU" + "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; + + EXPECT_EQ(encoded1, Base64Encode(text1)); + EXPECT_EQ(encoded2, Base64Encode(text2)); + EXPECT_EQ(encoded3, Base64Encode(data3)); +} + +TEST(data_encoding, Base64EncodeWrapLines) { + const std::string text1 = "hello world"; + const std::string encoded1 = "aGVsbG8gd29ybGQ=\n"; + + const std::string text2 = + "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque " + "molestie commodo. Viverra tincidunt integer erat ipsum, integer " + "molestie, arcu in, sit mauris ac a sed sit etiam."; + const std::string encoded2 = + "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n" + "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\n" + "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\n" + "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFtLg==\n"; + + brillo::Blob data3(256); + std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF. + const std::string encoded3 = + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\n" + "MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\n" + "YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n" + "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\n" + "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\n" + "8PHy8/T19vf4+fr7/P3+/w==\n"; + + EXPECT_EQ(encoded1, Base64EncodeWrapLines(text1)); + EXPECT_EQ(encoded2, Base64EncodeWrapLines(text2)); + EXPECT_EQ(encoded3, Base64EncodeWrapLines(data3)); +} + +TEST(data_encoding, Base64Decode) { + const std::string encoded1 = "dGVzdCBzdHJpbmc="; + + const std::string encoded2 = + "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n" + "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\r\n" + "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\r" + "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFt\n" + "Lg==\n\n\n"; + const std::string decoded2 = + "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque " + "molestie commodo. Viverra tincidunt integer erat ipsum, integer " + "molestie, arcu in, sit mauris ac a sed sit etiam."; + + const std::string encoded3 = + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ" + "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW" + "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en" + "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU" + "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; + brillo::Blob decoded3(256); + std::iota(decoded3.begin(), decoded3.end(), 0); // Fill with 0x00..0xFF. + + std::string decoded; + EXPECT_TRUE(Base64Decode(encoded1, &decoded)); + EXPECT_EQ("test string", decoded); + + EXPECT_TRUE(Base64Decode(encoded2, &decoded)); + EXPECT_EQ(decoded2, decoded); + + brillo::Blob decoded_blob; + EXPECT_TRUE(Base64Decode(encoded3, &decoded_blob)); + EXPECT_EQ(decoded3, decoded_blob); + + EXPECT_FALSE(Base64Decode("A", &decoded_blob)); + EXPECT_TRUE(decoded_blob.empty()); + + EXPECT_TRUE(Base64Decode("/w==", &decoded_blob)); + EXPECT_EQ((brillo::Blob{0xFF}), decoded_blob); + + EXPECT_TRUE(Base64Decode("//8=", &decoded_blob)); + EXPECT_EQ((brillo::Blob{0xFF, 0xFF}), decoded_blob); + + EXPECT_FALSE(Base64Decode("AAECAwQFB,cI", &decoded_blob)); + EXPECT_TRUE(decoded_blob.empty()); +} + +} // namespace data_encoding +} // namespace brillo diff --git a/brillo/dbus/async_event_sequencer.cc b/brillo/dbus/async_event_sequencer.cc new file mode 100644 index 0000000..862aa30 --- /dev/null +++ b/brillo/dbus/async_event_sequencer.cc @@ -0,0 +1,131 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +namespace brillo { + +namespace dbus_utils { + +AsyncEventSequencer::AsyncEventSequencer() { +} +AsyncEventSequencer::~AsyncEventSequencer() { +} + +AsyncEventSequencer::Handler AsyncEventSequencer::GetHandler( + const std::string& descriptive_message, + bool failure_is_fatal) { + CHECK(!started_) << "Cannot create handlers after OnAllTasksCompletedCall()"; + int unique_registration_id = ++registration_counter_; + outstanding_registrations_.insert(unique_registration_id); + return base::Bind(&AsyncEventSequencer::HandleFinish, + this, + unique_registration_id, + descriptive_message, + failure_is_fatal); +} + +AsyncEventSequencer::ExportHandler AsyncEventSequencer::GetExportHandler( + const std::string& interface_name, + const std::string& method_name, + const std::string& descriptive_message, + bool failure_is_fatal) { + auto finish_handler = GetHandler(descriptive_message, failure_is_fatal); + return base::Bind(&AsyncEventSequencer::HandleDBusMethodExported, + this, + finish_handler, + interface_name, + method_name); +} + +void AsyncEventSequencer::OnAllTasksCompletedCall( + std::vector actions) { + CHECK(!started_) << "OnAllTasksCompletedCall called twice!"; + started_ = true; + completion_actions_.assign(actions.begin(), actions.end()); + // All of our callbacks might have been called already. + PossiblyRunCompletionActions(); +} + +namespace { +void IgnoreSuccess(const AsyncEventSequencer::CompletionTask& task, + bool /*success*/) { + task.Run(); +} +void DoNothing(bool success) { +} +} // namespace + +AsyncEventSequencer::CompletionAction AsyncEventSequencer::WrapCompletionTask( + const CompletionTask& task) { + return base::Bind(&IgnoreSuccess, task); +} + +AsyncEventSequencer::CompletionAction +AsyncEventSequencer::GetDefaultCompletionAction() { + return base::Bind(&DoNothing); +} + +void AsyncEventSequencer::HandleFinish(int registration_number, + const std::string& error_message, + bool failure_is_fatal, + bool success) { + RetireRegistration(registration_number); + CheckForFailure(failure_is_fatal, success, error_message); + PossiblyRunCompletionActions(); +} + +void AsyncEventSequencer::HandleDBusMethodExported( + const AsyncEventSequencer::Handler& finish_handler, + const std::string& expected_interface_name, + const std::string& expected_method_name, + const std::string& actual_interface_name, + const std::string& actual_method_name, + bool success) { + CHECK_EQ(expected_method_name, actual_method_name) + << "Exported DBus method '" << actual_method_name << "' " + << "but expected '" << expected_method_name << "'"; + CHECK_EQ(expected_interface_name, actual_interface_name) + << "Exported method DBus interface '" << actual_interface_name << "' " + << "but expected '" << expected_interface_name << "'"; + finish_handler.Run(success); +} + +void AsyncEventSequencer::RetireRegistration(int registration_number) { + const size_t handlers_retired = + outstanding_registrations_.erase(registration_number); + CHECK_EQ(1U, handlers_retired) << "Tried to retire invalid handler " + << registration_number << ")"; +} + +void AsyncEventSequencer::CheckForFailure(bool failure_is_fatal, + bool success, + const std::string& error_message) { + if (failure_is_fatal) { + CHECK(success) << error_message; + } + if (!success) { + LOG(ERROR) << error_message; + had_failures_ = true; + } +} + +void AsyncEventSequencer::PossiblyRunCompletionActions() { + if (!started_ || !outstanding_registrations_.empty()) { + // Don't run completion actions if we have any outstanding + // Handlers outstanding or if any more handlers might + // be scheduled in the future. + return; + } + for (const auto& completion_action : completion_actions_) { + // Should this be put on the message loop or run directly? + completion_action.Run(!had_failures_); + } + // Discard our references to those actions. + completion_actions_.clear(); +} + +} // namespace dbus_utils + +} // namespace brillo diff --git a/brillo/dbus/async_event_sequencer.h b/brillo/dbus/async_event_sequencer.h new file mode 100644 index 0000000..719c9ad --- /dev/null +++ b/brillo/dbus/async_event_sequencer.h @@ -0,0 +1,113 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_ASYNC_EVENT_SEQUENCER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_ASYNC_EVENT_SEQUENCER_H_ + +#include +#include +#include + +#include +#include +#include +#include + +namespace brillo { + +namespace dbus_utils { + +// A helper class for coordinating the multiple async tasks. A consumer +// may grab any number of callbacks via Get*Handler() and schedule a list +// of completion actions to take. When all handlers obtained via Get*Handler() +// have been called, the AsyncEventSequencer will call its CompletionActions. +// +// Usage: +// +// void Init(const base::Callback cb) { +// scoped_refptr sequencer( +// new AsyncEventSequencer()); +// one_delegate_needing_init_.Init(sequencer->GetHandler( +// "my delegate failed to init", false)); +// dbus_init_delegate_.Init(sequencer->GetExportHandler( +// "org.test.Interface", "ExposedMethodName", +// "another delegate is flaky", false)); +// sequencer->OnAllTasksCompletedCall({cb}); +// } +class BRILLO_EXPORT AsyncEventSequencer + : public base::RefCounted { + public: + using Handler = base::Callback; + using ExportHandler = base::Callback; + using CompletionAction = base::Callback; + using CompletionTask = base::Callback; + + AsyncEventSequencer(); + + // Get a Finished handler callback. Each callback is "unique" in the sense + // that subsequent calls to GetHandler() will create new handlers + // which will need to be called before completion actions are run. + Handler GetHandler(const std::string& descriptive_message, + bool failure_is_fatal); + + // Like GetHandler except with a signature tailored to + // ExportedObject's ExportMethod callback requirements. Will also assert + // that the passed interface/method names from ExportedObject are correct. + ExportHandler GetExportHandler(const std::string& interface_name, + const std::string& method_name, + const std::string& descriptive_message, + bool failure_is_fatal); + + // Once all handlers obtained via GetHandler have run, + // we'll run each CompletionAction, then discard our references. + // No more handlers may be obtained after this call. + void OnAllTasksCompletedCall(std::vector actions); + + // Wrap a CompletionTask with a function that discards the result. + // This CompletionTask retains no references to the AsyncEventSequencer. + static CompletionAction WrapCompletionTask(const CompletionTask& task); + // Create a default CompletionAction that doesn't do anything when called. + static CompletionAction GetDefaultCompletionAction(); + + private: + // We'll partially bind this function before giving it back via + // GetHandler. Note that the returned callbacks have + // references to *this, which gives us the neat property that we'll + // destroy *this only when all our callbacks have been destroyed. + BRILLO_PRIVATE void HandleFinish(int registration_number, + const std::string& error_message, + bool failure_is_fatal, + bool success); + // Similar to HandleFinish. + BRILLO_PRIVATE void HandleDBusMethodExported( + const Handler& finish_handler, + const std::string& expected_interface_name, + const std::string& expected_method_name, + const std::string& actual_interface_name, + const std::string& actual_method_name, + bool success); + BRILLO_PRIVATE void RetireRegistration(int registration_number); + BRILLO_PRIVATE void CheckForFailure(bool failure_is_fatal, + bool success, + const std::string& error_message); + BRILLO_PRIVATE void PossiblyRunCompletionActions(); + + bool started_{false}; + int registration_counter_{0}; + std::set outstanding_registrations_; + std::vector completion_actions_; + bool had_failures_{false}; + // Ref counted objects have private destructors. + ~AsyncEventSequencer(); + friend class base::RefCounted; + DISALLOW_COPY_AND_ASSIGN(AsyncEventSequencer); +}; + +} // namespace dbus_utils + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_ASYNC_EVENT_SEQUENCER_H_ diff --git a/brillo/dbus/async_event_sequencer_unittest.cc b/brillo/dbus/async_event_sequencer_unittest.cc new file mode 100644 index 0000000..5f4c0e2 --- /dev/null +++ b/brillo/dbus/async_event_sequencer_unittest.cc @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +namespace brillo { + +namespace dbus_utils { + +namespace { + +const char kTestInterface[] = "org.test.if"; +const char kTestMethod1[] = "TestMethod1"; +const char kTestMethod2[] = "TestMethod2"; + +} // namespace + +class AsyncEventSequencerTest : public ::testing::Test { + public: + MOCK_METHOD1(HandleCompletion, void(bool all_succeeded)); + + void SetUp() { + aec_ = new AsyncEventSequencer(); + cb_ = base::Bind(&AsyncEventSequencerTest::HandleCompletion, + base::Unretained(this)); + } + + scoped_refptr aec_; + AsyncEventSequencer::CompletionAction cb_; +}; + +TEST_F(AsyncEventSequencerTest, WaitForCompletionActions) { + auto finished_handler = aec_->GetHandler("handler failed", false); + finished_handler.Run(true); + EXPECT_CALL(*this, HandleCompletion(true)).Times(1); + aec_->OnAllTasksCompletedCall({cb_}); +} + +TEST_F(AsyncEventSequencerTest, MultiInitActionsSucceed) { + auto finished_handler1 = aec_->GetHandler("handler failed", false); + auto finished_handler2 = aec_->GetHandler("handler failed", false); + aec_->OnAllTasksCompletedCall({cb_}); + finished_handler1.Run(true); + EXPECT_CALL(*this, HandleCompletion(true)).Times(1); + finished_handler2.Run(true); +} + +TEST_F(AsyncEventSequencerTest, SomeInitActionsFail) { + auto finished_handler1 = aec_->GetHandler("handler failed", false); + auto finished_handler2 = aec_->GetHandler("handler failed", false); + aec_->OnAllTasksCompletedCall({cb_}); + finished_handler1.Run(false); + EXPECT_CALL(*this, HandleCompletion(false)).Times(1); + finished_handler2.Run(true); +} + +TEST_F(AsyncEventSequencerTest, MultiDBusActionsSucceed) { + auto handler1 = aec_->GetExportHandler( + kTestInterface, kTestMethod1, "method export failed", false); + auto handler2 = aec_->GetExportHandler( + kTestInterface, kTestMethod2, "method export failed", false); + aec_->OnAllTasksCompletedCall({cb_}); + handler1.Run(kTestInterface, kTestMethod1, true); + EXPECT_CALL(*this, HandleCompletion(true)).Times(1); + handler2.Run(kTestInterface, kTestMethod2, true); +} + +TEST_F(AsyncEventSequencerTest, SomeDBusActionsFail) { + auto handler1 = aec_->GetExportHandler( + kTestInterface, kTestMethod1, "method export failed", false); + auto handler2 = aec_->GetExportHandler( + kTestInterface, kTestMethod2, "method export failed", false); + aec_->OnAllTasksCompletedCall({cb_}); + handler1.Run(kTestInterface, kTestMethod1, true); + EXPECT_CALL(*this, HandleCompletion(false)).Times(1); + handler2.Run(kTestInterface, kTestMethod2, false); +} + +TEST_F(AsyncEventSequencerTest, MixedActions) { + auto handler1 = aec_->GetExportHandler( + kTestInterface, kTestMethod1, "method export failed", false); + auto handler2 = aec_->GetHandler("handler failed", false); + aec_->OnAllTasksCompletedCall({cb_}); + handler1.Run(kTestInterface, kTestMethod1, true); + EXPECT_CALL(*this, HandleCompletion(true)).Times(1); + handler2.Run(true); +} + +} // namespace dbus_utils + +} // namespace brillo diff --git a/brillo/dbus/data_serialization.cc b/brillo/dbus/data_serialization.cc new file mode 100644 index 0000000..5c1d50e --- /dev/null +++ b/brillo/dbus/data_serialization.cc @@ -0,0 +1,321 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +void AppendValueToWriter(dbus::MessageWriter* writer, bool value) { + writer->AppendBool(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, uint8_t value) { + writer->AppendByte(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, int16_t value) { + writer->AppendInt16(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, uint16_t value) { + writer->AppendUint16(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, int32_t value) { + writer->AppendInt32(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, uint32_t value) { + writer->AppendUint32(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, int64_t value) { + writer->AppendInt64(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, uint64_t value) { + writer->AppendUint64(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, double value) { + writer->AppendDouble(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, + const std::string& value) { + writer->AppendString(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, const char* value) { + AppendValueToWriter(writer, std::string(value)); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, + const dbus::ObjectPath& value) { + writer->AppendObjectPath(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, + const dbus::FileDescriptor& value) { + writer->AppendFileDescriptor(value); +} + +void AppendValueToWriter(dbus::MessageWriter* writer, + const brillo::Any& value) { + value.AppendToDBusMessageWriter(writer); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool PopValueFromReader(dbus::MessageReader* reader, bool* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopBool(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, uint8_t* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopByte(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, int16_t* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopInt16(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, uint16_t* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopUint16(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, int32_t* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopInt32(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, uint32_t* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopUint32(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, int64_t* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopInt64(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, uint64_t* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopUint64(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, double* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopDouble(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, std::string* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopString(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, dbus::ObjectPath* value) { + dbus::MessageReader variant_reader(nullptr); + return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopObjectPath(value); +} + +bool PopValueFromReader(dbus::MessageReader* reader, + dbus::FileDescriptor* value) { + dbus::MessageReader variant_reader(nullptr); + bool ok = details::DescendIntoVariantIfPresent(&reader, &variant_reader) && + reader->PopFileDescriptor(value); + if (ok) + value->CheckValidity(); + return ok; +} + +namespace { + +// Helper methods for PopValueFromReader(dbus::MessageReader*, Any*) +// implementation. Pops a value of particular type from |reader| and assigns +// it to |value| of type Any. +template +bool PopTypedValueFromReader(dbus::MessageReader* reader, + brillo::Any* value) { + T data{}; + if (!PopValueFromReader(reader, &data)) + return false; + *value = std::move(data); + return true; +} + +// std::vector overload. +template +bool PopTypedArrayFromReader(dbus::MessageReader* reader, + brillo::Any* value) { + return PopTypedValueFromReader>(reader, value); +} + +// std::map overload. +template +bool PopTypedMapFromReader(dbus::MessageReader* reader, brillo::Any* value) { + return PopTypedValueFromReader>(reader, value); +} + +// Helper methods for reading common ARRAY signatures into a Variant. +// Note that only common types are supported. If an additional specific +// type signature is required, feel free to add support for it. +bool PopArrayValueFromReader(dbus::MessageReader* reader, + brillo::Any* value) { + std::string signature = reader->GetDataSignature(); + if (signature == "ab") + return PopTypedArrayFromReader(reader, value); + else if (signature == "ay") + return PopTypedArrayFromReader(reader, value); + else if (signature == "an") + return PopTypedArrayFromReader(reader, value); + else if (signature == "aq") + return PopTypedArrayFromReader(reader, value); + else if (signature == "ai") + return PopTypedArrayFromReader(reader, value); + else if (signature == "au") + return PopTypedArrayFromReader(reader, value); + else if (signature == "ax") + return PopTypedArrayFromReader(reader, value); + else if (signature == "at") + return PopTypedArrayFromReader(reader, value); + else if (signature == "ad") + return PopTypedArrayFromReader(reader, value); + else if (signature == "as") + return PopTypedArrayFromReader(reader, value); + else if (signature == "ao") + return PopTypedArrayFromReader(reader, value); + else if (signature == "av") + return PopTypedArrayFromReader(reader, value); + else if (signature == "a{ss}") + return PopTypedMapFromReader(reader, value); + else if (signature == "a{sv}") + return PopTypedValueFromReader(reader, value); + else if (signature == "aa{sv}") + return PopTypedArrayFromReader(reader, value); + else if (signature == "a{sa{ss}}") + return PopTypedMapFromReader< + std::string, std::map>(reader, value); + else if (signature == "a{sa{sv}}") + return PopTypedMapFromReader< + std::string, brillo::VariantDictionary>(reader, value); + else if (signature == "a{say}") + return PopTypedMapFromReader< + std::string, std::vector>(reader, value); + else if (signature == "a{uv}") + return PopTypedMapFromReader(reader, value); + else if (signature == "a(su)") + return PopTypedArrayFromReader< + std::tuple>(reader, value); + else if (signature == "a{uu}") + return PopTypedMapFromReader(reader, value); + else if (signature == "a(uu)") + return PopTypedArrayFromReader< + std::tuple>(reader, value); + + // When a use case for particular array signature is found, feel free + // to add handing for it here. + LOG(ERROR) << "Variant de-serialization of array containing data of " + << "type '" << signature << "' is not yet supported"; + return false; +} + +// Helper methods for reading common STRUCT signatures into a Variant. +// Note that only common types are supported. If an additional specific +// type signature is required, feel free to add support for it. +bool PopStructValueFromReader(dbus::MessageReader* reader, + brillo::Any* value) { + std::string signature = reader->GetDataSignature(); + if (signature == "(ii)") + return PopTypedValueFromReader>(reader, value); + else if (signature == "(ss)") + return PopTypedValueFromReader>(reader, + value); + else if (signature == "(ub)") + return PopTypedValueFromReader>(reader, value); + else if (signature == "(uu)") + return PopTypedValueFromReader>(reader, + value); + + // When a use case for particular struct signature is found, feel free + // to add handing for it here. + LOG(ERROR) << "Variant de-serialization of structs of type '" << signature + << "' is not yet supported"; + return false; +} + +} // anonymous namespace + +bool PopValueFromReader(dbus::MessageReader* reader, brillo::Any* value) { + dbus::MessageReader variant_reader(nullptr); + if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader)) + return false; + + switch (reader->GetDataType()) { + case dbus::Message::BYTE: + return PopTypedValueFromReader(reader, value); + case dbus::Message::BOOL: + return PopTypedValueFromReader(reader, value); + case dbus::Message::INT16: + return PopTypedValueFromReader(reader, value); + case dbus::Message::UINT16: + return PopTypedValueFromReader(reader, value); + case dbus::Message::INT32: + return PopTypedValueFromReader(reader, value); + case dbus::Message::UINT32: + return PopTypedValueFromReader(reader, value); + case dbus::Message::INT64: + return PopTypedValueFromReader(reader, value); + case dbus::Message::UINT64: + return PopTypedValueFromReader(reader, value); + case dbus::Message::DOUBLE: + return PopTypedValueFromReader(reader, value); + case dbus::Message::STRING: + return PopTypedValueFromReader(reader, value); + case dbus::Message::OBJECT_PATH: + return PopTypedValueFromReader(reader, value); + case dbus::Message::ARRAY: + return PopArrayValueFromReader(reader, value); + case dbus::Message::STRUCT: + return PopStructValueFromReader(reader, value); + case dbus::Message::DICT_ENTRY: + LOG(ERROR) << "Variant of DICT_ENTRY is invalid"; + return false; + case dbus::Message::VARIANT: + LOG(ERROR) << "Variant containing a variant is invalid"; + return false; + case dbus::Message::UNIX_FD: + CHECK(dbus::IsDBusTypeUnixFdSupported()) << "UNIX_FD data not supported"; + // dbus::FileDescriptor is not a copyable type. Cannot be returned via + // brillo::Any. Fail here. + LOG(ERROR) << "Cannot return FileDescriptor via Any"; + return false; + default: + LOG(FATAL) << "Unknown D-Bus data type: " << variant_reader.GetDataType(); + return false; + } + return true; +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/data_serialization.h b/brillo/dbus/data_serialization.h new file mode 100644 index 0000000..dbd96ac --- /dev/null +++ b/brillo/dbus/data_serialization.h @@ -0,0 +1,886 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DATA_SERIALIZATION_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DATA_SERIALIZATION_H_ + +// The main functionality provided by this header file is methods to serialize +// native C++ data over D-Bus. This includes three major parts: +// - Methods to get the D-Bus signature for a given C++ type: +// std::string GetDBusSignature(); +// - Methods to write arbitrary C++ data to D-Bus MessageWriter: +// void AppendValueToWriter(dbus::MessageWriter* writer, const T& value); +// void AppendValueToWriterAsVariant(dbus::MessageWriter*, const T&); +// - Methods to read arbitrary C++ data from D-Bus MessageReader: +// bool PopValueFromReader(dbus::MessageReader* reader, T* value); +// bool PopVariantValueFromReader(dbus::MessageReader* reader, T* value); +// +// There are a number of overloads to handle C++ equivalents of basic D-Bus +// types: +// D-Bus Type | D-Bus Signature | Native C++ type +// -------------------------------------------------- +// BYTE | y | uint8_t +// BOOL | b | bool +// INT16 | n | int16_t +// UINT16 | q | uint16_t +// INT32 | i | int32_t (int) +// UINT32 | u | uint32_t (unsigned) +// INT64 | x | int64_t +// UINT64 | t | uint64_t +// DOUBLE | d | double +// STRING | s | std::string +// OBJECT_PATH | o | dbus::ObjectPath +// ARRAY | aT | std::vector +// STRUCT | (UV) | std::pair +// | (UVW...) | std::tuple +// DICT | a{KV} | std::map +// VARIANT | v | brillo::Any +// UNIX_FD | h | dbus::FileDescriptor +// SIGNATURE | g | (unsupported) +// +// Additional overloads/specialization can be provided for custom types. +// In order to do that, provide overloads of AppendValueToWriter() and +// PopValueFromReader() functions in brillo::dbus_utils namespace for the +// CustomType. As well as a template specialization of DBusType<> for the same +// CustomType. This specialization must provide three static functions: +// - static std::string GetSignature(); +// - static void Write(dbus::MessageWriter* writer, const CustomType& value); +// - static bool Read(dbus::MessageReader* reader, CustomType* value); +// See an example in DBusUtils.CustomStruct unit test in +// brillo/dbus/data_serialization_unittest.cc. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace google { +namespace protobuf { +class MessageLite; +} // namespace protobuf +} // namespace google + +namespace brillo { + +// Forward-declare only. Can't include any.h right now because it needs +// AppendValueToWriter() declared below. +class Any; + +namespace dbus_utils { + +// Base class for DBusType for T not supported by D-Bus. This used to +// implement IsTypeSupported<> below. +struct Unsupported {}; + +// Generic definition of DBusType which will be specialized for particular +// types later. +// The second template parameter is used only in SFINAE situations to resolve +// class hierarchy chains for protobuf-derived classes. This type is defaulted +// to be 'void' in all other cases and simply ignored. +// See DBusType specialization for google::protobuf::MessageLite below for more +// detailed information. +template +struct DBusType : public Unsupported {}; + +// A helper type trait to determine if all of the types listed in Types... are +// supported by D-Bus. This is a generic forward-declaration which will be +// specialized for different type combinations. +template +struct IsTypeSupported; + +// Both T and the Types... must be supported for the complete set to be +// supported. +template +struct IsTypeSupported + : public std::integral_constant< + bool, + IsTypeSupported::value && IsTypeSupported::value> {}; + +// For a single type T, check if DBusType derives from Unsupported. +// If it does, then the type is not supported by the D-Bus. +template +struct IsTypeSupported + : public std::integral_constant< + bool, + !std::is_base_of>::value> {}; + +// Empty set is not supported. +template<> +struct IsTypeSupported<> : public std::false_type {}; + +//---------------------------------------------------------------------------- +// AppendValueToWriter(dbus::MessageWriter* writer, const T& value) +// Write the |value| of type T to D-Bus message. +// Explicitly delete the overloads for scalar types that are not supported by +// D-Bus. +void AppendValueToWriter(dbus::MessageWriter* writer, char value) = delete; +void AppendValueToWriter(dbus::MessageWriter* writer, float value) = delete; + +//---------------------------------------------------------------------------- +// PopValueFromReader(dbus::MessageWriter* writer, T* value) +// Reads the |value| of type T from D-Bus message. +// Explicitly delete the overloads for scalar types that are not supported by +// D-Bus. +void PopValueFromReader(dbus::MessageReader* reader, char* value) = delete; +void PopValueFromReader(dbus::MessageReader* reader, float* value) = delete; + +//---------------------------------------------------------------------------- +// Get D-Bus data signature from C++ data types. +// Specializations of a generic GetDBusSignature() provide signature strings +// for native C++ types. This function is available only for type supported +// by D-Bus. +template +inline typename std::enable_if::value, std::string>::type +GetDBusSignature() { + return DBusType::GetSignature(); +} + +namespace details { +// Helper method used by the many overloads of PopValueFromReader(). +// If the current value in the reader is of Variant type, the method descends +// into the Variant and updates the |*reader_ref| with the transient +// |variant_reader| MessageReader instance passed in. +// Returns false if it fails to descend into the Variant. +inline bool DescendIntoVariantIfPresent(dbus::MessageReader** reader_ref, + dbus::MessageReader* variant_reader) { + if ((*reader_ref)->GetDataType() != dbus::Message::VARIANT) + return true; + if (!(*reader_ref)->PopVariant(variant_reader)) + return false; + *reader_ref = variant_reader; + return true; +} + +// Helper method to format the type string of an array. +// Essentially it adds "a" in front of |element_signature|. +inline std::string GetArrayDBusSignature(const std::string& element_signature) { + return DBUS_TYPE_ARRAY_AS_STRING + element_signature; +} + +// Helper method to get a signature string for DICT_ENTRY. +// Returns "{KV}", where "K" and "V" are the type signatures for types +// KEY/VALUE. For example, GetDBusDictEntryType() would return +// "{si}". +template +inline std::string GetDBusDictEntryType() { + return DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + + GetDBusSignature() + GetDBusSignature() + + DBUS_DICT_ENTRY_END_CHAR_AS_STRING; +} + +} // namespace details + +//============================================================================= +// Specializations/overloads for AppendValueToWriter, PopValueFromReader and +// DBusType for various C++ types that can be serialized over D-Bus. + +// bool ----------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + bool value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + bool* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_BOOLEAN_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, bool value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, bool* value) { + return PopValueFromReader(reader, value); + } +}; + +// uint8_t -------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + uint8_t value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + uint8_t* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { return DBUS_TYPE_BYTE_AS_STRING; } + inline static void Write(dbus::MessageWriter* writer, uint8_t value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, uint8_t* value) { + return PopValueFromReader(reader, value); + } +}; + +// int16_t -------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + int16_t value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + int16_t* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { return DBUS_TYPE_INT16_AS_STRING; } + inline static void Write(dbus::MessageWriter* writer, int16_t value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, int16_t* value) { + return PopValueFromReader(reader, value); + } +}; + +// uint16_t ------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + uint16_t value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + uint16_t* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_UINT16_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, uint16_t value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, uint16_t* value) { + return PopValueFromReader(reader, value); + } +}; + +// int32_t -------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + int32_t value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + int32_t* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { return DBUS_TYPE_INT32_AS_STRING; } + inline static void Write(dbus::MessageWriter* writer, int32_t value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, int32_t* value) { + return PopValueFromReader(reader, value); + } +}; + +// uint32_t ------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + uint32_t value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + uint32_t* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_UINT32_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, uint32_t value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, uint32_t* value) { + return PopValueFromReader(reader, value); + } +}; + +// int64_t -------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + int64_t value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + int64_t* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { return DBUS_TYPE_INT64_AS_STRING; } + inline static void Write(dbus::MessageWriter* writer, int64_t value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, int64_t* value) { + return PopValueFromReader(reader, value); + } +}; + +// uint64_t ------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + uint64_t value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + uint64_t* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_UINT64_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, uint64_t value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, uint64_t* value) { + return PopValueFromReader(reader, value); + } +}; + +// double --------------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + double value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + double* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_DOUBLE_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, double value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, double* value) { + return PopValueFromReader(reader, value); + } +}; + +// std::string ---------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + const std::string& value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + std::string* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_STRING_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, + const std::string& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, std::string* value) { + return PopValueFromReader(reader, value); + } +}; + +// const char* +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + const char* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_STRING_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, const char* value) { + AppendValueToWriter(writer, value); + } +}; + +// const char[] +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_STRING_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, const char* value) { + AppendValueToWriter(writer, value); + } +}; + +// dbus::ObjectPath ----------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + const dbus::ObjectPath& value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + dbus::ObjectPath* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_OBJECT_PATH_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, + const dbus::ObjectPath& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, + dbus::ObjectPath* value) { + return PopValueFromReader(reader, value); + } +}; + +// dbus::FileDescriptor ------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + const dbus::FileDescriptor& value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + dbus::FileDescriptor* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_UNIX_FD_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, + const dbus::FileDescriptor& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, + dbus::FileDescriptor* value) { + return PopValueFromReader(reader, value); + } +}; + +// brillo::Any -------------------------------------------------------------- +BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, + const brillo::Any& value); +BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, + brillo::Any* value); + +template<> +struct DBusType { + inline static std::string GetSignature() { + return DBUS_TYPE_VARIANT_AS_STRING; + } + inline static void Write(dbus::MessageWriter* writer, + const brillo::Any& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, brillo::Any* value) { + return PopValueFromReader(reader, value); + } +}; + +// std::vector = D-Bus ARRAY. ------------------------------------------------- +template +typename std::enable_if::value>::type AppendValueToWriter( + dbus::MessageWriter* writer, + const std::vector& value) { + dbus::MessageWriter array_writer(nullptr); + writer->OpenArray(GetDBusSignature(), &array_writer); + for (const auto& element : value) { + // Use DBusType::Write() instead of AppendValueToWriter() to delay + // binding to AppendValueToWriter() to the point of instantiation of this + // template. + DBusType::Write(&array_writer, element); + } + writer->CloseContainer(&array_writer); +} + +template +typename std::enable_if::value, bool>::type +PopValueFromReader(dbus::MessageReader* reader, std::vector* value) { + dbus::MessageReader variant_reader(nullptr); + dbus::MessageReader array_reader(nullptr); + if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || + !reader->PopArray(&array_reader)) + return false; + value->clear(); + while (array_reader.HasMoreData()) { + T data; + // Use DBusType::Read() instead of PopValueFromReader() to delay + // binding to PopValueFromReader() to the point of instantiation of this + // template. + if (!DBusType::Read(&array_reader, &data)) + return false; + value->push_back(std::move(data)); + } + return true; +} + +namespace details { +// DBusArrayType<> is a helper base class for DBusType> that provides +// GetSignature/Write/Read methods for T types that are supported by D-Bus +// and not having those methods for types that are not supported by D-Bus. +template +struct DBusArrayType { + // Returns "aT", where "T" is the signature string for type T. + inline static std::string GetSignature() { + return GetArrayDBusSignature(GetDBusSignature()); + } + inline static void Write(dbus::MessageWriter* writer, + const std::vector& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, + std::vector* value) { + return PopValueFromReader(reader, value); + } +}; + +// Explicit specialization for unsupported type T. +template +struct DBusArrayType : public Unsupported {}; + +} // namespace details + +template +struct DBusType> + : public details::DBusArrayType::value, T, ALLOC> {}; + +// std::pair = D-Bus STRUCT with two elements. -------------------------------- +namespace details { + +// Helper class to get a D-Bus signature of a list of types. +// For example, TupleTraits::GetSignature() will +// return "ibs". +template +struct TupleTraits; + +template +struct TupleTraits { + static std::string GetSignature() { + return GetDBusSignature() + + TupleTraits::GetSignature(); + } +}; + +template<> +struct TupleTraits<> { + static std::string GetSignature() { return std::string{}; } +}; + +} // namespace details + +template +inline std::string GetStructDBusSignature() { + // Returns "(T...)", where "T..." is the signature strings for types T... + return DBUS_STRUCT_BEGIN_CHAR_AS_STRING + + details::TupleTraits::GetSignature() + + DBUS_STRUCT_END_CHAR_AS_STRING; +} + +template +typename std::enable_if::value>::type AppendValueToWriter( + dbus::MessageWriter* writer, + const std::pair& value) { + dbus::MessageWriter struct_writer(nullptr); + writer->OpenStruct(&struct_writer); + // Use DBusType::Write() instead of AppendValueToWriter() to delay + // binding to AppendValueToWriter() to the point of instantiation of this + // template. + DBusType::Write(&struct_writer, value.first); + DBusType::Write(&struct_writer, value.second); + writer->CloseContainer(&struct_writer); +} + +template +typename std::enable_if::value, bool>::type +PopValueFromReader(dbus::MessageReader* reader, std::pair* value) { + dbus::MessageReader variant_reader(nullptr); + dbus::MessageReader struct_reader(nullptr); + if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || + !reader->PopStruct(&struct_reader)) + return false; + // Use DBusType::Read() instead of PopValueFromReader() to delay + // binding to PopValueFromReader() to the point of instantiation of this + // template. + return DBusType::Read(&struct_reader, &value->first) && + DBusType::Read(&struct_reader, &value->second); +} + +namespace details { + +// DBusArrayType<> is a helper base class for DBusType> that provides +// GetSignature/Write/Read methods for types that are supported by D-Bus +// and not having those methods for types that are not supported by D-Bus. +template +struct DBusPairType { + // Returns "(UV)", where "U" and "V" are the signature strings for types U, V. + inline static std::string GetSignature() { + return GetStructDBusSignature(); + } + inline static void Write(dbus::MessageWriter* writer, + const std::pair& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, std::pair* value) { + return PopValueFromReader(reader, value); + } +}; + +// Either U, or V, or both are not supported by D-Bus. +template +struct DBusPairType : public Unsupported {}; + +} // namespace details + +template +struct DBusType> + : public details::DBusPairType::value, U, V> {}; + +// std::tuple = D-Bus STRUCT with arbitrary number of members. ---------------- +namespace details { + +// TupleIterator is a helper class to iterate over all the elements +// of a tuple from index I to N. TupleIterator<>::Read and ::Write methods +// are called for each element of the tuple and iteration continues until I == N +// in which case the specialization for I==N below stops the recursion. +template +struct TupleIterator { + // Tuple is just a convenience alias to a tuple containing elements of type T. + using Tuple = std::tuple; + // ValueType is the type of the element at index I. + using ValueType = typename std::tuple_element::type; + + // Write the tuple element at index I to D-Bus message. + static void Write(dbus::MessageWriter* writer, const Tuple& value) { + // Use DBusType::Write() instead of AppendValueToWriter() to delay + // binding to AppendValueToWriter() to the point of instantiation of this + // template. + DBusType::Write(writer, std::get(value)); + TupleIterator::Write(writer, value); + } + + // Read the tuple element at index I from D-Bus message. + static bool Read(dbus::MessageReader* reader, Tuple* value) { + // Use DBusType::Read() instead of PopValueFromReader() to delay + // binding to PopValueFromReader() to the point of instantiation of this + // template. + return DBusType::Read(reader, &std::get(*value)) && + TupleIterator::Read(reader, value); + } +}; + +// Specialization to end the iteration when the index reaches the last element. +template +struct TupleIterator { + using Tuple = std::tuple; + static void Write(dbus::MessageWriter* writer, const Tuple& value) {} + static bool Read(dbus::MessageReader* reader, Tuple* value) { return true; } +}; + +} // namespace details + +template +typename std::enable_if::value>::type AppendValueToWriter( + dbus::MessageWriter* writer, + const std::tuple& value) { + dbus::MessageWriter struct_writer(nullptr); + writer->OpenStruct(&struct_writer); + details::TupleIterator<0, sizeof...(T), T...>::Write(&struct_writer, value); + writer->CloseContainer(&struct_writer); +} + +template +typename std::enable_if::value, bool>::type +PopValueFromReader(dbus::MessageReader* reader, std::tuple* value) { + dbus::MessageReader variant_reader(nullptr); + dbus::MessageReader struct_reader(nullptr); + if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || + !reader->PopStruct(&struct_reader)) + return false; + return details::TupleIterator<0, sizeof...(T), T...>::Read(&struct_reader, + value); +} + +namespace details { + +// DBusTupleType<> is a helper base class for DBusType> that +// provides GetSignature/Write/Read methods for types that are supported by +// D-Bus and not having those methods for types that are not supported by D-Bus. +template +struct DBusTupleType { + // Returns "(T...)", where "T..." are the signature strings for types T... + inline static std::string GetSignature() { + return GetStructDBusSignature(); + } + inline static void Write(dbus::MessageWriter* writer, + const std::tuple& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, + std::tuple* value) { + return PopValueFromReader(reader, value); + } +}; + +// Some/all of types T... are not supported by D-Bus. +template +struct DBusTupleType : public Unsupported {}; + +} // namespace details + +template +struct DBusType> + : public details::DBusTupleType::value, T...> {}; + +// std::map = D-Bus ARRAY of DICT_ENTRY. -------------------------------------- +template +typename std::enable_if::value>::type +AppendValueToWriter(dbus::MessageWriter* writer, + const std::map& value) { + dbus::MessageWriter dict_writer(nullptr); + writer->OpenArray(details::GetDBusDictEntryType(), &dict_writer); + for (const auto& pair : value) { + dbus::MessageWriter entry_writer(nullptr); + dict_writer.OpenDictEntry(&entry_writer); + // Use DBusType::Write() instead of AppendValueToWriter() to delay + // binding to AppendValueToWriter() to the point of instantiation of this + // template. + DBusType::Write(&entry_writer, pair.first); + DBusType::Write(&entry_writer, pair.second); + dict_writer.CloseContainer(&entry_writer); + } + writer->CloseContainer(&dict_writer); +} + +template +typename std::enable_if::value, bool>::type +PopValueFromReader(dbus::MessageReader* reader, + std::map* value) { + dbus::MessageReader variant_reader(nullptr); + dbus::MessageReader array_reader(nullptr); + if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || + !reader->PopArray(&array_reader)) + return false; + value->clear(); + while (array_reader.HasMoreData()) { + dbus::MessageReader dict_entry_reader(nullptr); + if (!array_reader.PopDictEntry(&dict_entry_reader)) + return false; + KEY key; + VALUE data; + // Use DBusType::Read() instead of PopValueFromReader() to delay + // binding to PopValueFromReader() to the point of instantiation of this + // template. + if (!DBusType::Read(&dict_entry_reader, &key) || + !DBusType::Read(&dict_entry_reader, &data)) + return false; + value->emplace(std::move(key), std::move(data)); + } + return true; +} + +namespace details { + +// DBusArrayType<> is a helper base class for DBusType> that provides +// GetSignature/Write/Read methods for T types that are supported by D-Bus +// and not having those methods for types that are not supported by D-Bus. +template +struct DBusMapType { + // Returns "a{KV}", where "K" and "V" are the signature strings for types + // KEY/VALUE. + inline static std::string GetSignature() { + return GetArrayDBusSignature(GetDBusDictEntryType()); + } + inline static void Write(dbus::MessageWriter* writer, + const std::map& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, + std::map* value) { + return PopValueFromReader(reader, value); + } +}; + +// Types KEY, VALUE or both are not supported by D-Bus. +template +struct DBusMapType : public Unsupported {}; + +} // namespace details + +template +struct DBusType> + : public details::DBusMapType::value, + KEY, + VALUE, + PRED, + ALLOC> {}; + +// google::protobuf::MessageLite = D-Bus ARRAY of BYTE ------------------------ +inline void AppendValueToWriter(dbus::MessageWriter* writer, + const google::protobuf::MessageLite& value) { + writer->AppendProtoAsArrayOfBytes(value); +} + +inline bool PopValueFromReader(dbus::MessageReader* reader, + google::protobuf::MessageLite* value) { + return reader->PopArrayOfBytesAsProto(value); +} + +// is_protobuf_t is a helper type trait to determine if type T derives from +// google::protobuf::MessageLite. +template +using is_protobuf = std::is_base_of; + +// Specialize DBusType for classes that derive from protobuf::MessageLite. +// Here we perform a partial specialization of DBusType only for types +// that derive from google::protobuf::MessageLite. This is done by employing +// the second template parameter in DBusType and this basically relies on C++ +// SFINAE rules. "typename std::enable_if::value>::type" will +// evaluate to "void" for classes T that descend from MessageLite and will be +// an invalid construct for other types/classes which will automatically +// remove this particular specialization from name resolution context. +template +struct DBusType::value>::type> { + inline static std::string GetSignature() { + return GetDBusSignature>(); + } + inline static void Write(dbus::MessageWriter* writer, const T& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, T* value) { + return PopValueFromReader(reader, value); + } +}; + +//---------------------------------------------------------------------------- +// AppendValueToWriterAsVariant(dbus::MessageWriter* writer, const T& value) +// Write the |value| of type T to D-Bus message as a VARIANT. +// This overload is provided only if T is supported by D-Bus. +template +typename std::enable_if::value>::type +AppendValueToWriterAsVariant(dbus::MessageWriter* writer, const T& value) { + std::string data_type = GetDBusSignature(); + dbus::MessageWriter variant_writer(nullptr); + writer->OpenVariant(data_type, &variant_writer); + // Use DBusType::Write() instead of AppendValueToWriter() to delay + // binding to AppendValueToWriter() to the point of instantiation of this + // template. + DBusType::Write(&variant_writer, value); + writer->CloseContainer(&variant_writer); +} + +// Special case: do not allow to write a Variant containing a Variant. +// Just redirect to normal AppendValueToWriter(). +inline void AppendValueToWriterAsVariant(dbus::MessageWriter* writer, + const brillo::Any& value) { + return AppendValueToWriter(writer, value); +} + +//---------------------------------------------------------------------------- +// PopVariantValueFromReader(dbus::MessageWriter* writer, T* value) +// Reads a Variant containing the |value| of type T from D-Bus message. +// Note that the generic PopValueFromReader(...) can do this too. +// This method is provided for two reasons: +// 1. For API symmetry with AppendValueToWriter/AppendValueToWriterAsVariant. +// 2. To be used when it is important to assert that the data was sent +// specifically as a Variant. +// This overload is provided only if T is supported by D-Bus. +template +typename std::enable_if::value, bool>::type +PopVariantValueFromReader(dbus::MessageReader* reader, T* value) { + dbus::MessageReader variant_reader(nullptr); + if (!reader->PopVariant(&variant_reader)) + return false; + // Use DBusType::Read() instead of PopValueFromReader() to delay + // binding to PopValueFromReader() to the point of instantiation of this + // template. + return DBusType::Read(&variant_reader, value); +} + +// Special handling of request to read a Variant of Variant. +inline bool PopVariantValueFromReader(dbus::MessageReader* reader, Any* value) { + return PopValueFromReader(reader, value); +} + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DATA_SERIALIZATION_H_ diff --git a/brillo/dbus/data_serialization_unittest.cc b/brillo/dbus/data_serialization_unittest.cc new file mode 100644 index 0000000..879e52e --- /dev/null +++ b/brillo/dbus/data_serialization_unittest.cc @@ -0,0 +1,786 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include + +#include "unittests/test.pb.h" + +using dbus::FileDescriptor; +using dbus::Message; +using dbus::MessageReader; +using dbus::MessageWriter; +using dbus::ObjectPath; +using dbus::Response; + +namespace brillo { +namespace dbus_utils { + +TEST(DBusUtils, Supported_BasicTypes) { + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); + EXPECT_TRUE(IsTypeSupported::value); +} + +TEST(DBusUtils, Unsupported_BasicTypes) { + EXPECT_FALSE(IsTypeSupported::value); + EXPECT_FALSE(IsTypeSupported::value); +} + +TEST(DBusUtils, Supported_ComplexTypes) { + EXPECT_TRUE(IsTypeSupported>::value); + EXPECT_TRUE(IsTypeSupported>::value); + EXPECT_TRUE((IsTypeSupported>::value)); + EXPECT_TRUE( + (IsTypeSupported>>::value)); + EXPECT_TRUE((IsTypeSupported>::value)); + EXPECT_TRUE( + IsTypeSupported>::value); +} + +TEST(DBusUtils, Unsupported_ComplexTypes) { + EXPECT_FALSE(IsTypeSupported>::value); + EXPECT_FALSE((IsTypeSupported>::value)); + EXPECT_FALSE((IsTypeSupported>::value)); + EXPECT_FALSE((IsTypeSupported>::value)); + EXPECT_FALSE((IsTypeSupported>::value)); + EXPECT_FALSE((IsTypeSupported>::value)); +} + +TEST(DBusUtils, Supported_TypeSet) { + EXPECT_TRUE((IsTypeSupported::value)); + EXPECT_TRUE((IsTypeSupported, uint8_t>::value)); +} + +TEST(DBusUtils, Unupported_TypeSet) { + EXPECT_FALSE((IsTypeSupported::value)); + EXPECT_FALSE( + (IsTypeSupported, uint8_t>>::value)); + EXPECT_FALSE((IsTypeSupported::value)); + EXPECT_FALSE((IsTypeSupported, float>::value)); +} + +TEST(DBusUtils, Signatures_BasicTypes) { + EXPECT_EQ("b", GetDBusSignature()); + EXPECT_EQ("y", GetDBusSignature()); + EXPECT_EQ("n", GetDBusSignature()); + EXPECT_EQ("q", GetDBusSignature()); + EXPECT_EQ("i", GetDBusSignature()); + EXPECT_EQ("u", GetDBusSignature()); + EXPECT_EQ("x", GetDBusSignature()); + EXPECT_EQ("t", GetDBusSignature()); + EXPECT_EQ("d", GetDBusSignature()); + EXPECT_EQ("s", GetDBusSignature()); + EXPECT_EQ("o", GetDBusSignature()); + EXPECT_EQ("h", GetDBusSignature()); + EXPECT_EQ("v", GetDBusSignature()); +} + +TEST(DBusUtils, Signatures_Arrays) { + EXPECT_EQ("ab", GetDBusSignature>()); + EXPECT_EQ("ay", GetDBusSignature>()); + EXPECT_EQ("an", GetDBusSignature>()); + EXPECT_EQ("aq", GetDBusSignature>()); + EXPECT_EQ("ai", GetDBusSignature>()); + EXPECT_EQ("au", GetDBusSignature>()); + EXPECT_EQ("ax", GetDBusSignature>()); + EXPECT_EQ("at", GetDBusSignature>()); + EXPECT_EQ("ad", GetDBusSignature>()); + EXPECT_EQ("as", GetDBusSignature>()); + EXPECT_EQ("ao", GetDBusSignature>()); + EXPECT_EQ("ah", GetDBusSignature>()); + EXPECT_EQ("av", GetDBusSignature>()); + EXPECT_EQ("a(is)", + (GetDBusSignature>>())); + EXPECT_EQ("aad", GetDBusSignature>>()); +} + +TEST(DBusUtils, Signatures_Maps) { + EXPECT_EQ("a{sb}", (GetDBusSignature>())); + EXPECT_EQ("a{ss}", (GetDBusSignature>())); + EXPECT_EQ("a{sv}", (GetDBusSignature>())); + EXPECT_EQ("a{id}", (GetDBusSignature>())); + EXPECT_EQ( + "a{ia{ss}}", + (GetDBusSignature>>())); +} + +TEST(DBusUtils, Signatures_Pairs) { + EXPECT_EQ("(sb)", (GetDBusSignature>())); + EXPECT_EQ("(sv)", (GetDBusSignature>())); + EXPECT_EQ("(id)", (GetDBusSignature>())); +} + +TEST(DBusUtils, Signatures_Tuples) { + EXPECT_EQ("(i)", (GetDBusSignature>())); + EXPECT_EQ("(sv)", (GetDBusSignature>())); + EXPECT_EQ("(id(si))", + (GetDBusSignature< + std::tuple>>())); +} + +TEST(DBusUtils, Signatures_Protobufs) { + EXPECT_EQ("ay", (GetDBusSignature())); + EXPECT_EQ("ay", (GetDBusSignature())); +} + +// Test that a byte can be properly written and read. We only have this +// test for byte, as repeating this for other basic types is too redundant. +TEST(DBusUtils, AppendAndPopByte) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, uint8_t{123}); + EXPECT_EQ("y", message->GetSignature()); + + MessageReader reader(message.get()); + EXPECT_TRUE(reader.HasMoreData()); // Should have data to read. + EXPECT_EQ(Message::BYTE, reader.GetDataType()); + + bool bool_value = false; + // Should fail as the type is not bool here. + EXPECT_FALSE(PopValueFromReader(&reader, &bool_value)); + + uint8_t byte_value = 0; + EXPECT_TRUE(PopValueFromReader(&reader, &byte_value)); + EXPECT_EQ(123, byte_value); // Should match with the input. + EXPECT_FALSE(reader.HasMoreData()); // Should not have more data to read. + + // Try to get another byte. Should fail. + EXPECT_FALSE(PopValueFromReader(&reader, &byte_value)); +} + +// Check all basic types can be properly written and read. +TEST(DBusUtils, AppendAndPopBasicDataTypes) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + + // Append 0, true, 2, 3, 4, 5, 6, 7, 8.0, "string", "/object/path". + AppendValueToWriter(&writer, uint8_t{0}); + AppendValueToWriter(&writer, bool{true}); + AppendValueToWriter(&writer, int16_t{2}); + AppendValueToWriter(&writer, uint16_t{3}); + AppendValueToWriter(&writer, int32_t{4}); + AppendValueToWriter(&writer, uint32_t{5}); + AppendValueToWriter(&writer, int64_t{6}); + AppendValueToWriter(&writer, uint64_t{7}); + AppendValueToWriter(&writer, double{8.0}); + AppendValueToWriter(&writer, std::string{"string"}); + AppendValueToWriter(&writer, ObjectPath{"/object/path"}); + + EXPECT_EQ("ybnqiuxtdso", message->GetSignature()); + + uint8_t byte_value = 0; + bool bool_value = false; + int16_t int16_value = 0; + uint16_t uint16_value = 0; + int32_t int32_value = 0; + uint32_t uint32_value = 0; + int64_t int64_value = 0; + uint64_t uint64_value = 0; + double double_value = 0; + std::string string_value; + ObjectPath object_path_value; + + MessageReader reader(message.get()); + EXPECT_TRUE(reader.HasMoreData()); + EXPECT_TRUE(PopValueFromReader(&reader, &byte_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &int16_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &uint16_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &int32_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &uint32_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &int64_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &uint64_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &double_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &object_path_value)); + EXPECT_FALSE(reader.HasMoreData()); + + // 0, true, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path" should be returned. + EXPECT_EQ(0, byte_value); + EXPECT_TRUE(bool_value); + EXPECT_EQ(2, int16_value); + EXPECT_EQ(3U, uint16_value); + EXPECT_EQ(4, int32_value); + EXPECT_EQ(5U, uint32_value); + EXPECT_EQ(6, int64_value); + EXPECT_EQ(7U, uint64_value); + EXPECT_DOUBLE_EQ(8.0, double_value); + EXPECT_EQ("string", string_value); + EXPECT_EQ(ObjectPath{"/object/path"}, object_path_value); +} + +// Check all basic types can be properly written and read. +TEST(DBusUtils, AppendAndPopFileDescriptor) { + if (!dbus::IsDBusTypeUnixFdSupported()) { + LOG(WARNING) << "FD passing is not supported"; + return; + } + + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + + // Append stdout. + FileDescriptor temp(1); + // Descriptor should not be valid until checked. + EXPECT_FALSE(temp.is_valid()); + // NB: thread IO requirements not relevant for unit tests. + temp.CheckValidity(); + EXPECT_TRUE(temp.is_valid()); + AppendValueToWriter(&writer, temp); + + EXPECT_EQ("h", message->GetSignature()); + + FileDescriptor fd_value; + + MessageReader reader(message.get()); + EXPECT_TRUE(reader.HasMoreData()); + EXPECT_TRUE(PopValueFromReader(&reader, &fd_value)); + EXPECT_FALSE(reader.HasMoreData()); + // Descriptor is automatically checked for validity as part of + // PopValueFromReader() call. + EXPECT_TRUE(fd_value.is_valid()); +} + +// Check all variant types can be properly written and read. +TEST(DBusUtils, AppendAndPopVariantDataTypes) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + + // Append 10, false, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path". + AppendValueToWriterAsVariant(&writer, uint8_t{10}); + AppendValueToWriterAsVariant(&writer, bool{false}); + AppendValueToWriterAsVariant(&writer, int16_t{12}); + AppendValueToWriterAsVariant(&writer, uint16_t{13}); + AppendValueToWriterAsVariant(&writer, int32_t{14}); + AppendValueToWriterAsVariant(&writer, uint32_t{15}); + AppendValueToWriterAsVariant(&writer, int64_t{16}); + AppendValueToWriterAsVariant(&writer, uint64_t{17}); + AppendValueToWriterAsVariant(&writer, double{18.5}); + AppendValueToWriterAsVariant(&writer, std::string{"data"}); + AppendValueToWriterAsVariant(&writer, ObjectPath{"/obj/path"}); + AppendValueToWriterAsVariant(&writer, Any{17}); + AppendValueToWriterAsVariant(&writer, + Any{std::vector>{{6, 7}}}); + + EXPECT_EQ("vvvvvvvvvvvvv", message->GetSignature()); + + uint8_t byte_value = 0; + bool bool_value = true; + int16_t int16_value = 0; + uint16_t uint16_value = 0; + int32_t int32_value = 0; + uint32_t uint32_value = 0; + int64_t int64_value = 0; + uint64_t uint64_value = 0; + double double_value = 0; + std::string string_value; + ObjectPath object_path_value; + Any any_value; + Any any_vector_vector; + + MessageReader reader(message.get()); + EXPECT_TRUE(reader.HasMoreData()); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &byte_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &bool_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &int16_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint16_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &int32_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint32_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &int64_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint64_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &double_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &string_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &object_path_value)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &any_value)); + // Not implemented. + EXPECT_FALSE(PopVariantValueFromReader(&reader, &any_vector_vector)); + EXPECT_FALSE(reader.HasMoreData()); + + EXPECT_EQ(10, byte_value); + EXPECT_FALSE(bool_value); + EXPECT_EQ(12, int16_value); + EXPECT_EQ(13U, uint16_value); + EXPECT_EQ(14, int32_value); + EXPECT_EQ(15U, uint32_value); + EXPECT_EQ(16, int64_value); + EXPECT_EQ(17U, uint64_value); + EXPECT_DOUBLE_EQ(18.5, double_value); + EXPECT_EQ("data", string_value); + EXPECT_EQ(ObjectPath{"/obj/path"}, object_path_value); + EXPECT_EQ(17, any_value.Get()); + EXPECT_TRUE(any_vector_vector.IsEmpty()); +} + +TEST(DBusUtils, AppendAndPopBasicAny) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + + // Append 10, true, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path". + AppendValueToWriter(&writer, Any(uint8_t{10})); + AppendValueToWriter(&writer, Any(bool{true})); + AppendValueToWriter(&writer, Any(int16_t{12})); + AppendValueToWriter(&writer, Any(uint16_t{13})); + AppendValueToWriter(&writer, Any(int32_t{14})); + AppendValueToWriter(&writer, Any(uint32_t{15})); + AppendValueToWriter(&writer, Any(int64_t{16})); + AppendValueToWriter(&writer, Any(uint64_t{17})); + AppendValueToWriter(&writer, Any(double{18.5})); + AppendValueToWriter(&writer, Any(std::string{"data"})); + AppendValueToWriter(&writer, Any(ObjectPath{"/obj/path"})); + EXPECT_EQ("vvvvvvvvvvv", message->GetSignature()); + + Any byte_value; + Any bool_value; + Any int16_value; + Any uint16_value; + Any int32_value; + Any uint32_value; + Any int64_value; + Any uint64_value; + Any double_value; + Any string_value; + Any object_path_value; + + MessageReader reader(message.get()); + EXPECT_TRUE(reader.HasMoreData()); + EXPECT_TRUE(PopValueFromReader(&reader, &byte_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &int16_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &uint16_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &int32_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &uint32_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &int64_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &uint64_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &double_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &object_path_value)); + EXPECT_FALSE(reader.HasMoreData()); + + // Must be: 10, true, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path". + EXPECT_EQ(10, byte_value.Get()); + EXPECT_TRUE(bool_value.Get()); + EXPECT_EQ(12, int16_value.Get()); + EXPECT_EQ(13U, uint16_value.Get()); + EXPECT_EQ(14, int32_value.Get()); + EXPECT_EQ(15U, uint32_value.Get()); + EXPECT_EQ(16, int64_value.Get()); + EXPECT_EQ(17U, uint64_value.Get()); + EXPECT_DOUBLE_EQ(18.5, double_value.Get()); + EXPECT_EQ("data", string_value.Get()); + EXPECT_EQ(ObjectPath{"/obj/path"}, object_path_value.Get()); +} + +TEST(DBusUtils, ArrayOfBytes) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector bytes{1, 2, 3}; + AppendValueToWriter(&writer, bytes); + + EXPECT_EQ("ay", message->GetSignature()); + + MessageReader reader(message.get()); + std::vector bytes_out; + EXPECT_TRUE(PopValueFromReader(&reader, &bytes_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(bytes, bytes_out); +} + +TEST(DBusUtils, ArrayOfBytes_Empty) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector bytes; + AppendValueToWriter(&writer, bytes); + + EXPECT_EQ("ay", message->GetSignature()); + + MessageReader reader(message.get()); + std::vector bytes_out; + EXPECT_TRUE(PopValueFromReader(&reader, &bytes_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(bytes, bytes_out); +} + +TEST(DBusUtils, ArrayOfStrings) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector strings{"foo", "bar", "baz"}; + AppendValueToWriter(&writer, strings); + + EXPECT_EQ("as", message->GetSignature()); + + MessageReader reader(message.get()); + std::vector strings_out; + EXPECT_TRUE(PopValueFromReader(&reader, &strings_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(strings, strings_out); +} + +TEST(DBusUtils, ArrayOfInt64) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector values{-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, + std::numeric_limits::min(), + std::numeric_limits::max()}; + AppendValueToWriter(&writer, values); + + EXPECT_EQ("ax", message->GetSignature()); + + MessageReader reader(message.get()); + std::vector values_out; + EXPECT_TRUE(PopValueFromReader(&reader, &values_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(values, values_out); +} + +TEST(DBusUtils, ArrayOfObjectPaths) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector object_paths{ + ObjectPath("/object/path/1"), + ObjectPath("/object/path/2"), + ObjectPath("/object/path/3"), + }; + AppendValueToWriter(&writer, object_paths); + + EXPECT_EQ("ao", message->GetSignature()); + + MessageReader reader(message.get()); + std::vector object_paths_out; + EXPECT_TRUE(PopValueFromReader(&reader, &object_paths_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(object_paths, object_paths_out); +} + +TEST(DBusUtils, ArraysAsVariant) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector int_array{1, 2, 3}; + std::vector str_array{"foo", "bar", "baz"}; + std::vector dbl_array_empty{}; + std::map dict_ss{{"k1", "v1"}, {"k2", "v2"}}; + VariantDictionary dict_sv{{"k1", 1}, {"k2", "v2"}}; + AppendValueToWriterAsVariant(&writer, int_array); + AppendValueToWriterAsVariant(&writer, str_array); + AppendValueToWriterAsVariant(&writer, dbl_array_empty); + AppendValueToWriterAsVariant(&writer, dict_ss); + AppendValueToWriterAsVariant(&writer, dict_sv); + + EXPECT_EQ("vvvvv", message->GetSignature()); + + Any int_array_out; + Any str_array_out; + Any dbl_array_out; + Any dict_ss_out; + Any dict_sv_out; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_array_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &str_array_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &dbl_array_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &dict_ss_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &dict_sv_out)); + EXPECT_FALSE(reader.HasMoreData()); + + EXPECT_EQ(int_array, int_array_out.Get>()); + EXPECT_EQ(str_array, str_array_out.Get>()); + EXPECT_EQ(dbl_array_empty, dbl_array_out.Get>()); + EXPECT_EQ(dict_ss, (dict_ss_out.Get>())); + EXPECT_EQ(dict_sv["k1"].Get(), + dict_sv_out.Get().at("k1").Get()); + EXPECT_EQ(dict_sv["k2"].Get(), + dict_sv_out.Get().at("k2").Get()); +} + +TEST(DBusUtils, VariantDictionary) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + VariantDictionary values{ + {"key1", uint8_t{10}}, + {"key2", bool{true}}, + {"key3", int16_t{12}}, + {"key4", uint16_t{13}}, + {"key5", int32_t{14}}, + {"key6", uint32_t{15}}, + {"key7", int64_t{16}}, + {"key8", uint64_t{17}}, + {"key9", double{18.5}}, + {"keyA", std::string{"data"}}, + {"keyB", ObjectPath{"/obj/path"}}, + }; + AppendValueToWriter(&writer, values); + + EXPECT_EQ("a{sv}", message->GetSignature()); + + MessageReader reader(message.get()); + VariantDictionary values_out; + EXPECT_TRUE(PopValueFromReader(&reader, &values_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(values.size(), values_out.size()); + EXPECT_EQ(values["key1"].Get(), values_out["key1"].Get()); + EXPECT_EQ(values["key2"].Get(), values_out["key2"].Get()); + EXPECT_EQ(values["key3"].Get(), values_out["key3"].Get()); + EXPECT_EQ(values["key4"].Get(), values_out["key4"].Get()); + EXPECT_EQ(values["key5"].Get(), values_out["key5"].Get()); + EXPECT_EQ(values["key6"].Get(), values_out["key6"].Get()); + EXPECT_EQ(values["key7"].Get(), values_out["key7"].Get()); + EXPECT_EQ(values["key8"].Get(), values_out["key8"].Get()); + EXPECT_EQ(values["key9"].Get(), values_out["key9"].Get()); + EXPECT_EQ(values["keyA"].Get(), + values_out["keyA"].Get()); + EXPECT_EQ(values["keyB"].Get(), + values_out["keyB"].Get()); +} + +TEST(DBusUtils, StringToStringMap) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::map values{ + {"key1", "value1"}, + {"key2", "value2"}, + {"key3", "value3"}, + {"key4", "value4"}, + {"key5", "value5"}, + }; + AppendValueToWriter(&writer, values); + + EXPECT_EQ("a{ss}", message->GetSignature()); + + MessageReader reader(message.get()); + std::map values_out; + EXPECT_TRUE(PopValueFromReader(&reader, &values_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(values, values_out); +} + +TEST(DBusUtils, Pair) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::pair struct1{"value2", 3}; + AppendValueToWriter(&writer, struct1); + std::pair> struct2{1, {2, 3}}; + AppendValueToWriter(&writer, struct2); + + EXPECT_EQ("(si)(i(ii))", message->GetSignature()); + + std::pair struct1_out; + std::pair> struct2_out; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &struct1_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &struct2_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(struct1, struct1_out); + EXPECT_EQ(struct2, struct2_out); +} + +TEST(DBusUtils, Tuple) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::tuple struct1{"value2", 3}; + AppendValueToWriter(&writer, struct1); + std::tuple>> struct2{ + 1, "a", {{2, 3}} + }; + AppendValueToWriter(&writer, struct2); + + EXPECT_EQ("(si)(isa(ii))", message->GetSignature()); + + std::tuple struct1_out; + std::tuple>> struct2_out; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &struct1_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &struct2_out)); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(struct1, struct1_out); + EXPECT_EQ(struct2, struct2_out); +} + +TEST(DBusUtils, ReinterpretVariant) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector str_array{"foo", "bar", "baz"}; + std::map dict_ss{{"k1", "v1"}, {"k2", "v2"}}; + VariantDictionary dict_sv{{"k1", "v1"}, {"k2", "v2"}}; + AppendValueToWriterAsVariant(&writer, 123); + AppendValueToWriterAsVariant(&writer, str_array); + AppendValueToWriterAsVariant(&writer, 1.7); + AppendValueToWriterAsVariant(&writer, dict_ss); + AppendValueToWriter(&writer, dict_sv); + + EXPECT_EQ("vvvva{sv}", message->GetSignature()); + + int int_out = 0; + std::vector str_array_out; + double dbl_out = 0.0; + std::map dict_ss_out; + std::map dict_ss_out2; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &str_array_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &dbl_out)); + EXPECT_TRUE(PopValueFromReader(&reader, &dict_ss_out)); + EXPECT_TRUE(PopValueFromReader(&reader, + &dict_ss_out2)); // Read "a{sv}" as "a{ss}". + EXPECT_FALSE(reader.HasMoreData()); + + EXPECT_EQ(123, int_out); + EXPECT_EQ(str_array, str_array_out); + EXPECT_DOUBLE_EQ(1.7, dbl_out); + EXPECT_EQ(dict_ss, dict_ss_out); + EXPECT_EQ(dict_ss, dict_ss_out2); +} + +// Test handling of custom data types. +struct Person { + std::string first_name; + std::string last_name; + int age; + // Provide == operator so we can easily compare arrays of Person. + bool operator==(const Person& rhs) const { + return first_name == rhs.first_name && last_name == rhs.last_name && + age == rhs.age; + } +}; + +// Overload AppendValueToWriter() for "Person" structure. +void AppendValueToWriter(dbus::MessageWriter* writer, const Person& value) { + dbus::MessageWriter struct_writer(nullptr); + writer->OpenStruct(&struct_writer); + AppendValueToWriter(&struct_writer, value.first_name); + AppendValueToWriter(&struct_writer, value.last_name); + AppendValueToWriter(&struct_writer, value.age); + writer->CloseContainer(&struct_writer); +} + +// Overload PopValueFromReader() for "Person" structure. +bool PopValueFromReader(dbus::MessageReader* reader, Person* value) { + dbus::MessageReader variant_reader(nullptr); + dbus::MessageReader struct_reader(nullptr); + if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || + !reader->PopStruct(&struct_reader)) + return false; + return PopValueFromReader(&struct_reader, &value->first_name) && + PopValueFromReader(&struct_reader, &value->last_name) && + PopValueFromReader(&struct_reader, &value->age); +} + +// Specialize DBusType for "Person" structure. +template<> +struct DBusType { + inline static std::string GetSignature() { + return GetStructDBusSignature(); + } + inline static void Write(dbus::MessageWriter* writer, const Person& value) { + AppendValueToWriter(writer, value); + } + inline static bool Read(dbus::MessageReader* reader, Person* value) { + return PopValueFromReader(reader, value); + } +}; + +TEST(DBusUtils, CustomStruct) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector people{{"John", "Doe", 32}, {"Jane", "Smith", 48}}; + AppendValueToWriter(&writer, people); + AppendValueToWriterAsVariant(&writer, people); + AppendValueToWriterAsVariant(&writer, people); + + EXPECT_EQ("a(ssi)vv", message->GetSignature()); + + std::vector people_out1; + std::vector people_out2; + std::vector people_out3; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &people_out1)); + EXPECT_TRUE(PopValueFromReader(&reader, &people_out2)); + EXPECT_TRUE(PopVariantValueFromReader(&reader, &people_out3)); + EXPECT_FALSE(reader.HasMoreData()); + + EXPECT_EQ(people, people_out1); + EXPECT_EQ(people, people_out2); + EXPECT_EQ(people, people_out3); +} + +TEST(DBusUtils, CustomStructInComplexTypes) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + std::vector people{{"John", "Doe", 32}, {"Jane", "Smith", 48}}; + std::vector> data{ + { + {1, Person{"John", "Doe", 32}}, + {2, Person{"Jane", "Smith", 48}}, + } + }; + AppendValueToWriter(&writer, data); + + EXPECT_EQ("aa{i(ssi)}", message->GetSignature()); + + std::vector> data_out; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &data_out)); + EXPECT_FALSE(reader.HasMoreData()); + + EXPECT_EQ(data, data_out); +} + +TEST(DBusUtils, EmptyVariant) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + EXPECT_DEATH(AppendValueToWriter(&writer, Any{}), + "Must not be called on an empty Any"); +} + +TEST(DBusUtils, IncompatibleVariant) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + EXPECT_DEATH(AppendValueToWriter(&writer, Any{2.2f}), + "Type 'float' is not supported by D-Bus"); +} + +TEST(DBusUtils, Protobuf) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + + dbus_utils_test::TestMessage test_message; + test_message.set_foo(123); + test_message.set_bar("abcd"); + + AppendValueToWriter(&writer, test_message); + + EXPECT_EQ("ay", message->GetSignature()); + + dbus_utils_test::TestMessage test_message_out; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &test_message_out)); + EXPECT_FALSE(reader.HasMoreData()); + + EXPECT_EQ(123, test_message_out.foo()); + EXPECT_EQ("abcd", test_message_out.bar()); +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_method_invoker.cc b/brillo/dbus/dbus_method_invoker.cc new file mode 100644 index 0000000..94002c2 --- /dev/null +++ b/brillo/dbus/dbus_method_invoker.cc @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +namespace brillo { +namespace dbus_utils { + +void TranslateErrorResponse(const AsyncErrorCallback& callback, + dbus::ErrorResponse* resp) { + if (!callback.is_null()) { + ErrorPtr error; + dbus::MessageReader reader(resp); + std::string error_message; + if (ExtractMessageParameters(&reader, &error, &error_message)) + AddDBusError(&error, resp->GetErrorName(), error_message); + callback.Run(error.get()); + } +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_method_invoker.h b/brillo/dbus/dbus_method_invoker.h new file mode 100644 index 0000000..df8c3c5 --- /dev/null +++ b/brillo/dbus/dbus_method_invoker.h @@ -0,0 +1,324 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a way to call D-Bus methods on objects in remote processes +// as if they were native C++ function calls. + +// CallMethodAndBlock (along with CallMethodAndBlockWithTimeout) lets you call +// a D-Bus method synchronously and pass all the required parameters as C++ +// function arguments. CallMethodAndBlock relies on automatic C++ to D-Bus data +// serialization implemented in brillo/dbus/data_serialization.h. +// CallMethodAndBlock invokes the D-Bus method and returns the Response. + +// The method call response should be parsed with ExtractMethodCallResults(). +// The method takes an optional list of pointers to the expected return values +// of the D-Bus method. + +// CallMethod and CallMethodWithTimeout are similar to CallMethodAndBlock but +// make the calls asynchronously. They take two callbacks: one for successful +// method invocation and the second is for error conditions. + +// Here is an example of synchronous calls: +// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus: + +// using brillo::dbus_utils::CallMethodAndBlock; +// using brillo::dbus_utils::ExtractMethodCallResults; +// +// brillo::ErrorPtr error; +// auto resp = CallMethodAndBlock(obj, +// "org.chromium.MyService.MyInterface", +// "MyMethod", +// &error, +// 2, 8.7); +// std::string return_value; +// if (resp && ExtractMethodCallResults(resp.get(), &error, &return_value)) { +// // Use the |return_value|. +// } else { +// // An error occurred. Use |error| to get details. +// } + +// And here is how to call D-Bus methods asynchronously: +// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus: + +// using brillo::dbus_utils::CallMethod; +// using brillo::dbus_utils::ExtractMethodCallResults; +// +// void OnSuccess(const std::string& return_value) { +// // Use the |return_value|. +// } +// +// void OnError(brillo::Error* error) { +// // An error occurred. Use |error| to get details. +// } +// +// brillo::dbus_utils::CallMethod(obj, +// "org.chromium.MyService.MyInterface", +// "MyMethod", +// base::Bind(OnSuccess), +// base::Bind(OnError), +// 2, 8.7); + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +// A helper method to dispatch a blocking D-Bus method call. Can specify +// zero or more method call arguments in |args| which will be sent over D-Bus. +// This method sends a D-Bus message and blocks for a time period specified +// in |timeout_ms| while waiting for a reply. The time out is in milliseconds or +// -1 (DBUS_TIMEOUT_USE_DEFAULT) for default, or DBUS_TIMEOUT_INFINITE for no +// timeout. If a timeout occurs, the response object contains an error object +// with DBUS_ERROR_NO_REPLY error code (those constants come from libdbus +// [dbus/dbus.h]). +// Returns a dbus::Response object on success. On failure, returns nullptr and +// fills in additional error details into the |error| object. +template +inline std::unique_ptr CallMethodAndBlockWithTimeout( + int timeout_ms, + dbus::ObjectProxy* object, + const std::string& interface_name, + const std::string& method_name, + ErrorPtr* error, + const Args&... args) { + dbus::MethodCall method_call(interface_name, method_name); + // Add method arguments to the message buffer. + dbus::MessageWriter writer(&method_call); + DBusParamWriter::Append(&writer, args...); + dbus::ScopedDBusError dbus_error; + auto response = object->CallMethodAndBlockWithErrorDetails( + &method_call, timeout_ms, &dbus_error); + if (!response) { + if (dbus_error.is_set()) { + Error::AddTo(error, + FROM_HERE, + errors::dbus::kDomain, + dbus_error.name(), + dbus_error.message()); + } else { + Error::AddToPrintf(error, + FROM_HERE, + errors::dbus::kDomain, + DBUS_ERROR_FAILED, + "Failed to call D-Bus method: %s.%s", + interface_name.c_str(), + method_name.c_str()); + } + } + return std::unique_ptr(response.release()); +} + +// Same as CallMethodAndBlockWithTimeout() but uses a default timeout value. +template +inline std::unique_ptr CallMethodAndBlock( + dbus::ObjectProxy* object, + const std::string& interface_name, + const std::string& method_name, + ErrorPtr* error, + const Args&... args) { + return CallMethodAndBlockWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + object, + interface_name, + method_name, + error, + args...); +} + +namespace internal { +// In order to support non-copyable dbus::FileDescriptor, we have this +// internal::HackMove() helper function that does really nothing for normal +// types but uses Pass() for file descriptors so we can move them out from +// the temporaries created inside DBusParamReader<...>::Invoke(). +// If only libchrome supported real rvalues so we can just do std::move() and +// be done with it. +template +inline const T& HackMove(const T& val) { + return val; +} + +// Even though |val| here is passed as const&, the actual value is created +// inside DBusParamReader<...>::Invoke() and is temporary in nature, so it is +// safe to move the file descriptor out of |val|. That's why we are doing +// const_cast here. It is a bit hacky, but there is no negative side effects. +inline dbus::FileDescriptor HackMove(const dbus::FileDescriptor& val) { + return const_cast(val).Pass(); +} +} // namespace internal + +// Extracts the parameters of |ResultTypes...| types from the message reader +// and puts the values in the resulting |tuple|. Returns false on error and +// provides additional error details in |error| object. +template +inline bool ExtractMessageParametersAsTuple( + dbus::MessageReader* reader, + ErrorPtr* error, + std::tuple* val_tuple) { + auto callback = [val_tuple](const ResultTypes&... params) { + *val_tuple = std::forward_as_tuple(internal::HackMove(params)...); + }; + return DBusParamReader::Invoke( + callback, reader, error); +} +// Overload of ExtractMessageParametersAsTuple to handle reference types in +// tuples created with std::tie(). +template +inline bool ExtractMessageParametersAsTuple( + dbus::MessageReader* reader, + ErrorPtr* error, + std::tuple* ref_tuple) { + auto callback = [ref_tuple](const ResultTypes&... params) { + *ref_tuple = std::forward_as_tuple(internal::HackMove(params)...); + }; + return DBusParamReader::Invoke( + callback, reader, error); +} + +// A helper method to extract a list of values from a message buffer. +// The function will return false and provide detailed error information on +// failure. It fails if the D-Bus message buffer (represented by the |reader|) +// contains too many, too few parameters or the parameters are of wrong types +// (signatures). +// The usage pattern is as follows: +// +// int32_t data1; +// std::string data2; +// ErrorPtr error; +// if (ExtractMessageParameters(reader, &error, &data1, &data2)) { ... } +// +// The above example extracts an Int32 and a String from D-Bus message buffer. +template +inline bool ExtractMessageParameters(dbus::MessageReader* reader, + ErrorPtr* error, + ResultTypes*... results) { + auto ref_tuple = std::tie(*results...); + return ExtractMessageParametersAsTuple( + reader, error, &ref_tuple); +} + +// Convenient helper method to extract return value(s) of a D-Bus method call. +// |results| must be zero or more pointers to data expected to be returned +// from the method called. If an error occurs, returns false and provides +// additional details in |error| object. +// +// It is OK to call this method even if the D-Bus method doesn't expect +// any return values. Just do not specify any output |results|. In this case, +// ExtractMethodCallResults() will verify that the method didn't return any +// data in the |message|. +template +inline bool ExtractMethodCallResults(dbus::Message* message, + ErrorPtr* error, + ResultTypes*... results) { + CHECK(message) << "Unable to extract parameters from a NULL message."; + + dbus::MessageReader reader(message); + if (message->GetMessageType() == dbus::Message::MESSAGE_ERROR) { + std::string error_message; + if (ExtractMessageParameters(&reader, error, &error_message)) + AddDBusError(error, message->GetErrorName(), error_message); + return false; + } + return ExtractMessageParameters(&reader, error, results...); +} + +////////////////////////////////////////////////////////////////////////////// +// Asynchronous method invocation support + +using AsyncErrorCallback = base::Callback; + +// A helper function that translates dbus::ErrorResponse response +// from D-Bus into brillo::Error* and invokes the |callback|. +void BRILLO_EXPORT TranslateErrorResponse(const AsyncErrorCallback& callback, + dbus::ErrorResponse* resp); + +// A helper function that translates dbus::Response from D-Bus into +// a list of C++ values passed as parameters to |success_callback|. If the +// response message doesn't have the correct number of parameters, or they +// are of wrong types, an error is sent to |error_callback|. +template +void TranslateSuccessResponse( + const base::Callback& success_callback, + const AsyncErrorCallback& error_callback, + dbus::Response* resp) { + auto callback = [&success_callback](const OutArgs&... params) { + if (!success_callback.is_null()) { + success_callback.Run(params...); + } + }; + ErrorPtr error; + dbus::MessageReader reader(resp); + if (!DBusParamReader::Invoke(callback, &reader, &error) && + !error_callback.is_null()) { + error_callback.Run(error.get()); + } +} + +// A helper method to dispatch a non-blocking D-Bus method call. Can specify +// zero or more method call arguments in |params| which will be sent over D-Bus. +// This method sends a D-Bus message and returns immediately. +// When the remote method returns successfully, the success callback is +// invoked with the return value(s), if any. +// On error, the error callback is called. Note, the error callback can be +// called synchronously (before CallMethodWithTimeout returns) if there was +// a problem invoking a method (e.g. object or method doesn't exist). +// If the response is not received within |timeout_ms|, an error callback is +// called with DBUS_ERROR_NO_REPLY error code. +template +inline void CallMethodWithTimeout( + int timeout_ms, + dbus::ObjectProxy* object, + const std::string& interface_name, + const std::string& method_name, + const base::Callback& success_callback, + const AsyncErrorCallback& error_callback, + const InArgs&... params) { + dbus::MethodCall method_call(interface_name, method_name); + dbus::MessageWriter writer(&method_call); + DBusParamWriter::Append(&writer, params...); + + dbus::ObjectProxy::ErrorCallback dbus_error_callback = + base::Bind(&TranslateErrorResponse, error_callback); + dbus::ObjectProxy::ResponseCallback dbus_success_callback = base::Bind( + &TranslateSuccessResponse, success_callback, error_callback); + + object->CallMethodWithErrorCallback( + &method_call, timeout_ms, dbus_success_callback, dbus_error_callback); +} + +// Same as CallMethodWithTimeout() but uses a default timeout value. +template +inline void CallMethod(dbus::ObjectProxy* object, + const std::string& interface_name, + const std::string& method_name, + const base::Callback& success_callback, + const AsyncErrorCallback& error_callback, + const InArgs&... params) { + return CallMethodWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + object, + interface_name, + method_name, + success_callback, + error_callback, + params...); +} + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ diff --git a/brillo/dbus/dbus_method_invoker_unittest.cc b/brillo/dbus/dbus_method_invoker_unittest.cc new file mode 100644 index 0000000..44383aa --- /dev/null +++ b/brillo/dbus/dbus_method_invoker_unittest.cc @@ -0,0 +1,338 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "unittests/test.pb.h" + +using testing::AnyNumber; +using testing::InSequence; +using testing::Invoke; +using testing::Return; +using testing::_; + +using dbus::MessageReader; +using dbus::MessageWriter; +using dbus::Response; + +namespace brillo { +namespace dbus_utils { + +const char kTestPath[] = "/test/path"; +const char kTestServiceName[] = "org.test.Object"; +const char kTestInterface[] = "org.test.Object.TestInterface"; +const char kTestMethod1[] = "TestMethod1"; +const char kTestMethod2[] = "TestMethod2"; +const char kTestMethod3[] = "TestMethod3"; +const char kTestMethod4[] = "TestMethod4"; + +class DBusMethodInvokerTest : public testing::Test { + public: + void SetUp() override { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock exported object. + mock_object_proxy_ = new dbus::MockObjectProxy( + bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); + EXPECT_CALL(*bus_, + GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) + .WillRepeatedly(Return(mock_object_proxy_.get())); + int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT; + EXPECT_CALL(*mock_object_proxy_, + MockCallMethodAndBlockWithErrorDetails(_, def_timeout_ms, _)) + .WillRepeatedly(Invoke(this, &DBusMethodInvokerTest::CreateResponse)); + } + + void TearDown() override { bus_ = nullptr; } + + Response* CreateResponse(dbus::MethodCall* method_call, + int timeout_ms, + dbus::ScopedDBusError* dbus_error) { + if (method_call->GetInterface() == kTestInterface) { + if (method_call->GetMember() == kTestMethod1) { + MessageReader reader(method_call); + int v1, v2; + // Input: two ints. + // Output: sum of the ints converted to string. + if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) { + auto response = Response::CreateEmpty(); + MessageWriter writer(response.get()); + writer.AppendString(std::to_string(v1 + v2)); + return response.release(); + } + } else if (method_call->GetMember() == kTestMethod2) { + method_call->SetSerial(123); + dbus_set_error(dbus_error->get(), "org.MyError", "My error message"); + return nullptr; + } else if (method_call->GetMember() == kTestMethod3) { + MessageReader reader(method_call); + dbus_utils_test::TestMessage msg; + if (PopValueFromReader(&reader, &msg)) { + auto response = Response::CreateEmpty(); + MessageWriter writer(response.get()); + AppendValueToWriter(&writer, msg); + return response.release(); + } + } else if (method_call->GetMember() == kTestMethod4) { + method_call->SetSerial(123); + MessageReader reader(method_call); + dbus::FileDescriptor fd; + if (reader.PopFileDescriptor(&fd)) { + auto response = Response::CreateEmpty(); + MessageWriter writer(response.get()); + fd.CheckValidity(); + writer.AppendFileDescriptor(fd); + return response.release(); + } + } + } + + LOG(ERROR) << "Unexpected method call: " << method_call->ToString(); + return nullptr; + } + + std::string CallTestMethod(int v1, int v2) { + std::unique_ptr response = + brillo::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), + kTestInterface, kTestMethod1, + nullptr, v1, v2); + EXPECT_NE(nullptr, response.get()); + std::string result; + using brillo::dbus_utils::ExtractMethodCallResults; + EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result)); + return result; + } + + dbus_utils_test::TestMessage CallProtobufTestMethod( + const dbus_utils_test::TestMessage& message) { + std::unique_ptr response = + brillo::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), + kTestInterface, kTestMethod3, + nullptr, message); + EXPECT_NE(nullptr, response.get()); + dbus_utils_test::TestMessage result; + using brillo::dbus_utils::ExtractMethodCallResults; + EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result)); + return result; + } + + // Sends a file descriptor received over D-Bus back to the caller. + dbus::FileDescriptor EchoFD(const dbus::FileDescriptor& fd_in) { + std::unique_ptr response = + brillo::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), + kTestInterface, kTestMethod4, + nullptr, fd_in); + EXPECT_NE(nullptr, response.get()); + dbus::FileDescriptor fd_out; + using brillo::dbus_utils::ExtractMethodCallResults; + EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &fd_out)); + return fd_out.Pass(); + } + + scoped_refptr bus_; + scoped_refptr mock_object_proxy_; +}; + +TEST_F(DBusMethodInvokerTest, TestSuccess) { + EXPECT_EQ("4", CallTestMethod(2, 2)); + EXPECT_EQ("10", CallTestMethod(3, 7)); + EXPECT_EQ("-4", CallTestMethod(13, -17)); +} + +TEST_F(DBusMethodInvokerTest, TestFailure) { + brillo::ErrorPtr error; + std::unique_ptr response = + brillo::dbus_utils::CallMethodAndBlock( + mock_object_proxy_.get(), kTestInterface, kTestMethod2, &error); + EXPECT_EQ(nullptr, response.get()); + EXPECT_EQ(brillo::errors::dbus::kDomain, error->GetDomain()); + EXPECT_EQ("org.MyError", error->GetCode()); + EXPECT_EQ("My error message", error->GetMessage()); +} + +TEST_F(DBusMethodInvokerTest, TestProtobuf) { + dbus_utils_test::TestMessage test_message; + test_message.set_foo(123); + test_message.set_bar("bar"); + + dbus_utils_test::TestMessage resp = CallProtobufTestMethod(test_message); + + EXPECT_EQ(123, resp.foo()); + EXPECT_EQ("bar", resp.bar()); +} + +TEST_F(DBusMethodInvokerTest, TestFileDescriptors) { + // Passing a file descriptor over D-Bus would effectively duplicate the fd. + // So the resulting file descriptor value would be different but it still + // should be valid. + dbus::FileDescriptor fd_stdin(0); + fd_stdin.CheckValidity(); + EXPECT_NE(fd_stdin.value(), EchoFD(fd_stdin).value()); + dbus::FileDescriptor fd_stdout(1); + fd_stdout.CheckValidity(); + EXPECT_NE(fd_stdout.value(), EchoFD(fd_stdout).value()); + dbus::FileDescriptor fd_stderr(2); + fd_stderr.CheckValidity(); + EXPECT_NE(fd_stderr.value(), EchoFD(fd_stderr).value()); +} + +////////////////////////////////////////////////////////////////////////////// +// Asynchronous method invocation support + +class AsyncDBusMethodInvokerTest : public testing::Test { + public: + void SetUp() override { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock exported object. + mock_object_proxy_ = new dbus::MockObjectProxy( + bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); + EXPECT_CALL(*bus_, + GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) + .WillRepeatedly(Return(mock_object_proxy_.get())); + int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT; + EXPECT_CALL(*mock_object_proxy_, + CallMethodWithErrorCallback(_, def_timeout_ms, _, _)) + .WillRepeatedly(Invoke(this, &AsyncDBusMethodInvokerTest::HandleCall)); + } + + void TearDown() override { bus_ = nullptr; } + + void HandleCall(dbus::MethodCall* method_call, + int timeout_ms, + dbus::ObjectProxy::ResponseCallback success_callback, + dbus::ObjectProxy::ErrorCallback error_callback) { + if (method_call->GetInterface() == kTestInterface) { + if (method_call->GetMember() == kTestMethod1) { + MessageReader reader(method_call); + int v1, v2; + // Input: two ints. + // Output: sum of the ints converted to string. + if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) { + auto response = Response::CreateEmpty(); + MessageWriter writer(response.get()); + writer.AppendString(std::to_string(v1 + v2)); + success_callback.Run(response.get()); + } + return; + } else if (method_call->GetMember() == kTestMethod2) { + method_call->SetSerial(123); + auto error_response = dbus::ErrorResponse::FromMethodCall( + method_call, "org.MyError", "My error message"); + error_callback.Run(error_response.get()); + return; + } + } + + LOG(FATAL) << "Unexpected method call: " << method_call->ToString(); + } + + struct SuccessCallback { + SuccessCallback(const std::string& in_result, int* in_counter) + : result(in_result), counter(in_counter) {} + + explicit SuccessCallback(int* in_counter) : counter(in_counter) {} + + void operator()(const std::string& actual_result) { + (*counter)++; + EXPECT_EQ(result, actual_result); + } + std::string result; + int* counter; + }; + + struct ErrorCallback { + ErrorCallback(const std::string& in_domain, + const std::string& in_code, + const std::string& in_message, + int* in_counter) + : domain(in_domain), + code(in_code), + message(in_message), + counter(in_counter) {} + + explicit ErrorCallback(int* in_counter) : counter(in_counter) {} + + void operator()(brillo::Error* error) { + (*counter)++; + EXPECT_NE(nullptr, error); + EXPECT_EQ(domain, error->GetDomain()); + EXPECT_EQ(code, error->GetCode()); + EXPECT_EQ(message, error->GetMessage()); + } + + std::string domain; + std::string code; + std::string message; + int* counter; + }; + + scoped_refptr bus_; + scoped_refptr mock_object_proxy_; +}; + +TEST_F(AsyncDBusMethodInvokerTest, TestSuccess) { + int error_count = 0; + int success_count = 0; + brillo::dbus_utils::CallMethod( + mock_object_proxy_.get(), + kTestInterface, + kTestMethod1, + base::Bind(SuccessCallback{"4", &success_count}), + base::Bind(ErrorCallback{&error_count}), + 2, 2); + brillo::dbus_utils::CallMethod( + mock_object_proxy_.get(), + kTestInterface, + kTestMethod1, + base::Bind(SuccessCallback{"10", &success_count}), + base::Bind(ErrorCallback{&error_count}), + 3, 7); + brillo::dbus_utils::CallMethod( + mock_object_proxy_.get(), + kTestInterface, + kTestMethod1, + base::Bind(SuccessCallback{"-4", &success_count}), + base::Bind(ErrorCallback{&error_count}), + 13, -17); + EXPECT_EQ(0, error_count); + EXPECT_EQ(3, success_count); +} + +TEST_F(AsyncDBusMethodInvokerTest, TestFailure) { + int error_count = 0; + int success_count = 0; + brillo::dbus_utils::CallMethod( + mock_object_proxy_.get(), + kTestInterface, + kTestMethod2, + base::Bind(SuccessCallback{&success_count}), + base::Bind(ErrorCallback{brillo::errors::dbus::kDomain, + "org.MyError", + "My error message", + &error_count}), + 2, 2); + EXPECT_EQ(1, error_count); + EXPECT_EQ(0, success_count); +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_method_response.cc b/brillo/dbus/dbus_method_response.cc new file mode 100644 index 0000000..bc75ee0 --- /dev/null +++ b/brillo/dbus/dbus_method_response.cc @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { +namespace dbus_utils { + +DBusMethodResponseBase::DBusMethodResponseBase(dbus::MethodCall* method_call, + ResponseSender sender) + : sender_(sender), method_call_(method_call) { +} + +DBusMethodResponseBase::~DBusMethodResponseBase() { + if (method_call_) { + // Response hasn't been sent by the handler. Abort the call. + Abort(); + } +} + +void DBusMethodResponseBase::ReplyWithError(const brillo::Error* error) { + CheckCanSendResponse(); + auto response = GetDBusError(method_call_, error); + SendRawResponse(std::move(response)); +} + +void DBusMethodResponseBase::ReplyWithError( + const tracked_objects::Location& location, + const std::string& error_domain, + const std::string& error_code, + const std::string& error_message) { + ErrorPtr error; + Error::AddTo(&error, location, error_domain, error_code, error_message); + ReplyWithError(error.get()); +} + +void DBusMethodResponseBase::Abort() { + SendRawResponse(std::unique_ptr()); +} + +void DBusMethodResponseBase::SendRawResponse( + std::unique_ptr response) { + CheckCanSendResponse(); + method_call_ = nullptr; // Mark response as sent. + sender_.Run(scoped_ptr{response.release()}); +} + +std::unique_ptr +DBusMethodResponseBase::CreateCustomResponse() const { + return std::unique_ptr{ + dbus::Response::FromMethodCall(method_call_).release()}; +} + +bool DBusMethodResponseBase::IsResponseSent() const { + return (method_call_ == nullptr); +} + +void DBusMethodResponseBase::CheckCanSendResponse() const { + CHECK(method_call_) << "Response already sent"; +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_method_response.h b/brillo/dbus/dbus_method_response.h new file mode 100644 index 0000000..e30c71c --- /dev/null +++ b/brillo/dbus/dbus_method_response.h @@ -0,0 +1,98 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_RESPONSE_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_RESPONSE_H_ + +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { + +class Error; + +namespace dbus_utils { + +using ResponseSender = dbus::ExportedObject::ResponseSender; + +// DBusMethodResponseBase is a helper class used with asynchronous D-Bus method +// handlers to encapsulate the information needed to send the method call +// response when it is available. +class BRILLO_EXPORT DBusMethodResponseBase { + public: + DBusMethodResponseBase(dbus::MethodCall* method_call, ResponseSender sender); + virtual ~DBusMethodResponseBase(); + + // Sends an error response. Marshals the |error| object over D-Bus. + // If |error| is from the "dbus" error domain, takes the |error_code| from + // |error| and uses it as the DBus error name. + // For error is from other domains, the full error information (domain, error + // code, error message) is encoded into the D-Bus error message and returned + // to the caller as "org.freedesktop.DBus.Failed". + void ReplyWithError(const brillo::Error* error); + + // Constructs brillo::Error object from the parameters specified and send + // the error information over D-Bus using the method above. + void ReplyWithError(const tracked_objects::Location& location, + const std::string& error_domain, + const std::string& error_code, + const std::string& error_message); + + // Sends a raw D-Bus response message. + void SendRawResponse(std::unique_ptr response); + + // Creates a custom response object for the current method call. + std::unique_ptr CreateCustomResponse() const; + + // Checks if the response has been sent already. + bool IsResponseSent() const; + + protected: + void CheckCanSendResponse() const; + + // Aborts the method execution. Does not send any response message. + void Abort(); + + private: + // A callback to be called to send the method call response message. + ResponseSender sender_; + // |method_call_| is actually owned by |sender_| (it is embedded as unique_ptr + // in the bound parameter list in the Callback). We set it to nullptr after + // the method call response has been sent to ensure we can't possibly try + // to send a response again somehow. + dbus::MethodCall* method_call_; + + DISALLOW_COPY_AND_ASSIGN(DBusMethodResponseBase); +}; + +// DBusMethodResponse is an explicitly-typed version of DBusMethodResponse. +// Using DBusMethodResponse indicates the types a D-Bus method +// is expected to return. +template +class DBusMethodResponse : public DBusMethodResponseBase { + public: + // Make the base class's custom constructor available to DBusMethodResponse. + using DBusMethodResponseBase::DBusMethodResponseBase; + + // Sends the a successful response. |return_values| can contain a list + // of return values to be sent to the caller. + inline void Return(const Types&... return_values) { + CheckCanSendResponse(); + auto response = CreateCustomResponse(); + dbus::MessageWriter writer(response.get()); + DBusParamWriter::Append(&writer, return_values...); + SendRawResponse(std::move(response)); + } +}; + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_RESPONSE_H_ diff --git a/brillo/dbus/dbus_object.cc b/brillo/dbus/dbus_object.cc new file mode 100644 index 0000000..7b5834d --- /dev/null +++ b/brillo/dbus/dbus_object.cc @@ -0,0 +1,279 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +////////////////////////////////////////////////////////////////////////////// + +DBusInterface::DBusInterface(DBusObject* dbus_object, + const std::string& interface_name) + : dbus_object_(dbus_object), interface_name_(interface_name) { +} + +void DBusInterface::AddProperty(const std::string& property_name, + ExportedPropertyBase* prop_base) { + dbus_object_->property_set_.RegisterProperty( + interface_name_, property_name, prop_base); +} + +void DBusInterface::ExportAsync( + ExportedObjectManager* object_manager, + dbus::Bus* bus, + dbus::ExportedObject* exported_object, + const dbus::ObjectPath& object_path, + const AsyncEventSequencer::CompletionAction& completion_callback) { + VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '" + << object_path.value() << "'"; + scoped_refptr sequencer(new AsyncEventSequencer()); + for (const auto& pair : handlers_) { + std::string method_name = pair.first; + VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name; + std::string export_error = "Failed exporting " + method_name + " method"; + auto export_handler = sequencer->GetExportHandler( + interface_name_, method_name, export_error, true); + auto method_handler = + base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this)); + exported_object->ExportMethod( + interface_name_, method_name, method_handler, export_handler); + } + + std::vector actions; + if (object_manager) { + auto property_writer_callback = + dbus_object_->property_set_.GetPropertyWriter(interface_name_); + actions.push_back( + base::Bind(&DBusInterface::ClaimInterface, + weak_factory_.GetWeakPtr(), + object_manager->AsWeakPtr(), + object_path, + property_writer_callback)); + } + actions.push_back(completion_callback); + sequencer->OnAllTasksCompletedCall(actions); +} + +void DBusInterface::ExportAndBlock( + ExportedObjectManager* object_manager, + dbus::Bus* bus, + dbus::ExportedObject* exported_object, + const dbus::ObjectPath& object_path) { + VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '" + << object_path.value() << "'"; + for (const auto& pair : handlers_) { + std::string method_name = pair.first; + VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name; + auto method_handler = + base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this)); + if (!exported_object->ExportMethodAndBlock( + interface_name_, method_name, method_handler)) { + LOG(FATAL) << "Failed exporting " << method_name << " method"; + } + } + + if (object_manager) { + auto property_writer_callback = + dbus_object_->property_set_.GetPropertyWriter(interface_name_); + ClaimInterface(object_manager->AsWeakPtr(), + object_path, + property_writer_callback, + true); + } +} + +void DBusInterface::ClaimInterface( + base::WeakPtr object_manager, + const dbus::ObjectPath& object_path, + const ExportedPropertySet::PropertyWriter& writer, + bool all_succeeded) { + if (!all_succeeded || !object_manager) { + LOG(ERROR) << "Skipping claiming interface: " << interface_name_; + return; + } + object_manager->ClaimInterface(object_path, interface_name_, writer); + release_interface_cb_.Reset( + base::Bind(&ExportedObjectManager::ReleaseInterface, + object_manager, object_path, interface_name_)); +} + +void DBusInterface::HandleMethodCall(dbus::MethodCall* method_call, + ResponseSender sender) { + std::string method_name = method_call->GetMember(); + // Make a local copy of |interface_name_| because calling HandleMethod() + // can potentially kill this interface object... + std::string interface_name = interface_name_; + VLOG(1) << "Received method call request: " << interface_name << "." + << method_name << "(" << method_call->GetSignature() << ")"; + auto pair = handlers_.find(method_name); + if (pair == handlers_.end()) { + auto response = + dbus::ErrorResponse::FromMethodCall(method_call, + DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method: " + method_name); + sender.Run(response.Pass()); + return; + } + VLOG(1) << "Dispatching DBus method call: " << method_name; + pair->second->HandleMethod(method_call, sender); +} + +void DBusInterface::AddHandlerImpl( + const std::string& method_name, + std::unique_ptr handler) { + VLOG(1) << "Declaring method handler: " << interface_name_ << "." + << method_name; + auto res = handlers_.insert(std::make_pair(method_name, std::move(handler))); + CHECK(res.second) << "Method '" << method_name << "' already exists"; +} + +void DBusInterface::AddSignalImpl( + const std::string& signal_name, + const std::shared_ptr& signal) { + VLOG(1) << "Declaring a signal sink: " << interface_name_ << "." + << signal_name; + CHECK(signals_.insert(std::make_pair(signal_name, signal)).second) + << "The signal '" << signal_name << "' is already registered"; +} + +/////////////////////////////////////////////////////////////////////////////// + +DBusObject::DBusObject(ExportedObjectManager* object_manager, + const scoped_refptr& bus, + const dbus::ObjectPath& object_path) + : property_set_(bus.get()), bus_(bus), object_path_(object_path) { + if (object_manager) + object_manager_ = object_manager->AsWeakPtr(); +} + +DBusObject::~DBusObject() { + if (exported_object_) + exported_object_->Unregister(); +} + +DBusInterface* DBusObject::AddOrGetInterface( + const std::string& interface_name) { + auto iter = interfaces_.find(interface_name); + if (iter == interfaces_.end()) { + VLOG(1) << "Adding an interface '" << interface_name << "' to object '" + << object_path_.value() << "'."; + // Interface doesn't exist yet. Create one... + std::unique_ptr new_itf( + new DBusInterface(this, interface_name)); + iter = interfaces_.insert(std::make_pair(interface_name, + std::move(new_itf))).first; + } + return iter->second.get(); +} + +DBusInterface* DBusObject::FindInterface( + const std::string& interface_name) const { + auto itf_iter = interfaces_.find(interface_name); + return (itf_iter == interfaces_.end()) ? nullptr : itf_iter->second.get(); +} + +void DBusObject::RegisterAsync( + const AsyncEventSequencer::CompletionAction& completion_callback) { + VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'."; + CHECK(exported_object_ == nullptr) << "Object already registered."; + scoped_refptr sequencer(new AsyncEventSequencer()); + exported_object_ = bus_->GetExportedObject(object_path_); + + // Add the org.freedesktop.DBus.Properties interface to the object. + DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface); + prop_interface->AddSimpleMethodHandler( + dbus::kPropertiesGetAll, + base::Unretained(&property_set_), + &ExportedPropertySet::HandleGetAll); + prop_interface->AddSimpleMethodHandlerWithError( + dbus::kPropertiesGet, + base::Unretained(&property_set_), + &ExportedPropertySet::HandleGet); + prop_interface->AddSimpleMethodHandlerWithError( + dbus::kPropertiesSet, + base::Unretained(&property_set_), + &ExportedPropertySet::HandleSet); + property_set_.OnPropertiesInterfaceExported(prop_interface); + + // Export interface methods + for (const auto& pair : interfaces_) { + pair.second->ExportAsync( + object_manager_.get(), + bus_.get(), + exported_object_, + object_path_, + sequencer->GetHandler("Failed to export interface " + pair.first, + false)); + } + + sequencer->OnAllTasksCompletedCall({completion_callback}); +} + +void DBusObject::RegisterAndBlock() { + VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'."; + CHECK(exported_object_ == nullptr) << "Object already registered."; + exported_object_ = bus_->GetExportedObject(object_path_); + + // Add the org.freedesktop.DBus.Properties interface to the object. + DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface); + prop_interface->AddSimpleMethodHandler( + dbus::kPropertiesGetAll, + base::Unretained(&property_set_), + &ExportedPropertySet::HandleGetAll); + prop_interface->AddSimpleMethodHandlerWithError( + dbus::kPropertiesGet, + base::Unretained(&property_set_), + &ExportedPropertySet::HandleGet); + prop_interface->AddSimpleMethodHandlerWithError( + dbus::kPropertiesSet, + base::Unretained(&property_set_), + &ExportedPropertySet::HandleSet); + property_set_.OnPropertiesInterfaceExported(prop_interface); + + // Export interface methods + for (const auto& pair : interfaces_) { + pair.second->ExportAndBlock( + object_manager_.get(), + bus_.get(), + exported_object_, + object_path_); + } +} + +void DBusObject::UnregisterAsync() { + VLOG(1) << "Unregistering D-Bus object '" << object_path_.value() << "'."; + CHECK(exported_object_ != nullptr) << "Object not registered."; + + // This will unregister the object path from the bus. + exported_object_->Unregister(); + // This will remove |exported_object_| from bus's object table. This function + // will also post a task to unregister |exported_object_| (same as the call + // above), which will be a no-op since it is already done by then. + // By doing both in here, the object path is guarantee to be reusable upon + // return from this function. + bus_->UnregisterExportedObject(object_path_); + exported_object_ = nullptr; +} + +bool DBusObject::SendSignal(dbus::Signal* signal) { + if (exported_object_) { + exported_object_->SendSignal(signal); + return true; + } + LOG(ERROR) << "Trying to send a signal from an object that is not exported"; + return false; +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_object.h b/brillo/dbus/dbus_object.h new file mode 100644 index 0000000..1bd7ca7 --- /dev/null +++ b/brillo/dbus/dbus_object.h @@ -0,0 +1,579 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// DBusObject is a special helper class that simplifies the implementation of +// D-Bus objects in C++. It provides an easy way to define interfaces with +// methods and properties and offloads a lot of work to register the object and +// all of its interfaces, to marshal method calls (by converting D-Bus method +// parameters to native C++ types and invoking native method handlers), etc. + +// The basic usage pattern of this class is as follows: +/* +class MyDbusObject { + public: + MyDbusObject(ExportedObjectManager* object_manager, + const scoped_refptr& bus) + : dbus_object_(object_manager, bus, + dbus::ObjectPath("/org/chromium/my_obj")) {} + + void Init(const AsyncEventSequencer::CompletionAction& callback) { + DBusInterface* my_interface = + dbus_object_.AddOrGetInterface("org.chromium.MyInterface"); + my_interface->AddSimpleMethodHandler("Method1", this, + &MyDbusObject::Method1); + my_interface->AddSimpleMethodHandlerWithError("Method2", this, + &MyDbusObject::Method2); + my_interface->AddMethodHandler("Method3", this, &MyDbusObject::Method3); + my_interface->AddProperty("Property1", &prop1_); + my_interface->AddProperty("Property2", &prop2_); + prop1_.SetValue("prop1_value"); + prop2_.SetValue(50); + // Register the object by exporting its methods and properties and + // exposing them to D-Bus clients. + dbus_object_.RegisterAsync(callback); + } + + private: + DBusObject dbus_object_; + + // Make sure the properties outlive the DBusObject they are registered with. + brillo::dbus_utils::ExportedProperty prop1_; + brillo::dbus_utils::ExportedProperty prop2_; + int Method1() { return 5; } + bool Method2(brillo::ErrorPtr* error, const std::string& message); + void Method3(std::unique_ptr> response, + const std::string& message) { + if (message.empty()) { + response->ReplyWithError(brillo::errors::dbus::kDomain, + DBUS_ERROR_INVALID_ARGS, + "Message string cannot be empty"); + return; + } + int32_t message_len = message.length(); + response->Return(message_len); + } + + DISALLOW_COPY_AND_ASSIGN(MyDbusObject); +}; +*/ + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +class ExportedObjectManager; +class ExportedPropertyBase; +class DBusObject; + +// This is an implementation proxy class for a D-Bus interface of an object. +// The important functionality for the users is the ability to add D-Bus method +// handlers and define D-Bus object properties. This is achieved by using one +// of the overload of AddSimpleMethodHandler()/AddMethodHandler() and +// AddProperty() respectively. +// There are three overloads for DBusInterface::AddSimpleMethodHandler() and +// AddMethodHandler() each: +// 1. That takes a handler as base::Callback +// 2. That takes a static function +// 3. That takes a class instance pointer and a class member function +// The signature of the handler for AddSimpleMethodHandler must be one of: +// R(Args... args) [IN only] +// void(Args... args) [IN/OUT] +// The signature of the handler for AddSimpleMethodHandlerWithError must be: +// bool(ErrorPtr* error, Args... args) [IN/OUT] +// The signature of the handler for AddSimpleMethodHandlerWithErrorAndMessage: +// bool(ErrorPtr* error, dbus::Message* msg, Args... args) [IN/OUT] +// The signature of the handler for AddMethodHandler must be: +// void(std::unique_ptr> response, +// Args... args) [IN] +// The signature of the handler for AddMethodHandlerWithMessage must be: +// void(std::unique_ptr> response, +// dbus::Message* msg, Args... args) [IN] +// There is also an AddRawMethodHandler() call that lets provide a custom +// handler that can parse its own input parameter and construct a custom +// response. +// The signature of the handler for AddRawMethodHandler must be: +// void(dbus::MethodCall* method_call, ResponseSender sender) +class BRILLO_EXPORT DBusInterface final { + public: + DBusInterface(DBusObject* dbus_object, const std::string& interface_name); + + // Register sync DBus method handler for |method_name| as base::Callback. + template + inline void AddSimpleMethodHandler( + const std::string& method_name, + const base::Callback& handler) { + Handler>::Add( + this, method_name, handler); + } + + // Register sync D-Bus method handler for |method_name| as a static + // function. + template + inline void AddSimpleMethodHandler(const std::string& method_name, + R(*handler)(Args...)) { + Handler>::Add( + this, method_name, base::Bind(handler)); + } + + // Register sync D-Bus method handler for |method_name| as a class member + // function. + template + inline void AddSimpleMethodHandler(const std::string& method_name, + Instance instance, + R(Class::*handler)(Args...)) { + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Same as above but for const-method of a class. + template + inline void AddSimpleMethodHandler(const std::string& method_name, + Instance instance, + R(Class::*handler)(Args...) const) { + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Register sync DBus method handler for |method_name| as base::Callback. + template + inline void AddSimpleMethodHandlerWithError( + const std::string& method_name, + const base::Callback& handler) { + Handler>::Add( + this, method_name, handler); + } + + // Register sync D-Bus method handler for |method_name| as a static + // function. + template + inline void AddSimpleMethodHandlerWithError( + const std::string& method_name, + bool(*handler)(ErrorPtr*, Args...)) { + Handler>::Add( + this, method_name, base::Bind(handler)); + } + + // Register sync D-Bus method handler for |method_name| as a class member + // function. + template + inline void AddSimpleMethodHandlerWithError( + const std::string& method_name, + Instance instance, + bool(Class::*handler)(ErrorPtr*, Args...)) { + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Same as above but for const-method of a class. + template + inline void AddSimpleMethodHandlerWithError( + const std::string& method_name, + Instance instance, + bool(Class::*handler)(ErrorPtr*, Args...) const) { + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Register sync DBus method handler for |method_name| as base::Callback. + // Passing the method sender as a first parameter to the callback. + template + inline void AddSimpleMethodHandlerWithErrorAndMessage( + const std::string& method_name, + const base::Callback& + handler) { + Handler>::Add( + this, method_name, handler); + } + + // Register sync D-Bus method handler for |method_name| as a static + // function. Passing the method D-Bus message as the second parameter to the + // callback. + template + inline void AddSimpleMethodHandlerWithErrorAndMessage( + const std::string& method_name, + bool(*handler)(ErrorPtr*, dbus::Message*, Args...)) { + Handler>::Add( + this, method_name, base::Bind(handler)); + } + + // Register sync D-Bus method handler for |method_name| as a class member + // function. Passing the method D-Bus message as the second parameter to the + // callback. + template + inline void AddSimpleMethodHandlerWithErrorAndMessage( + const std::string& method_name, + Instance instance, + bool(Class::*handler)(ErrorPtr*, dbus::Message*, Args...)) { + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Same as above but for const-method of a class. + template + inline void AddSimpleMethodHandlerWithErrorAndMessage( + const std::string& method_name, + Instance instance, + bool(Class::*handler)(ErrorPtr*, dbus::Message*, Args...) const) { + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Register an async DBus method handler for |method_name| as base::Callback. + template + inline void AddMethodHandler( + const std::string& method_name, + const base::Callback, Args...)>& handler) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, handler); + } + + // Register an async D-Bus method handler for |method_name| as a static + // function. + template + inline void AddMethodHandler( + const std::string& method_name, + void (*handler)(std::unique_ptr, Args...)) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, base::Bind(handler)); + } + + // Register an async D-Bus method handler for |method_name| as a class member + // function. + template + inline void AddMethodHandler( + const std::string& method_name, + Instance instance, + void(Class::*handler)(std::unique_ptr, Args...)) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Same as above but for const-method of a class. + template + inline void AddMethodHandler( + const std::string& method_name, + Instance instance, + void(Class::*handler)(std::unique_ptr, Args...) const) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Register an async DBus method handler for |method_name| as base::Callback. + template + inline void AddMethodHandlerWithMessage( + const std::string& method_name, + const base::Callback, dbus::Message*, + Args...)>& handler) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, handler); + } + + // Register an async D-Bus method handler for |method_name| as a static + // function. + template + inline void AddMethodHandlerWithMessage( + const std::string& method_name, + void (*handler)(std::unique_ptr, dbus::Message*, Args...)) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, base::Bind(handler)); + } + + // Register an async D-Bus method handler for |method_name| as a class member + // function. + template + inline void AddMethodHandlerWithMessage( + const std::string& method_name, + Instance instance, + void(Class::*handler)(std::unique_ptr, + dbus::Message*, Args...)) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Same as above but for const-method of a class. + template + inline void AddMethodHandlerWithMessage( + const std::string& method_name, + Instance instance, + void(Class::*handler)(std::unique_ptr, dbus::Message*, + Args...) const) { + static_assert(std::is_base_of::value, + "Response must be DBusMethodResponse"); + Handler>::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Register a raw D-Bus method handler for |method_name| as base::Callback. + inline void AddRawMethodHandler( + const std::string& method_name, + const base::Callback& handler) { + Handler::Add(this, method_name, handler); + } + + // Register a raw D-Bus method handler for |method_name| as a class member + // function. + template + inline void AddRawMethodHandler( + const std::string& method_name, + Instance instance, + void(Class::*handler)(dbus::MethodCall*, ResponseSender)) { + Handler::Add( + this, method_name, base::Bind(handler, instance)); + } + + // Register a D-Bus property. + void AddProperty(const std::string& property_name, + ExportedPropertyBase* prop_base); + + // Registers a D-Bus signal that has a specified number and types (|Args|) of + // arguments. Returns a weak pointer to the DBusSignal object which can be + // used to send the signal on this interface when needed: + /* + DBusInterface* itf = dbus_object->AddOrGetInterface("Interface"); + auto signal = itf->RegisterSignal("MySignal"); + ... + // Send the Interface.MySig(12, true) signal. + if (signal.lock()->Send(12, true)) { ... } + */ + // Or if the signal signature is long or complex, you can alias the + // DBusSignal signal type and use RegisterSignalOfType method + // instead: + /* + DBusInterface* itf = dbus_object->AddOrGetInterface("Interface"); + using MySignal = DBusSignal; + auto signal = itf->RegisterSignalOfType("MySignal"); + ... + // Send the Interface.MySig(12, true) signal. + if (signal.lock()->Send(12, true)) { ... } + */ + // If the signal with the given name was already registered, the existing + // copy of the signal proxy object is returned as long as the method signature + // of the original signal matches the current call. If it doesn't, the method + // aborts. + + // RegisterSignalOfType can be used to create a signal if the type of the + // complete DBusSignal class which is pre-defined/aliased earlier. + template + inline std::weak_ptr RegisterSignalOfType( + const std::string& signal_name) { + auto signal = std::make_shared( + dbus_object_, interface_name_, signal_name); + AddSignalImpl(signal_name, signal); + return signal; + } + + // For simple signal arguments, you can specify their types directly in + // RegisterSignal(): + // auto signal = itf->RegisterSignal("SignalName"); + // This will create a callback signal object that expects one int argument. + template + inline std::weak_ptr> RegisterSignal( + const std::string& signal_name) { + return RegisterSignalOfType>(signal_name); + } + + private: + // Helper to create an instance of DBusInterfaceMethodHandlerInterface-derived + // handler and add it to the method handler map of the interface. + // This makes the actual AddXXXMethodHandler() methods very light-weight and + // easier to provide different overloads for various method handler kinds. + // Using struct here to allow partial specialization on HandlerType while + // letting the compiler to deduce the type of the callback without explicitly + // specifying it. + template + struct Handler { + template + inline static void Add(DBusInterface* self, + const std::string& method_name, + const CallbackType& callback) { + std::unique_ptr sync_method_handler( + new HandlerType(callback)); + self->AddHandlerImpl(method_name, std::move(sync_method_handler)); + } + }; + // A generic D-Bus method handler for the interface. It extracts the method + // name from |method_call|, looks up a registered handler from |handlers_| + // map and dispatched the call to that handler. + void HandleMethodCall(dbus::MethodCall* method_call, ResponseSender sender); + // Helper to add a handler for method |method_name| to the |handlers_| map. + // Not marked BRILLO_PRIVATE because it needs to be called by the inline + // template functions AddMethodHandler(...) + void AddHandlerImpl( + const std::string& method_name, + std::unique_ptr handler); + // Helper to add a signal object to the |signals_| map. + // Not marked BRILLO_PRIVATE because it needs to be called by the inline + // template function RegisterSignalOfType(...) + void AddSignalImpl(const std::string& signal_name, + const std::shared_ptr& signal); + // Exports all the methods and properties of this interface and claims the + // D-Bus interface. + // object_manager - ExportedObjectManager instance that notifies D-Bus + // listeners of a new interface being claimed. + // exported_object - instance of D-Bus object the interface is being added to. + // object_path - D-Bus object path for the object instance. + // interface_name - name of interface being registered. + // completion_callback - a callback to be called when the asynchronous + // registration operation is completed. + BRILLO_PRIVATE void ExportAsync( + ExportedObjectManager* object_manager, + dbus::Bus* bus, + dbus::ExportedObject* exported_object, + const dbus::ObjectPath& object_path, + const AsyncEventSequencer::CompletionAction& completion_callback); + // Exports all the methods and properties of this interface and claims the + // D-Bus interface synchronously. + // object_manager - ExportedObjectManager instance that notifies D-Bus + // listeners of a new interface being claimed. + // exported_object - instance of D-Bus object the interface is being added to. + // object_path - D-Bus object path for the object instance. + // interface_name - name of interface being registered. + BRILLO_PRIVATE void ExportAndBlock( + ExportedObjectManager* object_manager, + dbus::Bus* bus, + dbus::ExportedObject* exported_object, + const dbus::ObjectPath& object_path); + + BRILLO_PRIVATE void ClaimInterface( + base::WeakPtr object_manager, + const dbus::ObjectPath& object_path, + const ExportedPropertySet::PropertyWriter& writer, + bool all_succeeded); + + // Method registration map. + std::map> + handlers_; + // Signal registration map. + std::map> signals_; + + friend class DBusObject; + friend class DBusInterfaceTestHelper; + DBusObject* dbus_object_; + std::string interface_name_; + base::ScopedClosureRunner release_interface_cb_; + + base::WeakPtrFactory weak_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(DBusInterface); +}; + +// A D-Bus object implementation class. Manages the interfaces implemented +// by this object. +class BRILLO_EXPORT DBusObject { + public: + // object_manager - ExportedObjectManager instance that notifies D-Bus + // listeners of a new interface being claimed and property + // changes on those interfaces. + // object_path - D-Bus object path for the object instance. + DBusObject(ExportedObjectManager* object_manager, + const scoped_refptr& bus, + const dbus::ObjectPath& object_path); + virtual ~DBusObject(); + + // Returns an proxy handler for the interface |interface_name|. If the + // interface proxy does not exist yet, it will be automatically created. + DBusInterface* AddOrGetInterface(const std::string& interface_name); + + // Finds an interface with the given name. Returns nullptr if there is no + // interface registered by this name. + DBusInterface* FindInterface(const std::string& interface_name) const; + + // Registers the object instance with D-Bus. This is an asynchronous call + // that will call |completion_callback| when the object and all of its + // interfaces are registered. + virtual void RegisterAsync( + const AsyncEventSequencer::CompletionAction& completion_callback); + + // Registers the object instance with D-Bus. This is call is synchronous and + // will block until the object and all of its interfaces are registered. + virtual void RegisterAndBlock(); + + // Unregister the object instance with D-Bus. This will unregister the + // |exported_object_| and its path from the bus. The destruction of + // |exported_object_| will be deferred in an async task posted by the bus. + // It is guarantee that upon return from this call a new DBusObject with the + // same object path can be created/registered. + virtual void UnregisterAsync(); + + // Returns the ExportedObjectManager proxy, if any. If DBusObject has been + // constructed without an object manager, this method returns an empty + // smart pointer (containing nullptr). + const base::WeakPtr& GetObjectManager() const { + return object_manager_; + } + + // Sends a signal from the exported D-Bus object. + bool SendSignal(dbus::Signal* signal); + + // Returns the reference to dbus::Bus this object is associated with. + scoped_refptr GetBus() { return bus_; } + + private: + // A map of all the interfaces added to this object. + std::map> interfaces_; + // Exported property set for properties registered with the interfaces + // implemented by this D-Bus object. + ExportedPropertySet property_set_; + // Delegate object implementing org.freedesktop.DBus.ObjectManager interface. + base::WeakPtr object_manager_; + // D-Bus bus object. + scoped_refptr bus_; + // D-Bus object path for this object. + dbus::ObjectPath object_path_; + // D-Bus object instance once this object is successfully exported. + dbus::ExportedObject* exported_object_ = nullptr; // weak; owned by |bus_|. + + friend class DBusInterface; + DISALLOW_COPY_AND_ASSIGN(DBusObject); +}; + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_H_ diff --git a/brillo/dbus/dbus_object_internal_impl.h b/brillo/dbus/dbus_object_internal_impl.h new file mode 100644 index 0000000..4ed4bb9 --- /dev/null +++ b/brillo/dbus/dbus_object_internal_impl.h @@ -0,0 +1,363 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides internal implementation details of dispatching D-Bus +// method calls to a D-Bus object methods by reading the expected parameter +// values from D-Bus message buffer then invoking a native C++ callback with +// those parameters passed in. If the callback returns a value, that value is +// sent back to the caller of D-Bus method via the response message. + +// This is achieved by redirecting the parsing of parameter values from D-Bus +// message buffer to DBusParamReader helper class. +// DBusParamReader de-serializes the parameter values from the D-Bus message +// and calls the provided native C++ callback with those arguments. +// However it expects the callback with a simple signature like this: +// void callback(Args...); +// The method handlers for DBusObject, on the other hand, have one of the +// following signatures: +// void handler(Args...); +// ReturnType handler(Args...); +// bool handler(ErrorPtr* error, Args...); +// void handler(std::unique_ptr>, Args...); +// +// To make this all work, we craft a simple callback suitable for +// DBusParamReader using a lambda in DBusInvoker::Invoke() and redirect the call +// to the appropriate method handler using additional data captured by the +// lambda object. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +// This is an abstract base class to allow dispatching a native C++ callback +// method when a corresponding D-Bus method is called. +class DBusInterfaceMethodHandlerInterface { + public: + virtual ~DBusInterfaceMethodHandlerInterface() = default; + + // Returns true if the method has been handled synchronously (whether or not + // a success or error response message had been sent). + virtual void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) = 0; +}; + +// This is a special implementation of DBusInterfaceMethodHandlerInterface for +// extremely simple synchronous method handlers that cannot possibly fail +// (that is, they do not send an error response). +// The handler is expected to take an arbitrary number of arguments of type +// |Args...| which can contain both inputs (passed in by value or constant +// reference) and outputs (passed in as pointers)... +// It may also return a single value of type R (or could be a void function if +// no return value is to be sent to the caller). If the handler has a return +// value, then it cannot have any output parameters in its parameter list. +// The signature of the callback handler is expected to be: +// R(Args...) +template +class SimpleDBusInterfaceMethodHandler + : public DBusInterfaceMethodHandlerInterface { + public: + // A constructor that takes a |handler| to be called when HandleMethod() + // virtual function is invoked. + explicit SimpleDBusInterfaceMethodHandler( + const base::Callback& handler) : handler_(handler) {} + + void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) override { + DBusMethodResponse method_response(method_call, sender); + auto invoke_callback = [this, &method_response](const Args&... args) { + method_response.Return(handler_.Run(args...)); + }; + + ErrorPtr param_reader_error; + dbus::MessageReader reader(method_call); + // The handler is expected a return value, don't allow output parameters. + if (!DBusParamReader::Invoke( + invoke_callback, &reader, ¶m_reader_error)) { + // Error parsing method arguments. + method_response.ReplyWithError(param_reader_error.get()); + } + } + + private: + // C++ callback to be called when a DBus method is dispatched. + base::Callback handler_; + DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandler); +}; + +// Specialization of SimpleDBusInterfaceMethodHandlerInterface for +// R=void (methods with no return values). +template +class SimpleDBusInterfaceMethodHandler + : public DBusInterfaceMethodHandlerInterface { + public: + // A constructor that takes a |handler| to be called when HandleMethod() + // virtual function is invoked. + explicit SimpleDBusInterfaceMethodHandler( + const base::Callback& handler) : handler_(handler) {} + + void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) override { + DBusMethodResponseBase method_response(method_call, sender); + auto invoke_callback = [this, &method_response](const Args&... args) { + handler_.Run(args...); + auto response = method_response.CreateCustomResponse(); + dbus::MessageWriter writer(response.get()); + DBusParamWriter::AppendDBusOutParams(&writer, args...); + method_response.SendRawResponse(std::move(response)); + }; + + ErrorPtr param_reader_error; + dbus::MessageReader reader(method_call); + if (!DBusParamReader::Invoke( + invoke_callback, &reader, ¶m_reader_error)) { + // Error parsing method arguments. + method_response.ReplyWithError(param_reader_error.get()); + } + } + + private: + // C++ callback to be called when a DBus method is dispatched. + base::Callback handler_; + DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandler); +}; + +// An implementation of DBusInterfaceMethodHandlerInterface for simple +// synchronous method handlers that may fail and return an error response +// message. +// The handler is expected to take an arbitrary number of arguments of type +// |Args...| which can contain both inputs (passed in by value or constant +// reference) and outputs (passed in as pointers)... +// In case of an error, the handler must return false and set the error details +// into the |error| object provided. +// The signature of the callback handler is expected to be: +// bool(ErrorPtr*, Args...) +template +class SimpleDBusInterfaceMethodHandlerWithError + : public DBusInterfaceMethodHandlerInterface { + public: + // A constructor that takes a |handler| to be called when HandleMethod() + // virtual function is invoked. + explicit SimpleDBusInterfaceMethodHandlerWithError( + const base::Callback& handler) + : handler_(handler) {} + + void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) override { + DBusMethodResponseBase method_response(method_call, sender); + auto invoke_callback = [this, &method_response](const Args&... args) { + ErrorPtr error; + if (!handler_.Run(&error, args...)) { + method_response.ReplyWithError(error.get()); + } else { + auto response = method_response.CreateCustomResponse(); + dbus::MessageWriter writer(response.get()); + DBusParamWriter::AppendDBusOutParams(&writer, args...); + method_response.SendRawResponse(std::move(response)); + } + }; + + ErrorPtr param_reader_error; + dbus::MessageReader reader(method_call); + if (!DBusParamReader::Invoke( + invoke_callback, &reader, ¶m_reader_error)) { + // Error parsing method arguments. + method_response.ReplyWithError(param_reader_error.get()); + } + } + + private: + // C++ callback to be called when a DBus method is dispatched. + base::Callback handler_; + DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandlerWithError); +}; + +// An implementation of SimpleDBusInterfaceMethodHandlerWithErrorAndMessage +// which is almost identical to SimpleDBusInterfaceMethodHandlerWithError with +// the exception that the callback takes an additional parameter - raw D-Bus +// message used to invoke the method handler. +// The handler is expected to take an arbitrary number of arguments of type +// |Args...| which can contain both inputs (passed in by value or constant +// reference) and outputs (passed in as pointers)... +// In case of an error, the handler must return false and set the error details +// into the |error| object provided. +// The signature of the callback handler is expected to be: +// bool(ErrorPtr*, dbus::Message*, Args...) +template +class SimpleDBusInterfaceMethodHandlerWithErrorAndMessage + : public DBusInterfaceMethodHandlerInterface { + public: + // A constructor that takes a |handler| to be called when HandleMethod() + // virtual function is invoked. + explicit SimpleDBusInterfaceMethodHandlerWithErrorAndMessage( + const base::Callback& handler) + : handler_(handler) {} + + void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) override { + DBusMethodResponseBase method_response(method_call, sender); + auto invoke_callback = + [this, method_call, &method_response](const Args&... args) { + ErrorPtr error; + if (!handler_.Run(&error, method_call, args...)) { + method_response.ReplyWithError(error.get()); + } else { + auto response = method_response.CreateCustomResponse(); + dbus::MessageWriter writer(response.get()); + DBusParamWriter::AppendDBusOutParams(&writer, args...); + method_response.SendRawResponse(std::move(response)); + } + }; + + ErrorPtr param_reader_error; + dbus::MessageReader reader(method_call); + if (!DBusParamReader::Invoke( + invoke_callback, &reader, ¶m_reader_error)) { + // Error parsing method arguments. + method_response.ReplyWithError(param_reader_error.get()); + } + } + + private: + // C++ callback to be called when a DBus method is dispatched. + base::Callback handler_; + DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandlerWithErrorAndMessage); +}; + +// An implementation of DBusInterfaceMethodHandlerInterface for more generic +// (and possibly asynchronous) method handlers. The handler is expected +// to take an arbitrary number of input arguments of type |Args...| and send +// the method call response (including a possible error response) using +// the provided DBusMethodResponse object. +// The signature of the callback handler is expected to be: +// void(std::unique_ptr, Args...) +template +class DBusInterfaceMethodHandler : public DBusInterfaceMethodHandlerInterface { + public: + // A constructor that takes a |handler| to be called when HandleMethod() + // virtual function is invoked. + explicit DBusInterfaceMethodHandler( + const base::Callback, Args...)>& handler) + : handler_(handler) {} + + // This method forwards the call to |handler_| after extracting the required + // arguments from the DBus message buffer specified in |method_call|. + // The output parameters of |handler_| (if any) are sent back to the called. + void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) override { + auto invoke_callback = [this, method_call, &sender](const Args&... args) { + std::unique_ptr response(new Response(method_call, sender)); + handler_.Run(std::move(response), args...); + }; + + ErrorPtr param_reader_error; + dbus::MessageReader reader(method_call); + if (!DBusParamReader::Invoke( + invoke_callback, &reader, ¶m_reader_error)) { + // Error parsing method arguments. + DBusMethodResponseBase method_response(method_call, sender); + method_response.ReplyWithError(param_reader_error.get()); + } + } + + private: + // C++ callback to be called when a D-Bus method is dispatched. + base::Callback, Args...)> handler_; + + DISALLOW_COPY_AND_ASSIGN(DBusInterfaceMethodHandler); +}; + +// An implementation of DBusInterfaceMethodHandlerWithMessage which is almost +// identical to AddSimpleMethodHandlerWithError with the exception that the +// callback takes an additional parameter - raw D-Bus message. +// The handler is expected to take an arbitrary number of input arguments of +// type |Args...| and send the method call response (including a possible error +// response) using the provided DBusMethodResponse object. +// The signature of the callback handler is expected to be: +// void(std::unique_ptr, dbus::Message*, +// Args...); +template +class DBusInterfaceMethodHandlerWithMessage + : public DBusInterfaceMethodHandlerInterface { + public: + // A constructor that takes a |handler| to be called when HandleMethod() + // virtual function is invoked. + explicit DBusInterfaceMethodHandlerWithMessage( + const base::Callback, dbus::Message*, + Args...)>& handler) + : handler_(handler) {} + + // This method forwards the call to |handler_| after extracting the required + // arguments from the DBus message buffer specified in |method_call|. + // The output parameters of |handler_| (if any) are sent back to the called. + void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) override { + auto invoke_callback = [this, method_call, &sender](const Args&... args) { + std::unique_ptr response(new Response(method_call, sender)); + handler_.Run(std::move(response), method_call, args...); + }; + + ErrorPtr param_reader_error; + dbus::MessageReader reader(method_call); + if (!DBusParamReader::Invoke( + invoke_callback, &reader, ¶m_reader_error)) { + // Error parsing method arguments. + DBusMethodResponseBase method_response(method_call, sender); + method_response.ReplyWithError(param_reader_error.get()); + } + } + + private: + // C++ callback to be called when a D-Bus method is dispatched. + base::Callback, + dbus::Message*, Args...)> handler_; + + DISALLOW_COPY_AND_ASSIGN(DBusInterfaceMethodHandlerWithMessage); +}; + +// An implementation of DBusInterfaceMethodHandlerInterface that has custom +// processing of both input and output parameters. This class is used by +// DBusObject::AddRawMethodHandler and expects the callback to be of the +// following signature: +// void(dbus::MethodCall*, ResponseSender) +// It will be up to the callback to parse the input parameters from the +// message buffer and construct the D-Bus Response object. +class RawDBusInterfaceMethodHandler + : public DBusInterfaceMethodHandlerInterface { + public: + // A constructor that takes a |handler| to be called when HandleMethod() + // virtual function is invoked. + RawDBusInterfaceMethodHandler( + const base::Callback& handler) + : handler_(handler) {} + + void HandleMethod(dbus::MethodCall* method_call, + ResponseSender sender) override { + handler_.Run(method_call, sender); + } + + private: + // C++ callback to be called when a D-Bus method is dispatched. + base::Callback handler_; + + DISALLOW_COPY_AND_ASSIGN(RawDBusInterfaceMethodHandler); +}; + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_ diff --git a/brillo/dbus/dbus_object_test_helpers.h b/brillo/dbus/dbus_object_test_helpers.h new file mode 100644 index 0000000..6b90c5a --- /dev/null +++ b/brillo/dbus/dbus_object_test_helpers.h @@ -0,0 +1,143 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Helper utilities to simplify testing of D-Bus object implementations. +// Since the method handlers could now be asynchronous, they use callbacks to +// provide method return values. This makes it really difficult to invoke +// such handlers in unit tests (even if they are actually synchronous but +// still use DBusMethodResponse to send back the method results). +// This file provide testing-only helpers to make calling D-Bus method handlers +// easier. +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ + +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +// Helper friend class to call DBusInterface::HandleMethodCall() since it is +// a private method of the class and we don't want to make it public. +class DBusInterfaceTestHelper final { + public: + static void HandleMethodCall(DBusInterface* itf, + dbus::MethodCall* method_call, + ResponseSender sender) { + itf->HandleMethodCall(method_call, sender); + } +}; + +namespace testing { + +// This is a simple class that has weak pointer semantics and holds an +// instance of D-Bus method call response message. We use this in tests +// to get the response in case the handler processes a method call request +// synchronously. Otherwise the ResponseHolder object will be destroyed and +// ResponseHolder::ReceiveResponse() will not be called since we bind the +// callback to the object instance via a weak pointer. +struct ResponseHolder final : public base::SupportsWeakPtr { + void ReceiveResponse(scoped_ptr response) { + response_.reset(response.release()); + } + + std::unique_ptr response_; +}; + +// Dispatches a D-Bus method call to the corresponding handler. +// Used mostly for testing purposes. This method is inlined so that it is +// not included in the shipping code of libchromeos, and included at the +// call sites. Returns a response from the method handler or nullptr if the +// method hasn't provided the response message immediately +// (i.e. it is asynchronous). +inline std::unique_ptr CallMethod( + const DBusObject& object, dbus::MethodCall* method_call) { + DBusInterface* itf = object.FindInterface(method_call->GetInterface()); + std::unique_ptr response; + if (!itf) { + response = CreateDBusErrorResponse( + method_call, + DBUS_ERROR_UNKNOWN_INTERFACE, + "Interface you invoked a method on isn't known by the object."); + } else { + ResponseHolder response_holder; + DBusInterfaceTestHelper::HandleMethodCall( + itf, method_call, base::Bind(&ResponseHolder::ReceiveResponse, + response_holder.AsWeakPtr())); + response = std::move(response_holder.response_); + } + return response; +} + +// MethodHandlerInvoker is similar to CallMethod() function above, except +// it allows the callers to invoke the method handlers directly bypassing +// the DBusObject/DBusInterface infrastructure. +// This works only on synchronous methods though. The handler must reply +// before the handler exits. +template +struct MethodHandlerInvoker { + // MethodHandlerInvoker::Call() calls a member |method| of a class + // |instance| and passes the |args| to it. The method's return value provided + // via handler's DBusMethodResponse is then extracted and returned. + // If the method handler returns an error, the error information is passed + // to the caller via the |error| object (and the method returns a default + // value of type RetType as a placeholder). + // If the method handler asynchronous and did not provide a reply (success or + // error) before the handler exits, this method aborts with a CHECK(). + template + static RetType Call( + ErrorPtr* error, + Class* instance, + void(Class::*method)(std::unique_ptr>, + Params...), + Args... args) { + ResponseHolder response_holder; + dbus::MethodCall method_call("test.interface", "TestMethod"); + method_call.SetSerial(123); + std::unique_ptr> method_response{ + new DBusMethodResponse( + &method_call, base::Bind(&ResponseHolder::ReceiveResponse, + response_holder.AsWeakPtr())) + }; + (instance->*method)(std::move(method_response), args...); + CHECK(response_holder.response_.get()) + << "No response received. Asynchronous methods are not supported."; + RetType ret_val; + ExtractMethodCallResults(response_holder.response_.get(), error, &ret_val); + return ret_val; + } +}; + +// Specialization of MethodHandlerInvoker for methods that do not return +// values (void methods). +template<> +struct MethodHandlerInvoker { + template + static void Call( + ErrorPtr* error, + Class* instance, + void(Class::*method)(std::unique_ptr>, Params...), + Args... args) { + ResponseHolder response_holder; + dbus::MethodCall method_call("test.interface", "TestMethod"); + method_call.SetSerial(123); + std::unique_ptr> method_response{ + new DBusMethodResponse<>(&method_call, + base::Bind(&ResponseHolder::ReceiveResponse, + response_holder.AsWeakPtr())) + }; + (instance->*method)(std::move(method_response), args...); + CHECK(response_holder.response_.get()) + << "No response received. Asynchronous methods are not supported."; + ExtractMethodCallResults(response_holder.response_.get(), error); + } +}; + +} // namespace testing +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ diff --git a/brillo/dbus/dbus_object_unittest.cc b/brillo/dbus/dbus_object_unittest.cc new file mode 100644 index 0000000..f833530 --- /dev/null +++ b/brillo/dbus/dbus_object_unittest.cc @@ -0,0 +1,392 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::Mock; +using ::testing::_; + +namespace brillo { +namespace dbus_utils { + +namespace { + +const char kMethodsExportedOn[] = "/export"; + +const char kTestInterface1[] = "org.chromium.Test.MathInterface"; +const char kTestMethod_Add[] = "Add"; +const char kTestMethod_Negate[] = "Negate"; +const char kTestMethod_Positive[] = "Positive"; +const char kTestMethod_AddSubtract[] = "AddSubtract"; + +const char kTestInterface2[] = "org.chromium.Test.StringInterface"; +const char kTestMethod_StrLen[] = "StrLen"; +const char kTestMethod_CheckNonEmpty[] = "CheckNonEmpty"; + +const char kTestInterface3[] = "org.chromium.Test.NoOpInterface"; +const char kTestMethod_NoOp[] = "NoOp"; +const char kTestMethod_WithMessage[] = "TestWithMessage"; +const char kTestMethod_WithMessageAsync[] = "TestWithMessageAsync"; + +struct Calc { + int Add(int x, int y) { return x + y; } + int Negate(int x) { return -x; } + void Positive(std::unique_ptr> response, + double x) { + if (x >= 0.0) { + response->Return(x); + return; + } + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, "test", "not_positive", + "Negative value passed in"); + response->ReplyWithError(error.get()); + } + void AddSubtract(int x, int y, int* sum, int* diff) { + *sum = x + y; + *diff = x - y; + } +}; + +int StrLen(const std::string& str) { + return str.size(); +} + +bool CheckNonEmpty(ErrorPtr* error, const std::string& str) { + if (!str.empty()) + return true; + Error::AddTo(error, FROM_HERE, "test", "string_empty", "String is empty"); + return false; +} + +void NoOp() {} + +bool TestWithMessage(ErrorPtr* error, + dbus::Message* message, + std::string* str) { + *str = message->GetSender(); + return true; +} + +void TestWithMessageAsync( + std::unique_ptr> response, + dbus::Message* message) { + response->Return(message->GetSender()); +} + +} // namespace + +class DBusObjectTest : public ::testing::Test { + public: + virtual void SetUp() { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock exported object. + const dbus::ObjectPath kMethodsExportedOnPath{ + std::string{kMethodsExportedOn}}; + mock_exported_object_ = + new dbus::MockExportedObject(bus_.get(), kMethodsExportedOnPath); + EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath)) + .Times(AnyNumber()) + .WillRepeatedly(Return(mock_exported_object_.get())); + EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _)) + .Times(AnyNumber()); + EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1); + + dbus_object_ = std::unique_ptr( + new DBusObject(nullptr, bus_, kMethodsExportedOnPath)); + + DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1); + itf1->AddSimpleMethodHandler( + kTestMethod_Add, base::Unretained(&calc_), &Calc::Add); + itf1->AddSimpleMethodHandler( + kTestMethod_Negate, base::Unretained(&calc_), &Calc::Negate); + itf1->AddMethodHandler( + kTestMethod_Positive, base::Unretained(&calc_), &Calc::Positive); + itf1->AddSimpleMethodHandler( + kTestMethod_AddSubtract, base::Unretained(&calc_), &Calc::AddSubtract); + DBusInterface* itf2 = dbus_object_->AddOrGetInterface(kTestInterface2); + itf2->AddSimpleMethodHandler(kTestMethod_StrLen, StrLen); + itf2->AddSimpleMethodHandlerWithError(kTestMethod_CheckNonEmpty, + CheckNonEmpty); + DBusInterface* itf3 = dbus_object_->AddOrGetInterface(kTestInterface3); + base::Callback noop_callback = base::Bind(NoOp); + itf3->AddSimpleMethodHandler(kTestMethod_NoOp, noop_callback); + itf3->AddSimpleMethodHandlerWithErrorAndMessage( + kTestMethod_WithMessage, base::Bind(&TestWithMessage)); + itf3->AddMethodHandlerWithMessage(kTestMethod_WithMessageAsync, + base::Bind(&TestWithMessageAsync)); + + dbus_object_->RegisterAsync( + AsyncEventSequencer::GetDefaultCompletionAction()); + } + + void ExpectError(dbus::Response* response, const std::string& expected_code) { + EXPECT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + EXPECT_EQ(expected_code, response->GetErrorName()); + } + + scoped_refptr bus_; + scoped_refptr mock_exported_object_; + std::unique_ptr dbus_object_; + Calc calc_; +}; + +TEST_F(DBusObjectTest, Add) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendInt32(2); + writer.AppendInt32(3); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + int result; + ASSERT_TRUE(reader.PopInt32(&result)); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_EQ(5, result); +} + +TEST_F(DBusObjectTest, Negate) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Negate); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendInt32(98765); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + int result; + ASSERT_TRUE(reader.PopInt32(&result)); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_EQ(-98765, result); +} + +TEST_F(DBusObjectTest, PositiveSuccess) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendDouble(17.5); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + double result; + ASSERT_TRUE(reader.PopDouble(&result)); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_DOUBLE_EQ(17.5, result); +} + +TEST_F(DBusObjectTest, PositiveFailure) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendDouble(-23.2); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ExpectError(response.get(), DBUS_ERROR_FAILED); +} + +TEST_F(DBusObjectTest, AddSubtract) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_AddSubtract); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendInt32(2); + writer.AppendInt32(3); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + int sum = 0, diff = 0; + ASSERT_TRUE(reader.PopInt32(&sum)); + ASSERT_TRUE(reader.PopInt32(&diff)); + ASSERT_FALSE(reader.HasMoreData()); + EXPECT_EQ(5, sum); + EXPECT_EQ(-1, diff); +} + +TEST_F(DBusObjectTest, StrLen0) { + dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString(""); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + int result; + ASSERT_TRUE(reader.PopInt32(&result)); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_EQ(0, result); +} + +TEST_F(DBusObjectTest, StrLen4) { + dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString("test"); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + int result; + ASSERT_TRUE(reader.PopInt32(&result)); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_EQ(4, result); +} + +TEST_F(DBusObjectTest, CheckNonEmpty_Success) { + dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString("test"); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType()); + dbus::MessageReader reader(response.get()); + EXPECT_FALSE(reader.HasMoreData()); +} + +TEST_F(DBusObjectTest, CheckNonEmpty_Failure) { + dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString(""); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ErrorPtr error; + ExtractMethodCallResults(response.get(), &error); + ASSERT_NE(nullptr, error.get()); + EXPECT_EQ("test", error->GetDomain()); + EXPECT_EQ("string_empty", error->GetCode()); + EXPECT_EQ("String is empty", error->GetMessage()); +} + +TEST_F(DBusObjectTest, CheckNonEmpty_MissingParams) { + dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty); + method_call.SetSerial(123); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + dbus::MessageReader reader(response.get()); + std::string message; + ASSERT_TRUE(reader.PopString(&message)); + EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName()); + EXPECT_EQ("Too few parameters in a method call", message); + EXPECT_FALSE(reader.HasMoreData()); +} + +TEST_F(DBusObjectTest, NoOp) { + dbus::MethodCall method_call(kTestInterface3, kTestMethod_NoOp); + method_call.SetSerial(123); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(DBusObjectTest, TestWithMessage) { + const std::string sender{":1.2345"}; + dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessage); + method_call.SetSerial(123); + method_call.SetSender(sender); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + std::string message; + ASSERT_TRUE(reader.PopString(&message)); + ASSERT_FALSE(reader.HasMoreData()); + EXPECT_EQ(sender, message); +} + +TEST_F(DBusObjectTest, TestWithMessageAsync) { + const std::string sender{":6.7890"}; + dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessageAsync); + method_call.SetSerial(123); + method_call.SetSender(sender); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + std::string message; + ASSERT_TRUE(reader.PopString(&message)); + ASSERT_FALSE(reader.HasMoreData()); + EXPECT_EQ(sender, message); +} + +TEST_F(DBusObjectTest, TooFewParams) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendInt32(2); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS); +} + +TEST_F(DBusObjectTest, TooManyParams) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendInt32(1); + writer.AppendInt32(2); + writer.AppendInt32(3); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS); +} + +TEST_F(DBusObjectTest, ParamTypeMismatch) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendInt32(1); + writer.AppendBool(false); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS); +} + +TEST_F(DBusObjectTest, ParamAsVariant) { + dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendVariantOfInt32(10); + writer.AppendVariantOfInt32(3); + auto response = testing::CallMethod(*dbus_object_, &method_call); + dbus::MessageReader reader(response.get()); + int result; + ASSERT_TRUE(reader.PopInt32(&result)); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_EQ(13, result); +} + +TEST_F(DBusObjectTest, UnknownMethod) { + dbus::MethodCall method_call(kTestInterface2, kTestMethod_Add); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendInt32(1); + writer.AppendBool(false); + auto response = testing::CallMethod(*dbus_object_, &method_call); + ExpectError(response.get(), DBUS_ERROR_UNKNOWN_METHOD); +} + +TEST_F(DBusObjectTest, ShouldReleaseOnlyClaimedInterfaces) { + const dbus::ObjectPath kObjectManagerPath{std::string{"/"}}; + const dbus::ObjectPath kMethodsExportedOnPath{ + std::string{kMethodsExportedOn}}; + MockExportedObjectManager mock_object_manager{bus_, kObjectManagerPath}; + dbus_object_ = std::unique_ptr( + new DBusObject(&mock_object_manager, bus_, kMethodsExportedOnPath)); + EXPECT_CALL(mock_object_manager, ClaimInterface(_, _, _)).Times(0); + EXPECT_CALL(mock_object_manager, ReleaseInterface(_, _)).Times(0); + DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1); + itf1->AddSimpleMethodHandler( + kTestMethod_Add, base::Unretained(&calc_), &Calc::Add); + // When we tear down our DBusObject, it should release only interfaces it has + // previously claimed. This prevents a check failing inside the + // ExportedObjectManager. Since no interfaces have finished exporting + // handlers, nothing should be released. + dbus_object_.reset(); +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_param_reader.h b/brillo/dbus/dbus_param_reader.h new file mode 100644 index 0000000..f72f93c --- /dev/null +++ b/brillo/dbus/dbus_param_reader.h @@ -0,0 +1,165 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides generic method to parse function call arguments from +// D-Bus message buffer and subsequently invokes a provided native C++ callback +// with the parameter values passed as the callback arguments. + +// This functionality is achieved by parsing method arguments one by one, +// left to right from the C++ callback's type signature, and moving the parsed +// arguments to the back to the next call to DBusInvoke::Invoke's arguments as +// const refs. Each iteration has one fewer template specialization arguments, +// until there is only the return type remaining and we fall through to either +// the void or the non-void final specialization. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_PARAM_READER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_PARAM_READER_H_ + +#include + +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +// A generic DBusParamReader stub class which allows us to specialize on +// a variable list of expected function parameters later on. +// This struct in itself is not used. But its concrete template specializations +// defined below are. +// |allow_out_params| controls whether DBusParamReader allows the parameter +// list to contain OUT parameters (pointers). +template +struct DBusParamReader; + +// A generic specialization of DBusParamReader to handle variable function +// parameters. This specialization pops one parameter off the D-Bus message +// buffer and calls other specializations of DBusParamReader with fewer +// parameters to pop the remaining parameters. +// CurrentParam - the type of the current method parameter we are processing. +// RestOfParams - the types of remaining parameters to be processed. +template +struct DBusParamReader { + // DBusParamReader::Invoke() is a member function that actually extracts the + // current parameter from the message buffer. + // handler - the C++ callback functor to be called when all the + // parameters are processed. + // method_call - D-Bus method call object we are processing. + // reader - D-Bus message reader to pop the current argument value from. + // args... - the callback parameters processed so far. + template + static bool Invoke(const CallbackType& handler, + dbus::MessageReader* reader, + ErrorPtr* error, + const Args&... args) { + return InvokeHelper( + handler, reader, error, static_cast(args)...); + } + + // + // There are two specializations of this function: + // 1. For the case where ParamType is a value type (D-Bus IN parameter). + // 2. For the case where ParamType is a pointer (D-Bus OUT parameter). + // In the second case, the parameter is not popped off the message reader, + // since we do not expect the client to provide any data for it. + // However after the final handler is called, the values for the OUT + // parameters should be sent back in the method call response message. + + // Overload 1: ParamType is not a pointer. + template + static typename std::enable_if::value, bool>::type + InvokeHelper(const CallbackType& handler, + dbus::MessageReader* reader, + ErrorPtr* error, + const Args&... args) { + if (!reader->HasMoreData()) { + Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_INVALID_ARGS, + "Too few parameters in a method call"); + return false; + } + // ParamType could be a reference type (e.g. 'const std::string&'). + // Here we need a value type so we can create an object of this type and + // pop the value off the message buffer into. Using std::decay<> to get + // the value type. If ParamType is already a value type, ParamValueType will + // be the same as ParamType. + using ParamValueType = typename std::decay::type; + // The variable to hold the value of the current parameter we reading from + // the message buffer. + ParamValueType current_param; + if (!DBusType::Read(reader, ¤t_param)) { + Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_INVALID_ARGS, + "Method parameter type mismatch"); + return false; + } + // Call DBusParamReader::Invoke() to process the rest of parameters. + // Note that this is not a recursive call because it is calling a different + // method of a different class. We exclude the current parameter type + // (ParamType) from DBusParamReader<> template parameter list and forward + // all the parameters to the arguments of Invoke() and append the current + // parameter to the end of the parameter list. We pass it as a const + // reference to allow to use move-only types such as std::unique_ptr<> and + // to eliminate unnecessarily copying data. + return DBusParamReader::Invoke( + handler, reader, error, + static_cast(args)..., + static_cast(current_param)); + } + + // Overload 2: ParamType is a pointer. + template + static typename std::enable_if::value, bool>::type + InvokeHelper(const CallbackType& handler, + dbus::MessageReader* reader, + ErrorPtr* error, + const Args&... args) { + // ParamType is a pointer. This is expected to be an output parameter. + // Create storage for it and the handler will provide a value for it. + using ParamValueType = typename std::remove_pointer::type; + // The variable to hold the value of the current parameter we are passing + // to the handler. + ParamValueType current_param{}; // Default-initialize the value. + // Call DBusParamReader::Invoke() to process the rest of parameters. + // Note that this is not a recursive call because it is calling a different + // method of a different class. We exclude the current parameter type + // (ParamType) from DBusParamReader<> template parameter list and forward + // all the parameters to the arguments of Invoke() and append the current + // parameter to the end of the parameter list. + return DBusParamReader::Invoke( + handler, reader, error, + static_cast(args)..., + ¤t_param); + } +}; // struct DBusParamReader + +// The final specialization of DBusParamReader<> used when no more parameters +// are expected in the message buffer. Actually dispatches the call to the +// handler with all the accumulated arguments. +template +struct DBusParamReader { + template + static bool Invoke(const CallbackType& handler, + dbus::MessageReader* reader, + ErrorPtr* error, + const Args&... args) { + if (reader->HasMoreData()) { + Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_INVALID_ARGS, + "Too many parameters in a method call"); + return false; + } + handler(args...); + return true; + } +}; // struct DBusParamReader<> + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_PARAM_READER_H_ diff --git a/brillo/dbus/dbus_param_reader_unittest.cc b/brillo/dbus/dbus_param_reader_unittest.cc new file mode 100644 index 0000000..c2669a7 --- /dev/null +++ b/brillo/dbus/dbus_param_reader_unittest.cc @@ -0,0 +1,253 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include + +using dbus::MessageReader; +using dbus::MessageWriter; +using dbus::Response; + +namespace brillo { +namespace dbus_utils { + +TEST(DBusParamReader, NoArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called]() { called = true; }; + EXPECT_TRUE(DBusParamReader::Invoke(callback, &reader, nullptr)); + EXPECT_TRUE(called); +} + +TEST(DBusParamReader, OneArg) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, 123); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](int param1) { + EXPECT_EQ(123, param1); + called = true; + }; + EXPECT_TRUE( + (DBusParamReader::Invoke(callback, &reader, nullptr))); + EXPECT_TRUE(called); +} + +TEST(DBusParamReader, ManyArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, true); + AppendValueToWriter(&writer, 1972); + AppendValueToWriter(&writer, + VariantDictionary{{"key", std::string{"value"}}}); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](bool p1, int p2, const VariantDictionary& p3) { + EXPECT_TRUE(p1); + EXPECT_EQ(1972, p2); + EXPECT_EQ(1, p3.size()); + EXPECT_EQ("value", p3.find("key")->second.Get()); + called = true; + }; + EXPECT_TRUE((DBusParamReader::Invoke( + callback, &reader, nullptr))); + EXPECT_TRUE(called); +} + +TEST(DBusParamReader, TooManyArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, true); + AppendValueToWriter(&writer, 1972); + AppendValueToWriter(&writer, + VariantDictionary{{"key", std::string{"value"}}}); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](bool param1, int param2) { + EXPECT_TRUE(param1); + EXPECT_EQ(1972, param2); + called = true; + }; + ErrorPtr error; + EXPECT_FALSE( + (DBusParamReader::Invoke(callback, &reader, &error))); + EXPECT_FALSE(called); + EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); + EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); + EXPECT_EQ("Too many parameters in a method call", error->GetMessage()); +} + +TEST(DBusParamReader, TooFewArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, true); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](bool param1, int param2) { + EXPECT_TRUE(param1); + EXPECT_EQ(1972, param2); + called = true; + }; + ErrorPtr error; + EXPECT_FALSE( + (DBusParamReader::Invoke(callback, &reader, &error))); + EXPECT_FALSE(called); + EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); + EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); + EXPECT_EQ("Too few parameters in a method call", error->GetMessage()); +} + +TEST(DBusParamReader, TypeMismatch) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, true); + AppendValueToWriter(&writer, 1972); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](bool param1, double param2) { + EXPECT_TRUE(param1); + EXPECT_DOUBLE_EQ(1972.0, param2); + called = true; + }; + ErrorPtr error; + EXPECT_FALSE(( + DBusParamReader::Invoke(callback, &reader, &error))); + EXPECT_FALSE(called); + EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); + EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); + EXPECT_EQ("Method parameter type mismatch", error->GetMessage()); +} + +TEST(DBusParamReader, NoArgs_With_OUT) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](int* param1) { + EXPECT_EQ(0, *param1); + called = true; + }; + EXPECT_TRUE( + (DBusParamReader::Invoke(callback, &reader, nullptr))); + EXPECT_TRUE(called); +} + +TEST(DBusParamReader, OneArg_Before_OUT) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, 123); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](int param1, double* param2) { + EXPECT_EQ(123, param1); + EXPECT_DOUBLE_EQ(0.0, *param2); + called = true; + }; + EXPECT_TRUE(( + DBusParamReader::Invoke(callback, &reader, nullptr))); + EXPECT_TRUE(called); +} + +TEST(DBusParamReader, OneArg_After_OUT) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, 123); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](double* param1, int param2) { + EXPECT_DOUBLE_EQ(0.0, *param1); + EXPECT_EQ(123, param2); + called = true; + }; + EXPECT_TRUE(( + DBusParamReader::Invoke(callback, &reader, nullptr))); + EXPECT_TRUE(called); +} + +TEST(DBusParamReader, ManyArgs_With_OUT) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, true); + AppendValueToWriter(&writer, 1972); + AppendValueToWriter(&writer, + VariantDictionary{{"key", std::string{"value"}}}); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](bool p1, + std::string* p2, + int p3, + int* p4, + const VariantDictionary& p5, + bool* p6) { + EXPECT_TRUE(p1); + EXPECT_EQ("", *p2); + EXPECT_EQ(1972, p3); + EXPECT_EQ(0, *p4); + EXPECT_EQ(1, p5.size()); + EXPECT_EQ("value", p5.find("key")->second.Get()); + EXPECT_FALSE(*p6); + called = true; + }; + EXPECT_TRUE((DBusParamReader::Invoke(callback, &reader, nullptr))); + EXPECT_TRUE(called); +} + +TEST(DBusParamReader, TooManyArgs_With_OUT) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, true); + AppendValueToWriter(&writer, 1972); + AppendValueToWriter(&writer, + VariantDictionary{{"key", std::string{"value"}}}); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](bool param1, int param2, int* param3) { + EXPECT_TRUE(param1); + EXPECT_EQ(1972, param2); + EXPECT_EQ(0, *param3); + called = true; + }; + ErrorPtr error; + EXPECT_FALSE((DBusParamReader::Invoke( + callback, &reader, &error))); + EXPECT_FALSE(called); + EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); + EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); + EXPECT_EQ("Too many parameters in a method call", error->GetMessage()); +} + +TEST(DBusParamReader, TooFewArgs_With_OUT) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + AppendValueToWriter(&writer, true); + MessageReader reader(message.get()); + bool called = false; + auto callback = [&called](bool param1, int param2, int* param3) { + EXPECT_TRUE(param1); + EXPECT_EQ(1972, param2); + EXPECT_EQ(0, *param3); + called = true; + }; + ErrorPtr error; + EXPECT_FALSE((DBusParamReader::Invoke( + callback, &reader, &error))); + EXPECT_FALSE(called); + EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); + EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); + EXPECT_EQ("Too few parameters in a method call", error->GetMessage()); +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_param_writer.h b/brillo/dbus/dbus_param_writer.h new file mode 100644 index 0000000..3eb736e --- /dev/null +++ b/brillo/dbus/dbus_param_writer.h @@ -0,0 +1,80 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// DBusParamWriter::Append(writer, ...) provides functionality opposite +// to that of DBusParamReader. It writes each of the arguments to D-Bus message +// writer and return true if successful. + +// DBusParamWriter::AppendDBusOutParams(writer, ...) is similar to Append() +// but is used to send out the D-Bus OUT (pointer type) parameters in a D-Bus +// method response message. This method skips any non-pointer parameters and +// only appends the data for arguments that are pointers. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_PARAM_WRITER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_PARAM_WRITER_H_ + +#include +#include + +namespace brillo { +namespace dbus_utils { + +class DBusParamWriter final { + public: + // Generic writer method that takes 1 or more arguments. It recursively calls + // itself (each time with one fewer arguments) until no more is left. + template + static void Append(dbus::MessageWriter* writer, + const ParamType& param, + const RestOfParams&... rest) { + // Append the current |param| to D-Bus, then call Append() with one + // fewer arguments, until none is left and stand-alone version of + // Append(dbus::MessageWriter*) is called to end the iteration. + DBusType::Write(writer, param); + Append(writer, rest...); + } + + // The final overload of DBusParamWriter::Append() used when no more + // parameters are remaining to be written. + // Does nothing and finishes meta-recursion. + static void Append(dbus::MessageWriter* /*writer*/) {} + + // Generic writer method that takes 1 or more arguments. It recursively calls + // itself (each time with one fewer arguments) until no more is left. + // Handles non-pointer parameter by just skipping over it. + template + static void AppendDBusOutParams(dbus::MessageWriter* writer, + const ParamType& param, + const RestOfParams&... rest) { + // Skip the current |param| and call Append() with one fewer arguments, + // until none is left and stand-alone version of + // AppendDBusOutParams(dbus::MessageWriter*) is called to end the iteration. + AppendDBusOutParams(writer, rest...); + } + + // Generic writer method that takes 1 or more arguments. It recursively calls + // itself (each time with one fewer arguments) until no more is left. + // Handles only a parameter of pointer type and writes the data pointed to + // to the output message buffer. + template + static void AppendDBusOutParams(dbus::MessageWriter* writer, + ParamType* param, + const RestOfParams&... rest) { + // Append the current |param| to D-Bus, then call Append() with one + // fewer arguments, until none is left and stand-alone version of + // Append(dbus::MessageWriter*) is called to end the iteration. + DBusType::Write(writer, *param); + AppendDBusOutParams(writer, rest...); + } + + // The final overload of DBusParamWriter::AppendDBusOutParams() used when no + // more parameters are remaining to be written. + // Does nothing and finishes meta-recursion. + static void AppendDBusOutParams(dbus::MessageWriter* /*writer*/) {} +}; + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_PARAM_WRITER_H_ diff --git a/brillo/dbus/dbus_param_writer_unittest.cc b/brillo/dbus/dbus_param_writer_unittest.cc new file mode 100644 index 0000000..428c810 --- /dev/null +++ b/brillo/dbus/dbus_param_writer_unittest.cc @@ -0,0 +1,189 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include + +using dbus::MessageReader; +using dbus::MessageWriter; +using dbus::ObjectPath; +using dbus::Response; + +namespace brillo { +namespace dbus_utils { + +TEST(DBusParamWriter, Append_NoArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + DBusParamWriter::Append(&writer); + EXPECT_EQ("", message->GetSignature()); +} + +TEST(DBusParamWriter, Append_OneArg) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + DBusParamWriter::Append(&writer, int32_t{2}); + EXPECT_EQ("i", message->GetSignature()); + DBusParamWriter::Append(&writer, std::string{"foo"}); + EXPECT_EQ("is", message->GetSignature()); + DBusParamWriter::Append(&writer, ObjectPath{"/o"}); + EXPECT_EQ("iso", message->GetSignature()); + + int32_t int_value = 0; + std::string string_value; + ObjectPath path_value; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &path_value)); + + EXPECT_EQ(2, int_value); + EXPECT_EQ("foo", string_value); + EXPECT_EQ(ObjectPath{"/o"}, path_value); +} + +TEST(DBusParamWriter, Append_ManyArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + DBusParamWriter::Append(&writer, int32_t{9}, Any{7.5}, true); + EXPECT_EQ("ivb", message->GetSignature()); + + int32_t int_value = 0; + Any variant_value; + bool bool_value = false; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &variant_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); + + EXPECT_EQ(9, int_value); + EXPECT_DOUBLE_EQ(7.5, variant_value.Get()); + EXPECT_TRUE(bool_value); +} + +TEST(DBusParamWriter, AppendDBusOutParams_NoArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + DBusParamWriter::AppendDBusOutParams(&writer); + EXPECT_EQ("", message->GetSignature()); +} + +TEST(DBusParamWriter, AppendDBusOutParams_OneArg) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + int32_t int_value_in{5}; + std::string string_value_in{"bar"}; + ObjectPath path_value_in{"/obj/path"}; + + DBusParamWriter::AppendDBusOutParams(&writer, &int_value_in); + EXPECT_EQ("i", message->GetSignature()); + DBusParamWriter::AppendDBusOutParams(&writer, &string_value_in); + EXPECT_EQ("is", message->GetSignature()); + DBusParamWriter::AppendDBusOutParams(&writer, &path_value_in); + EXPECT_EQ("iso", message->GetSignature()); + + int32_t int_value = 0; + std::string string_value; + ObjectPath path_value; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &path_value)); + + EXPECT_EQ(5, int_value); + EXPECT_EQ("bar", string_value); + EXPECT_EQ(ObjectPath{"/obj/path"}, path_value); +} + +TEST(DBusParamWriter, AppendDBusOutParams_ManyArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + int32_t int_value_in{8}; + Any variant_value_in{8.5}; + bool bool_value_in{true}; + DBusParamWriter::AppendDBusOutParams( + &writer, &int_value_in, &variant_value_in, &bool_value_in); + EXPECT_EQ("ivb", message->GetSignature()); + + int32_t int_value = 0; + Any variant_value; + bool bool_value = false; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &variant_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); + + EXPECT_EQ(8, int_value); + EXPECT_DOUBLE_EQ(8.5, variant_value.Get()); + EXPECT_TRUE(bool_value); +} + +TEST(DBusParamWriter, AppendDBusOutParams_Mixed_NoArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + DBusParamWriter::AppendDBusOutParams(&writer, 3, 5); + EXPECT_EQ("", message->GetSignature()); +} + +TEST(DBusParamWriter, AppendDBusOutParams_Mixed_OneArg) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + int32_t int_value_in{5}; + std::string str_value_in{"bar"}; + ObjectPath path_value_in{"/obj"}; + + DBusParamWriter::AppendDBusOutParams(&writer, 2, &int_value_in); + EXPECT_EQ("i", message->GetSignature()); + DBusParamWriter::AppendDBusOutParams(&writer, &str_value_in, 0); + EXPECT_EQ("is", message->GetSignature()); + DBusParamWriter::AppendDBusOutParams(&writer, 1, &path_value_in, 2); + EXPECT_EQ("iso", message->GetSignature()); + + int32_t int_value = 0; + std::string string_value; + ObjectPath path_value; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &path_value)); + + EXPECT_EQ(5, int_value); + EXPECT_EQ("bar", string_value); + EXPECT_EQ(ObjectPath{"/obj"}, path_value); +} + +TEST(DBusParamWriter, AppendDBusOutParams_Mixed_ManyArgs) { + std::unique_ptr message(Response::CreateEmpty().release()); + MessageWriter writer(message.get()); + int32_t int_value_in{8}; + Any variant_value_in{7.5}; + bool bool_value_in{true}; + DBusParamWriter::AppendDBusOutParams( + &writer, 0, &int_value_in, 1, &variant_value_in, 2, &bool_value_in, 3); + EXPECT_EQ("ivb", message->GetSignature()); + + int32_t int_value = 0; + Any variant_value; + bool bool_value = false; + + MessageReader reader(message.get()); + EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &variant_value)); + EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); + + EXPECT_EQ(8, int_value); + EXPECT_DOUBLE_EQ(7.5, variant_value.Get()); + EXPECT_TRUE(bool_value); +} +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_property.h b/brillo/dbus/dbus_property.h new file mode 100644 index 0000000..a7b11df --- /dev/null +++ b/brillo/dbus/dbus_property.h @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_PROPERTY_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_PROPERTY_H_ + +#include +#include + +namespace brillo { +namespace dbus_utils { + +// Re-implementation of dbus::Property that can handle any type supported by +// D-Bus data serialization layer, such as vectors, maps, tuples, etc. +// This class is pretty much a copy of dbus::Property from dbus/property.h +// except that it provides the implementations for PopValueFromReader and +// AppendSetValueToWriter. +template +class Property : public dbus::PropertyBase { + public: + Property() = default; + + // Retrieves the cached value. + const T& value() const { return value_; } + + // Requests an updated value from the remote object incurring a + // round-trip. |callback| will be called when the new value is available. + // This may not be implemented by some interfaces. + void Get(dbus::PropertySet::GetCallback callback) { + property_set()->Get(this, callback); + } + + // Synchronous vesion of Get(). + bool GetAndBlock() { + return property_set()->GetAndBlock(this); + } + + // Requests that the remote object change the property value to |value|, + // |callback| will be called to indicate the success or failure of the + // request, however the new value may not be available depending on the + // remote object. + void Set(const T& value, dbus::PropertySet::SetCallback callback) { + set_value_ = value; + property_set()->Set(this, callback); + } + + // Synchronous version of Set(). + bool SetAndBlock(const T& value) { + set_value_ = value; + return property_set()->SetAndBlock(this); + } + + // Method used by PropertySet to retrieve the value from a MessageReader, + // no knowledge of the contained type is required, this method returns + // true if its expected type was found, false if not. + bool PopValueFromReader(dbus::MessageReader* reader) override { + return PopVariantValueFromReader(reader, &value_); + } + + // Method used by PropertySet to append the set value to a MessageWriter, + // no knowledge of the contained type is required. + // Implementation provided by specialization. + void AppendSetValueToWriter(dbus::MessageWriter* writer) override { + AppendValueToWriterAsVariant(writer, set_value_); + } + + // Method used by test and stub implementations of dbus::PropertySet::Set + // to replace the property value with the set value without using a + // dbus::MessageReader. + void ReplaceValueWithSetValue() override { + value_ = set_value_; + property_set()->NotifyPropertyChanged(name()); + } + + // Method used by test and stub implementations to directly set the + // value of a property. + void ReplaceValue(const T& value) { + value_ = value; + property_set()->NotifyPropertyChanged(name()); + } + + private: + // Current cached value of the property. + T value_; + + // Replacement value of the property. + T set_value_; +}; + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_PROPERTY_H_ diff --git a/brillo/dbus/dbus_service_watcher.cc b/brillo/dbus/dbus_service_watcher.cc new file mode 100644 index 0000000..ede2e6e --- /dev/null +++ b/brillo/dbus/dbus_service_watcher.cc @@ -0,0 +1,39 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { +namespace dbus_utils { + +DBusServiceWatcher::DBusServiceWatcher( + scoped_refptr bus, + const std::string& connection_name, + const base::Closure& on_connection_vanish) + : bus_{bus}, + connection_name_{connection_name}, + on_connection_vanish_{on_connection_vanish} { + monitoring_callback_ = base::Bind( + &DBusServiceWatcher::OnServiceOwnerChange, weak_factory_.GetWeakPtr()); + // Register to listen, and then request the current owner; + bus_->ListenForServiceOwnerChange(connection_name_, monitoring_callback_); + bus_->GetServiceOwner(connection_name_, monitoring_callback_); +} + +DBusServiceWatcher::~DBusServiceWatcher() { + bus_->UnlistenForServiceOwnerChange( + connection_name_, monitoring_callback_); +} + +void DBusServiceWatcher::OnServiceOwnerChange( + const std::string& service_owner) { + if (service_owner.empty()) { + on_connection_vanish_.Run(); + } +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_service_watcher.h b/brillo/dbus/dbus_service_watcher.h new file mode 100644 index 0000000..730f1f2 --- /dev/null +++ b/brillo/dbus/dbus_service_watcher.h @@ -0,0 +1,53 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_SERVICE_WATCHER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_SERVICE_WATCHER_H_ + +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +// DBusServiceWatcher just asks the bus to notify us when the owner of a remote +// DBus connection transitions to the empty string. After registering a +// callback to be notified of name owner transitions, for the given +// |connection_name|, DBusServiceWatcher asks for the current owner. If at any +// point an empty string is found for the connection name owner, +// DBusServiceWatcher will call back to notify of the connection vanishing. +// +// The chief value of this class is that it manages the lifetime of the +// registered callback in the Bus, because failure to remove callbacks will +// cause the Bus to crash the process on destruction. +class BRILLO_EXPORT DBusServiceWatcher { + public: + DBusServiceWatcher(scoped_refptr bus, + const std::string& connection_name, + const base::Closure& on_connection_vanish); + virtual ~DBusServiceWatcher(); + virtual std::string connection_name() const { return connection_name_; } + + private: + void OnServiceOwnerChange(const std::string& service_owner); + + scoped_refptr bus_; + const std::string connection_name_; + dbus::Bus::GetServiceOwnerCallback monitoring_callback_; + base::Closure on_connection_vanish_; + + base::WeakPtrFactory weak_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(DBusServiceWatcher); +}; + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_SERVICE_WATCHER_H_ diff --git a/brillo/dbus/dbus_signal.cc b/brillo/dbus/dbus_signal.cc new file mode 100644 index 0000000..d227bd7 --- /dev/null +++ b/brillo/dbus/dbus_signal.cc @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { +namespace dbus_utils { + +DBusSignalBase::DBusSignalBase(DBusObject* dbus_object, + const std::string& interface_name, + const std::string& signal_name) + : interface_name_(interface_name), + signal_name_(signal_name), + dbus_object_(dbus_object) { +} + +bool DBusSignalBase::SendSignal(dbus::Signal* signal) const { + // This sends the signal asynchronously. However, the raw message inside + // the signal object is ref-counted, so we're fine to pass a stack-allocated + // Signal object here. + return dbus_object_->SendSignal(signal); +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/dbus_signal.h b/brillo/dbus/dbus_signal.h new file mode 100644 index 0000000..b19f1c8 --- /dev/null +++ b/brillo/dbus/dbus_signal.h @@ -0,0 +1,68 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_SIGNAL_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_SIGNAL_H_ + +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +class DBusObject; + +// Base class for D-Bus signal proxy classes. +// Used mostly to store the polymorphic DBusSignal<...> in a single map +// container inside DBusInterface object. +class BRILLO_EXPORT DBusSignalBase { + public: + DBusSignalBase(DBusObject* dbus_object, + const std::string& interface_name, + const std::string& signal_name); + virtual ~DBusSignalBase() = default; + + protected: + bool SendSignal(dbus::Signal* signal) const; + + std::string interface_name_; + std::string signal_name_; + + private: + DBusObject* dbus_object_; + + DISALLOW_COPY_AND_ASSIGN(DBusSignalBase); +}; + +// DBusSignal<...> is a concrete signal proxy class that knows about the +// exact number of signal arguments and their types. +template +class DBusSignal : public DBusSignalBase { + public: + // Expose the custom constructor from DBusSignalBase. + using DBusSignalBase::DBusSignalBase; + ~DBusSignal() override = default; + + // DBusSignal<...>::Send(...) dispatches the signal with the given arguments. + bool Send(const Args&... args) const { + dbus::Signal signal(interface_name_, signal_name_); + dbus::MessageWriter signal_writer(&signal); + DBusParamWriter::Append(&signal_writer, args...); + return SendSignal(&signal); + } + + private: + DISALLOW_COPY_AND_ASSIGN(DBusSignal); +}; + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_SIGNAL_H_ diff --git a/brillo/dbus/dbus_signal_handler.h b/brillo/dbus/dbus_signal_handler.h new file mode 100644 index 0000000..dfa1e78 --- /dev/null +++ b/brillo/dbus/dbus_signal_handler.h @@ -0,0 +1,68 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_ + +#include + +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +// brillo::dbus_utils::ConnectToSignal() is a helper function similar to +// dbus::ObjectProxy::ConnectToSignal() but the |signal_callback| is an actual +// C++ signal handler with expected signal parameters as native method args. +// +// brillo::dbus_utils::ConnectToSignal() actually registers a stub signal +// handler with D-Bus which has a standard signature that matches +// dbus::ObjectProxy::SignalCallback. +// +// When a D-Bus signal is emitted, the stub handler is invoked, which unpacks +// the expected parameters from dbus::Signal message and then calls +// |signal_callback| with unpacked arguments. +// +// If the signal message doesn't contain correct number or types of arguments, +// an error message is logged to the system log and the signal is ignored +// (|signal_callback| is not invoked). +template +void ConnectToSignal( + dbus::ObjectProxy* object_proxy, + const std::string& interface_name, + const std::string& signal_name, + base::Callback signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) { + // DBusParamReader::Invoke() needs a functor object, not a base::Callback. + // Wrap the callback with lambda so we can redirect the call. + auto signal_callback_wrapper = [signal_callback](const Args&... args) { + if (!signal_callback.is_null()) { + signal_callback.Run(args...); + } + }; + + // Raw signal handler stub method. When called, unpacks the signal arguments + // from |signal| message buffer and redirects the call to + // |signal_callback_wrapper| which, in turn, would call the user-provided + // |signal_callback|. + auto dbus_signal_callback = [signal_callback_wrapper](dbus::Signal* signal) { + dbus::MessageReader reader(signal); + DBusParamReader::Invoke( + signal_callback_wrapper, &reader, nullptr); + }; + + // Register our stub handler with D-Bus ObjectProxy. + object_proxy->ConnectToSignal(interface_name, + signal_name, + base::Bind(dbus_signal_callback), + on_connected_callback); +} + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_ diff --git a/brillo/dbus/dbus_signal_handler_unittest.cc b/brillo/dbus/dbus_signal_handler_unittest.cc new file mode 100644 index 0000000..e0bea10 --- /dev/null +++ b/brillo/dbus/dbus_signal_handler_unittest.cc @@ -0,0 +1,145 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include + +using testing::AnyNumber; +using testing::Return; +using testing::SaveArg; +using testing::_; + +namespace brillo { +namespace dbus_utils { + +const char kTestPath[] = "/test/path"; +const char kTestServiceName[] = "org.test.Object"; +const char kInterface[] = "org.test.Object.TestInterface"; +const char kSignal[] = "TestSignal"; + +class DBusSignalHandlerTest : public testing::Test { + public: + void SetUp() override { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock object proxy. + mock_object_proxy_ = new dbus::MockObjectProxy( + bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); + EXPECT_CALL(*bus_, + GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) + .WillRepeatedly(Return(mock_object_proxy_.get())); + } + + void TearDown() override { bus_ = nullptr; } + + protected: + template + void CallSignal(SignalHandlerSink* sink, Args... args) { + dbus::ObjectProxy::SignalCallback signal_callback; + EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _)) + .WillOnce(SaveArg<2>(&signal_callback)); + + brillo::dbus_utils::ConnectToSignal( + mock_object_proxy_.get(), + kInterface, + kSignal, + base::Bind(&SignalHandlerSink::Handler, base::Unretained(sink)), + {}); + + dbus::Signal signal(kInterface, kSignal); + dbus::MessageWriter writer(&signal); + DBusParamWriter::Append(&writer, args...); + signal_callback.Run(&signal); + } + + scoped_refptr bus_; + scoped_refptr mock_object_proxy_; +}; + +TEST_F(DBusSignalHandlerTest, ConnectToSignal) { + EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _)) + .Times(1); + + brillo::dbus_utils::ConnectToSignal( + mock_object_proxy_.get(), kInterface, kSignal, base::Closure{}, {}); +} + +TEST_F(DBusSignalHandlerTest, CallSignal_3Args) { + class SignalHandlerSink { + public: + MOCK_METHOD3(Handler, void(int, int, double)); + } sink; + + EXPECT_CALL(sink, Handler(10, 20, 30.5)).Times(1); + CallSignal(&sink, 10, 20, 30.5); +} + +TEST_F(DBusSignalHandlerTest, CallSignal_2Args) { + class SignalHandlerSink { + public: + // Take string both by reference and by value to make sure this works too. + MOCK_METHOD2(Handler, void(const std::string&, std::string)); + } sink; + + EXPECT_CALL(sink, Handler(std::string{"foo"}, std::string{"bar"})).Times(1); + CallSignal(&sink, std::string{"foo"}, std::string{"bar"}); +} + +TEST_F(DBusSignalHandlerTest, CallSignal_NoArgs) { + class SignalHandlerSink { + public: + MOCK_METHOD0(Handler, void()); + } sink; + + EXPECT_CALL(sink, Handler()).Times(1); + CallSignal(&sink); +} + +TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooManyArgs) { + class SignalHandlerSink { + public: + MOCK_METHOD0(Handler, void()); + } sink; + + // Handler() expects no args, but we send an int. + EXPECT_CALL(sink, Handler()).Times(0); + CallSignal(&sink, 5); +} + +TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooFewArgs) { + class SignalHandlerSink { + public: + MOCK_METHOD2(Handler, void(std::string, bool)); + } sink; + + // Handler() expects 2 args while we send it just one. + EXPECT_CALL(sink, Handler(_, _)).Times(0); + CallSignal(&sink, std::string{"bar"}); +} + +TEST_F(DBusSignalHandlerTest, CallSignal_Error_TypeMismatchArgs) { + class SignalHandlerSink { + public: + MOCK_METHOD2(Handler, void(std::string, bool)); + } sink; + + // Handler() expects "sb" while we send it "ii". + EXPECT_CALL(sink, Handler(_, _)).Times(0); + CallSignal(&sink, 1, 2); +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/exported_object_manager.cc b/brillo/dbus/exported_object_manager.cc new file mode 100644 index 0000000..61dae68 --- /dev/null +++ b/brillo/dbus/exported_object_manager.cc @@ -0,0 +1,105 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include + +using brillo::dbus_utils::AsyncEventSequencer; + +namespace brillo { + +namespace dbus_utils { + +ExportedObjectManager::ExportedObjectManager(scoped_refptr bus, + const dbus::ObjectPath& path) + : bus_(bus), dbus_object_(nullptr, bus, path) { +} + +void ExportedObjectManager::RegisterAsync( + const AsyncEventSequencer::CompletionAction& completion_callback) { + VLOG(1) << "Registering object manager"; + bus_->AssertOnOriginThread(); + DBusInterface* itf = + dbus_object_.AddOrGetInterface(dbus::kObjectManagerInterface); + itf->AddSimpleMethodHandler(dbus::kObjectManagerGetManagedObjects, + base::Unretained(this), + &ExportedObjectManager::HandleGetManagedObjects); + + signal_itf_added_ = itf->RegisterSignalOfType( + dbus::kObjectManagerInterfacesAdded); + signal_itf_removed_ = itf->RegisterSignalOfType( + dbus::kObjectManagerInterfacesRemoved); + dbus_object_.RegisterAsync(completion_callback); +} + +void ExportedObjectManager::ClaimInterface( + const dbus::ObjectPath& path, + const std::string& interface_name, + const ExportedPropertySet::PropertyWriter& property_writer) { + bus_->AssertOnOriginThread(); + // We're sending signals that look like: + // org.freedesktop.DBus.ObjectManager.InterfacesAdded ( + // OBJPATH object_path, + // DICT> interfaces_and_properties); + VariantDictionary property_dict; + property_writer.Run(&property_dict); + std::map interfaces_and_properties{ + {interface_name, property_dict} + }; + signal_itf_added_.lock()->Send(path, interfaces_and_properties); + registered_objects_[path][interface_name] = property_writer; +} + +void ExportedObjectManager::ReleaseInterface( + const dbus::ObjectPath& path, + const std::string& interface_name) { + bus_->AssertOnOriginThread(); + auto interfaces_for_path_itr = registered_objects_.find(path); + CHECK(interfaces_for_path_itr != registered_objects_.end()) + << "Attempting to signal interface removal for path " << path.value() + << " which was never registered."; + auto& interfaces_for_path = interfaces_for_path_itr->second; + auto property_for_interface_itr = interfaces_for_path.find(interface_name); + CHECK(property_for_interface_itr != interfaces_for_path.end()) + << "Attempted to remove interface " << interface_name << " from " + << path.value() << ", but this interface was never registered."; + interfaces_for_path.erase(interface_name); + if (interfaces_for_path.empty()) + registered_objects_.erase(path); + + // We're sending signals that look like: + // org.freedesktop.DBus.ObjectManager.InterfacesRemoved ( + // OBJPATH object_path, ARRAY interfaces); + signal_itf_removed_.lock()->Send(path, + std::vector{interface_name}); +} + +ExportedObjectManager::ObjectMap +ExportedObjectManager::HandleGetManagedObjects() { + // Implements the GetManagedObjects method: + // + // org.freedesktop.DBus.ObjectManager.GetManagedObjects ( + // out DICT>> ) + bus_->AssertOnOriginThread(); + ExportedObjectManager::ObjectMap objects; + for (const auto path_pair : registered_objects_) { + std::map& interfaces = + objects[path_pair.first]; + const InterfaceProperties& interface2properties = path_pair.second; + for (const auto interface : interface2properties) { + interface.second.Run(&interfaces[interface.first]); + } + } + return objects; +} + +} // namespace dbus_utils + +} // namespace brillo diff --git a/brillo/dbus/exported_object_manager.h b/brillo/dbus/exported_object_manager.h new file mode 100644 index 0000000..584c0b6 --- /dev/null +++ b/brillo/dbus/exported_object_manager.h @@ -0,0 +1,135 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { + +namespace dbus_utils { + +// ExportedObjectManager is a delegate that implements the +// org.freedesktop.DBus.ObjectManager interface on behalf of another +// object. It handles sending signals when new interfaces are added. +// +// This class is very similar to the ExportedPropertySet class, except that +// it allows objects to expose an object manager interface rather than the +// properties interface. +// +// Example usage: +// +// class ExampleObjectManager { +// public: +// ExampleObjectManager(dbus::Bus* bus) +// : object_manager_(bus, "/my/objects/path") { } +// +// void RegisterAsync(const CompletionAction& cb) { +// object_manager_.RegisterAsync(cb); +// } +// void ClaimInterface(const dbus::ObjectPath& path, +// const std::string& interface_name, +// const ExportedPropertySet::PropertyWriter& writer) { +// object_manager_->ClaimInterface(...); +// } +// void ReleaseInterface(const dbus::ObjectPath& path, +// const std::string& interface_name) { +// object_manager_->ReleaseInterface(...); +// } +// +// private: +// ExportedObjectManager object_manager_; +// }; +// +// class MyObjectClaimingAnInterface { +// public: +// MyObjectClaimingAnInterface(ExampleObjectManager* object_manager) +// : object_manager_(object_manager) {} +// +// void OnInitFinish(bool success) { +// if (!success) { /* handle that */ } +// object_manager_->ClaimInterface( +// my_path_, my_interface_, my_properties_.GetWriter()); +// } +// +// private: +// struct Properties : public ExportedPropertySet { +// public: +// /* Lots of interesting properties. */ +// }; +// +// Properties my_properties_; +// ExampleObjectManager* object_manager_; +// }; +class BRILLO_EXPORT ExportedObjectManager + : public base::SupportsWeakPtr { + public: + using ObjectMap = + std::map>; + using InterfaceProperties = + std::map; + + ExportedObjectManager(scoped_refptr bus, + const dbus::ObjectPath& path); + virtual ~ExportedObjectManager() = default; + + // Registers methods implementing the ObjectManager interface on the object + // exported on the path given in the constructor. Must be called on the + // origin thread. + virtual void RegisterAsync( + const brillo::dbus_utils::AsyncEventSequencer::CompletionAction& + completion_callback); + + // Trigger a signal that |path| has added an interface |interface_name| + // with properties as given by |writer|. + virtual void ClaimInterface( + const dbus::ObjectPath& path, + const std::string& interface_name, + const ExportedPropertySet::PropertyWriter& writer); + + // Trigger a signal that |path| has removed an interface |interface_name|. + virtual void ReleaseInterface(const dbus::ObjectPath& path, + const std::string& interface_name); + + const scoped_refptr& GetBus() const { return bus_; } + + private: + BRILLO_PRIVATE ObjectMap HandleGetManagedObjects(); + + scoped_refptr bus_; + brillo::dbus_utils::DBusObject dbus_object_; + // Tracks all objects currently known to the ExportedObjectManager. + std::map registered_objects_; + + using SignalInterfacesAdded = + DBusSignal>; + using SignalInterfacesRemoved = + DBusSignal>; + + std::weak_ptr signal_itf_added_; + std::weak_ptr signal_itf_removed_; + + friend class ExportedObjectManagerTest; + DISALLOW_COPY_AND_ASSIGN(ExportedObjectManager); +}; + +} // namespace dbus_utils + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_ diff --git a/brillo/dbus/exported_object_manager_unittest.cc b/brillo/dbus/exported_object_manager_unittest.cc new file mode 100644 index 0000000..00fe108 --- /dev/null +++ b/brillo/dbus/exported_object_manager_unittest.cc @@ -0,0 +1,194 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using ::testing::AnyNumber; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::_; + +namespace brillo { + +namespace dbus_utils { + +namespace { + +const dbus::ObjectPath kTestPath(std::string("/test/om_path")); +const dbus::ObjectPath kClaimedTestPath(std::string("/test/claimed_path")); +const std::string kClaimedInterface("claimed.interface"); +const std::string kTestPropertyName("PropertyName"); +const std::string kTestPropertyValue("PropertyValue"); + +void WriteTestPropertyDict(VariantDictionary* dict) { + dict->insert(std::make_pair(kTestPropertyName, Any(kTestPropertyValue))); +} + +void ReadTestPropertyDict(dbus::MessageReader* reader) { + dbus::MessageReader all_properties(nullptr); + dbus::MessageReader each_property(nullptr); + ASSERT_TRUE(reader->PopArray(&all_properties)); + ASSERT_TRUE(all_properties.PopDictEntry(&each_property)); + std::string property_name; + std::string property_value; + ASSERT_TRUE(each_property.PopString(&property_name)); + ASSERT_TRUE(each_property.PopVariantOfString(&property_value)); + EXPECT_FALSE(each_property.HasMoreData()); + EXPECT_FALSE(all_properties.HasMoreData()); + EXPECT_EQ(property_name, kTestPropertyName); + EXPECT_EQ(property_value, kTestPropertyValue); +} + +void VerifyInterfaceClaimSignal(dbus::Signal* signal) { + EXPECT_EQ(signal->GetInterface(), std::string(dbus::kObjectManagerInterface)); + EXPECT_EQ(signal->GetMember(), + std::string(dbus::kObjectManagerInterfacesAdded)); + // org.freedesktop.DBus.ObjectManager.InterfacesAdded ( + // OBJPATH object_path, + // DICT> interfaces_and_properties); + dbus::MessageReader reader(signal); + dbus::MessageReader all_interfaces(nullptr); + dbus::MessageReader each_interface(nullptr); + dbus::ObjectPath path; + ASSERT_TRUE(reader.PopObjectPath(&path)); + ASSERT_TRUE(reader.PopArray(&all_interfaces)); + ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface)); + std::string interface_name; + ASSERT_TRUE(each_interface.PopString(&interface_name)); + ReadTestPropertyDict(&each_interface); + EXPECT_FALSE(each_interface.HasMoreData()); + EXPECT_FALSE(all_interfaces.HasMoreData()); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(interface_name, kClaimedInterface); + EXPECT_EQ(path, kClaimedTestPath); +} + +void VerifyInterfaceDropSignal(dbus::Signal* signal) { + EXPECT_EQ(signal->GetInterface(), std::string(dbus::kObjectManagerInterface)); + EXPECT_EQ(signal->GetMember(), + std::string(dbus::kObjectManagerInterfacesRemoved)); + // org.freedesktop.DBus.ObjectManager.InterfacesRemoved ( + // OBJPATH object_path, ARRAY interfaces); + dbus::MessageReader reader(signal); + dbus::MessageReader each_interface(nullptr); + dbus::ObjectPath path; + ASSERT_TRUE(reader.PopObjectPath(&path)); + ASSERT_TRUE(reader.PopArray(&each_interface)); + std::string interface_name; + ASSERT_TRUE(each_interface.PopString(&interface_name)); + EXPECT_FALSE(each_interface.HasMoreData()); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(interface_name, kClaimedInterface); + EXPECT_EQ(path, kClaimedTestPath); +} + +} // namespace + +class ExportedObjectManagerTest : public ::testing::Test { + public: + void SetUp() override { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock exported object. + mock_exported_object_ = new dbus::MockExportedObject(bus_.get(), kTestPath); + EXPECT_CALL(*bus_, GetExportedObject(kTestPath)).Times(1).WillOnce( + Return(mock_exported_object_.get())); + EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _)) + .Times(AnyNumber()); + om_.reset(new ExportedObjectManager(bus_.get(), kTestPath)); + property_writer_ = base::Bind(&WriteTestPropertyDict); + om_->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction()); + } + + void TearDown() override { + EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1); + om_.reset(); + bus_ = nullptr; + } + + std::unique_ptr CallHandleGetManagedObjects() { + dbus::MethodCall method_call(dbus::kObjectManagerInterface, + dbus::kObjectManagerGetManagedObjects); + method_call.SetSerial(1234); + return brillo::dbus_utils::testing::CallMethod(om_->dbus_object_, + &method_call); + } + + scoped_refptr bus_; + scoped_refptr mock_exported_object_; + std::unique_ptr om_; + ExportedPropertySet::PropertyWriter property_writer_; +}; + +TEST_F(ExportedObjectManagerTest, ClaimInterfaceSendsSignals) { + EXPECT_CALL(*mock_exported_object_, SendSignal(_)) + .Times(1).WillOnce(Invoke(&VerifyInterfaceClaimSignal)); + om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); +} + +TEST_F(ExportedObjectManagerTest, ReleaseInterfaceSendsSignals) { + InSequence dummy; + EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); + EXPECT_CALL(*mock_exported_object_, SendSignal(_)) + .Times(1).WillOnce(Invoke(&VerifyInterfaceDropSignal)); + om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); + om_->ReleaseInterface(kClaimedTestPath, kClaimedInterface); +} + +TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseEmptyCorrectness) { + auto response = CallHandleGetManagedObjects(); + dbus::MessageReader reader(response.get()); + dbus::MessageReader all_paths(nullptr); + ASSERT_TRUE(reader.PopArray(&all_paths)); + EXPECT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseCorrectness) { + // org.freedesktop.DBus.ObjectManager.GetManagedObjects ( + // out DICT>> ) + EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); + om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); + auto response = CallHandleGetManagedObjects(); + dbus::MessageReader reader(response.get()); + dbus::MessageReader all_paths(nullptr); + dbus::MessageReader each_path(nullptr); + dbus::MessageReader all_interfaces(nullptr); + dbus::MessageReader each_interface(nullptr); + ASSERT_TRUE(reader.PopArray(&all_paths)); + ASSERT_TRUE(all_paths.PopDictEntry(&each_path)); + dbus::ObjectPath path; + ASSERT_TRUE(each_path.PopObjectPath(&path)); + ASSERT_TRUE(each_path.PopArray(&all_interfaces)); + ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface)); + std::string interface_name; + ASSERT_TRUE(each_interface.PopString(&interface_name)); + ReadTestPropertyDict(&each_interface); + EXPECT_FALSE(each_interface.HasMoreData()); + EXPECT_FALSE(all_interfaces.HasMoreData()); + EXPECT_FALSE(each_path.HasMoreData()); + EXPECT_FALSE(all_paths.HasMoreData()); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(path, kClaimedTestPath); + EXPECT_EQ(interface_name, kClaimedInterface); +} + +} // namespace dbus_utils + +} // namespace brillo diff --git a/brillo/dbus/exported_property_set.cc b/brillo/dbus/exported_property_set.cc new file mode 100644 index 0000000..8d6ae65 --- /dev/null +++ b/brillo/dbus/exported_property_set.cc @@ -0,0 +1,174 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include // For kPropertyInterface + +#include +#include +#include + +using brillo::dbus_utils::AsyncEventSequencer; + +namespace brillo { + +namespace dbus_utils { + +ExportedPropertySet::ExportedPropertySet(dbus::Bus* bus) + : bus_(bus), weak_ptr_factory_(this) { +} + +void ExportedPropertySet::OnPropertiesInterfaceExported( + DBusInterface* prop_interface) { + signal_properties_changed_ = + prop_interface->RegisterSignalOfType( + dbus::kPropertiesChanged); +} + +ExportedPropertySet::PropertyWriter ExportedPropertySet::GetPropertyWriter( + const std::string& interface_name) { + return base::Bind(&ExportedPropertySet::WritePropertiesToDict, + weak_ptr_factory_.GetWeakPtr(), + interface_name); +} + +void ExportedPropertySet::RegisterProperty( + const std::string& interface_name, + const std::string& property_name, + ExportedPropertyBase* exported_property) { + bus_->AssertOnOriginThread(); + auto& prop_map = properties_[interface_name]; + auto res = prop_map.insert(std::make_pair(property_name, exported_property)); + CHECK(res.second) << "Property '" << property_name << "' already exists"; + // Technically, the property set exists longer than the properties themselves, + // so we could use Unretained here rather than a weak pointer. + ExportedPropertyBase::OnUpdateCallback cb = + base::Bind(&ExportedPropertySet::HandlePropertyUpdated, + weak_ptr_factory_.GetWeakPtr(), + interface_name, + property_name); + exported_property->SetUpdateCallback(cb); +} + +VariantDictionary ExportedPropertySet::HandleGetAll( + const std::string& interface_name) { + bus_->AssertOnOriginThread(); + return GetInterfaceProperties(interface_name); +} + +VariantDictionary ExportedPropertySet::GetInterfaceProperties( + const std::string& interface_name) const { + VariantDictionary properties; + auto property_map_itr = properties_.find(interface_name); + if (property_map_itr != properties_.end()) { + for (const auto& kv : property_map_itr->second) + properties.insert(std::make_pair(kv.first, kv.second->GetValue())); + } + return properties; +} + +void ExportedPropertySet::WritePropertiesToDict( + const std::string& interface_name, + VariantDictionary* dict) { + *dict = GetInterfaceProperties(interface_name); +} + +bool ExportedPropertySet::HandleGet(brillo::ErrorPtr* error, + const std::string& interface_name, + const std::string& property_name, + brillo::Any* result) { + bus_->AssertOnOriginThread(); + auto property_map_itr = properties_.find(interface_name); + if (property_map_itr == properties_.end()) { + brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface on object."); + return false; + } + LOG(INFO) << "Looking for " << property_name << " on " << interface_name; + auto property_itr = property_map_itr->second.find(property_name); + if (property_itr == property_map_itr->second.end()) { + brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property on interface."); + return false; + } + *result = property_itr->second->GetValue(); + return true; +} + +bool ExportedPropertySet::HandleSet(brillo::ErrorPtr* error, + const std::string& interface_name, + const std::string& property_name, + const brillo::Any& value) { + bus_->AssertOnOriginThread(); + auto property_map_itr = properties_.find(interface_name); + if (property_map_itr == properties_.end()) { + brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface on object."); + return false; + } + LOG(INFO) << "Looking for " << property_name << " on " << interface_name; + auto property_itr = property_map_itr->second.find(property_name); + if (property_itr == property_map_itr->second.end()) { + brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property on interface."); + return false; + } + + return property_itr->second->SetValue(error, value); +} + +void ExportedPropertySet::HandlePropertyUpdated( + const std::string& interface_name, + const std::string& property_name, + const ExportedPropertyBase* exported_property) { + bus_->AssertOnOriginThread(); + // Send signal only if the object has been exported successfully. + // This could happen when a property value is changed (which triggers + // the notification) before D-Bus interface is completely exported/claimed. + auto signal = signal_properties_changed_.lock(); + if (!signal) + return; + VariantDictionary changed_properties{ + {property_name, exported_property->GetValue()}}; + // The interface specification tells us to include this list of properties + // which have changed, but for whom no value is conveyed. Currently, we + // don't do anything interesting here. + std::vector invalidated_properties; // empty. + signal->Send(interface_name, changed_properties, invalidated_properties); +} + +void ExportedPropertyBase::NotifyPropertyChanged() { + // These is a brief period after the construction of an ExportedProperty + // when this callback is not initialized because the property has not + // been registered with the parent ExportedPropertySet. During this period + // users should be initializing values via SetValue, and no notifications + // should be triggered by the ExportedPropertySet. + if (!on_update_callback_.is_null()) { + on_update_callback_.Run(this); + } +} + +void ExportedPropertyBase::SetUpdateCallback(const OnUpdateCallback& cb) { + on_update_callback_ = cb; +} + +void ExportedPropertyBase::SetAccessMode( + ExportedPropertyBase::Access access_mode) { + access_mode_ = access_mode; +} + +ExportedPropertyBase::Access ExportedPropertyBase::GetAccessMode() const { + return access_mode_; +} + +} // namespace dbus_utils + +} // namespace brillo diff --git a/brillo/dbus/exported_property_set.h b/brillo/dbus/exported_property_set.h new file mode 100644 index 0000000..16f5086 --- /dev/null +++ b/brillo/dbus/exported_property_set.h @@ -0,0 +1,226 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_EXPORTED_PROPERTY_SET_H_ +#define LIBCHROMEOS_BRILLO_DBUS_EXPORTED_PROPERTY_SET_H_ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { + +namespace dbus_utils { + +// This class may be used to implement the org.freedesktop.DBus.Properties +// interface. It sends the update signal on property updates: +// +// org.freedesktop.DBus.Properties.PropertiesChanged ( +// STRING interface_name, +// DICT changed_properties, +// ARRAY invalidated_properties); +// +// +// and implements the required methods of the interface: +// +// org.freedesktop.DBus.Properties.Get(in STRING interface_name, +// in STRING property_name, +// out VARIANT value); +// org.freedesktop.DBus.Properties.Set(in STRING interface_name, +// in STRING property_name, +// in VARIANT value); +// org.freedesktop.DBus.Properties.GetAll(in STRING interface_name, +// out DICT props); +// +// This class is very similar to the PropertySet class in Chrome, except that +// it allows objects to expose properties rather than to consume them. +// It is used as part of DBusObject to implement D-Bus object properties on +// registered interfaces. See description of DBusObject class for more details. + +class DBusInterface; +class DBusObject; + +class BRILLO_EXPORT ExportedPropertyBase { + public: + enum class Access { + kReadOnly, + kWriteOnly, + kReadWrite, + }; + + ExportedPropertyBase() = default; + virtual ~ExportedPropertyBase() = default; + + using OnUpdateCallback = base::Callback; + + // Called by ExportedPropertySet to register a callback. This callback + // triggers ExportedPropertySet to send a signal from the properties + // interface of the exported object. + virtual void SetUpdateCallback(const OnUpdateCallback& cb); + + // Returns the contained value as Any. + virtual brillo::Any GetValue() const = 0; + + virtual bool SetValue(brillo::ErrorPtr* error, + const brillo::Any& value) = 0; + + void SetAccessMode(Access access_mode); + Access GetAccessMode() const; + + protected: + // Notify the listeners of OnUpdateCallback that the property has changed. + void NotifyPropertyChanged(); + + private: + OnUpdateCallback on_update_callback_; + // Default to read-only. + Access access_mode_{Access::kReadOnly}; +}; + +class BRILLO_EXPORT ExportedPropertySet { + public: + using PropertyWriter = base::Callback; + + explicit ExportedPropertySet(dbus::Bus* bus); + virtual ~ExportedPropertySet() = default; + + // Called to notify ExportedPropertySet that the Properties interface of the + // D-Bus object has been exported successfully and property notification + // signals can be sent out. + void OnPropertiesInterfaceExported(DBusInterface* prop_interface); + + // Return a callback that knows how to write this property set's properties + // to a message. This writer retains a weak pointer to this, and must + // only be invoked on the same thread as the rest of ExportedPropertySet. + PropertyWriter GetPropertyWriter(const std::string& interface_name); + + void RegisterProperty(const std::string& interface_name, + const std::string& property_name, + ExportedPropertyBase* exported_property); + + // D-Bus methods for org.freedesktop.DBus.Properties interface. + VariantDictionary HandleGetAll(const std::string& interface_name); + bool HandleGet(brillo::ErrorPtr* error, + const std::string& interface_name, + const std::string& property_name, + brillo::Any* result); + // While Properties.Set has a handler to complete the interface, we don't + // support writable properties. This is almost a feature, since bindings for + // many languages don't support errors coming back from invalid writes. + // Instead, use setters in exposed interfaces. + bool HandleSet(brillo::ErrorPtr* error, + const std::string& interface_name, + const std::string& property_name, + const brillo::Any& value); + // Returns a string-to-variant map of all the properties for the given + // interface and their values. + VariantDictionary GetInterfaceProperties( + const std::string& interface_name) const; + + private: + // Used to write the dictionary of string->variant to a message. + // This dictionary represents the property name/value pairs for the + // given interface. + BRILLO_PRIVATE void WritePropertiesToDict(const std::string& interface_name, + VariantDictionary* dict); + BRILLO_PRIVATE void HandlePropertyUpdated( + const std::string& interface_name, + const std::string& property_name, + const ExportedPropertyBase* exported_property); + + dbus::Bus* bus_; // weak; owned by outer DBusObject containing this object. + // This is a map from interface name -> property name -> pointer to property. + std::map> + properties_; + + // D-Bus callbacks may last longer the property set exporting those methods. + base::WeakPtrFactory weak_ptr_factory_; + + using SignalPropertiesChanged = + DBusSignal>; + + std::weak_ptr signal_properties_changed_; + + friend class DBusObject; + friend class ExportedPropertySetTest; + DISALLOW_COPY_AND_ASSIGN(ExportedPropertySet); +}; + +template +class ExportedProperty : public ExportedPropertyBase { + public: + ExportedProperty() = default; + ~ExportedProperty() override = default; + + // Retrieves the current value. + const T& value() const { return value_; } + + // Set the value exposed to remote applications. This triggers notifications + // of changes over the Properties interface. + void SetValue(const T& new_value) { + if (value_ != new_value) { + value_ = new_value; + this->NotifyPropertyChanged(); + } + } + + // Set the validator for value checking when setting the property by remote + // application. + void SetValidator( + const base::Callback& validator) { + validator_ = validator; + } + + // Implementation provided by specialization. + brillo::Any GetValue() const override { return value_; } + + bool SetValue(brillo::ErrorPtr* error, + const brillo::Any& value) override { + if (GetAccessMode() == ExportedPropertyBase::Access::kReadOnly) { + brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_PROPERTY_READ_ONLY, + "Property is read-only."); + return false; + } + if (!value.IsTypeCompatible()) { + brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_INVALID_ARGS, + "Argument type mismatched."); + return false; + } + if (value_ == value.Get()) { + // No change to the property value, nothing to be done. + return true; + } + if (!validator_.is_null() && !validator_.Run(error, value.Get())) { + return false; + } + value_ = value.Get(); + return true; + } + + private: + T value_{}; + base::Callback validator_; + + DISALLOW_COPY_AND_ASSIGN(ExportedProperty); +}; + +} // namespace dbus_utils + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_EXPORTED_PROPERTY_SET_H_ diff --git a/brillo/dbus/exported_property_set_unittest.cc b/brillo/dbus/exported_property_set_unittest.cc new file mode 100644 index 0000000..c0deace --- /dev/null +++ b/brillo/dbus/exported_property_set_unittest.cc @@ -0,0 +1,592 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::_; +using ::testing::Unused; + +namespace brillo { + +namespace dbus_utils { + +namespace { + +const char kBoolPropName[] = "BoolProp"; +const char kUint8PropName[] = "Uint8Prop"; +const char kInt16PropName[] = "Int16Prop"; +const char kUint16PropName[] = "Uint16Prop"; +const char kInt32PropName[] = "Int32Prop"; +const char kUint32PropName[] = "Uint32Prop"; +const char kInt64PropName[] = "Int64Prop"; +const char kUint64PropName[] = "Uint64Prop"; +const char kDoublePropName[] = "DoubleProp"; +const char kStringPropName[] = "StringProp"; +const char kPathPropName[] = "PathProp"; +const char kStringListPropName[] = "StringListProp"; +const char kPathListPropName[] = "PathListProp"; +const char kUint8ListPropName[] = "Uint8ListProp"; + +const char kTestInterface1[] = "org.chromium.TestInterface1"; +const char kTestInterface2[] = "org.chromium.TestInterface2"; +const char kTestInterface3[] = "org.chromium.TestInterface3"; + +const std::string kTestString("lies"); +const dbus::ObjectPath kMethodsExportedOnPath(std::string("/export")); +const dbus::ObjectPath kTestObjectPathInit(std::string("/path_init")); +const dbus::ObjectPath kTestObjectPathUpdate(std::string("/path_update")); + +} // namespace + +class ExportedPropertySetTest : public ::testing::Test { + public: + struct Properties { + public: + ExportedProperty bool_prop_; + ExportedProperty uint8_prop_; + ExportedProperty int16_prop_; + ExportedProperty uint16_prop_; + ExportedProperty int32_prop_; + ExportedProperty uint32_prop_; + ExportedProperty int64_prop_; + ExportedProperty uint64_prop_; + ExportedProperty double_prop_; + ExportedProperty string_prop_; + ExportedProperty path_prop_; + ExportedProperty> stringlist_prop_; + ExportedProperty> pathlist_prop_; + ExportedProperty> uint8list_prop_; + + Properties(scoped_refptr bus, const dbus::ObjectPath& path) + : dbus_object_(nullptr, bus, path) { + // The empty string is not a valid value for an ObjectPath. + path_prop_.SetValue(kTestObjectPathInit); + DBusInterface* itf1 = dbus_object_.AddOrGetInterface(kTestInterface1); + itf1->AddProperty(kBoolPropName, &bool_prop_); + itf1->AddProperty(kUint8PropName, &uint8_prop_); + itf1->AddProperty(kInt16PropName, &int16_prop_); + // I chose this weird grouping because N=2 is about all the permutations + // of GetAll that I want to anticipate. + DBusInterface* itf2 = dbus_object_.AddOrGetInterface(kTestInterface2); + itf2->AddProperty(kUint16PropName, &uint16_prop_); + itf2->AddProperty(kInt32PropName, &int32_prop_); + DBusInterface* itf3 = dbus_object_.AddOrGetInterface(kTestInterface3); + itf3->AddProperty(kUint32PropName, &uint32_prop_); + itf3->AddProperty(kInt64PropName, &int64_prop_); + itf3->AddProperty(kUint64PropName, &uint64_prop_); + itf3->AddProperty(kDoublePropName, &double_prop_); + itf3->AddProperty(kStringPropName, &string_prop_); + itf3->AddProperty(kPathPropName, &path_prop_); + itf3->AddProperty(kStringListPropName, &stringlist_prop_); + itf3->AddProperty(kPathListPropName, &pathlist_prop_); + itf3->AddProperty(kUint8ListPropName, &uint8list_prop_); + dbus_object_.RegisterAsync( + AsyncEventSequencer::GetDefaultCompletionAction()); + } + virtual ~Properties() {} + + DBusObject dbus_object_; + }; + + void SetUp() override { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock exported object. + mock_exported_object_ = + new dbus::MockExportedObject(bus_.get(), kMethodsExportedOnPath); + EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath)) + .Times(1).WillOnce(Return(mock_exported_object_.get())); + + EXPECT_CALL(*mock_exported_object_, + ExportMethod(dbus::kPropertiesInterface, _, _, _)).Times(3); + p_.reset(new Properties(bus_, kMethodsExportedOnPath)); + } + + void TearDown() override { + EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1); + } + + void AssertMethodReturnsError(dbus::MethodCall* method_call) { + method_call->SetSerial(123); + auto response = testing::CallMethod(p_->dbus_object_, method_call); + ASSERT_NE(dynamic_cast(response.get()), nullptr); + } + + std::unique_ptr GetPropertyOnInterface( + const std::string& interface_name, + const std::string& property_name) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString(interface_name); + writer.AppendString(property_name); + return testing::CallMethod(p_->dbus_object_, &method_call); + } + + std::unique_ptr SetPropertyOnInterface( + const std::string& interface_name, + const std::string& property_name, + const brillo::Any& value) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesSet); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString(interface_name); + writer.AppendString(property_name); + dbus_utils::AppendValueToWriter(&writer, value); + return testing::CallMethod(p_->dbus_object_, &method_call); + } + + std::unique_ptr last_response_; + scoped_refptr bus_; + scoped_refptr mock_exported_object_; + std::unique_ptr p_; +}; + +template +class PropertyValidatorObserver { + public: + PropertyValidatorObserver() + : validate_property_callback_( + base::Bind(&PropertyValidatorObserver::ValidateProperty, + base::Unretained(this))) {} + virtual ~PropertyValidatorObserver() {} + + MOCK_METHOD2_T(ValidateProperty, + bool(brillo::ErrorPtr* error, const T& value)); + + const base::Callback& + validate_property_callback() const { + return validate_property_callback_; + } + + private: + base::Callback + validate_property_callback_; + + DISALLOW_COPY_AND_ASSIGN(PropertyValidatorObserver); +}; + +TEST_F(ExportedPropertySetTest, UpdateNotifications) { + EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(14); + p_->bool_prop_.SetValue(true); + p_->uint8_prop_.SetValue(1); + p_->int16_prop_.SetValue(1); + p_->uint16_prop_.SetValue(1); + p_->int32_prop_.SetValue(1); + p_->uint32_prop_.SetValue(1); + p_->int64_prop_.SetValue(1); + p_->uint64_prop_.SetValue(1); + p_->double_prop_.SetValue(1.0); + p_->string_prop_.SetValue(kTestString); + p_->path_prop_.SetValue(kTestObjectPathUpdate); + p_->stringlist_prop_.SetValue({kTestString}); + p_->pathlist_prop_.SetValue({kTestObjectPathUpdate}); + p_->uint8list_prop_.SetValue({1}); +} + +TEST_F(ExportedPropertySetTest, UpdateToSameValue) { + EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); + p_->bool_prop_.SetValue(true); + p_->bool_prop_.SetValue(true); +} + +TEST_F(ExportedPropertySetTest, GetAllNoArgs) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGetAll); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetAllInvalidInterface) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGetAll); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString("org.chromium.BadInterface"); + auto response = testing::CallMethod(p_->dbus_object_, &method_call); + dbus::MessageReader response_reader(response.get()); + dbus::MessageReader dict_reader(nullptr); + ASSERT_TRUE(response_reader.PopArray(&dict_reader)); + // The response should just be a an empty array, since there are no properties + // on this interface. The spec doesn't say much about error conditions here, + // so I'm going to assume this is a valid implementation. + ASSERT_FALSE(dict_reader.HasMoreData()); + ASSERT_FALSE(response_reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetAllExtraArgs) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGetAll); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kTestInterface1); + writer.AppendString(kTestInterface1); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetAllCorrectness) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGetAll); + method_call.SetSerial(123); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kTestInterface2); + auto response = testing::CallMethod(p_->dbus_object_, &method_call); + dbus::MessageReader response_reader(response.get()); + dbus::MessageReader dict_reader(nullptr); + dbus::MessageReader entry_reader(nullptr); + ASSERT_TRUE(response_reader.PopArray(&dict_reader)); + ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader)); + std::string property_name; + ASSERT_TRUE(entry_reader.PopString(&property_name)); + uint16_t value16; + int32_t value32; + if (property_name.compare(kUint16PropName) == 0) { + ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16)); + ASSERT_FALSE(entry_reader.HasMoreData()); + ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader)); + ASSERT_TRUE(entry_reader.PopString(&property_name)); + ASSERT_EQ(property_name.compare(kInt32PropName), 0); + ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32)); + } else { + ASSERT_EQ(property_name.compare(kInt32PropName), 0); + ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32)); + ASSERT_FALSE(entry_reader.HasMoreData()); + ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader)); + ASSERT_TRUE(entry_reader.PopString(&property_name)); + ASSERT_EQ(property_name.compare(kUint16PropName), 0); + ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16)); + } + ASSERT_FALSE(entry_reader.HasMoreData()); + ASSERT_FALSE(dict_reader.HasMoreData()); + ASSERT_FALSE(response_reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetNoArgs) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetInvalidInterface) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + dbus::MessageWriter writer(&method_call); + writer.AppendString("org.chromium.BadInterface"); + writer.AppendString(kInt16PropName); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetBadPropertyName) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kTestInterface1); + writer.AppendString("IAmNotAProperty"); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetPropIfMismatch) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kTestInterface1); + writer.AppendString(kStringPropName); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetNoPropertyName) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kTestInterface1); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetExtraArgs) { + dbus::MethodCall method_call(dbus::kPropertiesInterface, + dbus::kPropertiesGet); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kTestInterface1); + writer.AppendString(kBoolPropName); + writer.AppendString("Extra param"); + AssertMethodReturnsError(&method_call); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithBool) { + auto response = GetPropertyOnInterface(kTestInterface1, kBoolPropName); + dbus::MessageReader reader(response.get()); + bool value; + ASSERT_TRUE(reader.PopVariantOfBool(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithUint8) { + auto response = GetPropertyOnInterface(kTestInterface1, kUint8PropName); + dbus::MessageReader reader(response.get()); + uint8_t value; + ASSERT_TRUE(reader.PopVariantOfByte(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithInt16) { + auto response = GetPropertyOnInterface(kTestInterface1, kInt16PropName); + dbus::MessageReader reader(response.get()); + int16_t value; + ASSERT_TRUE(reader.PopVariantOfInt16(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithUint16) { + auto response = GetPropertyOnInterface(kTestInterface2, kUint16PropName); + dbus::MessageReader reader(response.get()); + uint16_t value; + ASSERT_TRUE(reader.PopVariantOfUint16(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithInt32) { + auto response = GetPropertyOnInterface(kTestInterface2, kInt32PropName); + dbus::MessageReader reader(response.get()); + int32_t value; + ASSERT_TRUE(reader.PopVariantOfInt32(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithUint32) { + auto response = GetPropertyOnInterface(kTestInterface3, kUint32PropName); + dbus::MessageReader reader(response.get()); + uint32_t value; + ASSERT_TRUE(reader.PopVariantOfUint32(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithInt64) { + auto response = GetPropertyOnInterface(kTestInterface3, kInt64PropName); + dbus::MessageReader reader(response.get()); + int64_t value; + ASSERT_TRUE(reader.PopVariantOfInt64(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithUint64) { + auto response = GetPropertyOnInterface(kTestInterface3, kUint64PropName); + dbus::MessageReader reader(response.get()); + uint64_t value; + ASSERT_TRUE(reader.PopVariantOfUint64(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithDouble) { + auto response = GetPropertyOnInterface(kTestInterface3, kDoublePropName); + dbus::MessageReader reader(response.get()); + double value; + ASSERT_TRUE(reader.PopVariantOfDouble(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithString) { + auto response = GetPropertyOnInterface(kTestInterface3, kStringPropName); + dbus::MessageReader reader(response.get()); + std::string value; + ASSERT_TRUE(reader.PopVariantOfString(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithPath) { + auto response = GetPropertyOnInterface(kTestInterface3, kPathPropName); + dbus::MessageReader reader(response.get()); + dbus::ObjectPath value; + ASSERT_TRUE(reader.PopVariantOfObjectPath(&value)); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithStringList) { + auto response = GetPropertyOnInterface(kTestInterface3, kStringListPropName); + dbus::MessageReader reader(response.get()); + dbus::MessageReader variant_reader(nullptr); + std::vector value; + ASSERT_TRUE(reader.PopVariant(&variant_reader)); + ASSERT_TRUE(variant_reader.PopArrayOfStrings(&value)); + ASSERT_FALSE(variant_reader.HasMoreData()); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithPathList) { + auto response = GetPropertyOnInterface(kTestInterface3, kPathListPropName); + dbus::MessageReader reader(response.get()); + dbus::MessageReader variant_reader(nullptr); + std::vector value; + ASSERT_TRUE(reader.PopVariant(&variant_reader)); + ASSERT_TRUE(variant_reader.PopArrayOfObjectPaths(&value)); + ASSERT_FALSE(variant_reader.HasMoreData()); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, GetWorksWithUint8List) { + auto response = GetPropertyOnInterface(kTestInterface3, kPathListPropName); + dbus::MessageReader reader(response.get()); + dbus::MessageReader variant_reader(nullptr); + const uint8_t* buffer; + size_t buffer_len; + ASSERT_TRUE(reader.PopVariant(&variant_reader)); + // |buffer| remains under the control of the MessageReader. + ASSERT_TRUE(variant_reader.PopArrayOfBytes(&buffer, &buffer_len)); + ASSERT_FALSE(variant_reader.HasMoreData()); + ASSERT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedPropertySetTest, SetInvalidInterface) { + auto response = SetPropertyOnInterface( + "BadInterfaceName", kStringPropName, brillo::Any(kTestString)); + ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(DBUS_ERROR_UNKNOWN_INTERFACE, response->GetErrorName()); +} + +TEST_F(ExportedPropertySetTest, SetBadPropertyName) { + auto response = SetPropertyOnInterface( + kTestInterface3, "IAmNotAProperty", brillo::Any(kTestString)); + ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(DBUS_ERROR_UNKNOWN_PROPERTY, response->GetErrorName()); +} + +TEST_F(ExportedPropertySetTest, SetFailsWithReadOnlyProperty) { + auto response = SetPropertyOnInterface( + kTestInterface3, kStringPropName, brillo::Any(kTestString)); + ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(DBUS_ERROR_PROPERTY_READ_ONLY, response->GetErrorName()); +} + +TEST_F(ExportedPropertySetTest, SetFailsWithMismatchedValueType) { + p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); + auto response = SetPropertyOnInterface( + kTestInterface3, kStringPropName, brillo::Any(true)); + ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName()); +} + +namespace { + +bool SetInvalidProperty(brillo::ErrorPtr* error, Unused) { + brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, + DBUS_ERROR_INVALID_ARGS, "Invalid value"); + return false; +} + +} // namespace + +TEST_F(ExportedPropertySetTest, SetFailsWithValidator) { + PropertyValidatorObserver property_validator; + p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); + p_->string_prop_.SetValidator( + property_validator.validate_property_callback()); + + brillo::ErrorPtr error = brillo::Error::Create( + FROM_HERE, errors::dbus::kDomain, DBUS_ERROR_INVALID_ARGS, ""); + EXPECT_CALL(property_validator, ValidateProperty(_, kTestString)) + .WillOnce(Invoke(SetInvalidProperty)); + auto response = SetPropertyOnInterface( + kTestInterface3, kStringPropName, brillo::Any(kTestString)); + ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName()); +} + +TEST_F(ExportedPropertySetTest, SetWorksWithValidator) { + PropertyValidatorObserver property_validator; + p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); + p_->string_prop_.SetValidator( + property_validator.validate_property_callback()); + + EXPECT_CALL(property_validator, ValidateProperty(_, kTestString)) + .WillOnce(Return(true)); + auto response = SetPropertyOnInterface( + kTestInterface3, kStringPropName, brillo::Any(kTestString)); + ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(kTestString, p_->string_prop_.value()); +} + +TEST_F(ExportedPropertySetTest, SetWorksWithSameValue) { + PropertyValidatorObserver property_validator; + p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); + p_->string_prop_.SetValidator( + property_validator.validate_property_callback()); + EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); + p_->string_prop_.SetValue(kTestString); + + // No need to validate the value if it is the same as the current one. + EXPECT_CALL(property_validator, ValidateProperty(_, _)).Times(0); + auto response = SetPropertyOnInterface( + kTestInterface3, kStringPropName, brillo::Any(kTestString)); + ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(kTestString, p_->string_prop_.value()); +} + +TEST_F(ExportedPropertySetTest, SetWorksWithoutValidator) { + p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); + auto response = SetPropertyOnInterface( + kTestInterface3, kStringPropName, brillo::Any(kTestString)); + ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); + ASSERT_EQ(kTestString, p_->string_prop_.value()); +} + +namespace { + +void VerifySignal(dbus::Signal* signal) { + ASSERT_NE(signal, nullptr); + std::string interface_name; + std::string property_name; + uint8_t value; + dbus::MessageReader reader(signal); + dbus::MessageReader array_reader(signal); + dbus::MessageReader dict_reader(signal); + ASSERT_TRUE(reader.PopString(&interface_name)); + ASSERT_TRUE(reader.PopArray(&array_reader)); + ASSERT_TRUE(array_reader.PopDictEntry(&dict_reader)); + ASSERT_TRUE(dict_reader.PopString(&property_name)); + ASSERT_TRUE(dict_reader.PopVariantOfByte(&value)); + ASSERT_FALSE(dict_reader.HasMoreData()); + ASSERT_FALSE(array_reader.HasMoreData()); + ASSERT_TRUE(reader.HasMoreData()); + // Read the (empty) list of invalidated property names. + ASSERT_TRUE(reader.PopArray(&array_reader)); + ASSERT_FALSE(array_reader.HasMoreData()); + ASSERT_FALSE(reader.HasMoreData()); + ASSERT_EQ(value, 57); + ASSERT_EQ(property_name, std::string(kUint8PropName)); + ASSERT_EQ(interface_name, std::string(kTestInterface1)); +} + +} // namespace + +TEST_F(ExportedPropertySetTest, SignalsAreParsable) { + EXPECT_CALL(*mock_exported_object_, SendSignal(_)) + .Times(1).WillOnce(Invoke(&VerifySignal)); + p_->uint8_prop_.SetValue(57); +} + +} // namespace dbus_utils + +} // namespace brillo diff --git a/brillo/dbus/mock_dbus_object.h b/brillo/dbus/mock_dbus_object.h new file mode 100644 index 0000000..75cfb71 --- /dev/null +++ b/brillo/dbus/mock_dbus_object.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_MOCK_DBUS_OBJECT_H_ +#define LIBCHROMEOS_BRILLO_DBUS_MOCK_DBUS_OBJECT_H_ + +#include + +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +class MockDBusObject : public DBusObject { + public: + MockDBusObject(ExportedObjectManager* object_manager, + const scoped_refptr& bus, + const dbus::ObjectPath& object_path) + : DBusObject(object_manager, bus, object_path) {} + ~MockDBusObject() override = default; + + MOCK_METHOD1(RegisterAsync, + void(const AsyncEventSequencer::CompletionAction&)); +}; // class MockDBusObject + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_MOCK_DBUS_OBJECT_H_ diff --git a/brillo/dbus/mock_exported_object_manager.h b/brillo/dbus/mock_exported_object_manager.h new file mode 100644 index 0000000..5a45ec3 --- /dev/null +++ b/brillo/dbus/mock_exported_object_manager.h @@ -0,0 +1,42 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_ +#define LIBCHROMEOS_BRILLO_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_ + +#include + +#include +#include +#include +#include + +namespace brillo { + +namespace dbus_utils { + +class MockExportedObjectManager : public ExportedObjectManager { + public: + using CompletionAction = + brillo::dbus_utils::AsyncEventSequencer::CompletionAction; + + using ExportedObjectManager::ExportedObjectManager; + ~MockExportedObjectManager() override = default; + + MOCK_METHOD1(RegisterAsync, + void(const CompletionAction& completion_callback)); + MOCK_METHOD3(ClaimInterface, + void(const dbus::ObjectPath& path, + const std::string& interface_name, + const ExportedPropertySet::PropertyWriter& writer)); + MOCK_METHOD2(ReleaseInterface, + void(const dbus::ObjectPath& path, + const std::string& interface_name)); +}; + +} // namespace dbus_utils + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_ diff --git a/brillo/dbus/test.proto b/brillo/dbus/test.proto new file mode 100644 index 0000000..84607a3 --- /dev/null +++ b/brillo/dbus/test.proto @@ -0,0 +1,8 @@ +option optimize_for = LITE_RUNTIME; + +package dbus_utils_test; + +message TestMessage { + optional int32 foo = 1; + optional string bar = 2; +} diff --git a/brillo/dbus/utils.cc b/brillo/dbus/utils.cc new file mode 100644 index 0000000..4fbd0ef --- /dev/null +++ b/brillo/dbus/utils.cc @@ -0,0 +1,95 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +std::unique_ptr CreateDBusErrorResponse( + dbus::MethodCall* method_call, + const std::string& error_name, + const std::string& error_message) { + auto resp = dbus::ErrorResponse::FromMethodCall( + method_call, error_name, error_message); + return std::unique_ptr(resp.release()); +} + +std::unique_ptr GetDBusError(dbus::MethodCall* method_call, + const brillo::Error* error) { + CHECK(error) << "Error object must be specified"; + std::string error_name = DBUS_ERROR_FAILED; // Default error code. + std::string error_message; + + // Special case for "dbus" error domain. + // Pop the error code and message from the error chain and use them as the + // actual D-Bus error message. + if (error->GetDomain() == errors::dbus::kDomain) { + error_name = error->GetCode(); + error_message = error->GetMessage(); + error = error->GetInnerError(); + } + + // Append any inner errors to the error message. + while (error) { + // Format error string as "domain/code:message". + if (!error_message.empty()) + error_message += ';'; + error_message += + error->GetDomain() + '/' + error->GetCode() + ':' + error->GetMessage(); + error = error->GetInnerError(); + } + return CreateDBusErrorResponse(method_call, error_name, error_message); +} + +void AddDBusError(brillo::ErrorPtr* error, + const std::string& dbus_error_name, + const std::string& dbus_error_message) { + std::vector parts = string_utils::Split(dbus_error_message, ";"); + std::vector> errors; + for (const std::string& part : parts) { + // Each part should be in format of "domain/code:message" + size_t slash_pos = part.find('/'); + size_t colon_pos = part.find(':'); + if (slash_pos != std::string::npos && colon_pos != std::string::npos && + slash_pos < colon_pos) { + // If we have both '/' and ':' and in proper order, then we have a + // correctly encoded error object. + std::string domain = part.substr(0, slash_pos); + std::string code = part.substr(slash_pos + 1, colon_pos - slash_pos - 1); + std::string message = part.substr(colon_pos + 1); + errors.emplace_back(domain, code, message); + } else if (slash_pos == std::string::npos && + colon_pos == std::string::npos && errors.empty()) { + // If we don't have both '/' and ':' and this is the first error object, + // then we had a D-Bus error at the top of the error chain. + errors.emplace_back(errors::dbus::kDomain, dbus_error_name, part); + } else { + // We have a malformed part. The whole D-Bus error was most likely + // not generated by GetDBusError(). To be safe, stop parsing it + // and return the error as received from D-Bus. + errors.clear(); // Remove any errors accumulated so far. + errors.emplace_back( + errors::dbus::kDomain, dbus_error_name, dbus_error_message); + break; + } + } + + // Go backwards and add the parsed errors to the error chain. + for (auto it = errors.crbegin(); it != errors.crend(); ++it) { + Error::AddTo( + error, FROM_HERE, std::get<0>(*it), std::get<1>(*it), std::get<2>(*it)); + } +} + +} // namespace dbus_utils +} // namespace brillo diff --git a/brillo/dbus/utils.h b/brillo/dbus/utils.h new file mode 100644 index 0000000..af76ed4 --- /dev/null +++ b/brillo/dbus/utils.h @@ -0,0 +1,44 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_DBUS_UTILS_H_ +#define LIBCHROMEOS_BRILLO_DBUS_UTILS_H_ + +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { +namespace dbus_utils { + +// A helper function to create a D-Bus error response object as unique_ptr<>. +BRILLO_EXPORT std::unique_ptr CreateDBusErrorResponse( + dbus::MethodCall* method_call, + const std::string& error_name, + const std::string& error_message); + +// Create a D-Bus error response object from brillo::Error. If the last +// error in the error chain belongs to "dbus" error domain, its error code +// and message are directly translated to D-Bus error code and message. +// Any inner errors are formatted as "domain/code:message" string and appended +// to the D-Bus error message, delimited by semi-colons. +BRILLO_EXPORT std::unique_ptr GetDBusError( + dbus::MethodCall* method_call, + const brillo::Error* error); + +// AddDBusError() is the opposite of GetDBusError(). It de-serializes the Error +// object received over D-Bus. +BRILLO_EXPORT void AddDBusError(brillo::ErrorPtr* error, + const std::string& dbus_error_name, + const std::string& dbus_error_message); + +} // namespace dbus_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_DBUS_UTILS_H_ diff --git a/brillo/errors/error.cc b/brillo/errors/error.cc new file mode 100644 index 0000000..1236220 --- /dev/null +++ b/brillo/errors/error.cc @@ -0,0 +1,138 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +using brillo::Error; +using brillo::ErrorPtr; + +namespace { +inline void LogError(const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message) { + // Use logging::LogMessage() directly instead of LOG(ERROR) to substitute + // the current error location with the location passed in to the Error object. + // This way the log will contain the actual location of the error, and not + // as if it always comes from brillo/errors/error.cc(22). + logging::LogMessage( + location.file_name(), location.line_number(), logging::LOG_ERROR).stream() + << location.function_name() << "(...): " + << "Domain=" << domain << ", Code=" << code << ", Message=" << message; +} +} // anonymous namespace + +ErrorPtr Error::Create(const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message) { + return Create(location, domain, code, message, ErrorPtr()); +} + +ErrorPtr Error::Create(const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message, + ErrorPtr inner_error) { + LogError(location, domain, code, message); + return ErrorPtr( + new Error(location, domain, code, message, std::move(inner_error))); +} + +void Error::AddTo(ErrorPtr* error, + const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message) { + if (error) { + *error = Create(location, domain, code, message, std::move(*error)); + } else { + // Create already logs the error, but if |error| is nullptr, + // we still want to log the error... + LogError(location, domain, code, message); + } +} + +void Error::AddToPrintf(ErrorPtr* error, + const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const char* format, + ...) { + va_list ap; + va_start(ap, format); + std::string message = base::StringPrintV(format, ap); + va_end(ap); + AddTo(error, location, domain, code, message); +} + +ErrorPtr Error::Clone() const { + ErrorPtr inner_error = inner_error_ ? inner_error_->Clone() : nullptr; + return ErrorPtr( + new Error(location_, domain_, code_, message_, std::move(inner_error))); +} + +bool Error::HasDomain(const std::string& domain) const { + return FindErrorOfDomain(this, domain) != nullptr; +} + +bool Error::HasError(const std::string& domain, const std::string& code) const { + return FindError(this, domain, code) != nullptr; +} + +const Error* Error::GetFirstError() const { + const Error* err = this; + while (err->GetInnerError()) + err = err->GetInnerError(); + return err; +} + +Error::Error(const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message, + ErrorPtr inner_error) + : Error{tracked_objects::LocationSnapshot{location}, + domain, + code, + message, + std::move(inner_error)} { +} + +Error::Error(const tracked_objects::LocationSnapshot& location, + const std::string& domain, + const std::string& code, + const std::string& message, + ErrorPtr inner_error) + : domain_(domain), + code_(code), + message_(message), + location_(location), + inner_error_(std::move(inner_error)) { +} + +const Error* Error::FindErrorOfDomain(const Error* error_chain_start, + const std::string& domain) { + while (error_chain_start) { + if (error_chain_start->GetDomain() == domain) + break; + error_chain_start = error_chain_start->GetInnerError(); + } + return error_chain_start; +} + +const Error* Error::FindError(const Error* error_chain_start, + const std::string& domain, + const std::string& code) { + while (error_chain_start) { + if (error_chain_start->GetDomain() == domain && + error_chain_start->GetCode() == code) + break; + error_chain_start = error_chain_start->GetInnerError(); + } + return error_chain_start; +} diff --git a/brillo/errors/error.h b/brillo/errors/error.h new file mode 100644 index 0000000..2c0f089 --- /dev/null +++ b/brillo/errors/error.h @@ -0,0 +1,129 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_ERRORS_ERROR_H_ +#define LIBCHROMEOS_BRILLO_ERRORS_ERROR_H_ + +#include +#include + +#include +#include +#include + +namespace brillo { + +class Error; // Forward declaration. + +using ErrorPtr = std::unique_ptr; + +class BRILLO_EXPORT Error { + public: + virtual ~Error() = default; + + // Creates an instance of Error class. + static ErrorPtr Create(const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message); + static ErrorPtr Create(const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message, + ErrorPtr inner_error); + // If |error| is not nullptr, creates another instance of Error class, + // initializes it with specified arguments and adds it to the head of + // the error chain pointed to by |error|. + static void AddTo(ErrorPtr* error, + const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message); + // Same as the Error::AddTo above, but allows to pass in a printf-like + // format string and optional parameters to format the error message. + static void AddToPrintf(ErrorPtr* error, + const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const char* format, + ...) PRINTF_FORMAT(5, 6); + + // Clones error with all inner errors. + ErrorPtr Clone() const; + + // Returns the error domain, code and message + const std::string& GetDomain() const { return domain_; } + const std::string& GetCode() const { return code_; } + const std::string& GetMessage() const { return message_; } + + // Returns the location of the error in the source code. + const tracked_objects::LocationSnapshot& GetLocation() const { + return location_; + } + + // Checks if this or any of the inner errors in the chain has the specified + // error domain. + bool HasDomain(const std::string& domain) const; + + // Checks if this or any of the inner errors in the chain matches the + // specified error domain and code. + bool HasError(const std::string& domain, const std::string& code) const; + + // Gets a pointer to the inner error, if present. Returns nullptr otherwise. + const Error* GetInnerError() const { return inner_error_.get(); } + + // Gets a pointer to the first error occurred. + // Returns itself if no inner error are available. + const Error* GetFirstError() const; + + // Finds an error object of particular domain in the error chain stating at + // |error_chain_start|. Returns the a pointer to the first matching error + // object found. + // Returns nullptr if no match is found. + // This method is safe to call on a nullptr |error_chain_start| in which case + // the result will also be nullptr. + static const Error* FindErrorOfDomain(const Error* error_chain_start, + const std::string& domain); + // Finds an error of particular domain with the given code in the error chain + // stating at |error_chain_start|. Returns the pointer to the first matching + // error object. + // Returns nullptr if no match is found or if |error_chain_start| is nullptr. + static const Error* FindError(const Error* error_chain_start, + const std::string& domain, + const std::string& code); + + protected: + // Constructor is protected since this object is supposed to be + // created via the Create factory methods. + Error(const tracked_objects::Location& location, + const std::string& domain, + const std::string& code, + const std::string& message, + ErrorPtr inner_error); + + Error(const tracked_objects::LocationSnapshot& location, + const std::string& domain, + const std::string& code, + const std::string& message, + ErrorPtr inner_error); + + // Error domain. The domain defines the scopes for error codes. + // Two errors with the same code but different domains are different errors. + std::string domain_; + // Error code. A unique error code identifier within the given domain. + std::string code_; + // Human-readable error message. + std::string message_; + // Error origin in the source code. + tracked_objects::LocationSnapshot location_; + // Pointer to inner error, if any. This forms a chain of errors. + ErrorPtr inner_error_; + + private: + DISALLOW_COPY_AND_ASSIGN(Error); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_ERRORS_ERROR_H_ diff --git a/brillo/errors/error_codes.cc b/brillo/errors/error_codes.cc new file mode 100644 index 0000000..4e48b4e --- /dev/null +++ b/brillo/errors/error_codes.cc @@ -0,0 +1,225 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { +namespace errors { + +namespace dbus { +const char kDomain[] = "dbus"; +} // namespace dbus + +namespace json { +const char kDomain[] = "json_parser"; +const char kParseError[] = "json_parse_error"; +const char kObjectExpected[] = "json_object_expected"; +} // namespace json + +namespace http { +const char kDomain[] = "http"; +} // namespace http + +namespace system { +const char kDomain[] = "system"; + +namespace { +const struct ErrorMapEntry { + const char* error_code; + int errnum; +} error_map[] = { +#define ERROR_ENTRY(err) { #err, err } + ERROR_ENTRY(EPERM), // Operation not permitted + ERROR_ENTRY(ENOENT), // No such file or directory + ERROR_ENTRY(ESRCH), // No such process + ERROR_ENTRY(EINTR), // Interrupted system call + ERROR_ENTRY(EIO), // I/O error + ERROR_ENTRY(ENXIO), // No such device or address + ERROR_ENTRY(E2BIG), // Argument list too long + ERROR_ENTRY(ENOEXEC), // Exec format error + ERROR_ENTRY(EBADF), // Bad file number + ERROR_ENTRY(ECHILD), // No child processes + ERROR_ENTRY(EAGAIN), // Try again + ERROR_ENTRY(ENOMEM), // Out of memory + ERROR_ENTRY(EACCES), // Permission denied + ERROR_ENTRY(EFAULT), // Bad address + ERROR_ENTRY(ENOTBLK), // Block device required + ERROR_ENTRY(EBUSY), // Device or resource busy + ERROR_ENTRY(EEXIST), // File exists + ERROR_ENTRY(EXDEV), // Cross-device link + ERROR_ENTRY(ENODEV), // No such device + ERROR_ENTRY(ENOTDIR), // Not a directory + ERROR_ENTRY(EISDIR), // Is a directory + ERROR_ENTRY(EINVAL), // Invalid argument + ERROR_ENTRY(ENFILE), // File table overflow + ERROR_ENTRY(EMFILE), // Too many open files + ERROR_ENTRY(ENOTTY), // Not a typewriter + ERROR_ENTRY(ETXTBSY), // Text file busy + ERROR_ENTRY(EFBIG), // File too large + ERROR_ENTRY(ENOSPC), // No space left on device + ERROR_ENTRY(ESPIPE), // Illegal seek + ERROR_ENTRY(EROFS), // Read-only file system + ERROR_ENTRY(EMLINK), // Too many links + ERROR_ENTRY(EPIPE), // Broken pipe + ERROR_ENTRY(EDOM), // Math argument out of domain of func + ERROR_ENTRY(ERANGE), // Math result not representable + ERROR_ENTRY(EDEADLK), // Resource deadlock would occur + ERROR_ENTRY(ENAMETOOLONG), // File name too long + ERROR_ENTRY(ENOLCK), // No record locks available + ERROR_ENTRY(ENOSYS), // Function not implemented + ERROR_ENTRY(ENOTEMPTY), // Directory not empty + ERROR_ENTRY(ELOOP), // Too many symbolic links encountered + ERROR_ENTRY(ENOMSG), // No message of desired type + ERROR_ENTRY(EIDRM), // Identifier removed +#ifdef __linux__ + ERROR_ENTRY(ECHRNG), // Channel number out of range + ERROR_ENTRY(EL2NSYNC), // Level 2 not synchronized + ERROR_ENTRY(EL3HLT), // Level 3 halted + ERROR_ENTRY(EL3RST), // Level 3 reset + ERROR_ENTRY(ELNRNG), // Link number out of range + ERROR_ENTRY(EUNATCH), // Protocol driver not attached + ERROR_ENTRY(ENOCSI), // No CSI structure available + ERROR_ENTRY(EL2HLT), // Level 2 halted + ERROR_ENTRY(EBADE), // Invalid exchange + ERROR_ENTRY(EBADR), // Invalid request descriptor + ERROR_ENTRY(EXFULL), // Exchange full + ERROR_ENTRY(ENOANO), // No anode + ERROR_ENTRY(EBADRQC), // Invalid request code + ERROR_ENTRY(EBADSLT), // Invalid slot + ERROR_ENTRY(EBFONT), // Bad font file format +#endif // __linux__ + ERROR_ENTRY(ENOSTR), // Device not a stream + ERROR_ENTRY(ENODATA), // No data available + ERROR_ENTRY(ETIME), // Timer expired + ERROR_ENTRY(ENOSR), // Out of streams resources +#ifdef __linux__ + ERROR_ENTRY(ENONET), // Machine is not on the network + ERROR_ENTRY(ENOPKG), // Package not installed +#endif // __linux__ + ERROR_ENTRY(EREMOTE), // Object is remote + ERROR_ENTRY(ENOLINK), // Link has been severed +#ifdef __linux__ + ERROR_ENTRY(EADV), // Advertise error + ERROR_ENTRY(ESRMNT), // Srmount error + ERROR_ENTRY(ECOMM), // Communication error on send +#endif // __linux__ + ERROR_ENTRY(EPROTO), // Protocol error + ERROR_ENTRY(EMULTIHOP), // Multihop attempted +#ifdef __linux__ + ERROR_ENTRY(EDOTDOT), // RFS specific error +#endif // __linux__ + ERROR_ENTRY(EBADMSG), // Not a data message + ERROR_ENTRY(EOVERFLOW), // Value too large for defined data type +#ifdef __linux__ + ERROR_ENTRY(ENOTUNIQ), // Name not unique on network + ERROR_ENTRY(EBADFD), // File descriptor in bad state + ERROR_ENTRY(EREMCHG), // Remote address changed + ERROR_ENTRY(ELIBACC), // Can not access a needed shared library + ERROR_ENTRY(ELIBBAD), // Accessing a corrupted shared library + ERROR_ENTRY(ELIBSCN), // .lib section in a.out corrupted + ERROR_ENTRY(ELIBMAX), // Attempting to link in too many shared libs. + ERROR_ENTRY(ELIBEXEC), // Cannot exec a shared library directly +#endif // __linux__ + ERROR_ENTRY(EILSEQ), // Illegal byte sequence +#ifdef __linux__ + ERROR_ENTRY(ERESTART), // Interrupted system call should be restarted + ERROR_ENTRY(ESTRPIPE), // Streams pipe error +#endif // __linux__ + ERROR_ENTRY(EUSERS), // Too many users + ERROR_ENTRY(ENOTSOCK), // Socket operation on non-socket + ERROR_ENTRY(EDESTADDRREQ), // Destination address required + ERROR_ENTRY(EMSGSIZE), // Message too long + ERROR_ENTRY(EPROTOTYPE), // Protocol wrong type for socket + ERROR_ENTRY(ENOPROTOOPT), // Protocol not available + ERROR_ENTRY(EPROTONOSUPPORT), // Protocol not supported + ERROR_ENTRY(ESOCKTNOSUPPORT), // Socket type not supported + ERROR_ENTRY(EOPNOTSUPP), // Operation not supported o/transport endpoint + ERROR_ENTRY(EPFNOSUPPORT), // Protocol family not supported + ERROR_ENTRY(EAFNOSUPPORT), // Address family not supported by protocol + ERROR_ENTRY(EADDRINUSE), // Address already in use + ERROR_ENTRY(EADDRNOTAVAIL), // Cannot assign requested address + ERROR_ENTRY(ENETDOWN), // Network is down + ERROR_ENTRY(ENETUNREACH), // Network is unreachable + ERROR_ENTRY(ENETRESET), // Network dropped connection because of reset + ERROR_ENTRY(ECONNABORTED), // Software caused connection abort + ERROR_ENTRY(ECONNRESET), // Connection reset by peer + ERROR_ENTRY(ENOBUFS), // No buffer space available + ERROR_ENTRY(EISCONN), // Transport endpoint is already connected + ERROR_ENTRY(ENOTCONN), // Transport endpoint is not connected + ERROR_ENTRY(ESHUTDOWN), // Cannot send after transp. endpoint shutdown + ERROR_ENTRY(ETOOMANYREFS), // Too many references: cannot splice + ERROR_ENTRY(ETIMEDOUT), // Connection timed out + ERROR_ENTRY(ECONNREFUSED), // Connection refused + ERROR_ENTRY(EHOSTDOWN), // Host is down + ERROR_ENTRY(EHOSTUNREACH), // No route to host + ERROR_ENTRY(EALREADY), // Operation already in progress + ERROR_ENTRY(EINPROGRESS), // Operation now in progress + ERROR_ENTRY(ESTALE), // Stale file handle +#ifdef __linux__ + ERROR_ENTRY(EUCLEAN), // Structure needs cleaning + ERROR_ENTRY(ENOTNAM), // Not a XENIX named type file + ERROR_ENTRY(ENAVAIL), // No XENIX semaphores available + ERROR_ENTRY(EISNAM), // Is a named type file + ERROR_ENTRY(EREMOTEIO), // Remote I/O error +#endif // __linux__ + ERROR_ENTRY(EDQUOT), // Quota exceeded +#ifdef __linux__ + ERROR_ENTRY(ENOMEDIUM), // No medium found + ERROR_ENTRY(EMEDIUMTYPE), // Wrong medium type +#endif // __linux__ + ERROR_ENTRY(ECANCELED), // Operation Canceled +#ifdef __linux__ + ERROR_ENTRY(ENOKEY), // Required key not available + ERROR_ENTRY(EKEYEXPIRED), // Key has expired + ERROR_ENTRY(EKEYREVOKED), // Key has been revoked + ERROR_ENTRY(EKEYREJECTED), // Key was rejected by service +#endif // __linux__ + ERROR_ENTRY(EOWNERDEAD), // Owner died + ERROR_ENTRY(ENOTRECOVERABLE), // State not recoverable +#ifdef __linux__ + ERROR_ENTRY(ERFKILL), // Operation not possible due to RF-kill + ERROR_ENTRY(EHWPOISON), // Memory page has hardware error +#endif // __linux__ +#undef ERROR_ENTRY + // This list comes from system header. The elements are ordered + // by increasing errnum values which is the same order used in the header + // file. So, when new error codes are added to glibc, it should be relatively + // easy to identify them and add them to this list. +}; + +// Gets the error code string from system error code. If unknown system error +// number is provided, returns an empty string. +std::string ErrorCodeFromSystemError(int errnum) { + std::string error_code; + for (const ErrorMapEntry& entry : error_map) { + if (entry.errnum == errnum) { + error_code = entry.error_code; + break; + } + } + return error_code; +} + +} // anonymous namespace + +void AddSystemError(ErrorPtr* error, + const tracked_objects::Location& location, + int errnum) { + std::string message = base::safe_strerror(errnum); + std::string code = ErrorCodeFromSystemError(errnum); + if (message.empty()) + message = "Unknown error " + std::to_string(errnum); + + if (code.empty()) + code = "error_" + std::to_string(errnum); + + Error::AddTo(error, location, kDomain, code, message); +} + +} // namespace system + +} // namespace errors +} // namespace brillo diff --git a/brillo/errors/error_codes.h b/brillo/errors/error_codes.h new file mode 100644 index 0000000..0189e00 --- /dev/null +++ b/brillo/errors/error_codes.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_ERRORS_ERROR_CODES_H_ +#define LIBCHROMEOS_BRILLO_ERRORS_ERROR_CODES_H_ + +#include + +#include +#include + +namespace brillo { +namespace errors { + +namespace dbus { +BRILLO_EXPORT extern const char kDomain[]; +} // namespace dbus + +namespace json { +BRILLO_EXPORT extern const char kDomain[]; +BRILLO_EXPORT extern const char kParseError[]; +BRILLO_EXPORT extern const char kObjectExpected[]; +} // namespace json + +namespace http { +BRILLO_EXPORT extern const char kDomain[]; +} // namespace http + +namespace system { +BRILLO_EXPORT extern const char kDomain[]; + +// Adds an Error object to the error chain identified by |error|, using +// the system error code (see "errno"). +BRILLO_EXPORT void AddSystemError(ErrorPtr* error, + const tracked_objects::Location& location, + int errnum); +} // namespace system + +} // namespace errors +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_ERRORS_ERROR_CODES_H_ diff --git a/brillo/errors/error_codes_unittest.cc b/brillo/errors/error_codes_unittest.cc new file mode 100644 index 0000000..2baa28f --- /dev/null +++ b/brillo/errors/error_codes_unittest.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +using brillo::errors::system::AddSystemError; + +TEST(SystemErrorCodes, AddTo) { + brillo::ErrorPtr error; + + AddSystemError(&error, FROM_HERE, ENOENT); + EXPECT_EQ(brillo::errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("ENOENT", error->GetCode()); + EXPECT_EQ("No such file or directory", error->GetMessage()); + error.reset(); + + AddSystemError(&error, FROM_HERE, EPROTO); + EXPECT_EQ(brillo::errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EPROTO", error->GetCode()); + EXPECT_EQ("Protocol error", error->GetMessage()); + error.reset(); +} + +TEST(SystemErrorCodes, AddTo_UnknownError) { + brillo::ErrorPtr error; + AddSystemError(&error, FROM_HERE, 10000); + EXPECT_EQ(brillo::errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("error_10000", error->GetCode()); + EXPECT_EQ("Unknown error 10000", error->GetMessage()); +} diff --git a/brillo/errors/error_unittest.cc b/brillo/errors/error_unittest.cc new file mode 100644 index 0000000..517dab5 --- /dev/null +++ b/brillo/errors/error_unittest.cc @@ -0,0 +1,83 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +using brillo::Error; + +namespace { + +brillo::ErrorPtr GenerateNetworkError() { + tracked_objects::Location loc("GenerateNetworkError", + "error_unittest.cc", + 15, + ::tracked_objects::GetProgramCounter()); + return Error::Create(loc, "network", "not_found", "Resource not found"); +} + +brillo::ErrorPtr GenerateHttpError() { + brillo::ErrorPtr inner = GenerateNetworkError(); + return Error::Create(FROM_HERE, "HTTP", "404", "Not found", std::move(inner)); +} + +} // namespace + +TEST(Error, Single) { + brillo::ErrorPtr err = GenerateNetworkError(); + EXPECT_EQ("network", err->GetDomain()); + EXPECT_EQ("not_found", err->GetCode()); + EXPECT_EQ("Resource not found", err->GetMessage()); + EXPECT_EQ("GenerateNetworkError", err->GetLocation().function_name); + EXPECT_EQ("error_unittest.cc", err->GetLocation().file_name); + EXPECT_EQ(15, err->GetLocation().line_number); + EXPECT_EQ(nullptr, err->GetInnerError()); + EXPECT_TRUE(err->HasDomain("network")); + EXPECT_FALSE(err->HasDomain("HTTP")); + EXPECT_FALSE(err->HasDomain("foo")); + EXPECT_TRUE(err->HasError("network", "not_found")); + EXPECT_FALSE(err->HasError("network", "404")); + EXPECT_FALSE(err->HasError("HTTP", "404")); + EXPECT_FALSE(err->HasError("HTTP", "not_found")); + EXPECT_FALSE(err->HasError("foo", "bar")); +} + +TEST(Error, Nested) { + brillo::ErrorPtr err = GenerateHttpError(); + EXPECT_EQ("HTTP", err->GetDomain()); + EXPECT_EQ("404", err->GetCode()); + EXPECT_EQ("Not found", err->GetMessage()); + EXPECT_NE(nullptr, err->GetInnerError()); + EXPECT_EQ("network", err->GetInnerError()->GetDomain()); + EXPECT_TRUE(err->HasDomain("network")); + EXPECT_TRUE(err->HasDomain("HTTP")); + EXPECT_FALSE(err->HasDomain("foo")); + EXPECT_TRUE(err->HasError("network", "not_found")); + EXPECT_FALSE(err->HasError("network", "404")); + EXPECT_TRUE(err->HasError("HTTP", "404")); + EXPECT_FALSE(err->HasError("HTTP", "not_found")); + EXPECT_FALSE(err->HasError("foo", "bar")); +} + +TEST(Error, Clone) { + brillo::ErrorPtr err = GenerateHttpError(); + brillo::ErrorPtr clone = err->Clone(); + const brillo::Error* error1 = err.get(); + const brillo::Error* error2 = clone.get(); + while (error1 && error2) { + EXPECT_NE(error1, error2); + EXPECT_EQ(error1->GetDomain(), error2->GetDomain()); + EXPECT_EQ(error1->GetCode(), error2->GetCode()); + EXPECT_EQ(error1->GetMessage(), error2->GetMessage()); + EXPECT_EQ(error1->GetLocation().function_name, + error2->GetLocation().function_name); + EXPECT_EQ(error1->GetLocation().file_name, error2->GetLocation().file_name); + EXPECT_EQ(error1->GetLocation().line_number, + error2->GetLocation().line_number); + error1 = error1->GetInnerError(); + error2 = error2->GetInnerError(); + } + EXPECT_EQ(error1, error2); +} diff --git a/brillo/file_utils.cc b/brillo/file_utils.cc new file mode 100644 index 0000000..24ed347 --- /dev/null +++ b/brillo/file_utils.cc @@ -0,0 +1,165 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/file_utils.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { + +namespace { + +enum { + kPermissions600 = S_IRUSR | S_IWUSR, + kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO +}; + +// Verify that base file permission enums are compatible with S_Ixxx. If these +// asserts ever fail, we'll need to ensure that users of these functions switch +// away from using base permission enums and add a note to the function comments +// indicating that base enums can not be used. +static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH, + "base file permissions don't match unistd.h permissions"); +static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH, + "base file permissions don't match unistd.h permissions"); + +enum RegularFileOrDeleteResult { + kFailure = 0, // Failed to delete whatever was at the path. + kRegularFile = 1, // Regular file existed and was unchanged. + kEmpty = 2 // Anything that was at the path has been deleted. +}; + +// Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise +// deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult +// enum indicating what is at |path| after the function finishes. +RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path, + uid_t uid, + gid_t gid) { + // Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets + // us use the safer fstat() instead of having to use lstat(). + base::ScopedFD scoped_fd(HANDLE_EINTR(openat( + AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW))); + bool path_not_empty = (errno == ELOOP || scoped_fd != -1); + + // If there is a file/directory at |path|, see if it matches our criteria. + if (scoped_fd != -1) { + struct stat file_stat; + if (fstat(scoped_fd.get(), &file_stat) != -1 && + S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid && + file_stat.st_gid == gid) { + return kRegularFile; + } + } + + // If we get here and anything was at |path|, try to delete it so we can put + // our file there. + if (path_not_empty) { + if (!base::DeleteFile(path, true)) { + PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"'; + return kFailure; + } + } + + return kEmpty; +} + +// Handles common touch functionality but also provides an optional |fd_out| +// so that any further modifications to the file (e.g. permissions) can safely +// use the fd rather than the path. |fd_out| will only be set if a new file +// is created, otherwise it will be unchanged. +// If |fd_out| is null, this function will close the file, otherwise it's +// expected that |fd_out| will close the file when it goes out of scope. +bool TouchFileInternal(const base::FilePath& path, + uid_t uid, + gid_t gid, + base::ScopedFD* fd_out) { + RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid); + switch (result) { + case kFailure: + return false; + case kRegularFile: + return true; + case kEmpty: + break; + } + + // base::CreateDirectory() returns true if the directory already existed. + if (!base::CreateDirectory(path.DirName())) { + PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"'; + return false; + } + + // Create the file as owner-only initially. + base::ScopedFD scoped_fd( + HANDLE_EINTR(openat(AT_FDCWD, + path.value().c_str(), + O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC, + kPermissions600))); + if (scoped_fd == -1) { + PLOG(WARNING) << "Failed to create file \"" << path.value() << '"'; + return false; + } + + if (fd_out) { + fd_out->swap(scoped_fd); + } + return true; +} + +} // namespace + +bool TouchFile(const base::FilePath& path, + int new_file_permissions, + uid_t uid, + gid_t gid) { + // Make sure |permissions| doesn't have any out-of-range bits. + if (new_file_permissions & ~kPermissions777) { + LOG(WARNING) << "Illegal permissions: " << new_file_permissions; + return false; + } + + base::ScopedFD scoped_fd; + if (!TouchFileInternal(path, uid, gid, &scoped_fd)) { + return false; + } + + // scoped_fd is valid only if a new file was created. + if (scoped_fd != -1 && + HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) { + PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"'; + base::DeleteFile(path, false); + return false; + } + + return true; +} + +bool TouchFile(const base::FilePath& path) { + // Use TouchFile() instead of TouchFileInternal() to explicitly set + // permissions to 600 in case umask is set strangely. + return TouchFile(path, kPermissions600, geteuid(), getegid()); +} + +} // namespace brillo diff --git a/brillo/file_utils.h b/brillo/file_utils.h new file mode 100644 index 0000000..968588b --- /dev/null +++ b/brillo/file_utils.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_FILE_UTILS_H_ +#define LIBCHROMEOS_BRILLO_FILE_UTILS_H_ + +#include + +#include +#include + +namespace brillo { + +// Ensures a regular file owned by user |uid| and group |gid| exists at |path|. +// Any other entity at |path| will be deleted and replaced with an empty +// regular file. If a new file is needed, any missing parent directories will +// be created, and the file will be assigned |new_file_permissions|. +// Should be safe to use in all directories, including tmpdirs with the sticky +// bit set. +// Returns true if the file existed or was able to be created. +BRILLO_EXPORT bool TouchFile(const base::FilePath& path, + int new_file_permissions, + uid_t uid, + gid_t gid); + +// Convenience version of TouchFile() defaulting to 600 permissions and the +// current euid/egid. +// Should be safe to use in all directories, including tmpdirs with the sticky +// bit set. +BRILLO_EXPORT bool TouchFile(const base::FilePath& path); + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_FILE_UTILS_H_ diff --git a/brillo/file_utils_unittest.cc b/brillo/file_utils_unittest.cc new file mode 100644 index 0000000..f8898a0 --- /dev/null +++ b/brillo/file_utils_unittest.cc @@ -0,0 +1,135 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/file_utils.h" + +#include +#include + +#include + +#include +#include +#include + +namespace brillo { + +class FileUtilsTest : public testing::Test { + public: + FileUtilsTest() { + CHECK(temp_dir_.CreateUniqueTempDir()); + file_path_ = temp_dir_.path().Append("test.temp"); + } + + protected: + base::FilePath file_path_; + base::ScopedTempDir temp_dir_; + + // Writes |contents| to |file_path_|. Pulled into a separate function just + // to improve readability of tests. + void WriteFile(const std::string& contents) { + EXPECT_EQ(contents.length(), + base::WriteFile(file_path_, contents.c_str(), contents.length())); + } + + // Verifies that the file at |file_path_| exists and contains |contents|. + void ExpectFileContains(const std::string& contents) { + EXPECT_TRUE(base::PathExists(file_path_)); + std::string new_contents; + EXPECT_TRUE(base::ReadFileToString(file_path_, &new_contents)); + EXPECT_EQ(contents, new_contents); + } + + // Verifies that the file at |file_path_| has |permissions|. + void ExpectFilePermissions(int permissions) { + int actual_permissions; + EXPECT_TRUE(base::GetPosixFilePermissions(file_path_, &actual_permissions)); + EXPECT_EQ(permissions, actual_permissions); + } +}; + +namespace { + +enum { + kPermissions600 = + base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER, + kPermissions700 = base::FILE_PERMISSION_USER_MASK, + kPermissions777 = base::FILE_PERMISSION_MASK +}; + +} // namespace + +TEST_F(FileUtilsTest, TouchFileCreate) { + EXPECT_TRUE(TouchFile(file_path_)); + ExpectFileContains(""); + ExpectFilePermissions(kPermissions600); +} + +TEST_F(FileUtilsTest, TouchFileCreateThroughUmask) { + mode_t old_umask = umask(kPermissions777); + EXPECT_TRUE(TouchFile(file_path_)); + umask(old_umask); + ExpectFileContains(""); + ExpectFilePermissions(kPermissions600); +} + +TEST_F(FileUtilsTest, TouchFileCreateDirectoryStructure) { + file_path_ = temp_dir_.path().Append("foo/bar/baz/test.temp"); + EXPECT_TRUE(TouchFile(file_path_)); + ExpectFileContains(""); +} + +TEST_F(FileUtilsTest, TouchFileExisting) { + WriteFile("abcd"); + EXPECT_TRUE(TouchFile(file_path_)); + ExpectFileContains("abcd"); +} + +TEST_F(FileUtilsTest, TouchFileReplaceDirectory) { + EXPECT_TRUE(base::CreateDirectory(file_path_)); + EXPECT_TRUE(TouchFile(file_path_)); + EXPECT_FALSE(base::DirectoryExists(file_path_)); + ExpectFileContains(""); +} + +TEST_F(FileUtilsTest, TouchFileReplaceSymlink) { + base::FilePath symlink_target = temp_dir_.path().Append("target.temp"); + EXPECT_TRUE(base::CreateSymbolicLink(symlink_target, file_path_)); + EXPECT_TRUE(TouchFile(file_path_)); + EXPECT_FALSE(base::IsLink(file_path_)); + ExpectFileContains(""); +} + +TEST_F(FileUtilsTest, TouchFileReplaceOtherUser) { + WriteFile("abcd"); + EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid() + 1, getegid())); + ExpectFileContains(""); +} + +TEST_F(FileUtilsTest, TouchFileReplaceOtherGroup) { + WriteFile("abcd"); + EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid() + 1)); + ExpectFileContains(""); +} + +TEST_F(FileUtilsTest, TouchFileCreateWithAllPermissions) { + EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid())); + ExpectFileContains(""); + ExpectFilePermissions(kPermissions777); +} + +TEST_F(FileUtilsTest, TouchFileCreateWithOwnerPermissions) { + EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); + ExpectFileContains(""); + ExpectFilePermissions(kPermissions700); +} + +TEST_F(FileUtilsTest, TouchFileExistingPermissionsUnchanged) { + EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid())); + EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); + ExpectFileContains(""); + ExpectFilePermissions(kPermissions777); +} + +} // namespace brillo diff --git a/brillo/flag_helper.cc b/brillo/flag_helper.cc new file mode 100644 index 0000000..337e8da --- /dev/null +++ b/brillo/flag_helper.cc @@ -0,0 +1,267 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/flag_helper.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { + +Flag::Flag(const char* name, + const char* default_value, + const char* help, + bool visible) + : name_(name), + default_value_(default_value), + help_(help), + visible_(visible) { +} + +class HelpFlag : public brillo::Flag { + public: + HelpFlag() : Flag("help", "false", "Show this help message", true) {} + + bool SetValue(const std::string& value) override { return true; }; + const char* GetType() const override { return "bool"; } +}; + +BoolFlag::BoolFlag(const char* name, + bool* value, + bool* no_value, + const char* default_value, + const char* help, + bool visible) + : Flag(name, default_value, help, visible), + value_(value), + no_value_(no_value) { +} + +bool BoolFlag::SetValue(const std::string& value) { + if (value.empty()) { + *value_ = true; + } else { + if (!value.compare("true")) + *value_ = true; + else if (!value.compare("false")) + *value_ = false; + else + return false; + } + + *no_value_ = !*value_; + + return true; +} + +const char* BoolFlag::GetType() const { + return "bool"; +} + +Int32Flag::Int32Flag(const char* name, + int* value, + const char* default_value, + const char* help, + bool visible) + : Flag(name, default_value, help, visible), value_(value) { +} + +bool Int32Flag::SetValue(const std::string& value) { + return base::StringToInt(value, value_); +} + +const char* Int32Flag::GetType() const { + return "int"; +} + +Int64Flag::Int64Flag(const char* name, + int64_t* value, + const char* default_value, + const char* help, + bool visible) + : Flag(name, default_value, help, visible), value_(value) { +} + +bool Int64Flag::SetValue(const std::string& value) { + return base::StringToInt64(value, value_); +} + +const char* Int64Flag::GetType() const { + return "int64"; +} + +UInt64Flag::UInt64Flag(const char* name, + uint64_t* value, + const char* default_value, + const char* help, + bool visible) + : Flag(name, default_value, help, visible), value_(value) { +} + +bool UInt64Flag::SetValue(const std::string& value) { + return base::StringToUint64(value, value_); +} + +const char* UInt64Flag::GetType() const { + return "uint64"; +} + +DoubleFlag::DoubleFlag(const char* name, + double* value, + const char* default_value, + const char* help, + bool visible) + : Flag(name, default_value, help, visible), value_(value) { +} + +bool DoubleFlag::SetValue(const std::string& value) { + return base::StringToDouble(value, value_); +} + +const char* DoubleFlag::GetType() const { + return "double"; +} + +StringFlag::StringFlag(const char* name, + std::string* value, + const char* default_value, + const char* help, + bool visible) + : Flag(name, default_value, help, visible), value_(value) { +} + +bool StringFlag::SetValue(const std::string& value) { + value_->assign(value); + + return true; +} + +const char* StringFlag::GetType() const { + return "string"; +} + +namespace { +brillo::FlagHelper* instance_ = nullptr; +} // namespace + +FlagHelper::FlagHelper() : command_line_(nullptr) { + AddFlag(std::unique_ptr(new HelpFlag())); +} + +FlagHelper::~FlagHelper() { +} + +brillo::FlagHelper* FlagHelper::GetInstance() { + if (!instance_) + instance_ = new FlagHelper(); + + return instance_; +} + +void FlagHelper::ResetForTesting() { + delete instance_; + instance_ = nullptr; +} + +void FlagHelper::Init(int argc, + const char* const* argv, + std::string help_usage) { + brillo::FlagHelper* helper = GetInstance(); + if (!helper->command_line_) { + if (!base::CommandLine::InitializedForCurrentProcess()) + base::CommandLine::Init(argc, argv); + helper->command_line_ = base::CommandLine::ForCurrentProcess(); + } + + GetInstance()->SetUsageMessage(help_usage); + + GetInstance()->UpdateFlagValues(); +} + +void FlagHelper::UpdateFlagValues() { + std::string error_msg; + int error_code = EX_OK; + + // Check that base::CommandLine has been initialized. + CHECK(base::CommandLine::InitializedForCurrentProcess()); + + // If the --help flag exists, print out help message and exit. + if (command_line_->HasSwitch("help")) { + puts(GetHelpMessage().c_str()); + exit(EX_OK); + } + + // Iterate over the base::CommandLine switches. Update the value + // of the corresponding Flag if it exists, or output an error message + // if the flag wasn't defined. + const base::CommandLine::SwitchMap& switch_map = command_line_->GetSwitches(); + + for (const auto& pair : switch_map) { + const std::string& key = pair.first; + // Make sure we allow the standard logging switches (--v and --vmodule). + if (key == switches::kV || key == switches::kVModule) + continue; + + const std::string& value = pair.second; + + auto df_it = defined_flags_.find(key); + if (df_it != defined_flags_.end()) { + Flag* flag = df_it->second.get(); + if (!flag->SetValue(value)) { + base::StringAppendF( + &error_msg, + "ERROR: illegal value '%s' specified for %s flag '%s'\n", + value.c_str(), + flag->GetType(), + flag->name_); + error_code = EX_DATAERR; + } + } else { + base::StringAppendF( + &error_msg, "ERROR: unknown command line flag '%s'\n", key.c_str()); + error_code = EX_USAGE; + } + } + + if (error_code != EX_OK) { + puts(error_msg.c_str()); + exit(error_code); + } +} + +void FlagHelper::AddFlag(std::unique_ptr flag) { + defined_flags_.emplace(flag->name_, std::move(flag)); +} + +void FlagHelper::SetUsageMessage(std::string help_usage) { + help_usage_.assign(std::move(help_usage)); +} + +std::string FlagHelper::GetHelpMessage() const { + std::string help = help_usage_; + help.append("\n\n"); + for (const auto& pair : defined_flags_) { + const Flag* flag = pair.second.get(); + if (flag->visible_) { + base::StringAppendF(&help, + " --%s (%s) type: %s default: %s\n", + flag->name_, + flag->help_, + flag->GetType(), + flag->default_value_); + } + } + return help; +} + +} // namespace brillo diff --git a/brillo/flag_helper.h b/brillo/flag_helper.h new file mode 100644 index 0000000..4e770a2 --- /dev/null +++ b/brillo/flag_helper.h @@ -0,0 +1,274 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a helper class for dealing with command line flags. It uses +// base/command_line.h to parse flags from argv, but provides an API similar +// to gflags. Command line arguments with either '-' or '--' prefixes are +// treated as flags. Flags can optionally have a value set using an '=' +// delimeter, e.g. "--flag=value". An argument of "--" will terminate flag +// parsing, so that any subsequent arguments will be treated as non-flag +// arguments, regardless of prefix. Non-flag arguments are outside the scope +// of this class, and can instead be accessed through the GetArgs() function +// of the base::CommandLine singleton after FlagHelper initialization. +// +// The FlagHelper class will automatically take care of the --help flag, as +// well as aborting the program when unknown flags are passed to the +// application and when passed in parameters cannot be correctly parsed to +// their respective types. Developers define flags at compile time using the +// following macros from within main(): +// +// DEFINE_bool(name, default_value, help) +// DEFINE_int32(name, default_value, help) +// DEFINE_int64(name, default_value, help) +// DEFINE_uint64(name, default_value, help) +// DEFINE_double(name, default_value, help) +// DEFINE_string(name, default_value, help) +// +// Using the macro will create a scoped variable of the appropriate type +// with the name FLAGS_, that can be used to access the flag's +// value within the program. Here is an example of how the FlagHelper +// class is to be used: +// +// -- +// +// #include +// #include +// +// int main(int argc, char** argv) { +// DEFINE_int32(example, 0, "Example int flag"); +// brillo::FlagHelper::Init(argc, argv, "Test application."); +// +// printf("You passed in %d to --example command line flag\n", +// FLAGS_example); +// return 0; +// } +// +// -- +// +// In order to update the FLAGS_xxxx values from their defaults to the +// values passed in to the command line, Init(...) must be called after +// all the DEFINE_xxxx macros have instantiated the variables. + +#ifndef LIBCHROMEOS_BRILLO_FLAG_HELPER_H_ +#define LIBCHROMEOS_BRILLO_FLAG_HELPER_H_ + +#include +#include +#include + +#include +#include +#include + +namespace brillo { + +// The corresponding class representation of a command line flag, used +// to keep track of pointers to the FLAGS_xxxx variables so that they +// can be updated. +class Flag { + public: + Flag(const char* name, + const char* default_value, + const char* help, + bool visible); + virtual ~Flag() = default; + + // Sets the associated FLAGS_xxxx value, taking into account the flag type + virtual bool SetValue(const std::string& value) = 0; + + // Returns the type of the flag as a char array, for use in the help message + virtual const char* GetType() const = 0; + + const char* name_; + const char* default_value_; + const char* help_; + bool visible_; +}; + +class BRILLO_EXPORT BoolFlag final : public Flag { + public: + BoolFlag(const char* name, + bool* value, + bool* no_value, + const char* default_value, + const char* help, + bool visible); + bool SetValue(const std::string& value) override; + + const char* GetType() const override; + + private: + bool* value_; + bool* no_value_; +}; + +class BRILLO_EXPORT Int32Flag final : public Flag { + public: + Int32Flag(const char* name, + int* value, + const char* default_value, + const char* help, + bool visible); + bool SetValue(const std::string& value) override; + + const char* GetType() const override; + + private: + int* value_; +}; + +class BRILLO_EXPORT Int64Flag final : public Flag { + public: + Int64Flag(const char* name, + int64_t* value, + const char* default_value, + const char* help, + bool visible); + bool SetValue(const std::string& value) override; + + const char* GetType() const override; + + private: + int64_t* value_; +}; + +class BRILLO_EXPORT UInt64Flag final : public Flag { + public: + UInt64Flag(const char* name, + uint64_t* value, + const char* default_value, + const char* help, + bool visible); + bool SetValue(const std::string& value) override; + + const char* GetType() const override; + + private: + uint64_t* value_; +}; + +class BRILLO_EXPORT DoubleFlag final : public Flag { + public: + DoubleFlag(const char* name, + double* value, + const char* default_value, + const char* help, + bool visible); + bool SetValue(const std::string& value) override; + + const char* GetType() const override; + + private: + double* value_; +}; + +class BRILLO_EXPORT StringFlag final : public Flag { + public: + StringFlag(const char* name, + std::string* value, + const char* default_value, + const char* help, + bool visible); + bool SetValue(const std::string& value) override; + + const char* GetType() const override; + + private: + std::string* value_; +}; + +// The following macros are to be used from within main() to create +// scoped FLAGS_xxxx variables for easier access to command line flag +// values. FLAGS_noxxxx variables are also created, which are used to +// set bool flags to false. Creating the FLAGS_noxxxx variables here +// will also ensure a compiler error will be thrown if another flag +// is created with a conflicting name. +#define DEFINE_type(type, classtype, name, value, help) \ + type FLAGS_##name = value; \ + brillo::FlagHelper::GetInstance()->AddFlag(std::unique_ptr( \ + new brillo::classtype(#name, &FLAGS_##name, #value, help, true))); + +#define DEFINE_int32(name, value, help) \ + DEFINE_type(int, Int32Flag, name, value, help) +#define DEFINE_int64(name, value, help) \ + DEFINE_type(int64_t, Int64Flag, name, value, help) +#define DEFINE_uint64(name, value, help) \ + DEFINE_type(uint64_t, UInt64Flag, name, value, help) +#define DEFINE_double(name, value, help) \ + DEFINE_type(double, DoubleFlag, name, value, help) +#define DEFINE_string(name, value, help) \ + DEFINE_type(std::string, StringFlag, name, value, help) + +// Due to the FLAGS_no##name variables, can't re-use the same DEFINE_type macro +// for defining bool flags +#define DEFINE_bool(name, value, help) \ + bool FLAGS_##name = value; \ + bool FLAGS_no##name = !value; \ + brillo::FlagHelper::GetInstance()->AddFlag( \ + std::unique_ptr(new brillo::BoolFlag( \ + #name, &FLAGS_##name, &FLAGS_no##name, #value, help, true))); \ + brillo::FlagHelper::GetInstance()->AddFlag( \ + std::unique_ptr(new brillo::BoolFlag( \ + "no" #name, &FLAGS_no##name, &FLAGS_##name, #value, help, false))); + +// The FlagHelper class is a singleton class used for registering command +// line flags and pointers to their associated scoped variables, so that +// the variables can be updated once the command line arguments have been +// parsed by base::CommandLine. +class BRILLO_EXPORT FlagHelper final { + public: + // The singleton accessor function. + static FlagHelper* GetInstance(); + + // Resets the singleton object. Developers shouldn't ever need to use this, + // however it is required to be run at the end of every unit test to prevent + // Flag definitions from carrying over from previous tests. + static void ResetForTesting(); + + // Initializes the base::CommandLine class, then calls UpdateFlagValues(). + static void Init(int argc, const char* const* argv, std::string help_usage); + + // Only to be used for running unit tests. + void set_command_line_for_testing(base::CommandLine* command_line) { + command_line_ = command_line; + } + + // Checks all the parsed command line flags. This iterates over the switch + // map from base::CommandLine, and finds the corresponding Flag in order to + // update the FLAGS_xxxx values to the parsed value. If the --help flag is + // passed in, it outputs a help message and exits the program. If an unknown + // flag is passed in, it outputs an error message and exits the program with + // exit code EX_USAGE. + void UpdateFlagValues(); + + // Adds a flag to be tracked and updated once the command line is actually + // parsed. This function is an implementation detail, and is not meant + // to be used directly by developers. Developers should instead use the + // DEFINE_xxxx macros to register a command line flag. + void AddFlag(std::unique_ptr flag); + + // Sets the usage message, which is prepended to the --help message. + void SetUsageMessage(std::string help_usage); + + private: + FlagHelper(); + ~FlagHelper(); + + // Generates a help message from the Usage Message and registered flags. + std::string GetHelpMessage() const; + + std::string help_usage_; + std::map> defined_flags_; + + // base::CommandLine object for parsing the command line switches. This + // object isn't owned by this class, so don't need to delete it in the + // destructor. + base::CommandLine* command_line_; + + DISALLOW_COPY_AND_ASSIGN(FlagHelper); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_FLAG_HELPER_H_ diff --git a/brillo/flag_helper_unittest.cc b/brillo/flag_helper_unittest.cc new file mode 100644 index 0000000..29c6429 --- /dev/null +++ b/brillo/flag_helper_unittest.cc @@ -0,0 +1,362 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include +#include +#include + +#include + +namespace brillo { + +class FlagHelperTest : public ::testing::Test { + public: + FlagHelperTest() {} + ~FlagHelperTest() override { brillo::FlagHelper::ResetForTesting(); } + static void SetUpTestCase() { base::CommandLine::Init(0, nullptr); } +}; + +// Test that the DEFINE_xxxx macros can create the respective variables +// correctly with the default value. +TEST_F(FlagHelperTest, Defaults) { + DEFINE_bool(bool1, true, "Test bool flag"); + DEFINE_bool(bool2, false, "Test bool flag"); + DEFINE_int32(int32_1, INT32_MIN, "Test int32 flag"); + DEFINE_int32(int32_2, 0, "Test int32 flag"); + DEFINE_int32(int32_3, INT32_MAX, "Test int32 flag"); + DEFINE_int64(int64_1, INT64_MIN, "Test int64 flag"); + DEFINE_int64(int64_2, 0, "Test int64 flag"); + DEFINE_int64(int64_3, INT64_MAX, "Test int64 flag"); + DEFINE_uint64(uint64_1, 0, "Test uint64 flag"); + DEFINE_uint64(uint64_2, UINT_LEAST64_MAX, "Test uint64 flag"); + DEFINE_double(double_1, -100.5, "Test double flag"); + DEFINE_double(double_2, 0, "Test double flag"); + DEFINE_double(double_3, 100.5, "Test double flag"); + DEFINE_string(string_1, "", "Test string flag"); + DEFINE_string(string_2, "value", "Test string flag"); + + const char* argv[] = {"test_program"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + brillo::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue"); + + EXPECT_TRUE(FLAGS_bool1); + EXPECT_FALSE(FLAGS_bool2); + EXPECT_EQ(FLAGS_int32_1, INT32_MIN); + EXPECT_EQ(FLAGS_int32_2, 0); + EXPECT_EQ(FLAGS_int32_3, INT32_MAX); + EXPECT_EQ(FLAGS_int64_1, INT64_MIN); + EXPECT_EQ(FLAGS_int64_2, 0); + EXPECT_EQ(FLAGS_int64_3, INT64_MAX); + EXPECT_EQ(FLAGS_uint64_1, 0); + EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX); + EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5); + EXPECT_DOUBLE_EQ(FLAGS_double_2, 0); + EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5); + EXPECT_STREQ(FLAGS_string_1.c_str(), ""); + EXPECT_STREQ(FLAGS_string_2.c_str(), "value"); +} + +// Test that command line flag values are parsed and update the flag +// variable values correctly when using double '--' flags +TEST_F(FlagHelperTest, SetValueDoubleDash) { + DEFINE_bool(bool1, false, "Test bool flag"); + DEFINE_bool(bool2, true, "Test bool flag"); + DEFINE_bool(bool3, false, "Test bool flag"); + DEFINE_bool(bool4, true, "Test bool flag"); + DEFINE_int32(int32_1, 1, "Test int32 flag"); + DEFINE_int32(int32_2, 1, "Test int32 flag"); + DEFINE_int32(int32_3, 1, "Test int32 flag"); + DEFINE_int64(int64_1, 1, "Test int64 flag"); + DEFINE_int64(int64_2, 1, "Test int64 flag"); + DEFINE_int64(int64_3, 1, "Test int64 flag"); + DEFINE_uint64(uint64_1, 1, "Test uint64 flag"); + DEFINE_uint64(uint64_2, 1, "Test uint64 flag"); + DEFINE_double(double_1, 1, "Test double flag"); + DEFINE_double(double_2, 1, "Test double flag"); + DEFINE_double(double_3, 1, "Test double flag"); + DEFINE_string(string_1, "default", "Test string flag"); + DEFINE_string(string_2, "default", "Test string flag"); + + const char* argv[] = {"test_program", + "--bool1", + "--nobool2", + "--bool3=true", + "--bool4=false", + "--int32_1=-2147483648", + "--int32_2=0", + "--int32_3=2147483647", + "--int64_1=-9223372036854775808", + "--int64_2=0", + "--int64_3=9223372036854775807", + "--uint64_1=0", + "--uint64_2=18446744073709551615", + "--double_1=-100.5", + "--double_2=0", + "--double_3=100.5", + "--string_1=", + "--string_2=value"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + brillo::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue"); + + EXPECT_TRUE(FLAGS_bool1); + EXPECT_FALSE(FLAGS_bool2); + EXPECT_TRUE(FLAGS_bool3); + EXPECT_FALSE(FLAGS_bool4); + EXPECT_EQ(FLAGS_int32_1, INT32_MIN); + EXPECT_EQ(FLAGS_int32_2, 0); + EXPECT_EQ(FLAGS_int32_3, INT32_MAX); + EXPECT_EQ(FLAGS_int64_1, INT64_MIN); + EXPECT_EQ(FLAGS_int64_2, 0); + EXPECT_EQ(FLAGS_int64_3, INT64_MAX); + EXPECT_EQ(FLAGS_uint64_1, 0); + EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX); + EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5); + EXPECT_DOUBLE_EQ(FLAGS_double_2, 0); + EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5); + EXPECT_STREQ(FLAGS_string_1.c_str(), ""); + EXPECT_STREQ(FLAGS_string_2.c_str(), "value"); +} + +// Test that command line flag values are parsed and update the flag +// variable values correctly when using single '-' flags +TEST_F(FlagHelperTest, SetValueSingleDash) { + DEFINE_bool(bool1, false, "Test bool flag"); + DEFINE_bool(bool2, true, "Test bool flag"); + DEFINE_int32(int32_1, 1, "Test int32 flag"); + DEFINE_int32(int32_2, 1, "Test int32 flag"); + DEFINE_int32(int32_3, 1, "Test int32 flag"); + DEFINE_int64(int64_1, 1, "Test int64 flag"); + DEFINE_int64(int64_2, 1, "Test int64 flag"); + DEFINE_int64(int64_3, 1, "Test int64 flag"); + DEFINE_uint64(uint64_1, 1, "Test uint64 flag"); + DEFINE_uint64(uint64_2, 1, "Test uint64 flag"); + DEFINE_double(double_1, 1, "Test double flag"); + DEFINE_double(double_2, 1, "Test double flag"); + DEFINE_double(double_3, 1, "Test double flag"); + DEFINE_string(string_1, "default", "Test string flag"); + DEFINE_string(string_2, "default", "Test string flag"); + + const char* argv[] = {"test_program", + "-bool1", + "-nobool2", + "-int32_1=-2147483648", + "-int32_2=0", + "-int32_3=2147483647", + "-int64_1=-9223372036854775808", + "-int64_2=0", + "-int64_3=9223372036854775807", + "-uint64_1=0", + "-uint64_2=18446744073709551615", + "-double_1=-100.5", + "-double_2=0", + "-double_3=100.5", + "-string_1=", + "-string_2=value"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + brillo::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue"); + + EXPECT_TRUE(FLAGS_bool1); + EXPECT_FALSE(FLAGS_bool2); + EXPECT_EQ(FLAGS_int32_1, INT32_MIN); + EXPECT_EQ(FLAGS_int32_2, 0); + EXPECT_EQ(FLAGS_int32_3, INT32_MAX); + EXPECT_EQ(FLAGS_int64_1, INT64_MIN); + EXPECT_EQ(FLAGS_int64_2, 0); + EXPECT_EQ(FLAGS_int64_3, INT64_MAX); + EXPECT_EQ(FLAGS_uint64_1, 0); + EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX); + EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5); + EXPECT_DOUBLE_EQ(FLAGS_double_2, 0); + EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5); + EXPECT_STREQ(FLAGS_string_1.c_str(), ""); + EXPECT_STREQ(FLAGS_string_2.c_str(), "value"); +} + +// Test that a duplicated flag on the command line picks up the last +// value set. +TEST_F(FlagHelperTest, DuplicateSetValue) { + DEFINE_int32(int32_1, 0, "Test in32 flag"); + + const char* argv[] = {"test_program", "--int32_1=5", "--int32_1=10"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + brillo::FlagHelper::Init(arraysize(argv), argv, "TestDuplicateSetvalue"); + + EXPECT_EQ(FLAGS_int32_1, 10); +} + +// Test that flags set after the -- marker are not parsed as command line flags +TEST_F(FlagHelperTest, FlagTerminator) { + DEFINE_int32(int32_1, 0, "Test int32 flag"); + + const char* argv[] = {"test_program", "--int32_1=5", "--", "--int32_1=10"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + brillo::FlagHelper::Init(arraysize(argv), argv, "TestFlagTerminator"); + + EXPECT_EQ(FLAGS_int32_1, 5); +} + +// Test that help messages are generated correctly when the --help flag +// is passed to the program. +TEST_F(FlagHelperTest, HelpMessage) { + DEFINE_bool(bool_1, true, "Test bool flag"); + DEFINE_int32(int_1, 0, "Test int flag"); + DEFINE_int64(int64_1, 0, "Test int64 flag"); + DEFINE_uint64(uint64_1, 0, "Test uint64 flag"); + DEFINE_double(double_1, 0, "Test double flag"); + DEFINE_string(string_1, "", "Test string flag"); + + const char* argv[] = {"test_program", "--int_1=value", "--help"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + + FILE* orig = stdout; + stdout = stderr; + + ASSERT_EXIT( + brillo::FlagHelper::Init(arraysize(argv), argv, "TestHelpMessage"), + ::testing::ExitedWithCode(EX_OK), + "TestHelpMessage\n\n" + " --bool_1 \\(Test bool flag\\) type: bool default: true\n" + " --double_1 \\(Test double flag\\) type: double default: 0\n" + " --help \\(Show this help message\\) type: bool default: false\n" + " --int64_1 \\(Test int64 flag\\) type: int64 default: 0\n" + " --int_1 \\(Test int flag\\) type: int default: 0\n" + " --string_1 \\(Test string flag\\) type: string default: \"\"\n" + " --uint64_1 \\(Test uint64 flag\\) type: uint64 default: 0\n"); + + stdout = orig; +} + +// Test that passing in unknown command line flags causes the program +// to exit with EX_USAGE error code and corresponding error message. +TEST_F(FlagHelperTest, UnknownFlag) { + const char* argv[] = {"test_program", "--flag=value"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + + FILE* orig = stdout; + stdout = stderr; + + ASSERT_EXIT(brillo::FlagHelper::Init(arraysize(argv), argv, "TestIntExit"), + ::testing::ExitedWithCode(EX_USAGE), + "ERROR: unknown command line flag 'flag'"); + + stdout = orig; +} + +// Test that when passing an incorrect/unparsable type to a command line flag, +// the program exits with code EX_DATAERR and outputs a corresponding message. +TEST_F(FlagHelperTest, BoolParseError) { + DEFINE_bool(bool_1, 0, "Test bool flag"); + + const char* argv[] = {"test_program", "--bool_1=value"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + + FILE* orig = stdout; + stdout = stderr; + + ASSERT_EXIT( + brillo::FlagHelper::Init(arraysize(argv), argv, "TestBoolParseError"), + ::testing::ExitedWithCode(EX_DATAERR), + "ERROR: illegal value 'value' specified for bool flag 'bool_1'"); + + stdout = orig; +} + +// Test that when passing an incorrect/unparsable type to a command line flag, +// the program exits with code EX_DATAERR and outputs a corresponding message. +TEST_F(FlagHelperTest, Int32ParseError) { + DEFINE_int32(int_1, 0, "Test int flag"); + + const char* argv[] = {"test_program", "--int_1=value"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + + FILE* orig = stdout; + stdout = stderr; + + ASSERT_EXIT(brillo::FlagHelper::Init(arraysize(argv), + argv, + "TestInt32ParseError"), + ::testing::ExitedWithCode(EX_DATAERR), + "ERROR: illegal value 'value' specified for int flag 'int_1'"); + + stdout = orig; +} + +// Test that when passing an incorrect/unparsable type to a command line flag, +// the program exits with code EX_DATAERR and outputs a corresponding message. +TEST_F(FlagHelperTest, Int64ParseError) { + DEFINE_int64(int64_1, 0, "Test int64 flag"); + + const char* argv[] = {"test_program", "--int64_1=value"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + + FILE* orig = stdout; + stdout = stderr; + + ASSERT_EXIT( + brillo::FlagHelper::Init(arraysize(argv), argv, "TestInt64ParseError"), + ::testing::ExitedWithCode(EX_DATAERR), + "ERROR: illegal value 'value' specified for int64 flag " + "'int64_1'"); + + stdout = orig; +} + +// Test that when passing an incorrect/unparsable type to a command line flag, +// the program exits with code EX_DATAERR and outputs a corresponding message. +TEST_F(FlagHelperTest, UInt64ParseError) { + DEFINE_uint64(uint64_1, 0, "Test uint64 flag"); + + const char* argv[] = {"test_program", "--uint64_1=value"}; + base::CommandLine command_line(arraysize(argv), argv); + + brillo::FlagHelper::GetInstance()->set_command_line_for_testing( + &command_line); + + FILE* orig = stdout; + stdout = stderr; + + ASSERT_EXIT( + brillo::FlagHelper::Init(arraysize(argv), argv, "TestUInt64ParseError"), + ::testing::ExitedWithCode(EX_DATAERR), + "ERROR: illegal value 'value' specified for uint64 flag " + "'uint64_1'"); + + stdout = orig; +} + +} // namespace brillo diff --git a/brillo/glib/abstract_dbus_service.cc b/brillo/glib/abstract_dbus_service.cc new file mode 100644 index 0000000..c5ed27d --- /dev/null +++ b/brillo/glib/abstract_dbus_service.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "brillo/glib/abstract_dbus_service.h" + +namespace brillo { +namespace dbus { + +bool AbstractDbusService::Register(const brillo::dbus::BusConnection& conn) { + return RegisterExclusiveService(conn, + service_interface(), + service_name(), + service_path(), + service_object()); +} + +bool AbstractDbusService::Run() { + if (!main_loop()) { + LOG(ERROR) << "No run loop. Call Initialize before use."; + return false; + } + ::g_main_loop_run(main_loop()); + DLOG(INFO) << "Run() completed"; + return true; +} + +bool AbstractDbusService::Shutdown() { + ::g_main_loop_quit(main_loop()); + return true; +} + +} // namespace dbus +} // namespace brillo diff --git a/brillo/glib/abstract_dbus_service.h b/brillo/glib/abstract_dbus_service.h new file mode 100644 index 0000000..8b559df --- /dev/null +++ b/brillo/glib/abstract_dbus_service.h @@ -0,0 +1,50 @@ +// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_GLIB_ABSTRACT_DBUS_SERVICE_H_ +#define LIBCHROMEOS_BRILLO_GLIB_ABSTRACT_DBUS_SERVICE_H_ + +#include +#include + +namespace brillo { + +// \precondition No functions in the dbus namespace can be called before +// ::g_type_init(); + +namespace dbus { +class BRILLO_EXPORT AbstractDbusService { + public: + virtual ~AbstractDbusService() {} + + // Setup the wrapped GObject and the GMainLoop + virtual bool Initialize() = 0; + virtual bool Reset() = 0; + + // Registers the GObject as a service with the system DBus + // TODO(wad) make this testable by making BusConn and Proxy + // subclassing friendly. + virtual bool Register(const brillo::dbus::BusConnection& conn); + + // Starts the run loop + virtual bool Run(); + + // Stops the run loop + virtual bool Shutdown(); + + // Used internally during registration to set the + // proper service information. + virtual const char* service_name() const = 0; + virtual const char* service_path() const = 0; + virtual const char* service_interface() const = 0; + virtual GObject* service_object() const = 0; + + protected: + virtual GMainLoop* main_loop() = 0; +}; + +} // namespace dbus +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_GLIB_ABSTRACT_DBUS_SERVICE_H_ diff --git a/brillo/glib/dbus.cc b/brillo/glib/dbus.cc new file mode 100644 index 0000000..c56a88c --- /dev/null +++ b/brillo/glib/dbus.cc @@ -0,0 +1,356 @@ +// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/glib/dbus.h" + +#include +#include +#include + +#include +#include + +namespace brillo { +namespace dbus { + +bool CallPtrArray(const Proxy& proxy, + const char* method, + glib::ScopedPtrArray* result) { + glib::ScopedError error; + + ::GType g_type_array = ::dbus_g_type_get_collection("GPtrArray", + DBUS_TYPE_G_OBJECT_PATH); + + + if (!::dbus_g_proxy_call(proxy.gproxy(), method, &Resetter(&error).lvalue(), + G_TYPE_INVALID, g_type_array, + &Resetter(result).lvalue(), G_TYPE_INVALID)) { + LOG(WARNING) << "CallPtrArray failed: " + << (error->message ? error->message : "Unknown Error."); + return false; + } + + return true; +} + +BusConnection GetSystemBusConnection() { + glib::ScopedError error; + ::DBusGConnection* result = ::dbus_g_bus_get(DBUS_BUS_SYSTEM, + &Resetter(&error).lvalue()); + if (!result) { + LOG(ERROR) << "dbus_g_bus_get(DBUS_BUS_SYSTEM) failed: " + << ((error.get() && error->message) ? + error->message : "Unknown Error"); + return BusConnection(nullptr); + } + // Set to not exit when system bus is disconnected. + // This fixes the problem where when the dbus daemon is stopped, exit is + // called which kills Chrome. + ::dbus_connection_set_exit_on_disconnect( + ::dbus_g_connection_get_connection(result), FALSE); + return BusConnection(result); +} + +BusConnection GetPrivateBusConnection(const char* address) { + // Since dbus-glib does not have an API like dbus_g_connection_open_private(), + // we have to implement our own. + + // We have to call _dbus_g_value_types_init() to register standard marshalers + // just like as dbus_g_bus_get() and dbus_g_connection_open() do, but the + // function is not exported. So we call GetPrivateBusConnection() which calls + // dbus_g_bus_get() here instead. Note that if we don't call + // _dbus_g_value_types_init(), we might get "WARNING **: No demarshaller + // registered for type xxxxx" error and might not be able to handle incoming + // signals nor method calls. + { + BusConnection system_bus_connection = GetSystemBusConnection(); + if (!system_bus_connection.HasConnection()) { + return system_bus_connection; // returns NULL connection. + } + } + + ::DBusError error; + ::dbus_error_init(&error); + + ::DBusGConnection* result = nullptr; + ::DBusConnection* raw_connection + = ::dbus_connection_open_private(address, &error); + if (!raw_connection) { + LOG(WARNING) << "dbus_connection_open_private failed: " << address; + return BusConnection(nullptr); + } + + if (!::dbus_bus_register(raw_connection, &error)) { + LOG(ERROR) << "dbus_bus_register failed: " + << (error.message ? error.message : "Unknown Error."); + ::dbus_error_free(&error); + // TODO(yusukes): We don't call dbus_connection_close() nor g_object_unref() + // here for now since these calls might interfere with IBusBus connections + // in libcros and Chrome. See the comment in ~InputMethodStatusConnection() + // function in platform/cros/chromeos_input_method.cc for details. + return BusConnection(nullptr); + } + + ::dbus_connection_setup_with_g_main( + raw_connection, nullptr /* default context */); + + // A reference count of |raw_connection| is transferred to |result|. You don't + // have to (and should not) unref the |raw_connection|. + result = ::dbus_connection_get_g_connection(raw_connection); + CHECK(result); + + ::dbus_connection_set_exit_on_disconnect( + ::dbus_g_connection_get_connection(result), FALSE); + + return BusConnection(result); +} + +bool RetrieveProperties(const Proxy& proxy, + const char* interface, + glib::ScopedHashTable* result) { + glib::ScopedError error; + + if (!::dbus_g_proxy_call(proxy.gproxy(), "GetAll", &Resetter(&error).lvalue(), + G_TYPE_STRING, interface, G_TYPE_INVALID, + ::dbus_g_type_get_map("GHashTable", G_TYPE_STRING, + G_TYPE_VALUE), + &Resetter(result).lvalue(), G_TYPE_INVALID)) { + LOG(WARNING) << "RetrieveProperties failed: " + << (error->message ? error->message : "Unknown Error."); + return false; + } + return true; +} + +Proxy::Proxy() + : object_(nullptr) { +} + +// Set |connect_to_name_owner| true if you'd like to use +// dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name(). +Proxy::Proxy(const BusConnection& connection, + const char* name, + const char* path, + const char* interface, + bool connect_to_name_owner) + : object_(GetGProxy( + connection, name, path, interface, connect_to_name_owner)) { +} + +// Equivalent to Proxy(connection, name, path, interface, false). +Proxy::Proxy(const BusConnection& connection, + const char* name, + const char* path, + const char* interface) + : object_(GetGProxy(connection, name, path, interface, false)) { +} + +// Creates a peer proxy using dbus_g_proxy_new_for_peer. +Proxy::Proxy(const BusConnection& connection, + const char* path, + const char* interface) + : object_(GetGPeerProxy(connection, path, interface)) { +} + +Proxy::Proxy(const Proxy& x) + : object_(x.object_) { + if (object_) + ::g_object_ref(object_); +} + +Proxy::~Proxy() { + if (object_) + ::g_object_unref(object_); +} + +/* static */ +Proxy::value_type Proxy::GetGProxy(const BusConnection& connection, + const char* name, + const char* path, + const char* interface, + bool connect_to_name_owner) { + value_type result = nullptr; + if (connect_to_name_owner) { + glib::ScopedError error; + result = ::dbus_g_proxy_new_for_name_owner(connection.object_, + name, + path, + interface, + &Resetter(&error).lvalue()); + if (!result) { + DLOG(ERROR) << "Failed to construct proxy: " + << (error->message ? error->message : "Unknown Error") + << ": " << path; + } + } else { + result = ::dbus_g_proxy_new_for_name(connection.object_, + name, + path, + interface); + if (!result) { + LOG(ERROR) << "Failed to construct proxy: " << path; + } + } + return result; +} + +/* static */ +Proxy::value_type Proxy::GetGPeerProxy(const BusConnection& connection, + const char* path, + const char* interface) { + value_type result = ::dbus_g_proxy_new_for_peer(connection.object_, + path, + interface); + if (!result) + LOG(ERROR) << "Failed to construct peer proxy: " << path; + + return result; +} + +bool RegisterExclusiveService(const BusConnection& connection, + const char* interface_name, + const char* service_name, + const char* service_path, + GObject* object) { + CHECK(object); + CHECK(interface_name); + CHECK(service_name); + // Create a proxy to DBus itself so that we can request to become a + // service name owner and then register an object at the related service path. + Proxy proxy = brillo::dbus::Proxy(connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + // Exclusivity is determined by replacing any existing + // service, not queuing, and ensuring we are the primary + // owner after the name is ours. + glib::ScopedError err; + guint result = 0; + // TODO(wad) determine if we are moving away from using generated functions + if (!org_freedesktop_DBus_request_name(proxy.gproxy(), + service_name, + 0, + &result, + &Resetter(&err).lvalue())) { + LOG(ERROR) << "Unable to request service name: " + << (err->message ? err->message : "Unknown Error."); + return false; + } + + // Handle the error codes, releasing the name if exclusivity conditions + // are not met. + bool needs_release = false; + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + LOG(ERROR) << "Failed to become the primary owner. Releasing . . ."; + needs_release = true; + } + if (result == DBUS_REQUEST_NAME_REPLY_EXISTS) { + LOG(ERROR) << "Service name exists: " << service_name; + return false; + } else if (result == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { + LOG(ERROR) << "Service name request enqueued despite our flags. Releasing"; + needs_release = true; + } + LOG_IF(WARNING, result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) + << "Service name already owned by this process"; + if (needs_release) { + if (!org_freedesktop_DBus_release_name( + proxy.gproxy(), + service_name, + &result, + &Resetter(&err).lvalue())) { + LOG(ERROR) << "Unabled to release service name: " + << (err->message ? err->message : "Unknown Error."); + } + DLOG(INFO) << "ReleaseName returned code " << result; + return false; + } + + // Determine a path from the service name and register the object. + dbus_g_connection_register_g_object(connection.g_connection(), + service_path, + object); + return true; +} + +void CallMethodWithNoArguments(const char* service_name, + const char* path, + const char* interface_name, + const char* method_name) { + Proxy proxy(dbus::GetSystemBusConnection(), + service_name, + path, + interface_name); + ::dbus_g_proxy_call_no_reply(proxy.gproxy(), method_name, G_TYPE_INVALID); +} + +void SignalWatcher::StartMonitoring(const std::string& interface, + const std::string& signal) { + DCHECK(interface_.empty()) << "StartMonitoring() must be called only once"; + interface_ = interface; + signal_ = signal; + + // Snoop on D-Bus messages so we can get notified about signals. + DBusConnection* dbus_conn = dbus_g_connection_get_connection( + GetSystemBusConnection().g_connection()); + DCHECK(dbus_conn); + + DBusError error; + dbus_error_init(&error); + dbus_bus_add_match(dbus_conn, GetDBusMatchString().c_str(), &error); + if (dbus_error_is_set(&error)) { + LOG(DFATAL) << "Got error while adding D-Bus match rule: " << error.name + << " (" << error.message << ")"; + } + + if (!dbus_connection_add_filter(dbus_conn, + &SignalWatcher::FilterDBusMessage, + this, // user_data + nullptr)) { // free_data_function + LOG(DFATAL) << "Unable to add D-Bus filter"; + } +} + +SignalWatcher::~SignalWatcher() { + if (interface_.empty()) + return; + + DBusConnection* dbus_conn = dbus_g_connection_get_connection( + dbus::GetSystemBusConnection().g_connection()); + DCHECK(dbus_conn); + + dbus_connection_remove_filter(dbus_conn, + &SignalWatcher::FilterDBusMessage, + this); + + DBusError error; + dbus_error_init(&error); + dbus_bus_remove_match(dbus_conn, GetDBusMatchString().c_str(), &error); + if (dbus_error_is_set(&error)) { + LOG(DFATAL) << "Got error while removing D-Bus match rule: " << error.name + << " (" << error.message << ")"; + } +} + +std::string SignalWatcher::GetDBusMatchString() const { + return base::StringPrintf("type='signal', interface='%s', member='%s'", + interface_.c_str(), signal_.c_str()); +} + +/* static */ +DBusHandlerResult SignalWatcher::FilterDBusMessage(DBusConnection* dbus_conn, + DBusMessage* message, + void* data) { + SignalWatcher* self = static_cast(data); + if (dbus_message_is_signal( + message, self->interface_.c_str(), self->signal_.c_str())) { + self->OnSignal(message); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } +} + +} // namespace dbus +} // namespace brillo diff --git a/brillo/glib/dbus.h b/brillo/glib/dbus.h new file mode 100644 index 0000000..73a54c9 --- /dev/null +++ b/brillo/glib/dbus.h @@ -0,0 +1,468 @@ +// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_GLIB_DBUS_H_ +#define LIBCHROMEOS_BRILLO_GLIB_DBUS_H_ + +#include +#include + +#include +#include + +#include "base/logging.h" +#include +#include + +struct DBusMessage; +struct DBusConnection; + +namespace brillo { + +// \precondition No functions in the dbus namespace can be called before +// ::g_type_init(); + +namespace dbus { + +// \brief BusConnection manages the ref-count for a ::DBusGConnection*. +// +// A BusConnection has reference semantics bound to a particular communication +// bus. +// +// \models Copyable, Assignable +// \related GetSystemBusConnection() + +class BRILLO_EXPORT BusConnection { + public: + typedef ::DBusGConnection* value_type; + + BusConnection(const BusConnection& x) : object_(x.object_) { + if (object_) + ::dbus_g_connection_ref(object_); + } + + ~BusConnection() { + if (object_) + ::dbus_g_connection_unref(object_); + } + + BusConnection& operator=(BusConnection x) { + swap(*this, x); + return *this; + } + + const value_type& g_connection() const { + DCHECK(object_) << "referencing an empty connection"; + return object_; + } + + operator bool() const { return object_; } + + bool HasConnection() const { return object_; } + + private: + friend void swap(BusConnection& x, BusConnection& y); + + friend class Proxy; + friend BusConnection GetSystemBusConnection(); + friend BusConnection GetPrivateBusConnection(const char* address); + + // Constructor takes ownership + BRILLO_PRIVATE explicit BusConnection(::DBusGConnection* x) : object_(x) {} + + value_type object_; +}; + +inline void swap(BusConnection& x, BusConnection& y) { + std::swap(x.object_, y.object_); +} + +// \brief Proxy manages the ref-count for a ::DBusGProxy*. +// +// Proxy has reference semantics and represents a connection to on object on +// the bus. A proxy object is constructed with a connection to a bus, a name +// to an entity on the bus, a path to an object owned by the entity, and an +// interface protocol name used to communicate with the object. + +class BRILLO_EXPORT Proxy { + public: + typedef ::DBusGProxy* value_type; + + Proxy(); + + // Set |connect_to_name_owner| true if you'd like to use + // dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name(). + Proxy(const BusConnection& connection, + const char* name, + const char* path, + const char* interface, + bool connect_to_name_owner); + + // Equivalent to Proxy(connection, name, path, interface, false). + Proxy(const BusConnection& connection, + const char* name, + const char* path, + const char* interface); + + // Creates a peer proxy using dbus_g_proxy_new_for_peer. + Proxy(const BusConnection& connection, + const char* path, + const char* interface); + + Proxy(const Proxy& x); + + ~Proxy(); + + Proxy& operator=(Proxy x) { + swap(*this, x); + return *this; + } + + const char* path() const { + DCHECK(object_) << "referencing an empty proxy"; + return ::dbus_g_proxy_get_path(object_); + } + + // gproxy() returns a reference to the underlying ::DBusGProxy*. As this + // library evolves, the gproxy() will be moved to be private. + + const value_type& gproxy() const { + DCHECK(object_) << "referencing an empty proxy"; + return object_; + } + + operator bool() const { return object_; } + + private: + BRILLO_PRIVATE static value_type GetGProxy(const BusConnection& connection, + const char* name, + const char* path, + const char* interface, + bool connect_to_name_owner); + + BRILLO_PRIVATE static value_type GetGPeerProxy( + const BusConnection& connection, + const char* path, + const char* interface); + + BRILLO_PRIVATE operator int() const; // for safe bool cast + friend void swap(Proxy& x, Proxy& y); + + value_type object_; +}; + +inline void swap(Proxy& x, Proxy& y) { + std::swap(x.object_, y.object_); +} + +// \brief RegisterExclusiveService configures a GObject to run as a service on +// a supplied ::BusConnection. +// +// RegisterExclusiveService encapsulates the process of configuring the +// supplied \param object at \param service_path on the \param connection. +// Exclusivity is ensured by replacing any existing services at that named +// location and confirming that the connection is the primary owner. +// +// Type information for the \param object must be installed with +// dbus_g_object_type_install_info prior to use. + +BRILLO_EXPORT bool RegisterExclusiveService(const BusConnection& connection, + const char* interface_name, + const char* service_name, + const char* service_path, + GObject* object); + +template // F is a function signature +class MonitorConnection; + +template +class MonitorConnection { + public: + MonitorConnection(const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1), + void* object) + : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} + + static void Run(::DBusGProxy*, A1 x, MonitorConnection* self) { + self->monitor_(self->object_, x); + } + const Proxy& proxy() const { return proxy_; } + const std::string& name() const { return name_; } + + private: + Proxy proxy_; + std::string name_; + void (*monitor_)(void*, A1); + void* object_; +}; + +template +class MonitorConnection { + public: + MonitorConnection(const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1, A2), + void* object) + : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} + + static void Run(::DBusGProxy*, A1 x, A2 y, MonitorConnection* self) { + self->monitor_(self->object_, x, y); + } + const Proxy& proxy() const { return proxy_; } + const std::string& name() const { return name_; } + + private: + Proxy proxy_; + std::string name_; + void (*monitor_)(void*, A1, A2); + void* object_; +}; + +template +class MonitorConnection { + public: + MonitorConnection(const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1, A2, A3), + void* object) + : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} + + static void Run(::DBusGProxy*, A1 x, A2 y, A3 z, MonitorConnection* self) { + self->monitor_(self->object_, x, y, z); + } + const Proxy& proxy() const { return proxy_; } + const std::string& name() const { return name_; } + + private: + Proxy proxy_; + std::string name_; + void (*monitor_)(void*, A1, A2, A3); + void* object_; +}; + +template +class MonitorConnection { + public: + MonitorConnection(const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1, A2, A3, A4), + void* object) + : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} + + static void Run(::DBusGProxy*, + A1 x, + A2 y, + A3 z, + A4 w, + MonitorConnection* self) { + self->monitor_(self->object_, x, y, z, w); + } + const Proxy& proxy() const { return proxy_; } + const std::string& name() const { return name_; } + + private: + Proxy proxy_; + std::string name_; + void (*monitor_)(void*, A1, A2, A3, A4); + void* object_; +}; + +template +MonitorConnection* Monitor(const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1), + void* object) { + typedef MonitorConnection ConnectionType; + + ConnectionType* result = new ConnectionType(proxy, name, monitor, object); + + ::dbus_g_proxy_add_signal( + proxy.gproxy(), name, glib::type_to_gtypeid(), G_TYPE_INVALID); + ::dbus_g_proxy_connect_signal( + proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); + return result; +} + +template +MonitorConnection* Monitor(const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1, A2), + void* object) { + typedef MonitorConnection ConnectionType; + + ConnectionType* result = new ConnectionType(proxy, name, monitor, object); + + ::dbus_g_proxy_add_signal(proxy.gproxy(), + name, + glib::type_to_gtypeid(), + glib::type_to_gtypeid(), + G_TYPE_INVALID); + ::dbus_g_proxy_connect_signal( + proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); + return result; +} + +template +MonitorConnection* Monitor(const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1, A2, A3), + void* object) { + typedef MonitorConnection ConnectionType; + + ConnectionType* result = new ConnectionType(proxy, name, monitor, object); + + ::dbus_g_proxy_add_signal(proxy.gproxy(), + name, + glib::type_to_gtypeid(), + glib::type_to_gtypeid(), + glib::type_to_gtypeid(), + G_TYPE_INVALID); + ::dbus_g_proxy_connect_signal( + proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); + return result; +} + +template +MonitorConnection* Monitor( + const Proxy& proxy, + const char* name, + void (*monitor)(void*, A1, A2, A3, A4), + void* object) { + typedef MonitorConnection ConnectionType; + + ConnectionType* result = new ConnectionType(proxy, name, monitor, object); + + ::dbus_g_proxy_add_signal(proxy.gproxy(), + name, + glib::type_to_gtypeid(), + glib::type_to_gtypeid(), + glib::type_to_gtypeid(), + glib::type_to_gtypeid(), + G_TYPE_INVALID); + ::dbus_g_proxy_connect_signal( + proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); + return result; +} + +template +void Disconnect(MonitorConnection* connection) { + typedef MonitorConnection ConnectionType; + + ::dbus_g_proxy_disconnect_signal(connection->proxy().gproxy(), + connection->name().c_str(), + G_CALLBACK(&ConnectionType::Run), + connection); + delete connection; +} + +// \brief call_PtrArray() invokes a method on a proxy returning a +// glib::PtrArray. +// +// CallPtrArray is the first instance of what is likely to be a general +// way to make method calls to a proxy. It will likely be replaced with +// something like Call(proxy, method, arg1, arg2, ..., ResultType*) in the +// future. However, I don't yet have enough cases to generalize from. + +BRILLO_EXPORT bool CallPtrArray(const Proxy& proxy, + const char* method, + glib::ScopedPtrArray* result); + +// \brief RetrieveProperty() retrieves a property of an object associated with a +// proxy. +// +// Given a proxy to an object supporting the org.freedesktop.DBus.Properties +// interface, the RetrieveProperty() call will retrieve a property of the +// specified interface on the object storing it in \param result and returning +// \true. If the dbus call fails or the object returned is not of type \param T, +// then \false is returned and \param result is unchanged. +// +// \example +// Proxy proxy(GetSystemBusConnection(), +// "org.freedesktop.DeviceKit.Power", // A named entity on the bus +// battery_name, // Path to a battery on the bus +// "org.freedesktop.DBus.Properties") // Properties interface +// +// double x; +// if (RetrieveProperty(proxy, +// "org.freedesktop.DeviceKit.Power.Device", +// "percentage") +// std::cout << "Battery charge is " << x << "% of capacity."; +// \end_example + +template +inline bool RetrieveProperty(const Proxy& proxy, + const char* interface, + const char* property, + T* result) { + glib::ScopedError error; + glib::Value value; + + if (!::dbus_g_proxy_call(proxy.gproxy(), "Get", &Resetter(&error).lvalue(), + G_TYPE_STRING, interface, + G_TYPE_STRING, property, + G_TYPE_INVALID, + G_TYPE_VALUE, &value, + G_TYPE_INVALID)) { + LOG(ERROR) << "Getting property failed: " + << (error->message ? error->message : "Unknown Error."); + return false; + } + return glib::Retrieve(value, result); +} + +// \brief RetrieveProperties returns a HashTable of all properties for the +// specified interface. + +BRILLO_EXPORT bool RetrieveProperties(const Proxy& proxy, + const char* interface, + glib::ScopedHashTable* result); + +// \brief Returns a connection to the system bus. + +BRILLO_EXPORT BusConnection GetSystemBusConnection(); + +// \brief Returns a private connection to a bus at |address|. + +BRILLO_EXPORT BusConnection GetPrivateBusConnection(const char* address); + +// \brief Calls a method |method_name| with no arguments per the given |path| +// and |interface_name|. Ignores return value. + +BRILLO_EXPORT void CallMethodWithNoArguments(const char* service_name, + const char* path, + const char* interface_name, + const char* method_name); + +// \brief Low-level signal monitor base class. +// +// Used when there is no definite named signal sender (that Proxy +// could be used for). + +class BRILLO_EXPORT SignalWatcher { + public: + SignalWatcher() {} + ~SignalWatcher(); + void StartMonitoring(const std::string& interface, const std::string& signal); + + private: + // Callback invoked on the given signal arrival. + virtual void OnSignal(DBusMessage* message) = 0; + + // Returns a string matching the D-Bus messages that we want to listen for. + BRILLO_PRIVATE std::string GetDBusMatchString() const; + + // A D-Bus message filter to receive signals. + BRILLO_PRIVATE static DBusHandlerResult FilterDBusMessage( + DBusConnection* dbus_conn, + DBusMessage* message, + void* data); + std::string interface_; + std::string signal_; +}; + +} // namespace dbus +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_GLIB_DBUS_H_ diff --git a/brillo/glib/object.h b/brillo/glib/object.h new file mode 100644 index 0000000..c4dc58d --- /dev/null +++ b/brillo/glib/object.h @@ -0,0 +1,508 @@ +// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_GLIB_OBJECT_H_ +#define LIBCHROMEOS_BRILLO_GLIB_OBJECT_H_ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace brillo { + +namespace details { // NOLINT + +// \brief ResetHelper is a private class for use with Resetter(). +// +// ResetHelper passes ownership of a pointer to a scoped pointer type with reset +// on destruction. + +template // T models ScopedPtr +class ResetHelper { + public: + typedef typename T::element_type element_type; + + explicit ResetHelper(T* x) + : ptr_(nullptr), + scoped_(x) { + } + ~ResetHelper() { + scoped_->reset(ptr_); + } + element_type*& lvalue() { + return ptr_; + } + + private: + element_type* ptr_; + T* scoped_; +}; + +} // namespace details + +// \brief Resetter() is a utility function for passing pointers to +// scoped pointers. +// +// The Resetter() function return a temporary object containing an lvalue of +// \code T::element_type which can be assigned to. When the temporary object +// destructs, the associated scoped pointer is reset with the lvalue. It is of +// general use when a pointer is returned as an out-argument. +// +// \example +// void function(int** x) { +// *x = new int(10); +// } +// ... +// scoped_ptr x; +// function(Resetter(x).lvalue()); +// +// \end_example + +template // T models ScopedPtr +details::ResetHelper Resetter(T* x) { + return details::ResetHelper(x); +} + +// \precondition No functions in the glib namespace can be called before +// ::g_type_init(); + +namespace glib { + +// \brief type_to_gtypeid is a type function mapping from a canonical type to +// the GType typeid for the associated GType (see type_to_gtype). + +template ::GType type_to_gtypeid(); + +template < > +inline ::GType type_to_gtypeid() { + return G_TYPE_STRING; +} +template < > +inline ::GType type_to_gtypeid() { + return G_TYPE_STRING; +} +template < > +inline ::GType type_to_gtypeid< ::uint8_t>() { + return G_TYPE_UCHAR; +} +template < > +inline ::GType type_to_gtypeid() { + return G_TYPE_DOUBLE; +} +template < > +inline ::GType type_to_gtypeid() { + return G_TYPE_BOOLEAN; +} +class Value; +template < > +inline ::GType type_to_gtypeid() { + return G_TYPE_VALUE; +} + +template < > +inline ::GType type_to_gtypeid< ::uint32_t>() { + // REVISIT (seanparent) : There currently isn't any G_TYPE_UINT32, this code + // assumes sizeof(guint) == sizeof(guint32). Need a static_assert to assert + // that. + return G_TYPE_UINT; +} + +template < > +inline ::GType type_to_gtypeid< ::int64_t>() { + return G_TYPE_INT64; +} + +template < > +inline ::GType type_to_gtypeid< ::int32_t>() { + return G_TYPE_INT; +} + +// \brief Value (and Retrieve) support using std::string as well as const char* +// by promoting from const char* to the string. promote_from provides a mapping +// for this promotion (and possibly others in the future). + +template struct promotes_from { + typedef T type; +}; +template < > struct promotes_from { + typedef const char* type; +}; + +// \brief RawCast converts from a GValue to a value of a canonical type. +// +// RawCast is a low level function. Generally, use Cast() instead. +// +// \precondition \param x contains a value of type \param T. + +template +inline T RawCast(const ::GValue& x) { + // Use static_assert() to issue a meaningful compile-time error. + // To prevent this from happening for all references to RawCast, use sizeof(T) + // to make static_assert depend on type T and therefore prevent binding it + // unconditionally until the actual RawCast instantiation happens. + static_assert(sizeof(T) == 0, "Using RawCast on unsupported type"); + return T(); +} + +template < > +inline const char* RawCast(const ::GValue& x) { + return static_cast(::g_value_get_string(&x)); +} +template < > +inline double RawCast(const ::GValue& x) { + return static_cast(::g_value_get_double(&x)); +} +template < > +inline bool RawCast(const ::GValue& x) { + return static_cast(::g_value_get_boolean(&x)); +} +template < > +inline ::uint32_t RawCast< ::uint32_t>(const ::GValue& x) { + return static_cast< ::uint32_t>(::g_value_get_uint(&x)); +} +template < > +inline ::uint8_t RawCast< ::uint8_t>(const ::GValue& x) { + return static_cast< ::uint8_t>(::g_value_get_uchar(&x)); +} +template < > +inline ::int64_t RawCast< ::int64_t>(const ::GValue& x) { + return static_cast< ::int64_t>(::g_value_get_int64(&x)); +} +template < > +inline ::int32_t RawCast< ::int32_t>(const ::GValue& x) { + return static_cast< ::int32_t>(::g_value_get_int(&x)); +} + +inline void RawSet(GValue* x, const std::string& v) { + ::g_value_set_string(x, v.c_str()); +} +inline void RawSet(GValue* x, const char* v) { + ::g_value_set_string(x, v); +} +inline void RawSet(GValue* x, double v) { + ::g_value_set_double(x, v); +} +inline void RawSet(GValue* x, bool v) { + ::g_value_set_boolean(x, v); +} +inline void RawSet(GValue* x, ::uint32_t v) { + ::g_value_set_uint(x, v); +} +inline void RawSet(GValue* x, ::uint8_t v) { + ::g_value_set_uchar(x, v); +} +inline void RawSet(GValue* x, ::int64_t v) { + ::g_value_set_int64(x, v); +} +inline void RawSet(GValue* x, ::int32_t v) { + ::g_value_set_int(x, v); +} + +// \brief Value is a data type for managing GValues. +// +// A Value is a polymorphic container holding at most a single value. +// +// The Value wrapper ensures proper initialization, copies, and assignment of +// GValues. +// +// \note GValues are equationally incomplete and so can't support proper +// equality. The semantics of copy are verified with equality of retrieved +// values. + +class Value : public ::GValue { + public: + Value() + : GValue() { + } + explicit Value(const ::GValue& x) + : GValue() { + *this = *static_cast(&x); + } + template + explicit Value(T x) + : GValue() { + ::g_value_init(this, + type_to_gtypeid::type>()); + RawSet(this, x); + } + Value(const Value& x) + : GValue() { + if (x.empty()) + return; + ::g_value_init(this, G_VALUE_TYPE(&x)); + ::g_value_copy(&x, this); + } + ~Value() { + clear(); + } + Value& operator=(const Value& x) { + if (this == &x) + return *this; + clear(); + if (x.empty()) + return *this; + ::g_value_init(this, G_VALUE_TYPE(&x)); + ::g_value_copy(&x, this); + return *this; + } + template + Value& operator=(const T& x) { + clear(); + ::g_value_init(this, + type_to_gtypeid::type>()); + RawSet(this, x); + return *this; + } + + // Lower-case names to follow STL container conventions. + + void clear() { + if (!empty()) + ::g_value_unset(this); + } + + bool empty() const { + return G_VALUE_TYPE(this) == G_TYPE_INVALID; + } +}; + +template < > +inline const Value* RawCast(const ::GValue& x) { + return static_cast(&x); +} + +// \brief Retrieve gets a value from a GValue. +// +// \postcondition If \param x contains a value of type \param T, then the +// value is copied to \param result and \true is returned. Otherwise, \param +// result is unchanged and \false is returned. +// +// \precondition \param result is not \nullptr. + +template +bool Retrieve(const ::GValue& x, T* result) { + if (!G_VALUE_HOLDS(&x, type_to_gtypeid::type>())) { + LOG(WARNING) << "GValue retrieve failed. Expected: " + << g_type_name(type_to_gtypeid::type>()) + << ", Found: " << g_type_name(G_VALUE_TYPE(&x)); + return false; + } + + *result = RawCast::type>(x); + return true; +} + +inline bool Retrieve(const ::GValue& x, Value* result) { + *result = Value(x); + return true; +} + +// \brief ScopedError holds a ::GError* and deletes it on destruction. + +struct FreeError { + void operator()(::GError* x) const { + if (x) + ::g_error_free(x); + } +}; + +typedef ::scoped_ptr< ::GError, FreeError> ScopedError; + +// \brief ScopedArray holds a ::GArray* and deletes both the container and the +// segment containing the elements on destruction. + +struct FreeArray { + void operator()(::GArray* x) const { + if (x) + ::g_array_free(x, TRUE); + } +}; + +typedef ::scoped_ptr< ::GArray, FreeArray> ScopedArray; + +// \brief ScopedPtrArray adapts ::GPtrArray* to conform to the standard +// container requirements. +// +// \note ScopedPtrArray is only partially implemented and is being fleshed out +// as needed. +// +// \models Random Access Container, Back Insertion Sequence, ScopedPtrArray is +// not copyable and equationally incomplete. + +template // T models pointer +class ScopedPtrArray { + public: + typedef ::GPtrArray element_type; + + typedef T value_type; + typedef const value_type& const_reference; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + ScopedPtrArray() + : object_(0) { + } + + explicit ScopedPtrArray(::GPtrArray* x) + : object_(x) { + } + + ~ScopedPtrArray() { + clear(); + } + + iterator begin() { + return iterator(object_ ? object_->pdata : nullptr); + } + iterator end() { + return begin() + size(); + } + const_iterator begin() const { + return const_iterator(object_ ? object_->pdata : nullptr); + } + const_iterator end() const { + return begin() + size(); + } + + // \precondition x is a pointer to an object allocated with g_new(). + + void push_back(T x) { + if (!object_) + object_ = ::g_ptr_array_sized_new(1); + ::g_ptr_array_add(object_, ::gpointer(x)); + } + + T& operator[](std::size_t n) { + DCHECK(!(size() < n)) << "ScopedPtrArray index out-of-bound."; + return *(begin() + n); + } + + std::size_t size() const { + return object_ ? object_->len : 0; + } + + void clear() { + if (object_) { + std::for_each(begin(), end(), FreeHelper()); + ::g_ptr_array_free(object_, true); + object_ = nullptr; + } + } + + void reset(::GPtrArray* p = nullptr) { + if (p != object_) { + clear(); + object_ = p; + } + } + + private: + struct FreeHelper { + void operator()(T x) const { + ::g_free(::gpointer(x)); + } + }; + + template + friend void swap(ScopedPtrArray& x, ScopedPtrArray& y); + + ::GPtrArray* object_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPtrArray); +}; + +template +inline void swap(ScopedPtrArray& x, ScopedPtrArray& y) { + std::swap(x.object_, y.object_); +} + +// \brief ScopedHashTable manages the lifetime of a ::GHashTable* with an +// interface compatibitle with a scoped ptr. +// +// The ScopedHashTable is also the start of an adaptor to model a standard +// Container. The standard for an associative container would have an iterator +// returning a key value pair. However, that isn't possible with +// ::GHashTable because there is no interface returning a reference to the +// key value pair, only to retrieve the keys and values and individual elements. +// +// So the standard interface of find() wouldn't work. I considered implementing +// operator[] and count() - operator []. So retrieving a value would look like: +// +// if (table.count(key)) +// success = Retrieve(table[key], &value); +// +// But that requires hashing the key twice. +// For now I implemented a Retrieve member function to follow the pattern +// developed elsewhere in the code. +// +// bool success = Retrieve(key, &x); +// +// This is also a template to retrieve the corect type from the stored GValue +// type. +// +// I may revisit this and use scoped_ptr_malloc and a non-member function +// Retrieve() in the future. The Retrieve pattern is becoming common enough +// that I want to give some thought as to how to generalize it further. + +class ScopedHashTable { + public: + typedef ::GHashTable element_type; + + ScopedHashTable() + : object_(nullptr) { + } + + explicit ScopedHashTable(::GHashTable* p) + : object_(p) { + } + + ~ScopedHashTable() { + clear(); + } + + template + bool Retrieve(const char* key, T* result) const { + DCHECK(object_) << "Retrieve on empty ScopedHashTable."; + if (!object_) + return false; + + ::gpointer ptr = ::g_hash_table_lookup(object_, key); + if (!ptr) + return false; + return glib::Retrieve(*static_cast< ::GValue*>(ptr), result); + } + + void clear() { + if (object_) { + ::g_hash_table_unref(object_); + object_ = nullptr; + } + } + + GHashTable* get() { + return object_; + } + + void reset(::GHashTable* p = nullptr) { + if (p != object_) { + clear(); + object_ = p; + } + } + + private: + ::GHashTable* object_; +}; + +} // namespace glib +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_GLIB_OBJECT_H_ diff --git a/brillo/glib/object_unittest.cc b/brillo/glib/object_unittest.cc new file mode 100644 index 0000000..5bc4b85 --- /dev/null +++ b/brillo/glib/object_unittest.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/glib/object.h" + +#include + +#include +#include +#include +#include + +using brillo::glib::ScopedPtrArray; +using brillo::glib::ScopedError; +using brillo::glib::Retrieve; +using brillo::glib::Value; +using brillo::Resetter; + +namespace { // NOLINT + +template +void SetRetrieveTest(const T& x) { + Value tmp(x); + T result; + EXPECT_TRUE(Retrieve(tmp, &result)); + EXPECT_EQ(result, x); +} + +void ModifyValue(Value* x) { + *x = 1.0 / 1231415926.0; // An unlikely value +} + +template +void MutableRegularTestValue(const T& x, O modify) { + Value tmp(x); + Value y = tmp; // copy-construction + T result; + EXPECT_TRUE(Retrieve(y, &result)); + EXPECT_EQ(result, x); + modify(&y); + LOG(INFO) << "Warning Expected."; + EXPECT_TRUE(!(Retrieve(y, &result) && result == x)); + y = tmp; // assignment + EXPECT_TRUE(Retrieve(y, &result)); + EXPECT_EQ(result, x); + modify(&y); + LOG(INFO) << "Warning Expected."; + EXPECT_TRUE(!(Retrieve(y, &result) && result == x)); +} + +void OutArgument(int** x) { + *x = new int(10); // NOLINT +} + +} // namespace + +TEST(ResetterTest, All) { + scoped_ptr x; + OutArgument(&Resetter(&x).lvalue()); + EXPECT_EQ(*x, 10); +} + +TEST(RetrieveTest, Types) { + SetRetrieveTest(std::string("Hello!")); + SetRetrieveTest(static_cast(10)); + SetRetrieveTest(10.5); + SetRetrieveTest(true); +} + +TEST(ValueTest, All) { + Value x; // default construction + Value y = x; // copy with default value + x = y; // assignment with default value + Value z(1.5); + x = z; // assignment to default value + MutableRegularTestValue(std::string("Hello!"), &ModifyValue); +} + +TEST(ScopedErrorTest, All) { + ScopedError a; // default construction + ScopedError b(::g_error_new(::g_quark_from_static_string("error"), -1, + "")); // constructor + ::GError* c = ::g_error_new(::g_quark_from_static_string("error"), -1, + ""); + ::GError* d = ::g_error_new(::g_quark_from_static_string("error"), -1, + ""); + a.reset(c); // reset form 1 + (void)d; +} + +TEST(ScopedPtrArrayTest, Construction) { + const char item[] = "a string"; + char* a = static_cast(::g_malloc(sizeof(item))); + std::strcpy(a, &item[0]); // NOLINT + + ::GPtrArray* array = ::g_ptr_array_new(); + ::g_ptr_array_add(array, ::gpointer(a)); + + ScopedPtrArray x(array); + EXPECT_EQ(x.size(), 1); + EXPECT_EQ(x[0], a); // indexing +} + +TEST(ScopedPtrArrayTest, Reset) { + const char item[] = "a string"; + char* a = static_cast(::g_malloc(sizeof(item))); + std::strcpy(a, &item[0]); // NOLINT + + ScopedPtrArray x; // default construction + x.push_back(a); + EXPECT_EQ(x.size(), 1); + x.reset(); + EXPECT_EQ(x.size(), 0); + + char* b = static_cast(::g_malloc(sizeof(item))); + std::strcpy(b, &item[0]); // NOLINT + + ::GPtrArray* array = ::g_ptr_array_new(); + ::g_ptr_array_add(array, ::gpointer(b)); + + x.reset(array); + EXPECT_EQ(x.size(), 1); +} + +TEST(ScopedPtrArrayTest, Iteration) { + char* a[] = { static_cast(::g_malloc(1)), + static_cast(::g_malloc(1)), static_cast(::g_malloc(1)) }; + + ScopedPtrArray x; + std::copy(&a[0], &a[3], std::back_inserter(x)); + EXPECT_TRUE(std::equal(x.begin(), x.end(), &a[0])); +} + diff --git a/brillo/http/curl_api.cc b/brillo/http/curl_api.cc new file mode 100644 index 0000000..96a12f1 --- /dev/null +++ b/brillo/http/curl_api.cc @@ -0,0 +1,184 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { +namespace http { + +namespace { + +static_assert(CURLOPTTYPE_LONG == 0 && + CURLOPTTYPE_OBJECTPOINT == 10000 && + CURLOPTTYPE_FUNCTIONPOINT == 20000 && + CURLOPTTYPE_OFF_T == 30000, + "CURL option types are expected to be multiples of 10000"); + +inline bool VerifyOptionType(CURLoption option, int expected_type) { + int option_type = (static_cast(option) / 10000) * 10000; + return (option_type == expected_type); +} + +} // anonymous namespace + +CurlApi::CurlApi() { + curl_global_init(CURL_GLOBAL_ALL); +} + +CurlApi::~CurlApi() { + curl_global_cleanup(); +} + +CURL* CurlApi::EasyInit() { + return curl_easy_init(); +} + +void CurlApi::EasyCleanup(CURL* curl) { + curl_easy_cleanup(curl); +} + +CURLcode CurlApi::EasySetOptInt(CURL* curl, CURLoption option, int value) { + CHECK(VerifyOptionType(option, CURLOPTTYPE_LONG)) + << "Only options that expect a LONG data type must be specified here"; + // CURL actually uses "long" type, so have to make sure we feed it what it + // expects. + // NOLINTNEXTLINE(runtime/int) + return curl_easy_setopt(curl, option, static_cast(value)); +} + +CURLcode CurlApi::EasySetOptStr(CURL* curl, + CURLoption option, + const std::string& value) { + CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT)) + << "Only options that expect a STRING data type must be specified here"; + return curl_easy_setopt(curl, option, value.c_str()); +} + +CURLcode CurlApi::EasySetOptPtr(CURL* curl, CURLoption option, void* value) { + CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT)) + << "Only options that expect a pointer data type must be specified here"; + return curl_easy_setopt(curl, option, value); +} + +CURLcode CurlApi::EasySetOptCallback(CURL* curl, + CURLoption option, + intptr_t address) { + CHECK(VerifyOptionType(option, CURLOPTTYPE_FUNCTIONPOINT)) + << "Only options that expect a function pointers must be specified here"; + return curl_easy_setopt(curl, option, address); +} + +CURLcode CurlApi::EasySetOptOffT(CURL* curl, + CURLoption option, + curl_off_t value) { + CHECK(VerifyOptionType(option, CURLOPTTYPE_OFF_T)) + << "Only options that expect a large data size must be specified here"; + return curl_easy_setopt(curl, option, value); +} + +CURLcode CurlApi::EasyPerform(CURL* curl) { + return curl_easy_perform(curl); +} + +CURLcode CurlApi::EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const { + CHECK_EQ(CURLINFO_LONG, info & CURLINFO_TYPEMASK) << "Wrong option type"; + long data = 0; // NOLINT(runtime/int) - curl expects a long here. + CURLcode code = curl_easy_getinfo(curl, info, &data); + if (code == CURLE_OK) + *value = static_cast(data); + return code; +} + +CURLcode CurlApi::EasyGetInfoDbl(CURL* curl, + CURLINFO info, + double* value) const { + CHECK_EQ(CURLINFO_DOUBLE, info & CURLINFO_TYPEMASK) << "Wrong option type"; + return curl_easy_getinfo(curl, info, value); +} + +CURLcode CurlApi::EasyGetInfoStr(CURL* curl, + CURLINFO info, + std::string* value) const { + CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type"; + char* data = nullptr; + CURLcode code = curl_easy_getinfo(curl, info, &data); + if (code == CURLE_OK) + *value = data; + return code; +} + +CURLcode CurlApi::EasyGetInfoPtr(CURL* curl, + CURLINFO info, + void** value) const { + // CURL uses "string" type for generic pointer info. Go figure. + CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type"; + return curl_easy_getinfo(curl, info, value); +} + +std::string CurlApi::EasyStrError(CURLcode code) const { + return curl_easy_strerror(code); +} + +CURLM* CurlApi::MultiInit() { + return curl_multi_init(); +} + +CURLMcode CurlApi::MultiCleanup(CURLM* multi_handle) { + return curl_multi_cleanup(multi_handle); +} + +CURLMsg* CurlApi::MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) { + return curl_multi_info_read(multi_handle, msgs_in_queue); +} + +CURLMcode CurlApi::MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) { + return curl_multi_add_handle(multi_handle, curl_handle); +} + +CURLMcode CurlApi::MultiRemoveHandle(CURLM* multi_handle, CURL* curl_handle) { + return curl_multi_remove_handle(multi_handle, curl_handle); +} + +CURLMcode CurlApi::MultiSetSocketCallback(CURLM* multi_handle, + curl_socket_callback socket_callback, + void* userp) { + CURLMcode code = + curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback); + if (code != CURLM_OK) + return code; + return curl_multi_setopt(multi_handle, CURLMOPT_SOCKETDATA, userp); +} + +CURLMcode CurlApi::MultiSetTimerCallback( + CURLM* multi_handle, + curl_multi_timer_callback timer_callback, + void* userp) { + CURLMcode code = + curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback); + if (code != CURLM_OK) + return code; + return curl_multi_setopt(multi_handle, CURLMOPT_TIMERDATA, userp); +} + +CURLMcode CurlApi::MultiAssign(CURLM* multi_handle, + curl_socket_t sockfd, + void* sockp) { + return curl_multi_assign(multi_handle, sockfd, sockp); +} + +CURLMcode CurlApi::MultiSocketAction(CURLM* multi_handle, + curl_socket_t s, + int ev_bitmask, + int* running_handles) { + return curl_multi_socket_action(multi_handle, s, ev_bitmask, running_handles); +} + +std::string CurlApi::MultiStrError(CURLMcode code) const { + return curl_multi_strerror(code); +} + +} // namespace http +} // namespace brillo diff --git a/brillo/http/curl_api.h b/brillo/http/curl_api.h new file mode 100644 index 0000000..5c18158 --- /dev/null +++ b/brillo/http/curl_api.h @@ -0,0 +1,210 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_CURL_API_H_ +#define LIBCHROMEOS_BRILLO_HTTP_CURL_API_H_ + +#include + +#include + +#include +#include + +namespace brillo { +namespace http { + +// Abstract wrapper around libcurl C API that allows us to mock it out in tests. +class CurlInterface { + public: + CurlInterface() = default; + virtual ~CurlInterface() = default; + + // Wrapper around curl_easy_init(). + virtual CURL* EasyInit() = 0; + + // Wrapper around curl_easy_cleanup(). + virtual void EasyCleanup(CURL* curl) = 0; + + // Wrappers around curl_easy_setopt(). + virtual CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) = 0; + virtual CURLcode EasySetOptStr(CURL* curl, + CURLoption option, + const std::string& value) = 0; + virtual CURLcode EasySetOptPtr(CURL* curl, + CURLoption option, + void* value) = 0; + virtual CURLcode EasySetOptCallback(CURL* curl, + CURLoption option, + intptr_t address) = 0; + virtual CURLcode EasySetOptOffT(CURL* curl, + CURLoption option, + curl_off_t value) = 0; + + // A type-safe wrapper around function callback options. + template + inline CURLcode EasySetOptCallback(CURL* curl, + CURLoption option, + R(*callback)(Args...)) { + return EasySetOptCallback( + curl, option, reinterpret_cast(callback)); + } + + // Wrapper around curl_easy_perform(). + virtual CURLcode EasyPerform(CURL* curl) = 0; + + // Wrappers around curl_easy_getinfo(). + virtual CURLcode EasyGetInfoInt(CURL* curl, + CURLINFO info, + int* value) const = 0; + virtual CURLcode EasyGetInfoDbl(CURL* curl, + CURLINFO info, + double* value) const = 0; + virtual CURLcode EasyGetInfoStr(CURL* curl, + CURLINFO info, + std::string* value) const = 0; + virtual CURLcode EasyGetInfoPtr(CURL* curl, + CURLINFO info, + void** value) const = 0; + + // Wrapper around curl_easy_strerror(). + virtual std::string EasyStrError(CURLcode code) const = 0; + + // Wrapper around curl_multi_init(). + virtual CURLM* MultiInit() = 0; + + // Wrapper around curl_multi_cleanup(). + virtual CURLMcode MultiCleanup(CURLM* multi_handle) = 0; + + // Wrapper around curl_multi_info_read(). + virtual CURLMsg* MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) = 0; + + // Wrapper around curl_multi_add_handle(). + virtual CURLMcode MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) = 0; + + // Wrapper around curl_multi_remove_handle(). + virtual CURLMcode MultiRemoveHandle(CURLM* multi_handle, + CURL* curl_handle) = 0; + + // Wrapper around curl_multi_setopt(CURLMOPT_SOCKETFUNCTION/SOCKETDATA). + virtual CURLMcode MultiSetSocketCallback( + CURLM* multi_handle, + curl_socket_callback socket_callback, + void* userp) = 0; + + // Wrapper around curl_multi_setopt(CURLMOPT_TIMERFUNCTION/TIMERDATA). + virtual CURLMcode MultiSetTimerCallback( + CURLM* multi_handle, + curl_multi_timer_callback timer_callback, + void* userp) = 0; + + // Wrapper around curl_multi_assign(). + virtual CURLMcode MultiAssign(CURLM* multi_handle, + curl_socket_t sockfd, + void* sockp) = 0; + + // Wrapper around curl_multi_socket_action(). + virtual CURLMcode MultiSocketAction(CURLM* multi_handle, + curl_socket_t s, + int ev_bitmask, + int* running_handles) = 0; + + // Wrapper around curl_multi_strerror(). + virtual std::string MultiStrError(CURLMcode code) const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(CurlInterface); +}; + +class BRILLO_EXPORT CurlApi : public CurlInterface { + public: + CurlApi(); + ~CurlApi() override; + + // Wrapper around curl_easy_init(). + CURL* EasyInit() override; + + // Wrapper around curl_easy_cleanup(). + void EasyCleanup(CURL* curl) override; + + // Wrappers around curl_easy_setopt(). + CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) override; + CURLcode EasySetOptStr(CURL* curl, + CURLoption option, + const std::string& value) override; + CURLcode EasySetOptPtr(CURL* curl, CURLoption option, void* value) override; + CURLcode EasySetOptCallback(CURL* curl, + CURLoption option, + intptr_t address) override; + CURLcode EasySetOptOffT(CURL* curl, + CURLoption option, + curl_off_t value) override; + + // Wrapper around curl_easy_perform(). + CURLcode EasyPerform(CURL* curl) override; + + // Wrappers around curl_easy_getinfo(). + CURLcode EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const override; + CURLcode EasyGetInfoDbl(CURL* curl, + CURLINFO info, + double* value) const override; + CURLcode EasyGetInfoStr(CURL* curl, + CURLINFO info, + std::string* value) const override; + CURLcode EasyGetInfoPtr(CURL* curl, + CURLINFO info, + void** value) const override; + + // Wrapper around curl_easy_strerror(). + std::string EasyStrError(CURLcode code) const override; + + // Wrapper around curl_multi_init(). + CURLM* MultiInit() override; + + // Wrapper around curl_multi_cleanup(). + CURLMcode MultiCleanup(CURLM* multi_handle) override; + + // Wrapper around curl_multi_info_read(). + CURLMsg* MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) override; + + // Wrapper around curl_multi_add_handle(). + CURLMcode MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) override; + + // Wrapper around curl_multi_remove_handle(). + CURLMcode MultiRemoveHandle(CURLM* multi_handle, CURL* curl_handle) override; + + // Wrapper around curl_multi_setopt(CURLMOPT_SOCKETFUNCTION/SOCKETDATA). + CURLMcode MultiSetSocketCallback( + CURLM* multi_handle, + curl_socket_callback socket_callback, + void* userp) override; + + // Wrapper around curl_multi_setopt(CURLMOPT_TIMERFUNCTION/TIMERDATA). + CURLMcode MultiSetTimerCallback( + CURLM* multi_handle, + curl_multi_timer_callback timer_callback, + void* userp) override; + + // Wrapper around curl_multi_assign(). + CURLMcode MultiAssign(CURLM* multi_handle, + curl_socket_t sockfd, + void* sockp) override; + + // Wrapper around curl_multi_socket_action(). + CURLMcode MultiSocketAction(CURLM* multi_handle, + curl_socket_t s, + int ev_bitmask, + int* running_handles) override; + + // Wrapper around curl_multi_strerror(). + std::string MultiStrError(CURLMcode code) const override; + + private: + DISALLOW_COPY_AND_ASSIGN(CurlApi); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_CURL_API_H_ diff --git a/brillo/http/http_connection.h b/brillo/http/http_connection.h new file mode 100644 index 0000000..44d3ccb --- /dev/null +++ b/brillo/http/http_connection.h @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { + +class Response; + +/////////////////////////////////////////////////////////////////////////////// +// Connection class is the base class for HTTP communication session. +// It abstracts the implementation of underlying transport library (ex libcurl). +// When the Connection-derived class is constructed, it is pre-set up with +// basic initialization information necessary to initiate the server request +// connection (such as the URL, request method, etc - see +// Transport::CreateConnection() for more details). But most implementations +// would not probably initiate the physical connection until SendHeaders +// is called. +// You normally shouldn't worry about using this class directly. +// http::Request and http::Response classes use it for communication. +// Effectively this class is the interface for the request/response objects to +// the transport-specific instance of the communication channel with the +// destination server. It is created by Transport as part of initiating +// the connection to the destination URI and is shared between the request and +// response object until all the data is sent to the server and the response +// is received. The class does NOT represent a persistent TCP connection though +// (e.g. in keep-alive scenarios). +/////////////////////////////////////////////////////////////////////////////// +class BRILLO_EXPORT Connection + : public std::enable_shared_from_this { + public: + explicit Connection(const std::shared_ptr& transport) + : transport_(transport) {} + virtual ~Connection() = default; + + // The following methods are used by http::Request object to initiate the + // communication with the server, send the request data and receive the + // response. + + // Called by http::Request to initiate the connection with the server. + // This normally opens the socket and sends the request headers. + virtual bool SendHeaders(const HeaderList& headers, + brillo::ErrorPtr* error) = 0; + // If needed, this function can be called to send the request body data. + virtual bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) = 0; + // If needed, this function can be called to customize where the response + // data is streamed to. + virtual void SetResponseData(StreamPtr stream) = 0; + // This function is called when all the data is sent off and it's time + // to receive the response data. The method will block until the whole + // response message is received, or if an error occur in which case the + // function will return false and fill the error details in |error| object. + virtual bool FinishRequest(brillo::ErrorPtr* error) = 0; + // Send the request asynchronously and invoke the callback with the response + // received. Returns the ID of the pending async request. + virtual RequestID FinishRequestAsync(const SuccessCallback& success_callback, + const ErrorCallback& error_callback) = 0; + + // The following methods are used by http::Response object to obtain the + // response data. They are used only after the response data has been received + // since the http::Response object is not constructed until + // Request::GetResponse()/Request::GetResponseAndBlock() methods are called. + + // Returns the HTTP status code (e.g. 200 for success). + virtual int GetResponseStatusCode() const = 0; + // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED"). + virtual std::string GetResponseStatusText() const = 0; + // Returns the HTTP protocol version (e.g. "HTTP/1.1"). + virtual std::string GetProtocolVersion() const = 0; + // Returns the value of particular response header, or empty string if the + // headers wasn't received. + virtual std::string GetResponseHeader( + const std::string& header_name) const = 0; + // Returns the response data stream. This function can be called only once + // as it transfers ownership of the data stream to the caller. Subsequent + // calls will fail with "Stream closed" error. + // Returns empty stream on failure and fills in the error information in + // |error| object. + virtual StreamPtr ExtractDataStream(brillo::ErrorPtr* error) = 0; + + protected: + // |transport_| is mainly used to keep the object alive as long as the + // connection exists. But some implementations of Connection could use + // the Transport-derived class for their own needs as well. + std::shared_ptr transport_; + + private: + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_H_ diff --git a/brillo/http/http_connection_curl.cc b/brillo/http/http_connection_curl.cc new file mode 100644 index 0000000..4b8e96d --- /dev/null +++ b/brillo/http/http_connection_curl.cc @@ -0,0 +1,267 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { +namespace curl { + +static int curl_trace(CURL* handle, + curl_infotype type, + char* data, + size_t size, + void* userp) { + std::string msg(data, size); + + switch (type) { + case CURLINFO_TEXT: + VLOG(3) << "== Info: " << msg; + break; + case CURLINFO_HEADER_OUT: + VLOG(3) << "=> Send headers:\n" << msg; + break; + case CURLINFO_DATA_OUT: + VLOG(3) << "=> Send data:\n" << msg; + break; + case CURLINFO_SSL_DATA_OUT: + VLOG(3) << "=> Send SSL data" << msg; + break; + case CURLINFO_HEADER_IN: + VLOG(3) << "<= Recv header: " << msg; + break; + case CURLINFO_DATA_IN: + VLOG(3) << "<= Recv data:\n" << msg; + break; + case CURLINFO_SSL_DATA_IN: + VLOG(3) << "<= Recv SSL data" << msg; + break; + default: + break; + } + return 0; +} + +Connection::Connection(CURL* curl_handle, + const std::string& method, + const std::shared_ptr& curl_interface, + const std::shared_ptr& transport) + : http::Connection(transport), + method_(method), + curl_handle_(curl_handle), + curl_interface_(curl_interface) { + // Store the connection pointer inside the CURL handle so we can easily + // retrieve it when doing asynchronous I/O. + curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_PRIVATE, this); + VLOG(2) << "curl::Connection created: " << method_; +} + +Connection::~Connection() { + if (header_list_) + curl_slist_free_all(header_list_); + curl_interface_->EasyCleanup(curl_handle_); + VLOG(2) << "curl::Connection destroyed"; +} + +bool Connection::SendHeaders(const HeaderList& headers, + brillo::ErrorPtr* error) { + headers_.insert(headers.begin(), headers.end()); + return true; +} + +bool Connection::SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) { + request_data_stream_ = std::move(stream); + return true; +} + +void Connection::SetResponseData(StreamPtr stream) { + response_data_stream_ = std::move(stream); +} + +void Connection::PrepareRequest() { + if (VLOG_IS_ON(3)) { + curl_interface_->EasySetOptCallback( + curl_handle_, CURLOPT_DEBUGFUNCTION, &curl_trace); + curl_interface_->EasySetOptInt(curl_handle_, CURLOPT_VERBOSE, 1); + } + + if (method_ != request_type::kGet) { + // Set up HTTP request data. + uint64_t data_size = 0; + if (request_data_stream_ && request_data_stream_->CanGetSize()) + data_size = request_data_stream_->GetRemainingSize(); + + if (!request_data_stream_ || request_data_stream_->CanGetSize()) { + // Data size is known (either no data, or data size is available). + if (method_ == request_type::kPut) { + curl_interface_->EasySetOptOffT( + curl_handle_, CURLOPT_INFILESIZE_LARGE, data_size); + } else { + curl_interface_->EasySetOptOffT( + curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, data_size); + } + } else { + // Data size is unknown, so use chunked upload. + headers_.emplace(http::request_header::kTransferEncoding, "chunked"); + } + + if (request_data_stream_) { + curl_interface_->EasySetOptCallback( + curl_handle_, CURLOPT_READFUNCTION, &Connection::read_callback); + curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_READDATA, this); + } + } + + if (!headers_.empty()) { + CHECK(header_list_ == nullptr); + for (auto pair : headers_) { + std::string header = + brillo::string_utils::Join(": ", pair.first, pair.second); + VLOG(2) << "Request header: " << header; + header_list_ = curl_slist_append(header_list_, header.c_str()); + } + curl_interface_->EasySetOptPtr( + curl_handle_, CURLOPT_HTTPHEADER, header_list_); + } + + headers_.clear(); + + // Set up HTTP response data. + if (!response_data_stream_) + response_data_stream_ = MemoryStream::Create(nullptr); + if (method_ != request_type::kHead) { + curl_interface_->EasySetOptCallback( + curl_handle_, CURLOPT_WRITEFUNCTION, &Connection::write_callback); + curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_WRITEDATA, this); + } + + // HTTP response headers + curl_interface_->EasySetOptCallback( + curl_handle_, CURLOPT_HEADERFUNCTION, &Connection::header_callback); + curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HEADERDATA, this); +} + +bool Connection::FinishRequest(brillo::ErrorPtr* error) { + PrepareRequest(); + CURLcode ret = curl_interface_->EasyPerform(curl_handle_); + if (ret != CURLE_OK) { + Transport::AddEasyCurlError(error, FROM_HERE, ret, curl_interface_.get()); + } else { + // Rewind our data stream to the beginning so that it can be read back. + if (response_data_stream_->CanSeek() && + !response_data_stream_->SetPosition(0, error)) + return false; + LOG(INFO) << "Response: " << GetResponseStatusCode() << " (" + << GetResponseStatusText() << ")"; + } + return (ret == CURLE_OK); +} + +RequestID Connection::FinishRequestAsync( + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + PrepareRequest(); + return transport_->StartAsyncTransfer(this, success_callback, error_callback); +} + +int Connection::GetResponseStatusCode() const { + int status_code = 0; + curl_interface_->EasyGetInfoInt( + curl_handle_, CURLINFO_RESPONSE_CODE, &status_code); + return status_code; +} + +std::string Connection::GetResponseStatusText() const { + return status_text_; +} + +std::string Connection::GetProtocolVersion() const { + return protocol_version_; +} + +std::string Connection::GetResponseHeader( + const std::string& header_name) const { + auto p = headers_.find(header_name); + return p != headers_.end() ? p->second : std::string(); +} + +StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) { + if (!response_data_stream_) { + stream_utils::ErrorStreamClosed(FROM_HERE, error); + } + return std::move(response_data_stream_); +} + +size_t Connection::write_callback(char* ptr, + size_t size, + size_t num, + void* data) { + Connection* me = reinterpret_cast(data); + size_t data_len = size * num; + VLOG(1) << "Response data (" << data_len << "): " + << std::string{ptr, data_len}; + // TODO(nathanbullock): Currently we are relying on the stream not blocking, + // but if the stream is representing a pipe or some other construct that might + // block then this code will behave badly. + if (!me->response_data_stream_->WriteAllBlocking(ptr, data_len, nullptr)) { + LOG(ERROR) << "Failed to write response data"; + data_len = 0; + } + return data_len; +} + +size_t Connection::read_callback(char* ptr, + size_t size, + size_t num, + void* data) { + Connection* me = reinterpret_cast(data); + size_t data_len = size * num; + + size_t read_size = 0; + bool success = me->request_data_stream_->ReadBlocking(ptr, data_len, + &read_size, nullptr); + VLOG_IF(3, success) << "Sending data: " << std::string{ptr, read_size}; + return success ? read_size : CURL_READFUNC_ABORT; +} + +size_t Connection::header_callback(char* ptr, + size_t size, + size_t num, + void* data) { + using brillo::string_utils::SplitAtFirst; + Connection* me = reinterpret_cast(data); + size_t hdr_len = size * num; + std::string header(ptr, hdr_len); + // Remove newlines at the end of header line. + while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) { + header.pop_back(); + } + + VLOG(2) << "Response header: " << header; + + if (!me->status_text_set_) { + // First header - response code as "HTTP/1.1 200 OK". + // Need to extract the OK part + auto pair = SplitAtFirst(header, " "); + me->protocol_version_ = pair.first; + me->status_text_ = SplitAtFirst(pair.second, " ").second; + me->status_text_set_ = true; + } else { + auto pair = SplitAtFirst(header, ":"); + if (!pair.second.empty()) + me->headers_.insert(pair); + } + return hdr_len; +} + +} // namespace curl +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_connection_curl.h b/brillo/http/http_connection_curl.h new file mode 100644 index 0000000..f173025 --- /dev/null +++ b/brillo/http/http_connection_curl.h @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_CURL_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_CURL_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { +namespace curl { + +// This is a libcurl-based implementation of http::Connection. +class BRILLO_EXPORT Connection : public http::Connection { + public: + Connection(CURL* curl_handle, + const std::string& method, + const std::shared_ptr& curl_interface, + const std::shared_ptr& transport); + ~Connection() override; + + // Overrides from http::Connection. + // See http_connection.h for description of these methods. + bool SendHeaders(const HeaderList& headers, brillo::ErrorPtr* error) override; + bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) override; + void SetResponseData(StreamPtr stream) override; + bool FinishRequest(brillo::ErrorPtr* error) override; + RequestID FinishRequestAsync( + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) override; + + int GetResponseStatusCode() const override; + std::string GetResponseStatusText() const override; + std::string GetProtocolVersion() const override; + std::string GetResponseHeader(const std::string& header_name) const override; + StreamPtr ExtractDataStream(brillo::ErrorPtr* error) override; + + protected: + // Write data callback. Used by CURL when receiving response data. + BRILLO_PRIVATE static size_t write_callback(char* ptr, + size_t size, + size_t num, + void* data); + // Read data callback. Used by CURL when sending request body data. + BRILLO_PRIVATE static size_t read_callback(char* ptr, + size_t size, + size_t num, + void* data); + // Write header data callback. Used by CURL when receiving response headers. + BRILLO_PRIVATE static size_t header_callback(char* ptr, + size_t size, + size_t num, + void* data); + + // Helper method to set up the |curl_handle_| with all the parameters + // pertaining to the current connection. + BRILLO_PRIVATE void PrepareRequest(); + + // HTTP request verb, such as "GET", "POST", "PUT", ... + std::string method_; + + // Binary data for request body. + StreamPtr request_data_stream_; + + // Received response data. + StreamPtr response_data_stream_; + + // List of optional request headers provided by the caller. + // After request has been sent, contains the received response headers. + std::multimap headers_; + + // HTTP protocol version, such as HTTP/1.1 + std::string protocol_version_; + // Response status text, such as "OK" for 200, or "Forbidden" for 403 + std::string status_text_; + // Flag used when parsing response headers to separate the response status + // from the rest of response headers. + bool status_text_set_{false}; + + CURL* curl_handle_{nullptr}; + curl_slist* header_list_{nullptr}; + + std::shared_ptr curl_interface_; + + private: + friend class http::curl::Transport; + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +} // namespace curl +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_CURL_H_ diff --git a/brillo/http/http_connection_curl_unittest.cc b/brillo/http/http_connection_curl_unittest.cc new file mode 100644 index 0000000..90a5626 --- /dev/null +++ b/brillo/http/http_connection_curl_unittest.cc @@ -0,0 +1,324 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using testing::DoAll; +using testing::Invoke; +using testing::Return; +using testing::SetArgPointee; +using testing::_; + +namespace brillo { +namespace http { +namespace curl { + +namespace { + +using ReadWriteCallback = + size_t(char* ptr, size_t size, size_t num, void* data); + +// A helper class to simulate curl_easy_perform action. It invokes the +// read callbacks to obtain the request data from the Connection and then +// calls the header and write callbacks to "send" the response header and body. +class CurlPerformer { + public: + // During the tests, use the address of |this| as the CURL* handle. + // This allows the static Perform() method to obtain the instance pointer + // having only CURL*. + CURL* GetCurlHandle() { return reinterpret_cast(this); } + + // Callback to be invoked when mocking out curl_easy_perform() method. + static CURLcode Perform(CURL* curl) { + CurlPerformer* me = reinterpret_cast(curl); + return me->DoPerform(); + } + + // CURL callback functions and |connection| pointer needed to invoke the + // callbacks from the Connection class. + Connection* connection{nullptr}; + ReadWriteCallback* write_callback{nullptr}; + ReadWriteCallback* read_callback{nullptr}; + ReadWriteCallback* header_callback{nullptr}; + + // Request body read from the connection. + std::string request_body; + + // Response data to be sent back to connection. + std::string status_line; + HeaderList response_headers; + std::string response_body; + + private: + // The actual implementation of curl_easy_perform() fake. + CURLcode DoPerform() { + // Read request body. + char buffer[1024]; + for (;;) { + size_t size_read = read_callback(buffer, sizeof(buffer), 1, connection); + if (size_read == CURL_READFUNC_ABORT) + return CURLE_ABORTED_BY_CALLBACK; + if (size_read == CURL_READFUNC_PAUSE) + return CURLE_READ_ERROR; // Shouldn't happen. + if (size_read == 0) + break; + request_body.append(buffer, size_read); + } + + // Send the response headers. + std::vector header_lines; + header_lines.push_back(status_line + "\r\n"); + for (const auto& pair : response_headers) { + header_lines.push_back(string_utils::Join(": ", pair.first, pair.second) + + "\r\n"); + } + + for (const std::string& line : header_lines) { + CURLcode code = WriteString(header_callback, line); + if (code != CURLE_OK) + return code; + } + + // Send response body. + return WriteString(write_callback, response_body); + } + + // Helper method to send a string to a write callback. Keeps calling + // the callback until all the data is written. + CURLcode WriteString(ReadWriteCallback* callback, const std::string& str) { + size_t pos = 0; + size_t size_remaining = str.size(); + while (size_remaining) { + size_t size_written = callback( + const_cast(str.data() + pos), size_remaining, 1, connection); + if (size_written == CURL_WRITEFUNC_PAUSE) + return CURLE_WRITE_ERROR; // Shouldn't happen. + CHECK(size_written <= size_remaining) << "Unexpected size returned"; + size_remaining -= size_written; + pos += size_written; + } + return CURLE_OK; + } +}; + +// Custom matcher to validate the parameter of CURLOPT_HTTPHEADER CURL option +// which contains the request headers as curl_slist* chain. +MATCHER_P(HeadersMatch, headers, "") { + std::multiset test_headers; + for (const auto& pair : headers) + test_headers.insert(string_utils::Join(": ", pair.first, pair.second)); + + std::multiset src_headers; + const curl_slist* head = static_cast(arg); + while (head) { + src_headers.insert(head->data); + head = head->next; + } + + std::vector difference; + std::set_symmetric_difference(src_headers.begin(), src_headers.end(), + test_headers.begin(), test_headers.end(), + std::back_inserter(difference)); + return difference.empty(); +} + +// Custom action to save a CURL callback pointer into a member of CurlPerformer. +ACTION_TEMPLATE(SaveCallback, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_2_VALUE_PARAMS(performer, mem_ptr)) { + performer->*mem_ptr = reinterpret_cast(std::get(args)); +} + +} // anonymous namespace + +class HttpCurlConnectionTest : public testing::Test { + public: + void SetUp() override { + curl_api_ = std::make_shared(); + transport_ = std::make_shared(); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _)) + .WillOnce(Return(CURLE_OK)); + connection_ = std::make_shared( + handle_, request_type::kPost, curl_api_, transport_); + performer_.connection = connection_.get(); + } + + void TearDown() override { + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection_.reset(); + transport_.reset(); + curl_api_.reset(); + } + + protected: + std::shared_ptr curl_api_; + std::shared_ptr transport_; + CurlPerformer performer_; + CURL* handle_{performer_.GetCurlHandle()}; + std::shared_ptr connection_; +}; + +TEST_F(HttpCurlConnectionTest, FinishRequestAsync) { + std::string request_data{"Foo Bar Baz"}; + StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr); + EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr)); + EXPECT_TRUE(connection_->SendHeaders({{"X-Foo", "bar"}}, nullptr)); + + if (VLOG_IS_ON(3)) { + EXPECT_CALL(*curl_api_, + EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1)) + .WillOnce(Return(CURLE_OK)); + } + + EXPECT_CALL( + *curl_api_, + EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size())) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _)) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, _)) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _)) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, + EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _)) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*transport_, StartAsyncTransfer(connection_.get(), _, _)) + .Times(1); + connection_->FinishRequestAsync({}, {}); +} + +MATCHER_P(MatchStringBuffer, data, "") { + return data.compare(static_cast(arg)) == 0; +} + +TEST_F(HttpCurlConnectionTest, FinishRequest) { + std::string request_data{"Foo Bar Baz"}; + std::string response_data{"OK"}; + StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr); + HeaderList headers{ + {request_header::kAccept, "*/*"}, + {request_header::kContentType, mime::text::kPlain}, + {request_header::kContentLength, std::to_string(request_data.size())}, + {"X-Foo", "bar"}, + }; + std::unique_ptr response_stream(new MockStream); + EXPECT_CALL(*response_stream, + WriteAllBlocking(MatchStringBuffer(response_data), + response_data.size(), _)) + .WillOnce(Return(true)); + EXPECT_CALL(*response_stream, CanSeek()) + .WillOnce(Return(false)); + connection_->SetResponseData(std::move(response_stream)); + EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr)); + EXPECT_TRUE(connection_->SendHeaders(headers, nullptr)); + + // Expectations for Connection::FinishRequest() call. + if (VLOG_IS_ON(3)) { + EXPECT_CALL(*curl_api_, + EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1)) + .WillOnce(Return(CURLE_OK)); + } + + EXPECT_CALL( + *curl_api_, + EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size())) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _)) + .WillOnce( + DoAll(SaveCallback<2>(&performer_, &CurlPerformer::read_callback), + Return(CURLE_OK))); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _)) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, + EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, HeadersMatch(headers))) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _)) + .WillOnce( + DoAll(SaveCallback<2>(&performer_, &CurlPerformer::write_callback), + Return(CURLE_OK))); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _)) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, + EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _)) + .WillOnce( + DoAll(SaveCallback<2>(&performer_, &CurlPerformer::header_callback), + Return(CURLE_OK))); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _)) + .WillOnce(Return(CURLE_OK)); + + EXPECT_CALL(*curl_api_, EasyPerform(handle_)) + .WillOnce(Invoke(&CurlPerformer::Perform)); + + EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _)) + .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK))); + + // Set up the CurlPerformer with the response data expected to be received. + HeaderList response_headers{ + {response_header::kContentLength, std::to_string(response_data.size())}, + {response_header::kContentType, mime::text::kHtml}, + {"X-Foo", "baz"}, + }; + performer_.status_line = "HTTP/1.1 200 OK"; + performer_.response_body = response_data; + performer_.response_headers = response_headers; + + // Perform the request. + EXPECT_TRUE(connection_->FinishRequest(nullptr)); + + // Make sure we sent out the request body correctly. + EXPECT_EQ(request_data, performer_.request_body); + + // Validate the parsed response data. + EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _)) + .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK))); + EXPECT_EQ(status_code::Ok, connection_->GetResponseStatusCode()); + EXPECT_EQ("HTTP/1.1", connection_->GetProtocolVersion()); + EXPECT_EQ("OK", connection_->GetResponseStatusText()); + EXPECT_EQ(std::to_string(response_data.size()), + connection_->GetResponseHeader(response_header::kContentLength)); + EXPECT_EQ(mime::text::kHtml, + connection_->GetResponseHeader(response_header::kContentType)); + EXPECT_EQ("baz", connection_->GetResponseHeader("X-Foo")); + auto data_stream = connection_->ExtractDataStream(nullptr); + ASSERT_NE(nullptr, data_stream.get()); +} + +} // namespace curl +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_connection_fake.cc b/brillo/http/http_connection_fake.cc new file mode 100644 index 0000000..dfb40a3 --- /dev/null +++ b/brillo/http/http_connection_fake.cc @@ -0,0 +1,113 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { +namespace fake { + +Connection::Connection(const std::string& url, + const std::string& method, + const std::shared_ptr& transport) + : http::Connection(transport), request_(url, method) { + VLOG(1) << "fake::Connection created: " << method; +} + +Connection::~Connection() { + VLOG(1) << "fake::Connection destroyed"; +} + +bool Connection::SendHeaders(const HeaderList& headers, + brillo::ErrorPtr* error) { + request_.AddHeaders(headers); + return true; +} + +bool Connection::SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) { + request_.SetData(std::move(stream)); + return true; +} + +bool Connection::FinishRequest(brillo::ErrorPtr* error) { + using brillo::string_utils::ToString; + request_.AddHeaders( + {{request_header::kContentLength, ToString(request_.GetData().size())}}); + fake::Transport* transport = static_cast(transport_.get()); + CHECK(transport) << "Expecting a fake transport"; + auto handler = transport->GetHandler(request_.GetURL(), request_.GetMethod()); + if (handler.is_null()) { + LOG(ERROR) << "Received unexpected " << request_.GetMethod() + << " request at " << request_.GetURL(); + response_.ReplyText(status_code::NotFound, + "Not found", + brillo::mime::text::kHtml); + } else { + handler.Run(request_, &response_); + } + return true; +} + +RequestID Connection::FinishRequestAsync( + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + // Make sure the produced Closure holds a reference to the instance of this + // connection. + auto connection = std::static_pointer_cast(shared_from_this()); + auto callback = [connection, success_callback, error_callback] { + connection->FinishRequestAsyncHelper(success_callback, error_callback); + }; + transport_->RunCallbackAsync(FROM_HERE, base::Bind(callback)); + return 1; +} + +void Connection::FinishRequestAsyncHelper( + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + brillo::ErrorPtr error; + if (!FinishRequest(&error)) { + error_callback.Run(1, error.get()); + } else { + std::unique_ptr response{new Response{shared_from_this()}}; + success_callback.Run(1, std::move(response)); + } +} + +int Connection::GetResponseStatusCode() const { + return response_.GetStatusCode(); +} + +std::string Connection::GetResponseStatusText() const { + return response_.GetStatusText(); +} + +std::string Connection::GetProtocolVersion() const { + return response_.GetProtocolVersion(); +} + +std::string Connection::GetResponseHeader( + const std::string& header_name) const { + return response_.GetHeader(header_name); +} + +StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) { + // HEAD requests must not return body. + if (request_.GetMethod() != request_type::kHead) { + return MemoryStream::OpenRef(response_.GetData(), error); + } else { + // Return empty data stream for HEAD requests. + return MemoryStream::OpenCopyOf(nullptr, 0, error); + } +} + +} // namespace fake +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_connection_fake.h b/brillo/http/http_connection_fake.h new file mode 100644 index 0000000..cc05fbd --- /dev/null +++ b/brillo/http/http_connection_fake.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_FAKE_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_FAKE_H_ + +#include +#include +#include + +#include +#include +#include + +namespace brillo { +namespace http { +namespace fake { + +// This is a fake implementation of http::Connection for unit testing. +class Connection : public http::Connection { + public: + Connection(const std::string& url, + const std::string& method, + const std::shared_ptr& transport); + ~Connection() override; + + // Overrides from http::Connection. + // See http_connection.h for description of these methods. + bool SendHeaders(const HeaderList& headers, brillo::ErrorPtr* error) override; + bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) override; + void SetResponseData(StreamPtr stream) override {} + bool FinishRequest(brillo::ErrorPtr* error) override; + RequestID FinishRequestAsync(const SuccessCallback& success_callback, + const ErrorCallback& error_callback) override; + + int GetResponseStatusCode() const override; + std::string GetResponseStatusText() const override; + std::string GetProtocolVersion() const override; + std::string GetResponseHeader(const std::string& header_name) const override; + StreamPtr ExtractDataStream(brillo::ErrorPtr* error) override; + + private: + // A helper method for FinishRequestAsync() implementation. + void FinishRequestAsyncHelper(const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + + // Request and response objects passed to the user-provided request handler + // callback. The request object contains all the request information. + // The response object is the server response that is created by + // the handler in response to the request. + ServerRequest request_; + ServerResponse response_; + + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +} // namespace fake +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_CONNECTION_FAKE_H_ diff --git a/brillo/http/http_form_data.cc b/brillo/http/http_form_data.cc new file mode 100644 index 0000000..4d8f6f0 --- /dev/null +++ b/brillo/http/http_form_data.cc @@ -0,0 +1,221 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { + +namespace form_header { +const char kContentDisposition[] = "Content-Disposition"; +const char kContentTransferEncoding[] = "Content-Transfer-Encoding"; +const char kContentType[] = "Content-Type"; +} // namespace form_header + +const char content_disposition::kFile[] = "file"; +const char content_disposition::kFormData[] = "form-data"; + +FormField::FormField(const std::string& name, + const std::string& content_disposition, + const std::string& content_type, + const std::string& transfer_encoding) + : name_{name}, + content_disposition_{content_disposition}, + content_type_{content_type}, + transfer_encoding_{transfer_encoding} { +} + +std::string FormField::GetContentDisposition() const { + std::string disposition = content_disposition_; + if (!name_.empty()) + base::StringAppendF(&disposition, "; name=\"%s\"", name_.c_str()); + return disposition; +} + +std::string FormField::GetContentType() const { + return content_type_; +} + +std::string FormField::GetContentHeader() const { + HeaderList headers{ + {form_header::kContentDisposition, GetContentDisposition()} + }; + + if (!content_type_.empty()) + headers.emplace_back(form_header::kContentType, GetContentType()); + + if (!transfer_encoding_.empty()) { + headers.emplace_back(form_header::kContentTransferEncoding, + transfer_encoding_); + } + + std::string result; + for (const auto& pair : headers) { + base::StringAppendF( + &result, "%s: %s\r\n", pair.first.c_str(), pair.second.c_str()); + } + result += "\r\n"; + return result; +} + +TextFormField::TextFormField(const std::string& name, + const std::string& data, + const std::string& content_type, + const std::string& transfer_encoding) + : FormField{name, + content_disposition::kFormData, + content_type, + transfer_encoding}, + data_{data} { +} + +bool TextFormField::ExtractDataStreams(std::vector* streams) { + streams->push_back(MemoryStream::OpenCopyOf(data_, nullptr)); + return true; +} + +FileFormField::FileFormField(const std::string& name, + StreamPtr stream, + const std::string& file_name, + const std::string& content_disposition, + const std::string& content_type, + const std::string& transfer_encoding) + : FormField{name, content_disposition, content_type, transfer_encoding}, + stream_{std::move(stream)}, + file_name_{file_name} { +} + +std::string FileFormField::GetContentDisposition() const { + std::string disposition = FormField::GetContentDisposition(); + base::StringAppendF(&disposition, "; filename=\"%s\"", file_name_.c_str()); + return disposition; +} + +bool FileFormField::ExtractDataStreams(std::vector* streams) { + if (!stream_) + return false; + streams->push_back(std::move(stream_)); + return true; +} + +MultiPartFormField::MultiPartFormField(const std::string& name, + const std::string& content_type, + const std::string& boundary) + : FormField{name, + content_disposition::kFormData, + content_type.empty() ? mime::multipart::kMixed : content_type, + {}}, + boundary_{boundary} { + if (boundary_.empty()) + boundary_ = base::StringPrintf("%016" PRIx64, base::RandUint64()); +} + +bool MultiPartFormField::ExtractDataStreams(std::vector* streams) { + for (auto& part : parts_) { + std::string data = GetBoundaryStart() + part->GetContentHeader(); + streams->push_back(MemoryStream::OpenCopyOf(data, nullptr)); + if (!part->ExtractDataStreams(streams)) + return false; + + streams->push_back(MemoryStream::OpenRef("\r\n", nullptr)); + } + if (!parts_.empty()) { + std::string data = GetBoundaryEnd(); + streams->push_back(MemoryStream::OpenCopyOf(data, nullptr)); + } + return true; +} + +std::string MultiPartFormField::GetContentType() const { + return base::StringPrintf( + "%s; boundary=\"%s\"", content_type_.c_str(), boundary_.c_str()); +} + +void MultiPartFormField::AddCustomField(std::unique_ptr field) { + parts_.push_back(std::move(field)); +} + +void MultiPartFormField::AddTextField(const std::string& name, + const std::string& data) { + AddCustomField(std::unique_ptr{new TextFormField{name, data}}); +} + +bool MultiPartFormField::AddFileField(const std::string& name, + const base::FilePath& file_path, + const std::string& content_disposition, + const std::string& content_type, + brillo::ErrorPtr* error) { + StreamPtr stream = FileStream::Open(file_path, Stream::AccessMode::READ, + FileStream::Disposition::OPEN_EXISTING, + error); + if (!stream) + return false; + std::string file_name = file_path.BaseName().value(); + std::unique_ptr file_field{new FileFormField{name, + std::move(stream), + file_name, + content_disposition, + content_type, + "binary"}}; + AddCustomField(std::move(file_field)); + return true; +} + +std::string MultiPartFormField::GetBoundaryStart() const { + return base::StringPrintf("--%s\r\n", boundary_.c_str()); +} + +std::string MultiPartFormField::GetBoundaryEnd() const { + return base::StringPrintf("--%s--", boundary_.c_str()); +} + +FormData::FormData() : FormData{std::string{}} { +} + +FormData::FormData(const std::string& boundary) + : form_data_{"", mime::multipart::kFormData, boundary} { +} + +void FormData::AddCustomField(std::unique_ptr field) { + form_data_.AddCustomField(std::move(field)); +} + +void FormData::AddTextField(const std::string& name, const std::string& data) { + form_data_.AddTextField(name, data); +} + +bool FormData::AddFileField(const std::string& name, + const base::FilePath& file_path, + const std::string& content_type, + brillo::ErrorPtr* error) { + return form_data_.AddFileField( + name, file_path, content_disposition::kFormData, content_type, error); +} + +std::string FormData::GetContentType() const { + return form_data_.GetContentType(); +} + +StreamPtr FormData::ExtractDataStream() { + std::vector source_streams; + if (form_data_.ExtractDataStreams(&source_streams)) + return InputStreamSet::Create(std::move(source_streams), nullptr); + return {}; +} + +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_form_data.h b/brillo/http/http_form_data.h new file mode 100644 index 0000000..8bbc946 --- /dev/null +++ b/brillo/http/http_form_data.h @@ -0,0 +1,233 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_FORM_DATA_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_FORM_DATA_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { + +namespace content_disposition { +BRILLO_EXPORT extern const char kFormData[]; +BRILLO_EXPORT extern const char kFile[]; +} // namespace content_disposition + +// An abstract base class for all types of form fields used by FormData class. +// This class represents basic information about a form part in +// multipart/form-data and multipart/mixed content. +// For more details on multipart content, see the following RFC: +// http://www.ietf.org/rfc/rfc2388 +// For more details on MIME and content headers, see the following RFC: +// http://www.ietf.org/rfc/rfc2045 +class BRILLO_EXPORT FormField { + public: + // The constructor that takes the basic data part information common to + // all part types. An example of part's headers could include: + // + // Content-Disposition: form-data; name="field1" + // Content-Type: text/plain;charset=windows-1250 + // Content-Transfer-Encoding: quoted-printable + // + // The constructor parameters correspond to the basic part attributes: + // |name| = the part name ("name" parameter of Content-Disposition header; + // "field1" in the example above) + // |content_disposition| = the part disposition ("form-data" in the example) + // |content_type| = the content type ("text/plain;charset=windows-1250") + // |transfer_encoding| = the encoding type for transport ("quoted-printable") + // See http://www.ietf.org/rfc/rfc2045, section 6.1 + FormField(const std::string& name, + const std::string& content_disposition, + const std::string& content_type, + const std::string& transfer_encoding); + virtual ~FormField() = default; + + // Returns the full Content-Disposition header value. This might include the + // disposition type itself as well as the field "name" and/or "filename" + // parameters. + virtual std::string GetContentDisposition() const; + + // Returns the full content type of field data. MultiPartFormField overloads + // this method to append "boundary" parameter to it. + virtual std::string GetContentType() const; + + // Returns a string with all of the field headers, delimited by CRLF + // characters ("\r\n"). + std::string GetContentHeader() const; + + // Adds the data stream(s) to the list of streams to read from. + // This is a potentially destructive operation and can be guaranteed to + // succeed only on the first try. Subsequent calls will fail for certain + // types of form fields. + virtual bool ExtractDataStreams(std::vector* streams) = 0; + + protected: + // Form field name. If not empty, it will be appended to Content-Disposition + // field header using "name" attribute. + std::string name_; + + // Form field disposition. Most of the time this will be "form-data". But for + // nested file uploads inside "multipart/mixed" sections, this can be "file". + std::string content_disposition_; + + // Content type. If omitted (empty), "plain/text" assumed. + std::string content_type_; + + // Transfer encoding for field data. If omitted, "7bit" is assumed. For most + // binary contents (e.g. for file content), use "binary". + std::string transfer_encoding_; + + private: + DISALLOW_COPY_AND_ASSIGN(FormField); +}; + +// Simple text form field. +class BRILLO_EXPORT TextFormField : public FormField { + public: + // Constructor. Parameters: + // name: field name + // data: field text data + // content_type: the data content type. Empty if not specified. + // transfer_encoding: the encoding type of data. If omitted, no encoding + // is specified (and "7bit" is assumed). + TextFormField(const std::string& name, + const std::string& data, + const std::string& content_type = {}, + const std::string& transfer_encoding = {}); + + bool ExtractDataStreams(std::vector* streams) override; + + private: + std::string data_; // Buffer/reader for field data. + + DISALLOW_COPY_AND_ASSIGN(TextFormField); +}; + +// File upload form field. +class BRILLO_EXPORT FileFormField : public FormField { + public: + // Constructor. Parameters: + // name: field name + // stream: open stream with the contents of the file. + // file_name: just the base file name of the file, e.g. "file.txt". + // Used in "filename" parameter of Content-Disposition header. + // content_type: valid content type of the file. + // transfer_encoding: the encoding type of data. + // If omitted, "binary" is used. + FileFormField(const std::string& name, + StreamPtr stream, + const std::string& file_name, + const std::string& content_disposition, + const std::string& content_type, + const std::string& transfer_encoding = {}); + + // Override from FormField. + // Appends "filename" parameter to Content-Disposition header. + std::string GetContentDisposition() const override; + + bool ExtractDataStreams(std::vector* streams) override; + + private: + StreamPtr stream_; + std::string file_name_; + + DISALLOW_COPY_AND_ASSIGN(FileFormField); +}; + +// Multipart form field. +// This is used directly by FormData class to build the request body for +// form upload. It can also be used with multiple file uploads for a single +// file field, when the uploaded files should be sent as "multipart/mixed". +class BRILLO_EXPORT MultiPartFormField : public FormField { + public: + // Constructor. Parameters: + // name: field name + // content_type: valid content type. If omitted, "multipart/mixed" is used. + // boundary: multipart boundary separator. + // If omitted/empty, a random string is generated. + MultiPartFormField(const std::string& name, + const std::string& content_type = {}, + const std::string& boundary = {}); + + // Override from FormField. + // Appends "boundary" parameter to Content-Type header. + std::string GetContentType() const override; + + bool ExtractDataStreams(std::vector* streams) override; + + // Adds a form field to the form data. The |field| could be a simple text + // field, a file upload field or a multipart form field. + void AddCustomField(std::unique_ptr field); + + // Adds a simple text form field. + void AddTextField(const std::string& name, const std::string& data); + + // Adds a file upload form field using a file path. + bool AddFileField(const std::string& name, + const base::FilePath& file_path, + const std::string& content_disposition, + const std::string& content_type, + brillo::ErrorPtr* error); + + // Returns a boundary string used to separate multipart form fields. + const std::string& GetBoundary() const { return boundary_; } + + private: + // Returns the starting boundary string: "--". + std::string GetBoundaryStart() const; + // Returns the ending boundary string: "----". + std::string GetBoundaryEnd() const; + + std::string boundary_; // Boundary string used as field separator. + std::vector> parts_; // Form field list. + + DISALLOW_COPY_AND_ASSIGN(MultiPartFormField); +}; + +// A class representing a multipart form data for sending as HTTP POST request. +class BRILLO_EXPORT FormData final { + public: + FormData(); + // Allows to specify a custom |boundary| separator string. + explicit FormData(const std::string& boundary); + + // Adds a form field to the form data. The |field| could be a simple text + // field, a file upload field or a multipart form field. + void AddCustomField(std::unique_ptr field); + + // Adds a simple text form field. + void AddTextField(const std::string& name, const std::string& data); + + // Adds a file upload form field using a file path. + bool AddFileField(const std::string& name, + const base::FilePath& file_path, + const std::string& content_type, + brillo::ErrorPtr* error); + + // Returns the complete content type string to be used in HTTP requests. + std::string GetContentType() const; + + // Returns the data stream for the form data. This is a potentially + // destructive operation and can be called only once. + StreamPtr ExtractDataStream(); + + private: + MultiPartFormField form_data_; + + DISALLOW_COPY_AND_ASSIGN(FormData); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_FORM_DATA_H_ diff --git a/brillo/http/http_form_data_unittest.cc b/brillo/http/http_form_data_unittest.cc new file mode 100644 index 0000000..842225d --- /dev/null +++ b/brillo/http/http_form_data_unittest.cc @@ -0,0 +1,202 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { +namespace { +std::string GetFormFieldData(FormField* field) { + std::vector streams; + CHECK(field->ExtractDataStreams(&streams)); + StreamPtr stream = InputStreamSet::Create(std::move(streams), nullptr); + + std::vector data(stream->GetSize()); + EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); + return std::string{data.begin(), data.end()}; +} +} // anonymous namespace + +TEST(HttpFormData, TextFormField) { + TextFormField form_field{"field1", "abcdefg", mime::text::kPlain, "7bit"}; + const char expected_header[] = + "Content-Disposition: form-data; name=\"field1\"\r\n" + "Content-Type: text/plain\r\n" + "Content-Transfer-Encoding: 7bit\r\n" + "\r\n"; + EXPECT_EQ(expected_header, form_field.GetContentHeader()); + EXPECT_EQ("abcdefg", GetFormFieldData(&form_field)); +} + +TEST(HttpFormData, FileFormField) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + std::string file_content{"text line1\ntext line2\n"}; + base::FilePath file_name = dir.path().Append("sample.txt"); + ASSERT_EQ(file_content.size(), + static_cast(base::WriteFile( + file_name, file_content.data(), file_content.size()))); + + StreamPtr stream = FileStream::Open(file_name, Stream::AccessMode::READ, + FileStream::Disposition::OPEN_EXISTING, + nullptr); + ASSERT_NE(nullptr, stream); + FileFormField form_field{"test_file", + std::move(stream), + "sample.txt", + content_disposition::kFormData, + mime::text::kPlain, + ""}; + const char expected_header[] = + "Content-Disposition: form-data; name=\"test_file\";" + " filename=\"sample.txt\"\r\n" + "Content-Type: text/plain\r\n" + "\r\n"; + EXPECT_EQ(expected_header, form_field.GetContentHeader()); + EXPECT_EQ(file_content, GetFormFieldData(&form_field)); +} + +TEST(HttpFormData, MultiPartFormField) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + std::string file1{"text line1\ntext line2\n"}; + base::FilePath filename1 = dir.path().Append("sample.txt"); + ASSERT_EQ(file1.size(), + static_cast( + base::WriteFile(filename1, file1.data(), file1.size()))); + std::string file2{"\x01\x02\x03\x04\x05"}; + base::FilePath filename2 = dir.path().Append("test.bin"); + ASSERT_EQ(file2.size(), + static_cast( + base::WriteFile(filename2, file2.data(), file2.size()))); + + MultiPartFormField form_field{"foo", mime::multipart::kFormData, "Delimiter"}; + form_field.AddTextField("name", "John Doe"); + EXPECT_TRUE(form_field.AddFileField("file1", + filename1, + content_disposition::kFormData, + mime::text::kPlain, + nullptr)); + EXPECT_TRUE(form_field.AddFileField("file2", + filename2, + content_disposition::kFormData, + mime::application::kOctet_stream, + nullptr)); + const char expected_header[] = + "Content-Disposition: form-data; name=\"foo\"\r\n" + "Content-Type: multipart/form-data; boundary=\"Delimiter\"\r\n" + "\r\n"; + EXPECT_EQ(expected_header, form_field.GetContentHeader()); + const char expected_data[] = + "--Delimiter\r\n" + "Content-Disposition: form-data; name=\"name\"\r\n" + "\r\n" + "John Doe\r\n" + "--Delimiter\r\n" + "Content-Disposition: form-data; name=\"file1\";" + " filename=\"sample.txt\"\r\n" + "Content-Type: text/plain\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" + "text line1\ntext line2\n\r\n" + "--Delimiter\r\n" + "Content-Disposition: form-data; name=\"file2\";" + " filename=\"test.bin\"\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" + "\x01\x02\x03\x04\x05\r\n" + "--Delimiter--"; + EXPECT_EQ(expected_data, GetFormFieldData(&form_field)); +} + +TEST(HttpFormData, MultiPartBoundary) { + const int count = 10; + std::set boundaries; + for (int i = 0; i < count; i++) { + MultiPartFormField field{""}; + std::string boundary = field.GetBoundary(); + boundaries.insert(boundary); + // Our generated boundary must be 16 character long and contain lowercase + // hexadecimal digits only. + EXPECT_EQ(16u, boundary.size()); + EXPECT_EQ(std::string::npos, + boundary.find_first_not_of("0123456789abcdef")); + } + // Now make sure the boundary strings were generated at random, so we should + // get |count| unique boundary strings. However since the strings are random, + // there is a very slim change of generating the same string twice, so + // expect at least 90% of unique strings. 90% is picked arbitrarily here. + int expected_min_unique = count * 9 / 10; + EXPECT_GE(boundaries.size(), expected_min_unique); +} + +TEST(HttpFormData, FormData) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + std::string file1{"text line1\ntext line2\n"}; + base::FilePath filename1 = dir.path().Append("sample.txt"); + ASSERT_EQ(file1.size(), + static_cast( + base::WriteFile(filename1, file1.data(), file1.size()))); + std::string file2{"\x01\x02\x03\x04\x05"}; + base::FilePath filename2 = dir.path().Append("test.bin"); + ASSERT_EQ(file2.size(), + static_cast( + base::WriteFile(filename2, file2.data(), file2.size()))); + + FormData form_data{"boundary1"}; + form_data.AddTextField("name", "John Doe"); + std::unique_ptr files{ + new MultiPartFormField{"files", "", "boundary2"}}; + EXPECT_TRUE(files->AddFileField( + "", filename1, content_disposition::kFile, mime::text::kPlain, nullptr)); + EXPECT_TRUE(files->AddFileField("", + filename2, + content_disposition::kFile, + mime::application::kOctet_stream, + nullptr)); + form_data.AddCustomField(std::move(files)); + EXPECT_EQ("multipart/form-data; boundary=\"boundary1\"", + form_data.GetContentType()); + + StreamPtr stream = form_data.ExtractDataStream(); + std::vector data(stream->GetSize()); + EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); + const char expected_data[] = + "--boundary1\r\n" + "Content-Disposition: form-data; name=\"name\"\r\n" + "\r\n" + "John Doe\r\n" + "--boundary1\r\n" + "Content-Disposition: form-data; name=\"files\"\r\n" + "Content-Type: multipart/mixed; boundary=\"boundary2\"\r\n" + "\r\n" + "--boundary2\r\n" + "Content-Disposition: file; filename=\"sample.txt\"\r\n" + "Content-Type: text/plain\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" + "text line1\ntext line2\n\r\n" + "--boundary2\r\n" + "Content-Disposition: file; filename=\"test.bin\"\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" + "\x01\x02\x03\x04\x05\r\n" + "--boundary2--\r\n" + "--boundary1--"; + EXPECT_EQ(expected_data, (std::string{data.begin(), data.end()})); +} +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_request.cc b/brillo/http/http_request.cc new file mode 100644 index 0000000..2784b8e --- /dev/null +++ b/brillo/http/http_request.cc @@ -0,0 +1,357 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { + +// request_type +const char request_type::kOptions[] = "OPTIONS"; +const char request_type::kGet[] = "GET"; +const char request_type::kHead[] = "HEAD"; +const char request_type::kPost[] = "POST"; +const char request_type::kPut[] = "PUT"; +const char request_type::kPatch[] = "PATCH"; +const char request_type::kDelete[] = "DELETE"; +const char request_type::kTrace[] = "TRACE"; +const char request_type::kConnect[] = "CONNECT"; +const char request_type::kCopy[] = "COPY"; +const char request_type::kMove[] = "MOVE"; + +// request_header +const char request_header::kAccept[] = "Accept"; +const char request_header::kAcceptCharset[] = "Accept-Charset"; +const char request_header::kAcceptEncoding[] = "Accept-Encoding"; +const char request_header::kAcceptLanguage[] = "Accept-Language"; +const char request_header::kAllow[] = "Allow"; +const char request_header::kAuthorization[] = "Authorization"; +const char request_header::kCacheControl[] = "Cache-Control"; +const char request_header::kConnection[] = "Connection"; +const char request_header::kContentEncoding[] = "Content-Encoding"; +const char request_header::kContentLanguage[] = "Content-Language"; +const char request_header::kContentLength[] = "Content-Length"; +const char request_header::kContentLocation[] = "Content-Location"; +const char request_header::kContentMd5[] = "Content-MD5"; +const char request_header::kContentRange[] = "Content-Range"; +const char request_header::kContentType[] = "Content-Type"; +const char request_header::kCookie[] = "Cookie"; +const char request_header::kDate[] = "Date"; +const char request_header::kExpect[] = "Expect"; +const char request_header::kExpires[] = "Expires"; +const char request_header::kFrom[] = "From"; +const char request_header::kHost[] = "Host"; +const char request_header::kIfMatch[] = "If-Match"; +const char request_header::kIfModifiedSince[] = "If-Modified-Since"; +const char request_header::kIfNoneMatch[] = "If-None-Match"; +const char request_header::kIfRange[] = "If-Range"; +const char request_header::kIfUnmodifiedSince[] = "If-Unmodified-Since"; +const char request_header::kLastModified[] = "Last-Modified"; +const char request_header::kMaxForwards[] = "Max-Forwards"; +const char request_header::kPragma[] = "Pragma"; +const char request_header::kProxyAuthorization[] = "Proxy-Authorization"; +const char request_header::kRange[] = "Range"; +const char request_header::kReferer[] = "Referer"; +const char request_header::kTE[] = "TE"; +const char request_header::kTrailer[] = "Trailer"; +const char request_header::kTransferEncoding[] = "Transfer-Encoding"; +const char request_header::kUpgrade[] = "Upgrade"; +const char request_header::kUserAgent[] = "User-Agent"; +const char request_header::kVia[] = "Via"; +const char request_header::kWarning[] = "Warning"; + +// response_header +const char response_header::kAcceptRanges[] = "Accept-Ranges"; +const char response_header::kAge[] = "Age"; +const char response_header::kAllow[] = "Allow"; +const char response_header::kCacheControl[] = "Cache-Control"; +const char response_header::kConnection[] = "Connection"; +const char response_header::kContentEncoding[] = "Content-Encoding"; +const char response_header::kContentLanguage[] = "Content-Language"; +const char response_header::kContentLength[] = "Content-Length"; +const char response_header::kContentLocation[] = "Content-Location"; +const char response_header::kContentMd5[] = "Content-MD5"; +const char response_header::kContentRange[] = "Content-Range"; +const char response_header::kContentType[] = "Content-Type"; +const char response_header::kDate[] = "Date"; +const char response_header::kETag[] = "ETag"; +const char response_header::kExpires[] = "Expires"; +const char response_header::kLastModified[] = "Last-Modified"; +const char response_header::kLocation[] = "Location"; +const char response_header::kPragma[] = "Pragma"; +const char response_header::kProxyAuthenticate[] = "Proxy-Authenticate"; +const char response_header::kRetryAfter[] = "Retry-After"; +const char response_header::kServer[] = "Server"; +const char response_header::kSetCookie[] = "Set-Cookie"; +const char response_header::kTrailer[] = "Trailer"; +const char response_header::kTransferEncoding[] = "Transfer-Encoding"; +const char response_header::kUpgrade[] = "Upgrade"; +const char response_header::kVary[] = "Vary"; +const char response_header::kVia[] = "Via"; +const char response_header::kWarning[] = "Warning"; +const char response_header::kWwwAuthenticate[] = "WWW-Authenticate"; + +// *********************************************************** +// ********************** Request Class ********************** +// *********************************************************** +Request::Request(const std::string& url, + const std::string& method, + std::shared_ptr transport) + : transport_(transport), request_url_(url), method_(method) { + VLOG(1) << "http::Request created"; + if (!transport_) + transport_ = http::Transport::CreateDefault(); +} + +Request::~Request() { + VLOG(1) << "http::Request destroyed"; +} + +void Request::AddRange(int64_t bytes) { + DCHECK(transport_) << "Request already sent"; + if (bytes < 0) { + ranges_.emplace_back(Request::range_value_omitted, -bytes); + } else { + ranges_.emplace_back(bytes, Request::range_value_omitted); + } +} + +void Request::AddRange(uint64_t from_byte, uint64_t to_byte) { + DCHECK(transport_) << "Request already sent"; + ranges_.emplace_back(from_byte, to_byte); +} + +std::unique_ptr Request::GetResponseAndBlock( + brillo::ErrorPtr* error) { + if (!SendRequestIfNeeded(error) || !connection_->FinishRequest(error)) + return std::unique_ptr(); + std::unique_ptr response(new Response(connection_)); + connection_.reset(); + transport_.reset(); // Indicate that the response has been received + return response; +} + +RequestID Request::GetResponse(const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + ErrorPtr error; + if (!SendRequestIfNeeded(&error)) { + transport_->RunCallbackAsync( + FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); + return 0; + } + RequestID id = + connection_->FinishRequestAsync(success_callback, error_callback); + connection_.reset(); + transport_.reset(); // Indicate that the request has been dispatched. + return id; +} + +void Request::SetAccept(const std::string& accept_mime_types) { + DCHECK(transport_) << "Request already sent"; + accept_ = accept_mime_types; +} + +const std::string& Request::GetAccept() const { + return accept_; +} + +void Request::SetContentType(const std::string& contentType) { + DCHECK(transport_) << "Request already sent"; + content_type_ = contentType; +} + +const std::string& Request::GetContentType() const { + return content_type_; +} + +void Request::AddHeader(const std::string& header, const std::string& value) { + DCHECK(transport_) << "Request already sent"; + headers_.emplace(header, value); +} + +void Request::AddHeaders(const HeaderList& headers) { + DCHECK(transport_) << "Request already sent"; + headers_.insert(headers.begin(), headers.end()); +} + +bool Request::AddRequestBody(const void* data, + size_t size, + brillo::ErrorPtr* error) { + if (!SendRequestIfNeeded(error)) + return false; + StreamPtr stream = MemoryStream::OpenCopyOf(data, size, error); + return stream && connection_->SetRequestData(std::move(stream), error); +} + +bool Request::AddRequestBody(StreamPtr stream, brillo::ErrorPtr* error) { + return SendRequestIfNeeded(error) && + connection_->SetRequestData(std::move(stream), error); +} + +bool Request::AddRequestBodyAsFormData(std::unique_ptr form_data, + brillo::ErrorPtr* error) { + AddHeader(request_header::kContentType, form_data->GetContentType()); + if (!SendRequestIfNeeded(error)) + return false; + return connection_->SetRequestData(form_data->ExtractDataStream(), error); +} + +bool Request::AddResponseStream(StreamPtr stream, brillo::ErrorPtr* error) { + if (!SendRequestIfNeeded(error)) + return false; + connection_->SetResponseData(std::move(stream)); + return true; +} + +const std::string& Request::GetRequestURL() const { + return request_url_; +} + +const std::string& Request::GetRequestMethod() const { + return method_; +} + +void Request::SetReferer(const std::string& referer) { + DCHECK(transport_) << "Request already sent"; + referer_ = referer; +} + +const std::string& Request::GetReferer() const { + return referer_; +} + +void Request::SetUserAgent(const std::string& user_agent) { + DCHECK(transport_) << "Request already sent"; + user_agent_ = user_agent; +} + +const std::string& Request::GetUserAgent() const { + return user_agent_; +} + +bool Request::SendRequestIfNeeded(brillo::ErrorPtr* error) { + if (transport_) { + if (!connection_) { + http::HeaderList headers = brillo::MapToVector(headers_); + std::vector ranges; + if (method_ != request_type::kHead) { + ranges.reserve(ranges_.size()); + for (auto p : ranges_) { + if (p.first != range_value_omitted || + p.second != range_value_omitted) { + std::string range; + if (p.first != range_value_omitted) { + range = brillo::string_utils::ToString(p.first); + } + range += '-'; + if (p.second != range_value_omitted) { + range += brillo::string_utils::ToString(p.second); + } + ranges.push_back(range); + } + } + } + if (!ranges.empty()) + headers.emplace_back( + request_header::kRange, + "bytes=" + brillo::string_utils::Join(",", ranges)); + + headers.emplace_back(request_header::kAccept, GetAccept()); + if (method_ != request_type::kGet && method_ != request_type::kHead) { + if (!content_type_.empty()) + headers.emplace_back(request_header::kContentType, content_type_); + } + connection_ = transport_->CreateConnection( + request_url_, method_, headers, user_agent_, referer_, error); + } + + if (connection_) + return true; + } else { + brillo::Error::AddTo(error, + FROM_HERE, + http::kErrorDomain, + "response_already_received", + "HTTP response already received"); + } + return false; +} + +// ************************************************************ +// ********************** Response Class ********************** +// ************************************************************ +Response::Response(const std::shared_ptr& connection) + : connection_{connection} { + VLOG(1) << "http::Response created"; +} + +Response::~Response() { + VLOG(1) << "http::Response destroyed"; +} + +bool Response::IsSuccessful() const { + int code = GetStatusCode(); + return code >= status_code::Continue && code < status_code::BadRequest; +} + +int Response::GetStatusCode() const { + if (!connection_) + return -1; + + return connection_->GetResponseStatusCode(); +} + +std::string Response::GetStatusText() const { + if (!connection_) + return std::string(); + + return connection_->GetResponseStatusText(); +} + +std::string Response::GetContentType() const { + return GetHeader(response_header::kContentType); +} + +StreamPtr Response::ExtractDataStream(ErrorPtr* error) { + return connection_->ExtractDataStream(error); +} + +std::vector Response::ExtractData() { + std::vector data; + StreamPtr src_stream = connection_->ExtractDataStream(nullptr); + StreamPtr dest_stream = MemoryStream::CreateRef(&data, nullptr); + if (src_stream && dest_stream) { + char buffer[1024]; + size_t read = 0; + while (src_stream->ReadBlocking(buffer, sizeof(buffer), &read, nullptr) && + read > 0) { + CHECK(dest_stream->WriteAllBlocking(buffer, read, nullptr)); + } + } + return data; +} + +std::string Response::ExtractDataAsString() { + std::vector data = ExtractData(); + return std::string{data.begin(), data.end()}; +} + +std::string Response::GetHeader(const std::string& header_name) const { + if (connection_) + return connection_->GetResponseHeader(header_name); + + return std::string(); +} + +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_request.h b/brillo/http/http_request.h new file mode 100644 index 0000000..b47a50c --- /dev/null +++ b/brillo/http/http_request.h @@ -0,0 +1,381 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_REQUEST_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_REQUEST_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { + +// HTTP request verbs +namespace request_type { +BRILLO_EXPORT extern const char kOptions[]; +BRILLO_EXPORT extern const char kGet[]; +BRILLO_EXPORT extern const char kHead[]; +BRILLO_EXPORT extern const char kPost[]; +BRILLO_EXPORT extern const char kPut[]; +BRILLO_EXPORT extern const char kPatch[]; // Non-standard HTTP/1.1 verb +BRILLO_EXPORT extern const char kDelete[]; +BRILLO_EXPORT extern const char kTrace[]; +BRILLO_EXPORT extern const char kConnect[]; +BRILLO_EXPORT extern const char kCopy[]; // Non-standard HTTP/1.1 verb +BRILLO_EXPORT extern const char kMove[]; // Non-standard HTTP/1.1 verb +} // namespace request_type + +// HTTP request header names +namespace request_header { +BRILLO_EXPORT extern const char kAccept[]; +BRILLO_EXPORT extern const char kAcceptCharset[]; +BRILLO_EXPORT extern const char kAcceptEncoding[]; +BRILLO_EXPORT extern const char kAcceptLanguage[]; +BRILLO_EXPORT extern const char kAllow[]; +BRILLO_EXPORT extern const char kAuthorization[]; +BRILLO_EXPORT extern const char kCacheControl[]; +BRILLO_EXPORT extern const char kConnection[]; +BRILLO_EXPORT extern const char kContentEncoding[]; +BRILLO_EXPORT extern const char kContentLanguage[]; +BRILLO_EXPORT extern const char kContentLength[]; +BRILLO_EXPORT extern const char kContentLocation[]; +BRILLO_EXPORT extern const char kContentMd5[]; +BRILLO_EXPORT extern const char kContentRange[]; +BRILLO_EXPORT extern const char kContentType[]; +BRILLO_EXPORT extern const char kCookie[]; +BRILLO_EXPORT extern const char kDate[]; +BRILLO_EXPORT extern const char kExpect[]; +BRILLO_EXPORT extern const char kExpires[]; +BRILLO_EXPORT extern const char kFrom[]; +BRILLO_EXPORT extern const char kHost[]; +BRILLO_EXPORT extern const char kIfMatch[]; +BRILLO_EXPORT extern const char kIfModifiedSince[]; +BRILLO_EXPORT extern const char kIfNoneMatch[]; +BRILLO_EXPORT extern const char kIfRange[]; +BRILLO_EXPORT extern const char kIfUnmodifiedSince[]; +BRILLO_EXPORT extern const char kLastModified[]; +BRILLO_EXPORT extern const char kMaxForwards[]; +BRILLO_EXPORT extern const char kPragma[]; +BRILLO_EXPORT extern const char kProxyAuthorization[]; +BRILLO_EXPORT extern const char kRange[]; +BRILLO_EXPORT extern const char kReferer[]; +BRILLO_EXPORT extern const char kTE[]; +BRILLO_EXPORT extern const char kTrailer[]; +BRILLO_EXPORT extern const char kTransferEncoding[]; +BRILLO_EXPORT extern const char kUpgrade[]; +BRILLO_EXPORT extern const char kUserAgent[]; +BRILLO_EXPORT extern const char kVia[]; +BRILLO_EXPORT extern const char kWarning[]; +} // namespace request_header + +// HTTP response header names +namespace response_header { +BRILLO_EXPORT extern const char kAcceptRanges[]; +BRILLO_EXPORT extern const char kAge[]; +BRILLO_EXPORT extern const char kAllow[]; +BRILLO_EXPORT extern const char kCacheControl[]; +BRILLO_EXPORT extern const char kConnection[]; +BRILLO_EXPORT extern const char kContentEncoding[]; +BRILLO_EXPORT extern const char kContentLanguage[]; +BRILLO_EXPORT extern const char kContentLength[]; +BRILLO_EXPORT extern const char kContentLocation[]; +BRILLO_EXPORT extern const char kContentMd5[]; +BRILLO_EXPORT extern const char kContentRange[]; +BRILLO_EXPORT extern const char kContentType[]; +BRILLO_EXPORT extern const char kDate[]; +BRILLO_EXPORT extern const char kETag[]; +BRILLO_EXPORT extern const char kExpires[]; +BRILLO_EXPORT extern const char kLastModified[]; +BRILLO_EXPORT extern const char kLocation[]; +BRILLO_EXPORT extern const char kPragma[]; +BRILLO_EXPORT extern const char kProxyAuthenticate[]; +BRILLO_EXPORT extern const char kRetryAfter[]; +BRILLO_EXPORT extern const char kServer[]; +BRILLO_EXPORT extern const char kSetCookie[]; +BRILLO_EXPORT extern const char kTrailer[]; +BRILLO_EXPORT extern const char kTransferEncoding[]; +BRILLO_EXPORT extern const char kUpgrade[]; +BRILLO_EXPORT extern const char kVary[]; +BRILLO_EXPORT extern const char kVia[]; +BRILLO_EXPORT extern const char kWarning[]; +BRILLO_EXPORT extern const char kWwwAuthenticate[]; +} // namespace response_header + +// HTTP request status (error) codes +namespace status_code { +// OK to continue with request +static const int Continue = 100; +// Server has switched protocols in upgrade header +static const int SwitchProtocols = 101; + +// Request completed +static const int Ok = 200; +// Object created, reason = new URI +static const int Created = 201; +// Async completion (TBS) +static const int Accepted = 202; +// Partial completion +static const int Partial = 203; +// No info to return +static const int NoContent = 204; +// Request completed, but clear form +static const int ResetContent = 205; +// Partial GET fulfilled +static const int PartialContent = 206; + +// Server couldn't decide what to return +static const int Ambiguous = 300; +// Object permanently moved +static const int Moved = 301; +// Object temporarily moved +static const int Redirect = 302; +// Redirection w/ new access method +static const int RedirectMethod = 303; +// If-Modified-Since was not modified +static const int NotModified = 304; +// Redirection to proxy, location header specifies proxy to use +static const int UseProxy = 305; +// HTTP/1.1: keep same verb +static const int RedirectKeepVerb = 307; + +// Invalid syntax +static const int BadRequest = 400; +// Access denied +static const int Denied = 401; +// Payment required +static const int PaymentRequired = 402; +// Request forbidden +static const int Forbidden = 403; +// Object not found +static const int NotFound = 404; +// Method is not allowed +static const int BadMethod = 405; +// No response acceptable to client found +static const int NoneAcceptable = 406; +// Proxy authentication required +static const int ProxyAuthRequired = 407; +// Server timed out waiting for request +static const int RequestTimeout = 408; +// User should resubmit with more info +static const int Conflict = 409; +// The resource is no longer available +static const int Gone = 410; +// The server refused to accept request w/o a length +static const int LengthRequired = 411; +// Precondition given in request failed +static const int PrecondionFailed = 412; +// Request entity was too large +static const int RequestTooLarge = 413; +// Request URI too long +static const int UriTooLong = 414; +// Unsupported media type +static const int UnsupportedMedia = 415; +// Retry after doing the appropriate action. +static const int RetryWith = 449; + +// Internal server error +static const int InternalServerError = 500; +// Request not supported +static const int NotSupported = 501; +// Error response received from gateway +static const int BadGateway = 502; +// Temporarily overloaded +static const int ServiceUnavailable = 503; +// Timed out waiting for gateway +static const int GatewayTimeout = 504; +// HTTP version not supported +static const int VersionNotSupported = 505; +} // namespace status_code + +class Response; // Just a forward declaration. +class FormData; + +/////////////////////////////////////////////////////////////////////////////// +// Request class is the main object used to set up and initiate an HTTP +// communication session. It is used to specify the HTTP request method, +// request URL and many optional parameters (such as HTTP headers, user agent, +// referer URL and so on. +// +// Once everything is setup, GetResponse() method is used to send the request +// and obtain the server response. The returned Response object can be +// used to inspect the response code, HTTP headers and/or response body. +/////////////////////////////////////////////////////////////////////////////// +class BRILLO_EXPORT Request final { + public: + // The main constructor. |url| specifies the remote host address/path + // to send the request to. |method| is the HTTP request verb and + // |transport| is the HTTP transport implementation for server communications. + Request(const std::string& url, + const std::string& method, + std::shared_ptr transport); + ~Request(); + + // Gets/Sets "Accept:" header value. The default value is "*/*" if not set. + void SetAccept(const std::string& accept_mime_types); + const std::string& GetAccept() const; + + // Gets/Sets "Content-Type:" header value + void SetContentType(const std::string& content_type); + const std::string& GetContentType() const; + + // Adds additional HTTP request header + void AddHeader(const std::string& header, const std::string& value); + void AddHeaders(const HeaderList& headers); + + // Removes HTTP request header + void RemoveHeader(const std::string& header); + + // Adds a request body. This is not to be used with GET method + bool AddRequestBody(const void* data, size_t size, brillo::ErrorPtr* error); + bool AddRequestBody(StreamPtr stream, brillo::ErrorPtr* error); + + // Adds a request body. This is not to be used with GET method. + // This method also sets the correct content-type of the request, including + // the multipart data boundary. + bool AddRequestBodyAsFormData(std::unique_ptr form_data, + brillo::ErrorPtr* error); + + // Adds a stream for the response. Otherwise a MemoryStream will be used. + bool AddResponseStream(StreamPtr stream, brillo::ErrorPtr* error); + + // Makes a request for a subrange of data. Specifies a partial range with + // either from beginning of the data to the specified offset (if |bytes| is + // negative) or from the specified offset to the end of data (if |bytes| is + // positive). + // All individual ranges will be sent as part of "Range:" HTTP request header. + void AddRange(int64_t bytes); + + // Makes a request for a subrange of data. Specifies a full range with + // start and end bytes from the beginning of the requested data. + // All individual ranges will be sent as part of "Range:" HTTP request header. + void AddRange(uint64_t from_byte, uint64_t to_byte); + + // Returns the request URL + const std::string& GetRequestURL() const; + + // Returns the request verb. + const std::string& GetRequestMethod() const; + + // Gets/Sets a request referer URL (sent as "Referer:" request header). + void SetReferer(const std::string& referer); + const std::string& GetReferer() const; + + // Gets/Sets a user agent string (sent as "User-Agent:" request header). + void SetUserAgent(const std::string& user_agent); + const std::string& GetUserAgent() const; + + // Sends the request to the server and blocks until the response is received, + // which is returned as the response object. + // In case the server couldn't be reached for whatever reason, returns + // empty unique_ptr (null). In such a case, the additional error information + // can be returned through the optional supplied |error| parameter. + std::unique_ptr GetResponseAndBlock(brillo::ErrorPtr* error); + + // Sends out the request and invokes the |success_callback| when the response + // is received. In case of an error, the |error_callback| is invoked. + // Returns the ID of the asynchronous request created. + RequestID GetResponse(const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + + private: + friend class HttpRequestTest; + + // Helper function to create an http::Connection and send off request headers. + BRILLO_PRIVATE bool SendRequestIfNeeded(brillo::ErrorPtr* error); + + // Implementation that provides particular HTTP transport. + std::shared_ptr transport_; + + // An established connection for adding request body. This connection + // is maintained by the request object after the headers have been + // sent and before the response is requested. + std::shared_ptr connection_; + + // Full request URL, such as "http://www.host.com/path/to/object" + const std::string request_url_; + // HTTP request verb, such as "GET", "POST", "PUT", ... + const std::string method_; + + // Referrer URL, if any. Sent to the server via "Referer: " header. + std::string referer_; + // User agent string, if any. Sent to the server via "User-Agent: " header. + std::string user_agent_; + // Content type of the request body data. + // Sent to the server via "Content-Type: " header. + std::string content_type_; + // List of acceptable response data types. + // Sent to the server via "Accept: " header. + std::string accept_ = "*/*"; + + // List of optional request headers provided by the caller. + std::multimap headers_; + // List of optional data ranges to request partial content from the server. + // Sent to the server as "Range: " header. + std::vector> ranges_; + + // range_value_omitted is used in |ranges_| list to indicate omitted value. + // E.g. range (10,range_value_omitted) represents bytes from 10 to the end + // of the data stream. + const uint64_t range_value_omitted = std::numeric_limits::max(); + + DISALLOW_COPY_AND_ASSIGN(Request); +}; + +/////////////////////////////////////////////////////////////////////////////// +// Response class is returned from Request::GetResponse() and is a way +// to get to response status, error codes, response HTTP headers and response +// data (body) if available. +/////////////////////////////////////////////////////////////////////////////// +class BRILLO_EXPORT Response final { + public: + explicit Response(const std::shared_ptr& connection); + ~Response(); + + // Returns true if server returned a success code (status code below 400). + bool IsSuccessful() const; + + // Returns the HTTP status code (e.g. 200 for success) + int GetStatusCode() const; + + // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED"). + std::string GetStatusText() const; + + // Returns the content type of the response data. + std::string GetContentType() const; + + // Returns response data stream by transferring ownership of the data stream + // from Response class to the caller. + StreamPtr ExtractDataStream(ErrorPtr* error); + + // Extracts the data from the underlying response data stream as a byte array. + std::vector ExtractData(); + + // Extracts the data from the underlying response data stream as a string. + std::string ExtractDataAsString(); + + // Returns a value of a given response HTTP header. + std::string GetHeader(const std::string& header_name) const; + + private: + friend class HttpRequestTest; + + std::shared_ptr connection_; + + DISALLOW_COPY_AND_ASSIGN(Response); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_REQUEST_H_ diff --git a/brillo/http/http_request_unittest.cc b/brillo/http/http_request_unittest.cc new file mode 100644 index 0000000..ab6c2a5 --- /dev/null +++ b/brillo/http/http_request_unittest.cc @@ -0,0 +1,202 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using testing::DoAll; +using testing::Invoke; +using testing::Return; +using testing::SetArgPointee; +using testing::Unused; +using testing::WithArg; +using testing::_; + +namespace brillo { +namespace http { + +MATCHER_P(ContainsStringData, str, "") { + if (arg->GetSize() != str.size()) + return false; + + std::string data; + char buf[100]; + size_t read = 0; + while (arg->ReadBlocking(buf, sizeof(buf), &read, nullptr) && read > 0) { + data.append(buf, read); + } + return data == str; +} + +class HttpRequestTest : public testing::Test { + public: + void SetUp() override { + transport_ = std::make_shared(); + connection_ = std::make_shared(transport_); + } + + void TearDown() override { + // Having shared pointers to mock objects (some of methods in these tests + // return shared pointers to connection and transport) could cause the + // test expectations to hold on to the mock object without releasing them + // at the end of a test, causing Mock's object leak detection to erroneously + // detect mock object "leaks". Verify and clear the expectations manually + // and explicitly to ensure the shared pointer refcounters are not + // preventing the mocks to be destroyed at the end of each test. + testing::Mock::VerifyAndClearExpectations(connection_.get()); + connection_.reset(); + testing::Mock::VerifyAndClearExpectations(transport_.get()); + transport_.reset(); + } + + protected: + std::shared_ptr transport_; + std::shared_ptr connection_; +}; + +TEST_F(HttpRequestTest, Defaults) { + Request request{"http://www.foo.bar", request_type::kPost, transport_}; + EXPECT_TRUE(request.GetContentType().empty()); + EXPECT_TRUE(request.GetReferer().empty()); + EXPECT_TRUE(request.GetUserAgent().empty()); + EXPECT_EQ("*/*", request.GetAccept()); + EXPECT_EQ("http://www.foo.bar", request.GetRequestURL()); + EXPECT_EQ(request_type::kPost, request.GetRequestMethod()); + + Request request2{"http://www.foo.bar/baz", request_type::kGet, transport_}; + EXPECT_EQ("http://www.foo.bar/baz", request2.GetRequestURL()); + EXPECT_EQ(request_type::kGet, request2.GetRequestMethod()); +} + +TEST_F(HttpRequestTest, ContentType) { + Request request{"http://www.foo.bar", request_type::kPost, transport_}; + request.SetContentType(mime::image::kJpeg); + EXPECT_EQ(mime::image::kJpeg, request.GetContentType()); +} + +TEST_F(HttpRequestTest, Referer) { + Request request{"http://www.foo.bar", request_type::kPost, transport_}; + request.SetReferer("http://www.foo.bar/baz"); + EXPECT_EQ("http://www.foo.bar/baz", request.GetReferer()); +} + +TEST_F(HttpRequestTest, UserAgent) { + Request request{"http://www.foo.bar", request_type::kPost, transport_}; + request.SetUserAgent("FooBar Browser"); + EXPECT_EQ("FooBar Browser", request.GetUserAgent()); +} + +TEST_F(HttpRequestTest, Accept) { + Request request{"http://www.foo.bar", request_type::kPost, transport_}; + request.SetAccept("text/*, text/html, text/html;level=1, */*"); + EXPECT_EQ("text/*, text/html, text/html;level=1, */*", request.GetAccept()); +} + +TEST_F(HttpRequestTest, GetResponseAndBlock) { + Request request{"http://www.foo.bar", request_type::kPost, transport_}; + request.SetUserAgent("FooBar Browser"); + request.SetReferer("http://www.foo.bar/baz"); + request.SetAccept("text/*, text/html, text/html;level=1, */*"); + request.AddHeader(request_header::kAcceptEncoding, "compress, gzip"); + request.AddHeaders({ + {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"}, + {request_header::kConnection, "close"}, + }); + request.AddRange(-10); + request.AddRange(100, 200); + request.AddRange(300); + std::string req_body{"Foo bar baz"}; + request.AddHeader(request_header::kContentType, mime::text::kPlain); + + EXPECT_CALL(*transport_, CreateConnection( + "http://www.foo.bar", + request_type::kPost, + HeaderList{ + {request_header::kAcceptEncoding, "compress, gzip"}, + {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"}, + {request_header::kConnection, "close"}, + {request_header::kContentType, mime::text::kPlain}, + {request_header::kRange, "bytes=-10,100-200,300-"}, + {request_header::kAccept, "text/*, text/html, text/html;level=1, */*"}, + }, + "FooBar Browser", + "http://www.foo.bar/baz", + nullptr)).WillOnce(Return(connection_)); + + EXPECT_CALL(*connection_, MockSetRequestData(ContainsStringData(req_body), _)) + .WillOnce(Return(true)); + + EXPECT_TRUE( + request.AddRequestBody(req_body.data(), req_body.size(), nullptr)); + + EXPECT_CALL(*connection_, FinishRequest(_)).WillOnce(Return(true)); + auto resp = request.GetResponseAndBlock(nullptr); + EXPECT_NE(nullptr, resp.get()); +} + +TEST_F(HttpRequestTest, GetResponse) { + Request request{"http://foo.bar", request_type::kGet, transport_}; + + std::string resp_data{"FooBar response body"}; + auto read_data = + [&resp_data](void* buffer, Unused, size_t* read, Unused) -> bool { + memcpy(buffer, resp_data.data(), resp_data.size()); + *read = resp_data.size(); + return true; + }; + + auto success_callback = + [this, &resp_data](RequestID request_id, std::unique_ptr resp) { + EXPECT_EQ(23, request_id); + EXPECT_CALL(*connection_, GetResponseStatusCode()) + .WillOnce(Return(status_code::Partial)); + EXPECT_EQ(status_code::Partial, resp->GetStatusCode()); + + EXPECT_CALL(*connection_, GetResponseStatusText()) + .WillOnce(Return("Partial completion")); + EXPECT_EQ("Partial completion", resp->GetStatusText()); + + EXPECT_CALL(*connection_, GetResponseHeader(response_header::kContentType)) + .WillOnce(Return(mime::text::kHtml)); + EXPECT_EQ(mime::text::kHtml, resp->GetContentType()); + + EXPECT_EQ(resp_data, resp->ExtractDataAsString()); + }; + + auto finish_request_async = + [this, &read_data, &resp_data](const SuccessCallback& success_callback) { + std::unique_ptr mock_stream{new MockStream}; + EXPECT_CALL(*mock_stream, ReadBlocking(_, _, _, _)) + .WillOnce(Invoke(read_data)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + + EXPECT_CALL(*connection_, MockExtractDataStream(_)) + .WillOnce(Return(mock_stream.release())); + std::unique_ptr resp{new Response{connection_}}; + success_callback.Run(23, std::move(resp)); + }; + + EXPECT_CALL( + *transport_, + CreateConnection("http://foo.bar", request_type::kGet, _, "", "", _)) + .WillOnce(Return(connection_)); + + EXPECT_CALL(*connection_, FinishRequestAsync(_, _)) + .WillOnce(DoAll(WithArg<0>(Invoke(finish_request_async)), Return(23))); + + EXPECT_EQ(23, request.GetResponse(base::Bind(success_callback), {})); +} + +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_transport.cc b/brillo/http/http_transport.cc new file mode 100644 index 0000000..d77eabe --- /dev/null +++ b/brillo/http/http_transport.cc @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { +namespace http { + +const char kErrorDomain[] = "http_transport"; + +std::shared_ptr Transport::CreateDefault() { + return std::make_shared(std::make_shared()); +} + +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_transport.h b/brillo/http/http_transport.h new file mode 100644 index 0000000..5b56b5c --- /dev/null +++ b/brillo/http/http_transport.h @@ -0,0 +1,95 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { + +BRILLO_EXPORT extern const char kErrorDomain[]; + +class Request; +class Response; +class Connection; + +using RequestID = int; + +using HeaderList = std::vector>; +using SuccessCallback = + base::Callback)>; +using ErrorCallback = base::Callback; + +/////////////////////////////////////////////////////////////////////////////// +// Transport is a base class for specific implementation of HTTP communication. +// This class (and its underlying implementation) is used by http::Request and +// http::Response classes to provide HTTP functionality to the clients. +/////////////////////////////////////////////////////////////////////////////// +class BRILLO_EXPORT Transport : public std::enable_shared_from_this { + public: + Transport() = default; + virtual ~Transport() = default; + + // Creates a connection object and initializes it with the specified data. + // |transport| is a shared pointer to this transport object instance, + // used to maintain the object alive as long as the connection exists. + // The |url| here is the full URL specified in the request. It is passed + // to the underlying transport (e.g. CURL) to establish the connection. + virtual std::shared_ptr CreateConnection( + const std::string& url, + const std::string& method, + const HeaderList& headers, + const std::string& user_agent, + const std::string& referer, + brillo::ErrorPtr* error) = 0; + + // Runs |callback| on the task runner (message loop) associated with the + // transport. For transports that do not contain references to real message + // loops (e.g. a fake transport), calls the callback immediately. + virtual void RunCallbackAsync(const tracked_objects::Location& from_here, + const base::Closure& callback) = 0; + + // Initiates an asynchronous transfer on the given |connection|. + // The actual implementation of an async I/O is transport-specific. + // Returns a request ID which can be used to cancel the request. + virtual RequestID StartAsyncTransfer( + Connection* connection, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) = 0; + + // Cancels a pending asynchronous request. This will cancel a pending request + // scheduled by the transport while the I/O operations are still in progress. + // As soon as all I/O completes for the request/response, or when an error + // occurs, the success/error callbacks are invoked and the request is + // considered complete and can no longer be canceled. + // Returns false if pending request with |request_id| is not found (e.g. it + // has already completed/its callbacks are dispatched). + virtual bool CancelRequest(RequestID request_id) = 0; + + // Set the default timeout of requests made. + virtual void SetDefaultTimeout(base::TimeDelta timeout) = 0; + + // Creates a default http::Transport (currently, using http::curl::Transport). + static std::shared_ptr CreateDefault(); + + private: + DISALLOW_COPY_AND_ASSIGN(Transport); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_H_ diff --git a/brillo/http/http_transport_curl.cc b/brillo/http/http_transport_curl.cc new file mode 100644 index 0000000..048429e --- /dev/null +++ b/brillo/http/http_transport_curl.cc @@ -0,0 +1,513 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +const char kCACertificatePath[] = +#ifdef __ANDROID__ + "/system/etc/security/cacerts"; +#else + "/usr/share/brillo-ca-certificates"; +#endif + +} // namespace + +namespace brillo { +namespace http { +namespace curl { + +// This is a class that stores connection data on particular CURL socket +// and provides file descriptor watcher to monitor read and/or write operations +// on the socket's file descriptor. +class Transport::SocketPollData : public base::MessageLoopForIO::Watcher { + public: + SocketPollData(const std::shared_ptr& curl_interface, + CURLM* curl_multi_handle, + Transport* transport, + curl_socket_t socket_fd) + : curl_interface_(curl_interface), + curl_multi_handle_(curl_multi_handle), + transport_(transport), + socket_fd_(socket_fd) {} + + // Returns the pointer for the socket-specific file descriptor watcher. + base::MessageLoopForIO::FileDescriptorWatcher* GetWatcher() { + return &file_descriptor_watcher_; + } + + private: + // Overrides from base::MessageLoopForIO::Watcher. + void OnFileCanReadWithoutBlocking(int fd) override { + OnSocketReady(fd, CURL_CSELECT_IN); + } + void OnFileCanWriteWithoutBlocking(int fd) override { + OnSocketReady(fd, CURL_CSELECT_OUT); + } + + // Data on the socket is available to be read from or written to. + // Notify CURL of the action it needs to take on the socket file descriptor. + void OnSocketReady(int fd, int action) { + CHECK_EQ(socket_fd_, fd) << "Unexpected socket file descriptor"; + int still_running_count = 0; + CURLMcode code = curl_interface_->MultiSocketAction( + curl_multi_handle_, socket_fd_, action, &still_running_count); + CHECK_NE(CURLM_CALL_MULTI_PERFORM, code) + << "CURL should no longer return CURLM_CALL_MULTI_PERFORM here"; + + if (code == CURLM_OK) + transport_->ProcessAsyncCurlMessages(); + } + + // The CURL interface to use. + std::shared_ptr curl_interface_; + // CURL multi-handle associated with the transport. + CURLM* curl_multi_handle_; + // Transport object itself. + Transport* transport_; + // The socket file descriptor for the connection. + curl_socket_t socket_fd_; + // File descriptor watcher to notify us of asynchronous I/O on the FD. + base::MessageLoopForIO::FileDescriptorWatcher file_descriptor_watcher_; + + DISALLOW_COPY_AND_ASSIGN(SocketPollData); +}; + +// The request data associated with an asynchronous operation on a particular +// connection. +struct Transport::AsyncRequestData { + // Success/error callbacks to be invoked at the end of the request. + SuccessCallback success_callback; + ErrorCallback error_callback; + // We store a connection here to make sure the object is alive for + // as long as asynchronous operation is running. + std::shared_ptr connection; + // The ID of this request. + RequestID request_id; +}; + +Transport::Transport(const std::shared_ptr& curl_interface) + : curl_interface_{curl_interface} { + VLOG(2) << "curl::Transport created"; +} + +Transport::Transport(const std::shared_ptr& curl_interface, + const std::string& proxy) + : curl_interface_{curl_interface}, proxy_{proxy} { + VLOG(2) << "curl::Transport created with proxy " << proxy; +} + +Transport::~Transport() { + ShutDownAsyncCurl(); + VLOG(2) << "curl::Transport destroyed"; +} + +std::shared_ptr Transport::CreateConnection( + const std::string& url, + const std::string& method, + const HeaderList& headers, + const std::string& user_agent, + const std::string& referer, + brillo::ErrorPtr* error) { + std::shared_ptr connection; + CURL* curl_handle = curl_interface_->EasyInit(); + if (!curl_handle) { + LOG(ERROR) << "Failed to initialize CURL"; + brillo::Error::AddTo(error, FROM_HERE, http::kErrorDomain, + "curl_init_failed", "Failed to initialize CURL"); + return connection; + } + + LOG(INFO) << "Sending a " << method << " request to " << url; + CURLcode code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_URL, url); + + if (code == CURLE_OK) { + code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_CAPATH, + kCACertificatePath); + } + if (code == CURLE_OK) { + code = + curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1); + } + if (code == CURLE_OK) { + code = + curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2); + } + if (code == CURLE_OK && !user_agent.empty()) { + code = curl_interface_->EasySetOptStr( + curl_handle, CURLOPT_USERAGENT, user_agent); + } + if (code == CURLE_OK && !referer.empty()) { + code = + curl_interface_->EasySetOptStr(curl_handle, CURLOPT_REFERER, referer); + } + if (code == CURLE_OK && !proxy_.empty()) { + code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_PROXY, proxy_); + } + if (code == CURLE_OK) { + int64_t timeout_ms = connection_timeout_.InMillisecondsRoundedUp(); + + if (timeout_ms > 0 && timeout_ms <= std::numeric_limits::max()) { + code = curl_interface_->EasySetOptInt( + curl_handle, CURLOPT_TIMEOUT_MS, + static_cast(timeout_ms)); + } + } + + // Setup HTTP request method and optional request body. + if (code == CURLE_OK) { + if (method == request_type::kGet) { + code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_HTTPGET, 1); + } else if (method == request_type::kHead) { + code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_NOBODY, 1); + } else if (method == request_type::kPut) { + code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_UPLOAD, 1); + } else { + // POST and custom request methods + code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_POST, 1); + if (code == CURLE_OK) { + code = curl_interface_->EasySetOptPtr( + curl_handle, CURLOPT_POSTFIELDS, nullptr); + } + if (code == CURLE_OK && method != request_type::kPost) { + code = curl_interface_->EasySetOptStr( + curl_handle, CURLOPT_CUSTOMREQUEST, method); + } + } + } + + if (code != CURLE_OK) { + AddEasyCurlError(error, FROM_HERE, code, curl_interface_.get()); + curl_interface_->EasyCleanup(curl_handle); + return connection; + } + + connection = std::make_shared( + curl_handle, method, curl_interface_, shared_from_this()); + if (!connection->SendHeaders(headers, error)) { + connection.reset(); + } + return connection; +} + +void Transport::RunCallbackAsync(const tracked_objects::Location& from_here, + const base::Closure& callback) { + base::MessageLoopForIO::current()->PostTask(from_here, callback); +} + +RequestID Transport::StartAsyncTransfer(http::Connection* connection, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + brillo::ErrorPtr error; + if (!SetupAsyncCurl(&error)) { + RunCallbackAsync( + FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); + return 0; + } + + RequestID request_id = ++last_request_id_; + + auto curl_connection = static_cast(connection); + std::unique_ptr request_data{new AsyncRequestData}; + // Add the request data to |async_requests_| before adding the CURL handle + // in case CURL feels like calling the socket callback synchronously which + // will need the data to be in |async_requests_| map already. + request_data->success_callback = success_callback; + request_data->error_callback = error_callback; + request_data->connection = + std::static_pointer_cast(curl_connection->shared_from_this()); + request_data->request_id = request_id; + async_requests_.emplace(curl_connection, std::move(request_data)); + request_id_map_.emplace(request_id, curl_connection); + + // Add the connection's CURL handle to the multi-handle. + CURLMcode code = curl_interface_->MultiAddHandle( + curl_multi_handle_, curl_connection->curl_handle_); + if (code != CURLM_OK) { + brillo::ErrorPtr error; + AddMultiCurlError(&error, FROM_HERE, code, curl_interface_.get()); + RunCallbackAsync( + FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); + async_requests_.erase(curl_connection); + request_id_map_.erase(request_id); + return 0; + } + LOG(INFO) << "Started asynchronous HTTP request with ID " << request_id; + return request_id; +} + +bool Transport::CancelRequest(RequestID request_id) { + auto p = request_id_map_.find(request_id); + if (p == request_id_map_.end()) { + // The request must have been completed already... + // This is not necessarily an error condition, so fail gracefully. + LOG(WARNING) << "HTTP request #" << request_id << " not found"; + return false; + } + LOG(INFO) << "Canceling HTTP request #" << request_id; + CleanAsyncConnection(p->second); + return true; +} + +void Transport::SetDefaultTimeout(base::TimeDelta timeout) { + connection_timeout_ = timeout; +} + +void Transport::AddEasyCurlError(brillo::ErrorPtr* error, + const tracked_objects::Location& location, + CURLcode code, + CurlInterface* curl_interface) { + brillo::Error::AddTo(error, location, "curl_easy_error", + brillo::string_utils::ToString(code), + curl_interface->EasyStrError(code)); +} + +void Transport::AddMultiCurlError(brillo::ErrorPtr* error, + const tracked_objects::Location& location, + CURLMcode code, + CurlInterface* curl_interface) { + brillo::Error::AddTo(error, location, "curl_multi_error", + brillo::string_utils::ToString(code), + curl_interface->MultiStrError(code)); +} + +bool Transport::SetupAsyncCurl(brillo::ErrorPtr* error) { + if (curl_multi_handle_) + return true; + + curl_multi_handle_ = curl_interface_->MultiInit(); + if (!curl_multi_handle_) { + LOG(ERROR) << "Failed to initialize CURL"; + brillo::Error::AddTo(error, FROM_HERE, http::kErrorDomain, + "curl_init_failed", "Failed to initialize CURL"); + return false; + } + + CURLMcode code = curl_interface_->MultiSetSocketCallback( + curl_multi_handle_, &Transport::MultiSocketCallback, this); + if (code == CURLM_OK) { + code = curl_interface_->MultiSetTimerCallback( + curl_multi_handle_, &Transport::MultiTimerCallback, this); + } + if (code != CURLM_OK) { + AddMultiCurlError(error, FROM_HERE, code, curl_interface_.get()); + return false; + } + return true; +} + +void Transport::ShutDownAsyncCurl() { + if (!curl_multi_handle_) + return; + LOG_IF(WARNING, !poll_data_map_.empty()) + << "There are pending requests at the time of transport's shutdown"; + // Make sure we are not leaking any memory here. + for (const auto& pair : poll_data_map_) + delete pair.second; + poll_data_map_.clear(); + curl_interface_->MultiCleanup(curl_multi_handle_); + curl_multi_handle_ = nullptr; +} + +int Transport::MultiSocketCallback(CURL* easy, + curl_socket_t s, + int what, + void* userp, + void* socketp) { + auto transport = static_cast(userp); + CHECK(transport) << "Transport must be set for this callback"; + auto poll_data = static_cast(socketp); + if (!poll_data) { + // We haven't attached polling data to this socket yet. Let's do this now. + poll_data = new SocketPollData{transport->curl_interface_, + transport->curl_multi_handle_, + transport, + s}; + transport->poll_data_map_.emplace(std::make_pair(easy, s), poll_data); + transport->curl_interface_->MultiAssign( + transport->curl_multi_handle_, s, poll_data); + } + + if (what == CURL_POLL_NONE) { + return 0; + } else if (what == CURL_POLL_REMOVE) { + // Remove the attached data from the socket. + transport->curl_interface_->MultiAssign( + transport->curl_multi_handle_, s, nullptr); + transport->poll_data_map_.erase(std::make_pair(easy, s)); + + // Make sure we stop watching the socket file descriptor now, before + // we schedule the SocketPollData for deletion. + poll_data->GetWatcher()->StopWatchingFileDescriptor(); + // This method can be called indirectly from SocketPollData::OnSocketReady, + // so delay destruction of SocketPollData object till the next loop cycle. + base::MessageLoopForIO::current()->DeleteSoon(FROM_HERE, poll_data); + return 0; + } + + base::MessageLoopForIO::Mode watch_mode = base::MessageLoopForIO::WATCH_READ; + switch (what) { + case CURL_POLL_IN: + watch_mode = base::MessageLoopForIO::WATCH_READ; + break; + case CURL_POLL_OUT: + watch_mode = base::MessageLoopForIO::WATCH_WRITE; + break; + case CURL_POLL_INOUT: + watch_mode = base::MessageLoopForIO::WATCH_READ_WRITE; + break; + default: + LOG(FATAL) << "Unknown CURL socket action: " << what; + break; + } + + // WatchFileDescriptor() can be called with the same controller object + // (watcher) to amend the watch mode, however this has cumulative effect. + // For example, if we were watching a file descriptor for READ operations + // and now call it to watch for WRITE, it will end up watching for both + // READ and WRITE. This is not what we want here, so stop watching the + // file descriptor on previous controller before starting with a different + // mode. + if (!poll_data->GetWatcher()->StopWatchingFileDescriptor()) + LOG(WARNING) << "Failed to stop watching the previous socket descriptor"; + CHECK(base::MessageLoopForIO::current()->WatchFileDescriptor( + s, true, watch_mode, poll_data->GetWatcher(), poll_data)) + << "Failed to watch the CURL socket."; + return 0; +} + +// CURL actually uses "long" types in callback signatures, so we must comply. +int Transport::MultiTimerCallback(CURLM* multi, + long timeout_ms, // NOLINT(runtime/int) + void* userp) { + auto transport = static_cast(userp); + // Cancel any previous timer callbacks. + transport->weak_ptr_factory_for_timer_.InvalidateWeakPtrs(); + if (timeout_ms >= 0) { + base::MessageLoopForIO::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&Transport::OnTimer, + transport->weak_ptr_factory_for_timer_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(timeout_ms)); + } + return 0; +} + +void Transport::OnTimer() { + if (curl_multi_handle_) { + int still_running_count = 0; + curl_interface_->MultiSocketAction( + curl_multi_handle_, CURL_SOCKET_TIMEOUT, 0, &still_running_count); + ProcessAsyncCurlMessages(); + } +} + +void Transport::ProcessAsyncCurlMessages() { + CURLMsg* msg = nullptr; + int msgs_left = 0; + while ((msg = curl_interface_->MultiInfoRead(curl_multi_handle_, + &msgs_left))) { + if (msg->msg == CURLMSG_DONE) { + // Async I/O complete for a connection. Invoke the user callbacks. + Connection* connection = nullptr; + CHECK_EQ(CURLE_OK, + curl_interface_->EasyGetInfoPtr( + msg->easy_handle, + CURLINFO_PRIVATE, + reinterpret_cast(&connection))); + CHECK(connection != nullptr); + OnTransferComplete(connection, msg->data.result); + } + } +} + +void Transport::OnTransferComplete(Connection* connection, CURLcode code) { + auto p = async_requests_.find(connection); + CHECK(p != async_requests_.end()) << "Unknown connection"; + AsyncRequestData* request_data = p->second.get(); + LOG(INFO) << "HTTP request # " << request_data->request_id + << " has completed " + << (code == CURLE_OK ? "successfully" : "with an error"); + if (code != CURLE_OK) { + brillo::ErrorPtr error; + AddEasyCurlError(&error, FROM_HERE, code, curl_interface_.get()); + RunCallbackAsync(FROM_HERE, + base::Bind(request_data->error_callback, + p->second->request_id, + base::Owned(error.release()))); + } else { + LOG(INFO) << "Response: " << connection->GetResponseStatusCode() << " (" + << connection->GetResponseStatusText() << ")"; + brillo::ErrorPtr error; + // Rewind the response data stream to the beginning so the clients can + // read the data back. + const auto& stream = request_data->connection->response_data_stream_; + if (stream && stream->CanSeek() && !stream->SetPosition(0, &error)) { + RunCallbackAsync(FROM_HERE, + base::Bind(request_data->error_callback, + p->second->request_id, + base::Owned(error.release()))); + } else { + std::unique_ptr resp{new Response{request_data->connection}}; + RunCallbackAsync(FROM_HERE, + base::Bind(request_data->success_callback, + p->second->request_id, + base::Passed(&resp))); + } + } + // In case of an error on CURL side, we would have dispatched the error + // callback and we need to clean up the current connection, however the + // error callback has no reference to the connection itself and + // |async_requests_| is the only reference to the shared pointer that + // maintains the lifetime of |connection| and possibly even this Transport + // object instance. As a result, if we call CleanAsyncConnection() directly, + // there is a chance that this object might be deleted. + // Instead, schedule an asynchronous task to clean up the connection. + RunCallbackAsync(FROM_HERE, + base::Bind(&Transport::CleanAsyncConnection, + weak_ptr_factory_.GetWeakPtr(), + connection)); +} + +void Transport::CleanAsyncConnection(Connection* connection) { + auto p = async_requests_.find(connection); + CHECK(p != async_requests_.end()) << "Unknown connection"; + // Remove the request data from the map first, since this might be the only + // reference to the Connection class and even possibly to this Transport. + auto request_data = std::move(p->second); + + // Remove associated request ID. + request_id_map_.erase(request_data->request_id); + + // Remove the connection's CURL handle from multi-handle. + curl_interface_->MultiRemoveHandle(curl_multi_handle_, + connection->curl_handle_); + + // Remove all the socket data associated with this connection. + auto iter = poll_data_map_.begin(); + while (iter != poll_data_map_.end()) { + if (iter->first.first == connection->curl_handle_) + iter = poll_data_map_.erase(iter); + else + ++iter; + } + // Remove pending asynchronous request data. + // This must be last since there is a chance of this object being + // destroyed as the result. See the comment in Transport::OnTransferComplete. + async_requests_.erase(p); +} + +} // namespace curl +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_transport_curl.h b/brillo/http/http_transport_curl.h new file mode 100644 index 0000000..e07f56f --- /dev/null +++ b/brillo/http/http_transport_curl.h @@ -0,0 +1,140 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_ + +#include +#include +#include + +#include +#include +#include +#include + +namespace brillo { +namespace http { +namespace curl { + +class Connection; + +/////////////////////////////////////////////////////////////////////////////// +// An implementation of http::Transport that uses libcurl for +// HTTP communications. This class (as http::Transport base) +// is used by http::Request and http::Response classes to provide HTTP +// functionality to the clients. +// See http_transport.h for more details. +/////////////////////////////////////////////////////////////////////////////// +class BRILLO_EXPORT Transport : public http::Transport { + public: + // Constructs the transport using the current message loop for async + // operations. + explicit Transport(const std::shared_ptr& curl_interface); + // Creates a transport object using a proxy. + // |proxy| is of the form [protocol://][user:password@]host[:port]. + // If not defined, protocol is assumed to be http://. + Transport(const std::shared_ptr& curl_interface, + const std::string& proxy); + ~Transport() override; + + // Overrides from http::Transport. + std::shared_ptr CreateConnection( + const std::string& url, + const std::string& method, + const HeaderList& headers, + const std::string& user_agent, + const std::string& referer, + brillo::ErrorPtr* error) override; + + void RunCallbackAsync(const tracked_objects::Location& from_here, + const base::Closure& callback) override; + + RequestID StartAsyncTransfer(http::Connection* connection, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) override; + + bool CancelRequest(RequestID request_id) override; + + void SetDefaultTimeout(base::TimeDelta timeout) override; + + // Helper methods to convert CURL error codes (CURLcode and CURLMcode) + // into brillo::Error object. + static void AddEasyCurlError(brillo::ErrorPtr* error, + const tracked_objects::Location& location, + CURLcode code, + CurlInterface* curl_interface); + + static void AddMultiCurlError(brillo::ErrorPtr* error, + const tracked_objects::Location& location, + CURLMcode code, + CurlInterface* curl_interface); + + private: + // Forward-declaration of internal implementation structures. + struct AsyncRequestData; + class SocketPollData; + + // Initializes CURL for async operation. + bool SetupAsyncCurl(brillo::ErrorPtr* error); + + // Stops CURL's async operations. + void ShutDownAsyncCurl(); + + // Handles all pending async messages from CURL. + void ProcessAsyncCurlMessages(); + + // Processes the transfer completion message (success or failure). + void OnTransferComplete(http::curl::Connection* connection, + CURLcode code); + + // Cleans up internal data for a completed/canceled asynchronous operation + // on a connection. + void CleanAsyncConnection(http::curl::Connection* connection); + + // Called after a timeout delay requested by CURL has elapsed. + void OnTimer(); + + // Callback for CURL to handle curl_socket_callback() notifications. + // The parameters correspond to those of curl_socket_callback(). + static int MultiSocketCallback(CURL* easy, + curl_socket_t s, + int what, + void* userp, + void* socketp); + + // Callback for CURL to handle curl_multi_timer_callback() notifications. + // The parameters correspond to those of curl_multi_timer_callback(). + // CURL actually uses "long" types in callback signatures, so we must comply. + static int MultiTimerCallback(CURLM* multi, + long timeout_ms, // NOLINT(runtime/int) + void* userp); + + std::shared_ptr curl_interface_; + std::string proxy_; + // CURL "multi"-handle for processing requests on multiple connections. + CURLM* curl_multi_handle_{nullptr}; + // A map to find a corresponding Connection* using a request ID. + std::map request_id_map_; + // Stores the connection-specific asynchronous data (such as the success + // and error callbacks that need to be called at the end of the async + // operation). + std::map> async_requests_; + // Internal data associated with in-progress asynchronous operations. + std::map, SocketPollData*> poll_data_map_; + // The last request ID used for asynchronous operations. + RequestID last_request_id_{0}; + // The connection timeout for the requests made. + base::TimeDelta connection_timeout_; + + base::WeakPtrFactory weak_ptr_factory_for_timer_{this}; + base::WeakPtrFactory weak_ptr_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(Transport); +}; + +} // namespace curl +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_ diff --git a/brillo/http/http_transport_curl_unittest.cc b/brillo/http/http_transport_curl_unittest.cc new file mode 100644 index 0000000..131c066 --- /dev/null +++ b/brillo/http/http_transport_curl_unittest.cc @@ -0,0 +1,305 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using testing::DoAll; +using testing::Invoke; +using testing::Return; +using testing::SaveArg; +using testing::SetArgPointee; +using testing::WithoutArgs; +using testing::_; + +namespace brillo { +namespace http { +namespace curl { + +class HttpCurlTransportTest : public testing::Test { + public: + void SetUp() override { + curl_api_ = std::make_shared(); + transport_ = std::make_shared(curl_api_); + handle_ = reinterpret_cast(100); // Mock handle value. + EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_)); + EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _)) + .WillRepeatedly(Return(CURLE_OK)); + } + + void TearDown() override { + transport_.reset(); + curl_api_.reset(); + } + + protected: + std::shared_ptr curl_api_; + std::shared_ptr transport_; + CURL* handle_{nullptr}; +}; + +TEST_F(HttpCurlTransportTest, RequestGet) { + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_USERAGENT, "User Agent")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_REFERER, "http://foo.bar/baz")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) + .WillOnce(Return(CURLE_OK)); + auto connection = transport_->CreateConnection("http://foo.bar/get", + request_type::kGet, + {}, + "User Agent", + "http://foo.bar/baz", + nullptr); + EXPECT_NE(nullptr, connection.get()); + + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection.reset(); +} + +TEST_F(HttpCurlTransportTest, RequestHead) { + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/head")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_NOBODY, 1)) + .WillOnce(Return(CURLE_OK)); + auto connection = transport_->CreateConnection( + "http://foo.bar/head", request_type::kHead, {}, "", "", nullptr); + EXPECT_NE(nullptr, connection.get()); + + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection.reset(); +} + +TEST_F(HttpCurlTransportTest, RequestPut) { + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/put")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_UPLOAD, 1)) + .WillOnce(Return(CURLE_OK)); + auto connection = transport_->CreateConnection( + "http://foo.bar/put", request_type::kPut, {}, "", "", nullptr); + EXPECT_NE(nullptr, connection.get()); + + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection.reset(); +} + +TEST_F(HttpCurlTransportTest, RequestPost) { + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/post")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr)) + .WillOnce(Return(CURLE_OK)); + auto connection = transport_->CreateConnection( + "http://www.foo.bar/post", request_type::kPost, {}, "", "", nullptr); + EXPECT_NE(nullptr, connection.get()); + + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection.reset(); +} + +TEST_F(HttpCurlTransportTest, RequestPatch) { + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/patch")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL( + *curl_api_, + EasySetOptStr(handle_, CURLOPT_CUSTOMREQUEST, request_type::kPatch)) + .WillOnce(Return(CURLE_OK)); + auto connection = transport_->CreateConnection( + "http://www.foo.bar/patch", request_type::kPatch, {}, "", "", nullptr); + EXPECT_NE(nullptr, connection.get()); + + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection.reset(); +} + +TEST_F(HttpCurlTransportTest, CurlFailure) { + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) + .WillOnce(Return(CURLE_OUT_OF_MEMORY)); + EXPECT_CALL(*curl_api_, EasyStrError(CURLE_OUT_OF_MEMORY)) + .WillOnce(Return("Out of Memory")); + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + ErrorPtr error; + auto connection = transport_->CreateConnection( + "http://foo.bar/get", request_type::kGet, {}, "", "", &error); + + EXPECT_EQ(nullptr, connection.get()); + EXPECT_EQ("curl_easy_error", error->GetDomain()); + EXPECT_EQ(std::to_string(CURLE_OUT_OF_MEMORY), error->GetCode()); + EXPECT_EQ("Out of Memory", error->GetMessage()); +} + +class HttpCurlTransportAsyncTest : public testing::Test { + public: + void SetUp() override { + curl_api_ = std::make_shared(); + transport_ = std::make_shared(curl_api_); + EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_)); + EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _)) + .WillOnce(Return(CURLE_OK)); + } + + protected: + std::shared_ptr curl_api_; + std::shared_ptr transport_; + CURL* handle_{reinterpret_cast(123)}; // Mock handle value. + CURLM* multi_handle_{reinterpret_cast(456)}; // Mock handle value. + curl_socket_t dummy_socket_{789}; +}; + +TEST_F(HttpCurlTransportAsyncTest, StartAsyncTransfer) { + // This test is a bit tricky because it deals with asynchronous I/O which + // relies on a message loop to run all the async tasks. + // For this, create a temporary I/O message loop and run it ourselves for the + // duration of the test. + base::MessageLoopForIO message_loop; + base::RunLoop run_loop; + + // Initial expectations for creating a CURL connection. + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) + .WillOnce(Return(CURLE_OK)); + auto connection = transport_->CreateConnection( + "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr); + ASSERT_NE(nullptr, connection.get()); + + // Success/error callback needed to report the result of an async operation. + int success_call_count = 0; + auto success_callback = [&success_call_count, &run_loop]( + RequestID request_id, std::unique_ptr resp) { + base::MessageLoop::current()->PostTask(FROM_HERE, run_loop.QuitClosure()); + success_call_count++; + }; + + auto error_callback = [](RequestID request_id, const Error* error) { + FAIL() << "This callback shouldn't have been called"; + }; + + EXPECT_CALL(*curl_api_, MultiInit()).WillOnce(Return(multi_handle_)); + EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _)) + .WillRepeatedly(DoAll(SetArgPointee<2>(200), Return(CURLE_OK))); + + curl_socket_callback socket_callback = nullptr; + EXPECT_CALL(*curl_api_, + MultiSetSocketCallback(multi_handle_, _, transport_.get())) + .WillOnce(DoAll(SaveArg<1>(&socket_callback), Return(CURLM_OK))); + + curl_multi_timer_callback timer_callback = nullptr; + EXPECT_CALL(*curl_api_, + MultiSetTimerCallback(multi_handle_, _, transport_.get())) + .WillOnce(DoAll(SaveArg<1>(&timer_callback), Return(CURLM_OK))); + + EXPECT_CALL(*curl_api_, MultiAddHandle(multi_handle_, handle_)) + .WillOnce(Return(CURLM_OK)); + + EXPECT_EQ(1, transport_->StartAsyncTransfer(connection.get(), + base::Bind(success_callback), + base::Bind(error_callback))); + EXPECT_EQ(0, success_call_count); + + timer_callback(multi_handle_, 1, transport_.get()); + + auto do_socket_action = [&socket_callback, this] { + EXPECT_CALL(*curl_api_, MultiAssign(multi_handle_, dummy_socket_, _)) + .Times(2).WillRepeatedly(Return(CURLM_OK)); + EXPECT_EQ(0, socket_callback(handle_, dummy_socket_, CURL_POLL_REMOVE, + transport_.get(), nullptr)); + }; + + EXPECT_CALL(*curl_api_, + MultiSocketAction(multi_handle_, CURL_SOCKET_TIMEOUT, 0, _)) + .WillOnce(DoAll(SetArgPointee<3>(1), + WithoutArgs(Invoke(do_socket_action)), + Return(CURLM_OK))) + .WillRepeatedly(DoAll(SetArgPointee<3>(0), Return(CURLM_OK))); + + CURLMsg msg = {}; + msg.msg = CURLMSG_DONE; + msg.easy_handle = handle_; + msg.data.result = CURLE_OK; + + EXPECT_CALL(*curl_api_, MultiInfoRead(multi_handle_, _)) + .WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg))) + .WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(nullptr))); + EXPECT_CALL(*curl_api_, EasyGetInfoPtr(handle_, CURLINFO_PRIVATE, _)) + .WillRepeatedly(DoAll(SetArgPointee<2>(connection.get()), + Return(CURLE_OK))); + + EXPECT_CALL(*curl_api_, MultiRemoveHandle(multi_handle_, handle_)) + .WillOnce(Return(CURLM_OK)); + + // Just in case something goes wrong and |success_callback| isn't called, + // post a time-out quit closure to abort the message loop after 1 second. + message_loop.PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(1)); + run_loop.Run(); + EXPECT_EQ(1, success_call_count); + + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection.reset(); + + EXPECT_CALL(*curl_api_, MultiCleanup(multi_handle_)) + .WillOnce(Return(CURLM_OK)); + transport_.reset(); +} + +TEST_F(HttpCurlTransportTest, RequestGetTimeout) { + transport_->SetDefaultTimeout(base::TimeDelta::FromMilliseconds(2000)); + EXPECT_CALL(*curl_api_, + EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_TIMEOUT_MS, 2000)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) + .WillOnce(Return(CURLE_OK)); + auto connection = transport_->CreateConnection( + "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr); + EXPECT_NE(nullptr, connection.get()); + + EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); + connection.reset(); +} + +} // namespace curl +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_transport_fake.cc b/brillo/http/http_transport_fake.cc new file mode 100644 index 0000000..3833449 --- /dev/null +++ b/brillo/http/http_transport_fake.cc @@ -0,0 +1,332 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { + +using http::fake::Transport; +using http::fake::ServerRequestResponseBase; +using http::fake::ServerRequest; +using http::fake::ServerResponse; + +Transport::Transport() { + VLOG(1) << "fake::Transport created"; +} + +Transport::~Transport() { + VLOG(1) << "fake::Transport destroyed"; +} + +std::shared_ptr Transport::CreateConnection( + const std::string& url, + const std::string& method, + const HeaderList& headers, + const std::string& user_agent, + const std::string& referer, + brillo::ErrorPtr* error) { + std::shared_ptr connection; + if (create_connection_error_) { + if (error) + *error = std::move(create_connection_error_); + return connection; + } + HeaderList headers_copy = headers; + if (!user_agent.empty()) { + headers_copy.push_back( + std::make_pair(http::request_header::kUserAgent, user_agent)); + } + if (!referer.empty()) { + headers_copy.push_back( + std::make_pair(http::request_header::kReferer, referer)); + } + connection = + std::make_shared(url, method, shared_from_this()); + CHECK(connection) << "Unable to create Connection object"; + if (!connection->SendHeaders(headers_copy, error)) + connection.reset(); + request_count_++; + return connection; +} + +void Transport::RunCallbackAsync(const tracked_objects::Location& from_here, + const base::Closure& callback) { + if (!async_) { + callback.Run(); + return; + } + async_callback_queue_.push(callback); +} + +bool Transport::HandleOneAsyncRequest() { + if (async_callback_queue_.empty()) + return false; + + base::Closure callback = async_callback_queue_.front(); + async_callback_queue_.pop(); + callback.Run(); + return true; +} + +void Transport::HandleAllAsyncRequests() { + while (!async_callback_queue_.empty()) + HandleOneAsyncRequest(); +} + +http::RequestID Transport::StartAsyncTransfer( + http::Connection* connection, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + // Fake transport doesn't use this method. + LOG(FATAL) << "This method should not be called on fake transport"; + return 0; +} + +bool Transport::CancelRequest(RequestID request_id) { + return false; +} + +void Transport::SetDefaultTimeout(base::TimeDelta timeout) { +} + +static inline std::string GetHandlerMapKey(const std::string& url, + const std::string& method) { + return method + ":" + url; +} + +void Transport::AddHandler(const std::string& url, + const std::string& method, + const HandlerCallback& handler) { + // Make sure we can override/replace existing handlers. + handlers_[GetHandlerMapKey(url, method)] = handler; +} + +void Transport::AddSimpleReplyHandler(const std::string& url, + const std::string& method, + int status_code, + const std::string& reply_text, + const std::string& mime_type) { + auto handler = [status_code, reply_text, mime_type]( + const ServerRequest& request, ServerResponse* response) { + response->ReplyText(status_code, reply_text, mime_type); + }; + AddHandler(url, method, base::Bind(handler)); +} + +Transport::HandlerCallback Transport::GetHandler( + const std::string& url, + const std::string& method) const { + // First try the exact combination of URL/Method + auto p = handlers_.find(GetHandlerMapKey(url, method)); + if (p != handlers_.end()) + return p->second; + // If not found, try URL/* + p = handlers_.find(GetHandlerMapKey(url, "*")); + if (p != handlers_.end()) + return p->second; + // If still not found, try */method + p = handlers_.find(GetHandlerMapKey("*", method)); + if (p != handlers_.end()) + return p->second; + // Finally, try */* + p = handlers_.find(GetHandlerMapKey("*", "*")); + return (p != handlers_.end()) ? p->second : HandlerCallback(); +} + +void ServerRequestResponseBase::SetData(StreamPtr stream) { + data_.clear(); + if (stream) { + uint8_t buffer[1024]; + size_t size = 0; + if (stream->CanGetSize()) + data_.reserve(stream->GetRemainingSize()); + + do { + CHECK(stream->ReadBlocking(buffer, sizeof(buffer), &size, nullptr)); + data_.insert(data_.end(), buffer, buffer + size); + } while (size > 0); + } +} + +std::string ServerRequestResponseBase::GetDataAsString() const { + if (data_.empty()) + return std::string(); + auto chars = reinterpret_cast(data_.data()); + return std::string(chars, data_.size()); +} + +std::unique_ptr +ServerRequestResponseBase::GetDataAsJson() const { + if (brillo::mime::RemoveParameters( + GetHeader(request_header::kContentType)) == + brillo::mime::application::kJson) { + auto value = base::JSONReader::Read(GetDataAsString()); + if (value) { + base::DictionaryValue* dict = nullptr; + if (value->GetAsDictionary(&dict)) { + // |value| is now owned by |dict|. + base::IgnoreResult(value.release()); + return std::unique_ptr(dict); + } + } + } + return std::unique_ptr(); +} + +std::string ServerRequestResponseBase::GetDataAsNormalizedJsonString() const { + std::string value; + // Make sure we serialize the JSON back without any pretty print so + // the string comparison works correctly. + auto json = GetDataAsJson(); + if (json) + base::JSONWriter::Write(*json, &value); + return value; +} + +void ServerRequestResponseBase::AddHeaders(const HeaderList& headers) { + for (const auto& pair : headers) { + if (pair.second.empty()) + headers_.erase(pair.first); + else + headers_.insert(pair); + } +} + +std::string ServerRequestResponseBase::GetHeader( + const std::string& header_name) const { + auto p = headers_.find(header_name); + return p != headers_.end() ? p->second : std::string(); +} + +ServerRequest::ServerRequest(const std::string& url, const std::string& method) + : method_(method) { + auto params = brillo::url::GetQueryStringParameters(url); + url_ = brillo::url::RemoveQueryString(url, true); + form_fields_.insert(params.begin(), params.end()); +} + +std::string ServerRequest::GetFormField(const std::string& field_name) const { + if (!form_fields_parsed_) { + std::string mime_type = brillo::mime::RemoveParameters( + GetHeader(request_header::kContentType)); + if (mime_type == brillo::mime::application::kWwwFormUrlEncoded && + !GetData().empty()) { + auto fields = brillo::data_encoding::WebParamsDecode(GetDataAsString()); + form_fields_.insert(fields.begin(), fields.end()); + } + form_fields_parsed_ = true; + } + auto p = form_fields_.find(field_name); + return p != form_fields_.end() ? p->second : std::string(); +} + +void ServerResponse::Reply(int status_code, + const void* data, + size_t data_size, + const std::string& mime_type) { + data_.clear(); + status_code_ = status_code; + SetData(MemoryStream::OpenCopyOf(data, data_size, nullptr)); + AddHeaders({{response_header::kContentLength, + brillo::string_utils::ToString(data_size)}, + {response_header::kContentType, mime_type}}); +} + +void ServerResponse::ReplyText(int status_code, + const std::string& text, + const std::string& mime_type) { + Reply(status_code, text.data(), text.size(), mime_type); +} + +void ServerResponse::ReplyJson(int status_code, const base::Value* json) { + std::string text; + base::JSONWriter::WriteWithOptions( + *json, base::JSONWriter::OPTIONS_PRETTY_PRINT, &text); + std::string mime_type = + brillo::mime::AppendParameter(brillo::mime::application::kJson, + brillo::mime::parameters::kCharset, + "utf-8"); + ReplyText(status_code, text, mime_type); +} + +void ServerResponse::ReplyJson(int status_code, + const http::FormFieldList& fields) { + base::DictionaryValue json; + for (const auto& pair : fields) { + json.SetString(pair.first, pair.second); + } + ReplyJson(status_code, &json); +} + +std::string ServerResponse::GetStatusText() const { + static std::vector> status_text_map = { + {100, "Continue"}, + {101, "Switching Protocols"}, + {102, "Processing"}, + {200, "OK"}, + {201, "Created"}, + {202, "Accepted"}, + {203, "Non-Authoritative Information"}, + {204, "No Content"}, + {205, "Reset Content"}, + {206, "Partial Content"}, + {207, "Multi-Status"}, + {208, "Already Reported"}, + {226, "IM Used"}, + {300, "Multiple Choices"}, + {301, "Moved Permanently"}, + {302, "Found"}, + {303, "See Other"}, + {304, "Not Modified"}, + {305, "Use Proxy"}, + {306, "Switch Proxy"}, + {307, "Temporary Redirect"}, + {308, "Permanent Redirect"}, + {400, "Bad Request"}, + {401, "Unauthorized"}, + {402, "Payment Required"}, + {403, "Forbidden"}, + {404, "Not Found"}, + {405, "Method Not Allowed"}, + {406, "Not Acceptable"}, + {407, "Proxy Authentication Required"}, + {408, "Request Timeout"}, + {409, "Conflict"}, + {410, "Gone"}, + {411, "Length Required"}, + {412, "Precondition Failed"}, + {413, "Request Entity Too Large"}, + {414, "Request - URI Too Long"}, + {415, "Unsupported Media Type"}, + {429, "Too Many Requests"}, + {431, "Request Header Fields Too Large"}, + {500, "Internal Server Error"}, + {501, "Not Implemented"}, + {502, "Bad Gateway"}, + {503, "Service Unavailable"}, + {504, "Gateway Timeout"}, + {505, "HTTP Version Not Supported"}, + }; + + for (const auto& pair : status_text_map) { + if (pair.first == status_code_) + return pair.second; + } + return std::string(); +} + +} // namespace brillo diff --git a/brillo/http/http_transport_fake.h b/brillo/http/http_transport_fake.h new file mode 100644 index 0000000..3d6aecb --- /dev/null +++ b/brillo/http/http_transport_fake.h @@ -0,0 +1,265 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_FAKE_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_FAKE_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace brillo { +namespace http { +namespace fake { + +class ServerRequest; +class ServerResponse; +class Connection; + +/////////////////////////////////////////////////////////////////////////////// +// A fake implementation of http::Transport that simulates HTTP communication +// with a server. +/////////////////////////////////////////////////////////////////////////////// +class Transport : public http::Transport { + public: + Transport(); + ~Transport() override; + + // Server handler callback signature. + using HandlerCallback = + base::Callback; + + // This method allows the test code to provide a callback to handle requests + // for specific URL/HTTP-verb combination. When a specific |method| request + // is made on the given |url|, the |handler| will be invoked and all the + // request data will be filled in the |ServerRequest| parameter. Any server + // response should be returned through the |ServerResponse| parameter. + // Either |method| or |url| (or both) can be specified as "*" to handle + // any requests. So, ("http://localhost","*") will handle any request type + // on that URL and ("*","GET") will handle any GET requests. + // The lookup starts with the most specific data pair to the catch-all (*,*). + void AddHandler(const std::string& url, + const std::string& method, + const HandlerCallback& handler); + // Simple version of AddHandler. AddSimpleReplyHandler just returns the + // specified text response of given MIME type. + void AddSimpleReplyHandler(const std::string& url, + const std::string& method, + int status_code, + const std::string& reply_text, + const std::string& mime_type); + // Retrieve a handler for specific |url| and request |method|. + HandlerCallback GetHandler(const std::string& url, + const std::string& method) const; + + // For tests that want to assert on the number of HTTP requests sent, + // these methods can be used to do just that. + int GetRequestCount() const { return request_count_; } + void ResetRequestCount() { request_count_ = 0; } + + // For tests that wish to simulate critical transport errors, this method + // can be used to specify the error to be returned when creating a connection. + void SetCreateConnectionError(brillo::ErrorPtr create_connection_error) { + create_connection_error_ = std::move(create_connection_error); + } + + // For tests that really need async operations with message loop, call this + // function with true. + void SetAsyncMode(bool async) { async_ = async; } + + // Pops one callback from the top of |async_callback_queue_| and invokes it. + // Returns false if the queue is empty. + bool HandleOneAsyncRequest(); + + // Invokes all the callbacks currently queued in |async_callback_queue_|. + void HandleAllAsyncRequests(); + + // Overrides from http::Transport. + std::shared_ptr CreateConnection( + const std::string& url, + const std::string& method, + const HeaderList& headers, + const std::string& user_agent, + const std::string& referer, + brillo::ErrorPtr* error) override; + + void RunCallbackAsync(const tracked_objects::Location& from_here, + const base::Closure& callback) override; + + RequestID StartAsyncTransfer(http::Connection* connection, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) override; + + bool CancelRequest(RequestID request_id) override; + + void SetDefaultTimeout(base::TimeDelta timeout) override; + + private: + // A list of user-supplied request handlers. + std::map handlers_; + // Counter incremented each time a request is made. + int request_count_{0}; + bool async_{false}; + // A list of queued callbacks that need to be called at some point. + // Call HandleOneAsyncRequest() or HandleAllAsyncRequests() to invoke them. + std::queue async_callback_queue_; + + // Fake error to be returned from CreateConnection method. + brillo::ErrorPtr create_connection_error_; + + DISALLOW_COPY_AND_ASSIGN(Transport); +}; + +/////////////////////////////////////////////////////////////////////////////// +// A base class for ServerRequest and ServerResponse. It provides common +// functionality to work with request/response HTTP headers and data. +/////////////////////////////////////////////////////////////////////////////// +class ServerRequestResponseBase { + public: + ServerRequestResponseBase() = default; + + // Add/retrieve request/response body data. + void SetData(StreamPtr stream); + const std::vector& GetData() const { return data_; } + std::string GetDataAsString() const; + std::unique_ptr GetDataAsJson() const; + // Parses the data into a JSON object and writes it back to JSON to normalize + // its string representation (no pretty print, extra spaces, etc). + std::string GetDataAsNormalizedJsonString() const; + + // Add/retrieve request/response HTTP headers. + void AddHeaders(const HeaderList& headers); + std::string GetHeader(const std::string& header_name) const; + const std::multimap& GetHeaders() const { + return headers_; + } + + protected: + // Data buffer. + std::vector data_; + // Header map. + std::multimap headers_; + + private: + DISALLOW_COPY_AND_ASSIGN(ServerRequestResponseBase); +}; + +/////////////////////////////////////////////////////////////////////////////// +// A container class that encapsulates all the HTTP server request information. +/////////////////////////////////////////////////////////////////////////////// +class ServerRequest : public ServerRequestResponseBase { + public: + ServerRequest(const std::string& url, const std::string& method); + + // Get the actual request URL. Does not include the query string or fragment. + const std::string& GetURL() const { return url_; } + // Get the request method. + const std::string& GetMethod() const { return method_; } + // Get the POST/GET request parameters. These are parsed query string + // parameters from the URL. In addition, for POST requests with + // application/x-www-form-urlencoded content type, the request body is also + // parsed and individual fields can be accessed through this method. + std::string GetFormField(const std::string& field_name) const; + + private: + // Request URL (without query string or URL fragment). + std::string url_; + // Request method + std::string method_; + // List of available request data form fields. + mutable std::map form_fields_; + // Flag used on first request to GetFormField to parse the body of HTTP POST + // request with application/x-www-form-urlencoded content. + mutable bool form_fields_parsed_ = false; + + DISALLOW_COPY_AND_ASSIGN(ServerRequest); +}; + +/////////////////////////////////////////////////////////////////////////////// +// A container class that encapsulates all the HTTP server response information. +// The request handler will use this class to provide a response to the caller. +// Call the Reply() or the appropriate ReplyNNN() specialization to provide +// the response data. Additional calls to AddHeaders() can be made to provide +// custom response headers. The Reply-methods will already provide the +// following response headers: +// Content-Length +// Content-Type +/////////////////////////////////////////////////////////////////////////////// +class ServerResponse : public ServerRequestResponseBase { + public: + ServerResponse() = default; + + // Generic reply method. + void Reply(int status_code, + const void* data, + size_t data_size, + const std::string& mime_type); + // Reply with text body. + void ReplyText(int status_code, + const std::string& text, + const std::string& mime_type); + // Reply with JSON object. The content type will be "application/json". + void ReplyJson(int status_code, const base::Value* json); + // Special form for JSON response for simple objects that have a flat + // list of key-value pairs of string type. + void ReplyJson(int status_code, const FormFieldList& fields); + + // Specialized overload to send the binary data as an array of simple + // data elements. Only trivial data types (scalars, POD structures, etc) + // can be used. + template + void Reply(int status_code, + const std::vector& data, + const std::string& mime_type) { + // Make sure T doesn't have virtual functions, custom constructors, etc. + static_assert(std::is_trivial::value, "Only simple data is supported"); + Reply(status_code, data.data(), data.size() * sizeof(T), mime_type); + } + + // Specialized overload to send the binary data. + // Only trivial data types (scalars, POD structures, etc) can be used. + template + void Reply(int status_code, const T& data, const std::string& mime_type) { + // Make sure T doesn't have virtual functions, custom constructors, etc. + static_assert(std::is_trivial::value, "Only simple data is supported"); + Reply(status_code, &data, sizeof(T), mime_type); + } + + // For handlers that want to simulate versions of HTTP protocol other + // than HTTP/1.1, call this method with the custom version string, + // for example "HTTP/1.0". + void SetProtocolVersion(const std::string& protocol_version) { + protocol_version_ = protocol_version; + } + + protected: + // These methods are helpers to implement corresponding functionality + // of fake::Connection. + friend class Connection; + // Helper for fake::Connection::GetResponseStatusCode(). + int GetStatusCode() const { return status_code_; } + // Helper for fake::Connection::GetResponseStatusText(). + std::string GetStatusText() const; + // Helper for fake::Connection::GetProtocolVersion(). + std::string GetProtocolVersion() const { return protocol_version_; } + + private: + int status_code_ = 0; + std::string protocol_version_ = "HTTP/1.1"; + + DISALLOW_COPY_AND_ASSIGN(ServerResponse); +}; + +} // namespace fake +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_TRANSPORT_FAKE_H_ diff --git a/brillo/http/http_utils.cc b/brillo/http/http_utils.cc new file mode 100644 index 0000000..c8f5802 --- /dev/null +++ b/brillo/http/http_utils.cc @@ -0,0 +1,440 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using brillo::mime::AppendParameter; +using brillo::mime::RemoveParameters; + +namespace brillo { +namespace http { + +std::unique_ptr GetAndBlock(const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + return SendRequestWithNoDataAndBlock( + request_type::kGet, url, headers, transport, error); +} + +RequestID Get(const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + return SendRequestWithNoData(request_type::kGet, + url, + headers, + transport, + success_callback, + error_callback); +} + +std::unique_ptr HeadAndBlock(const std::string& url, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + return SendRequestWithNoDataAndBlock( + request_type::kHead, url, {}, transport, error); +} + +RequestID Head(const std::string& url, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + return SendRequestWithNoData(request_type::kHead, + url, + {}, + transport, + success_callback, + error_callback); +} + +std::unique_ptr PostTextAndBlock(const std::string& url, + const std::string& data, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + return PostBinaryAndBlock( + url, data.data(), data.size(), mime_type, headers, transport, error); +} + +RequestID PostText(const std::string& url, + const std::string& data, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + return PostBinary(url, + data.data(), + data.size(), + mime_type, + headers, + transport, + success_callback, + error_callback); +} + +std::unique_ptr SendRequestAndBlock( + const std::string& method, + const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + Request request(url, method, transport); + request.AddHeaders(headers); + if (data_size > 0) { + CHECK(!mime_type.empty()) << "MIME type must be specified if request body " + "message is provided"; + request.SetContentType(mime_type); + if (!request.AddRequestBody(data, data_size, error)) + return std::unique_ptr(); + } + return request.GetResponseAndBlock(error); +} + +std::unique_ptr SendRequestWithNoDataAndBlock( + const std::string& method, + const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + return SendRequestAndBlock( + method, url, nullptr, 0, {}, headers, transport, error); +} + +RequestID SendRequest(const std::string& method, + const std::string& url, + StreamPtr stream, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + Request request(url, method, transport); + request.AddHeaders(headers); + if (stream && (!stream->CanGetSize() || stream->GetRemainingSize() > 0)) { + CHECK(!mime_type.empty()) << "MIME type must be specified if request body " + "message is provided"; + request.SetContentType(mime_type); + brillo::ErrorPtr error; + if (!request.AddRequestBody(std::move(stream), &error)) { + transport->RunCallbackAsync( + FROM_HERE, base::Bind(error_callback, + 0, base::Owned(error.release()))); + return 0; + } + } + return request.GetResponse(success_callback, error_callback); +} + +RequestID SendRequest(const std::string& method, + const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + return SendRequest(method, + url, + MemoryStream::OpenCopyOf(data, data_size, nullptr), + mime_type, + headers, + transport, + success_callback, + error_callback); +} + +RequestID SendRequestWithNoData(const std::string& method, + const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + return SendRequest(method, + url, + {}, + {}, + headers, + transport, + success_callback, + error_callback); +} + +std::unique_ptr PostBinaryAndBlock( + const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + return SendRequestAndBlock(request_type::kPost, + url, + data, + data_size, + mime_type, + headers, + transport, + error); +} + +RequestID PostBinary(const std::string& url, + StreamPtr stream, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + return SendRequest(request_type::kPost, + url, + std::move(stream), + mime_type, + headers, + transport, + success_callback, + error_callback); +} + +RequestID PostBinary(const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + return SendRequest(request_type::kPost, + url, + data, + data_size, + mime_type, + headers, + transport, + success_callback, + error_callback); +} + +std::unique_ptr PostFormDataAndBlock( + const std::string& url, + const FormFieldList& data, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + std::string encoded_data = brillo::data_encoding::WebParamsEncode(data); + return PostBinaryAndBlock(url, + encoded_data.c_str(), + encoded_data.size(), + brillo::mime::application::kWwwFormUrlEncoded, + headers, + transport, + error); +} + +std::unique_ptr PostFormDataAndBlock( + const std::string& url, + std::unique_ptr form_data, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + Request request(url, request_type::kPost, transport); + request.AddHeaders(headers); + if (!request.AddRequestBodyAsFormData(std::move(form_data), error)) + return std::unique_ptr(); + return request.GetResponseAndBlock(error); +} + +RequestID PostFormData(const std::string& url, + const FormFieldList& data, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + std::string encoded_data = brillo::data_encoding::WebParamsEncode(data); + return PostBinary(url, + encoded_data.c_str(), + encoded_data.size(), + brillo::mime::application::kWwwFormUrlEncoded, + headers, + transport, + success_callback, + error_callback); +} + +RequestID PostFormData(const std::string& url, + std::unique_ptr form_data, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + Request request(url, request_type::kPost, transport); + request.AddHeaders(headers); + brillo::ErrorPtr error; + if (!request.AddRequestBodyAsFormData(std::move(form_data), &error)) { + transport->RunCallbackAsync( + FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); + return 0; + } + return request.GetResponse(success_callback, error_callback); +} + +std::unique_ptr PostJsonAndBlock(const std::string& url, + const base::Value* json, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + std::string data; + if (json) + base::JSONWriter::Write(*json, &data); + std::string mime_type = AppendParameter(brillo::mime::application::kJson, + brillo::mime::parameters::kCharset, + "utf-8"); + return PostBinaryAndBlock( + url, data.c_str(), data.size(), mime_type, headers, transport, error); +} + +RequestID PostJson(const std::string& url, + std::unique_ptr json, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + std::string data; + if (json) + base::JSONWriter::Write(*json, &data); + std::string mime_type = AppendParameter(brillo::mime::application::kJson, + brillo::mime::parameters::kCharset, + "utf-8"); + return PostBinary(url, + data.c_str(), + data.size(), + mime_type, + headers, + transport, + success_callback, + error_callback); +} + +std::unique_ptr PatchJsonAndBlock( + const std::string& url, + const base::Value* json, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error) { + std::string data; + if (json) + base::JSONWriter::Write(*json, &data); + std::string mime_type = AppendParameter(brillo::mime::application::kJson, + brillo::mime::parameters::kCharset, + "utf-8"); + return SendRequestAndBlock(request_type::kPatch, + url, + data.c_str(), + data.size(), + mime_type, + headers, + transport, + error); +} + +RequestID PatchJson(const std::string& url, + std::unique_ptr json, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + std::string data; + if (json) + base::JSONWriter::Write(*json, &data); + std::string mime_type = + AppendParameter(brillo::mime::application::kJson, + brillo::mime::parameters::kCharset, "utf-8"); + return SendRequest(request_type::kPatch, url, data.c_str(), data.size(), + mime_type, headers, transport, success_callback, + error_callback); +} + +std::unique_ptr ParseJsonResponse( + Response* response, + int* status_code, + brillo::ErrorPtr* error) { + if (!response) + return std::unique_ptr(); + + if (status_code) + *status_code = response->GetStatusCode(); + + // Make sure we have a correct content type. Do not try to parse + // binary files, or HTML output. Limit to application/json and text/plain. + auto content_type = RemoveParameters(response->GetContentType()); + if (content_type != brillo::mime::application::kJson && + content_type != brillo::mime::text::kPlain) { + brillo::Error::AddTo(error, FROM_HERE, brillo::errors::json::kDomain, + "non_json_content_type", + "Unexpected response content type: " + content_type); + return std::unique_ptr(); + } + + std::string json = response->ExtractDataAsString(); + std::string error_message; + auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC, + nullptr, &error_message); + if (!value) { + brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::json::kDomain, + brillo::errors::json::kParseError, + "Error '%s' occurred parsing JSON string '%s'", + error_message.c_str(), json.c_str()); + return std::unique_ptr(); + } + base::DictionaryValue* dict_value = nullptr; + if (!value->GetAsDictionary(&dict_value)) { + brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::json::kDomain, + brillo::errors::json::kObjectExpected, + "Response is not a valid JSON object: '%s'", + json.c_str()); + return std::unique_ptr(); + } else { + // |value| is now owned by |dict_value|, so release the scoped_ptr now. + base::IgnoreResult(value.release()); + } + return std::unique_ptr(dict_value); +} + +std::string GetCanonicalHeaderName(const std::string& name) { + std::string canonical_name = name; + bool word_begin = true; + for (char& c : canonical_name) { + if (c == '-') { + word_begin = true; + } else { + if (word_begin) { + c = toupper(c); + } else { + c = tolower(c); + } + word_begin = false; + } + } + return canonical_name; +} + +} // namespace http +} // namespace brillo diff --git a/brillo/http/http_utils.h b/brillo/http/http_utils.h new file mode 100644 index 0000000..7d5f937 --- /dev/null +++ b/brillo/http/http_utils.h @@ -0,0 +1,319 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_HTTP_UTILS_H_ +#define LIBCHROMEOS_BRILLO_HTTP_HTTP_UTILS_H_ + +#include +#include +#include + +#include +#include +#include +#include + +namespace base { +class Value; +class DictionaryValue; +} // namespace base + +namespace brillo { +namespace http { + +using FormFieldList = std::vector>; + +//////////////////////////////////////////////////////////////////////////////// +// The following are simple utility helper functions for common HTTP operations +// that use http::Request object behind the scenes and set it up accordingly. +// The values for request method, data MIME type, request header names should +// not be directly encoded in most cases, but use predefined constants from +// http_request.h. +// So, instead of calling: +// SendRequestAndBlock("POST", +// "http://url", +// "data", 4, +// "text/plain", +// {{"Authorization", "Bearer TOKEN"}}, +// transport, error); +// You should do use this instead: +// SendRequestAndBlock(brillo::http::request_type::kPost, +// "http://url", +// "data", 4, +// brillo::mime::text::kPlain, +// {{brillo::http::request_header::kAuthorization, +// "Bearer TOKEN"}}, +// transport, error); +// +// For more advanced functionality you need to use Request/Response objects +// directly. +//////////////////////////////////////////////////////////////////////////////// + +// Performs a generic HTTP request with binary data. Success status, +// returned data and additional information (such as returned HTTP headers) +// can be obtained from the returned Response object. +BRILLO_EXPORT std::unique_ptr SendRequestAndBlock( + const std::string& method, + const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Same as above, but without sending the request body. +// This is especially useful for requests like "GET" and "HEAD". +BRILLO_EXPORT std::unique_ptr SendRequestWithNoDataAndBlock( + const std::string& method, + const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Same as above but asynchronous. On success, |success_callback| is called +// with the response object. On failure, |error_callback| is called with the +// error details. +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID SendRequest( + const std::string& method, + const std::string& url, + StreamPtr stream, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Same as above, but takes a memory buffer. The pointer should be valid only +// until the function returns. The data is copied into an internal buffer to be +// available for the duration of the asynchronous operation. +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID SendRequest( + const std::string& method, + const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Asynchronous version of SendRequestNoData(). +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID SendRequestWithNoData( + const std::string& method, + const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a GET request. Success status, returned data and additional +// information (such as returned HTTP headers) can be obtained from +// the returned Response object. +BRILLO_EXPORT std::unique_ptr GetAndBlock( + const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Asynchronous version of http::Get(). +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID Get( + const std::string& url, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a HEAD request. Success status and additional +// information (such as returned HTTP headers) can be obtained from +// the returned Response object. +BRILLO_EXPORT std::unique_ptr HeadAndBlock( + const std::string& url, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Performs an asynchronous HEAD request. +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID Head( + const std::string& url, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a POST request with binary data. Success status, returned data +// and additional information (such as returned HTTP headers) can be obtained +// from the returned Response object. +BRILLO_EXPORT std::unique_ptr PostBinaryAndBlock( + const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Async version of PostBinary(). +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID PostBinary( + const std::string& url, + StreamPtr stream, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Same as above, but takes a memory buffer. The pointer should be valid only +// until the function returns. The data is copied into an internal buffer +// to be available for the duration of the asynchronous operation. +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID PostBinary( + const std::string& url, + const void* data, + size_t data_size, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a POST request with text data. Success status, returned data +// and additional information (such as returned HTTP headers) can be obtained +// from the returned Response object. +BRILLO_EXPORT std::unique_ptr PostTextAndBlock( + const std::string& url, + const std::string& data, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Async version of PostText(). +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID PostText( + const std::string& url, + const std::string& data, + const std::string& mime_type, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a POST request with form data. Success status, returned data +// and additional information (such as returned HTTP headers) can be obtained +// from the returned Response object. The form data is a list of key/value +// pairs. The data is posed as "application/x-www-form-urlencoded". +BRILLO_EXPORT std::unique_ptr PostFormDataAndBlock( + const std::string& url, + const FormFieldList& data, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Async version of PostFormData() above. +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID PostFormData( + const std::string& url, + const FormFieldList& data, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a POST request with form data, including binary file uploads. +// Success status, returned data and additional information (such as returned +// HTTP headers) can be obtained from the returned Response object. +// The data is posed as "multipart/form-data". +BRILLO_EXPORT std::unique_ptr PostFormDataAndBlock( + const std::string& url, + std::unique_ptr form_data, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Async version of PostFormData() above. +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID PostFormData( + const std::string& url, + std::unique_ptr form_data, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a POST request with JSON data. Success status, returned data +// and additional information (such as returned HTTP headers) can be obtained +// from the returned Response object. If a JSON response is expected, +// use ParseJsonResponse() method on the returned Response object. +BRILLO_EXPORT std::unique_ptr PostJsonAndBlock( + const std::string& url, + const base::Value* json, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Async version of PostJson(). +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID PostJson( + const std::string& url, + std::unique_ptr json, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Performs a PATCH request with JSON data. Success status, returned data +// and additional information (such as returned HTTP headers) can be obtained +// from the returned Response object. If a JSON response is expected, +// use ParseJsonResponse() method on the returned Response object. +BRILLO_EXPORT std::unique_ptr PatchJsonAndBlock( + const std::string& url, + const base::Value* json, + const HeaderList& headers, + std::shared_ptr transport, + brillo::ErrorPtr* error); + +// Async version of PatchJson(). +// Returns the ID of the request which can be used to cancel the pending +// request using Transport::CancelRequest(). +BRILLO_EXPORT RequestID PatchJson( + const std::string& url, + std::unique_ptr json, + const HeaderList& headers, + std::shared_ptr transport, + const SuccessCallback& success_callback, + const ErrorCallback& error_callback); + +// Given an http::Response object, parse the body data into Json object. +// Returns null if failed. Optional |error| can be passed in to +// get the extended error information as to why the parse failed. +BRILLO_EXPORT std::unique_ptr ParseJsonResponse( + Response* response, + int* status_code, + brillo::ErrorPtr* error); + +// Converts a request header name to canonical form (lowercase with uppercase +// first letter and each letter after a hyphen ('-')). +// "content-TYPE" will be converted to "Content-Type". +BRILLO_EXPORT std::string GetCanonicalHeaderName(const std::string& name); + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_HTTP_UTILS_H_ diff --git a/brillo/http/http_utils_unittest.cc b/brillo/http/http_utils_unittest.cc new file mode 100644 index 0000000..ee408b8 --- /dev/null +++ b/brillo/http/http_utils_unittest.cc @@ -0,0 +1,497 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { +namespace http { + +static const char kFakeUrl[] = "http://localhost"; +static const char kEchoUrl[] = "http://localhost/echo"; +static const char kMethodEchoUrl[] = "http://localhost/echo/method"; + +///////////////////// Generic helper request handlers ///////////////////////// +// Returns the request data back with the same content type. +static void EchoDataHandler(const fake::ServerRequest& request, + fake::ServerResponse* response) { + response->Reply(status_code::Ok, + request.GetData(), + request.GetHeader(request_header::kContentType)); +} + +// Returns the request method as a plain text response. +static void EchoMethodHandler(const fake::ServerRequest& request, + fake::ServerResponse* response) { + response->ReplyText( + status_code::Ok, request.GetMethod(), brillo::mime::text::kPlain); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST(HttpUtils, SendRequest_BinaryData) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler( + kEchoUrl, request_type::kPost, base::Bind(EchoDataHandler)); + + // Test binary data round-tripping. + std::vector custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + auto response = + http::SendRequestAndBlock(request_type::kPost, + kEchoUrl, + custom_data.data(), + custom_data.size(), + brillo::mime::application::kOctet_stream, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kOctet_stream, + response->GetContentType()); + EXPECT_EQ(custom_data, response->ExtractData()); +} + +TEST(HttpUtils, SendRequestAsync_BinaryData) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler( + kEchoUrl, request_type::kPost, base::Bind(EchoDataHandler)); + + // Test binary data round-tripping. + std::vector custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + auto success_callback = + [&custom_data](RequestID id, std::unique_ptr response) { + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kOctet_stream, + response->GetContentType()); + EXPECT_EQ(custom_data, response->ExtractData()); + }; + auto error_callback = [](RequestID id, const Error* error) { + FAIL() << "This callback shouldn't have been called"; + }; + http::SendRequest(request_type::kPost, + kEchoUrl, + custom_data.data(), + custom_data.size(), + brillo::mime::application::kOctet_stream, + {}, + transport, + base::Bind(success_callback), + base::Bind(error_callback)); +} + +TEST(HttpUtils, SendRequest_Post) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + // Test binary data round-tripping. + std::vector custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + + // Check the correct HTTP method used. + auto response = + http::SendRequestAndBlock(request_type::kPost, + kMethodEchoUrl, + custom_data.data(), + custom_data.size(), + brillo::mime::application::kOctet_stream, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kPost, response->ExtractDataAsString()); +} + +TEST(HttpUtils, SendRequest_Get) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + auto response = http::SendRequestAndBlock(request_type::kGet, + kMethodEchoUrl, + nullptr, + 0, + std::string{}, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kGet, response->ExtractDataAsString()); +} + +TEST(HttpUtils, SendRequest_Put) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + auto response = http::SendRequestAndBlock(request_type::kPut, + kMethodEchoUrl, + nullptr, + 0, + std::string{}, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kPut, response->ExtractDataAsString()); +} + +TEST(HttpUtils, SendRequest_NotFound) { + std::shared_ptr transport(new fake::Transport); + // Test failed response (URL not found). + auto response = http::SendRequestWithNoDataAndBlock( + request_type::kGet, "http://blah.com", {}, transport, nullptr); + EXPECT_FALSE(response->IsSuccessful()); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); +} + +TEST(HttpUtils, SendRequestAsync_NotFound) { + std::shared_ptr transport(new fake::Transport); + // Test failed response (URL not found). + auto success_callback = + [](RequestID request_id, std::unique_ptr response) { + EXPECT_FALSE(response->IsSuccessful()); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); + }; + auto error_callback = [](RequestID request_id, const Error* error) { + FAIL() << "This callback shouldn't have been called"; + }; + http::SendRequestWithNoData(request_type::kGet, + "http://blah.com", + {}, + transport, + base::Bind(success_callback), + base::Bind(error_callback)); +} + +TEST(HttpUtils, SendRequest_Headers) { + std::shared_ptr transport(new fake::Transport); + + static const char json_echo_url[] = "http://localhost/echo/json"; + auto JsonEchoHandler = + [](const fake::ServerRequest& request, fake::ServerResponse* response) { + base::DictionaryValue json; + json.SetString("method", request.GetMethod()); + json.SetString("data", request.GetDataAsString()); + for (const auto& pair : request.GetHeaders()) { + json.SetString("header." + pair.first, pair.second); + } + response->ReplyJson(status_code::Ok, &json); + }; + transport->AddHandler(json_echo_url, "*", base::Bind(JsonEchoHandler)); + auto response = http::SendRequestAndBlock( + request_type::kPost, json_echo_url, "abcd", 4, + brillo::mime::application::kOctet_stream, { + {request_header::kCookie, "flavor=vanilla"}, + {request_header::kIfMatch, "*"}, + }, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kJson, + brillo::mime::RemoveParameters(response->GetContentType())); + auto json = ParseJsonResponse(response.get(), nullptr, nullptr); + std::string value; + EXPECT_TRUE(json->GetString("method", &value)); + EXPECT_EQ(request_type::kPost, value); + EXPECT_TRUE(json->GetString("data", &value)); + EXPECT_EQ("abcd", value); + EXPECT_TRUE(json->GetString("header.Cookie", &value)); + EXPECT_EQ("flavor=vanilla", value); + EXPECT_TRUE(json->GetString("header.Content-Type", &value)); + EXPECT_EQ(brillo::mime::application::kOctet_stream, value); + EXPECT_TRUE(json->GetString("header.Content-Length", &value)); + EXPECT_EQ("4", value); + EXPECT_TRUE(json->GetString("header.If-Match", &value)); + EXPECT_EQ("*", value); +} + +TEST(HttpUtils, Get) { + // Sends back the "?test=..." portion of URL. + // So if we do GET "http://localhost?test=blah", this handler responds + // with "blah" as text/plain. + auto GetHandler = + [](const fake::ServerRequest& request, fake::ServerResponse* response) { + EXPECT_EQ(request_type::kGet, request.GetMethod()); + EXPECT_EQ("0", request.GetHeader(request_header::kContentLength)); + EXPECT_EQ("", request.GetHeader(request_header::kContentType)); + response->ReplyText(status_code::Ok, + request.GetFormField("test"), + brillo::mime::text::kPlain); + }; + + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kGet, base::Bind(GetHandler)); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + // Make sure Get() actually does the GET request + auto response = http::GetAndBlock(kMethodEchoUrl, {}, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kGet, response->ExtractDataAsString()); + + for (std::string data : {"blah", "some data", ""}) { + std::string url = brillo::url::AppendQueryParam(kFakeUrl, "test", data); + response = http::GetAndBlock(url, {}, transport, nullptr); + EXPECT_EQ(data, response->ExtractDataAsString()); + } +} + +TEST(HttpUtils, Head) { + auto HeadHandler = + [](const fake::ServerRequest& request, fake::ServerResponse* response) { + EXPECT_EQ(request_type::kHead, request.GetMethod()); + EXPECT_EQ("0", request.GetHeader(request_header::kContentLength)); + EXPECT_EQ("", request.GetHeader(request_header::kContentType)); + response->ReplyText(status_code::Ok, "blah", brillo::mime::text::kPlain); + }; + + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler)); + + auto response = http::HeadAndBlock(kFakeUrl, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ("", response->ExtractDataAsString()); // Must not have actual body. + EXPECT_EQ("4", response->GetHeader(request_header::kContentLength)); +} + +TEST(HttpUtils, PostBinary) { + auto Handler = + [](const fake::ServerRequest& request, fake::ServerResponse* response) { + EXPECT_EQ(request_type::kPost, request.GetMethod()); + EXPECT_EQ("256", request.GetHeader(request_header::kContentLength)); + EXPECT_EQ(brillo::mime::application::kOctet_stream, + request.GetHeader(request_header::kContentType)); + const auto& data = request.GetData(); + EXPECT_EQ(256, data.size()); + + // Sum up all the bytes. + int sum = std::accumulate(data.begin(), data.end(), 0); + EXPECT_EQ(32640, sum); // sum(i, i => [0, 255]) = 32640. + response->ReplyText(status_code::Ok, "", brillo::mime::text::kPlain); + }; + + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(Handler)); + + /// Fill the data buffer with bytes from 0x00 to 0xFF. + std::vector data(256); + std::iota(data.begin(), data.end(), 0); + + auto response = http::PostBinaryAndBlock(kFakeUrl, + data.data(), + data.size(), + mime::application::kOctet_stream, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); +} + +TEST(HttpUtils, PostText) { + std::string fake_data = "Some data"; + auto PostHandler = [fake_data](const fake::ServerRequest& request, + fake::ServerResponse* response) { + EXPECT_EQ(request_type::kPost, request.GetMethod()); + EXPECT_EQ(fake_data.size(), + std::stoul(request.GetHeader(request_header::kContentLength))); + EXPECT_EQ(brillo::mime::text::kPlain, + request.GetHeader(request_header::kContentType)); + response->ReplyText(status_code::Ok, + request.GetDataAsString(), + brillo::mime::text::kPlain); + }; + + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(PostHandler)); + + auto response = http::PostTextAndBlock(kFakeUrl, + fake_data, + brillo::mime::text::kPlain, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(fake_data, response->ExtractDataAsString()); +} + +TEST(HttpUtils, PostFormData) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler( + kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler)); + + auto response = http::PostFormDataAndBlock( + kFakeUrl, { + {"key", "value"}, + {"field", "field value"}, + }, {}, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kWwwFormUrlEncoded, + response->GetContentType()); + EXPECT_EQ("key=value&field=field+value", response->ExtractDataAsString()); +} + +TEST(HttpUtils, PostMultipartFormData) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler( + kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler)); + + std::unique_ptr form_data{new FormData{"boundary123"}}; + form_data->AddTextField("key1", "value1"); + form_data->AddTextField("key2", "value2"); + std::string expected_content_type = form_data->GetContentType(); + auto response = http::PostFormDataAndBlock( + kFakeUrl, std::move(form_data), {}, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(expected_content_type, response->GetContentType()); + const char expected_value[] = + "--boundary123\r\n" + "Content-Disposition: form-data; name=\"key1\"\r\n" + "\r\n" + "value1\r\n" + "--boundary123\r\n" + "Content-Disposition: form-data; name=\"key2\"\r\n" + "\r\n" + "value2\r\n" + "--boundary123--"; + EXPECT_EQ(expected_value, response->ExtractDataAsString()); +} + +TEST(HttpUtils, PostPatchJson) { + auto JsonHandler = + [](const fake::ServerRequest& request, fake::ServerResponse* response) { + auto mime_type = brillo::mime::RemoveParameters( + request.GetHeader(request_header::kContentType)); + EXPECT_EQ(brillo::mime::application::kJson, mime_type); + response->ReplyJson( + status_code::Ok, + { + {"method", request.GetMethod()}, {"data", request.GetDataAsString()}, + }); + }; + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kFakeUrl, "*", base::Bind(JsonHandler)); + + base::DictionaryValue json; + json.SetString("key1", "val1"); + json.SetString("key2", "val2"); + std::string value; + + // Test POST + auto response = + http::PostJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr); + auto resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr); + EXPECT_NE(nullptr, resp_json.get()); + EXPECT_TRUE(resp_json->GetString("method", &value)); + EXPECT_EQ(request_type::kPost, value); + EXPECT_TRUE(resp_json->GetString("data", &value)); + EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value); + + // Test PATCH + response = http::PatchJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr); + resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr); + EXPECT_NE(nullptr, resp_json.get()); + EXPECT_TRUE(resp_json->GetString("method", &value)); + EXPECT_EQ(request_type::kPatch, value); + EXPECT_TRUE(resp_json->GetString("data", &value)); + EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value); +} + +TEST(HttpUtils, ParseJsonResponse) { + auto JsonHandler = + [](const fake::ServerRequest& request, fake::ServerResponse* response) { + int status_code = std::stoi(request.GetFormField("code")); + response->ReplyJson(status_code, {{"data", request.GetFormField("value")}}); + }; + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(JsonHandler)); + + // Test valid JSON responses (with success or error codes). + for (auto item : {"200;data", "400;wrong", "500;Internal Server error"}) { + auto pair = brillo::string_utils::SplitAtFirst(item, ";"); + auto response = http::PostFormDataAndBlock( + kFakeUrl, { + {"code", pair.first}, + {"value", pair.second}, + }, {}, transport, nullptr); + int code = 0; + auto json = http::ParseJsonResponse(response.get(), &code, nullptr); + EXPECT_NE(nullptr, json.get()); + std::string value; + EXPECT_TRUE(json->GetString("data", &value)); + EXPECT_EQ(pair.first, brillo::string_utils::ToString(code)); + EXPECT_EQ(pair.second, value); + } + + // Test invalid (non-JSON) response. + auto response = http::GetAndBlock("http://bad.url", {}, transport, nullptr); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); + EXPECT_EQ(brillo::mime::text::kHtml, response->GetContentType()); + int code = 0; + auto json = http::ParseJsonResponse(response.get(), &code, nullptr); + EXPECT_EQ(nullptr, json.get()); + EXPECT_EQ(status_code::NotFound, code); +} + +TEST(HttpUtils, SendRequest_Failure) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message"); + transport->SetCreateConnectionError(std::move(error)); + error.reset(); // Just to make sure it is empty... + auto response = http::SendRequestWithNoDataAndBlock( + request_type::kGet, "http://blah.com", {}, transport, &error); + EXPECT_EQ(nullptr, response.get()); + EXPECT_EQ("test_domain", error->GetDomain()); + EXPECT_EQ("test_code", error->GetCode()); + EXPECT_EQ("Test message", error->GetMessage()); +} + +TEST(HttpUtils, SendRequestAsync_Failure) { + std::shared_ptr transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message"); + transport->SetCreateConnectionError(std::move(error)); + auto success_callback = + [](RequestID request_id, std::unique_ptr response) { + FAIL() << "This callback shouldn't have been called"; + }; + auto error_callback = [](RequestID request_id, const Error* error) { + EXPECT_EQ("test_domain", error->GetDomain()); + EXPECT_EQ("test_code", error->GetCode()); + EXPECT_EQ("Test message", error->GetMessage()); + }; + http::SendRequestWithNoData(request_type::kGet, + "http://blah.com", + {}, + transport, + base::Bind(success_callback), + base::Bind(error_callback)); +} + +TEST(HttpUtils, GetCanonicalHeaderName) { + EXPECT_EQ("Foo", GetCanonicalHeaderName("foo")); + EXPECT_EQ("Bar", GetCanonicalHeaderName("BaR")); + EXPECT_EQ("Baz", GetCanonicalHeaderName("BAZ")); + EXPECT_EQ("Foo-Bar", GetCanonicalHeaderName("foo-bar")); + EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("foo-Bar-BAZ")); + EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("FOO-BAR-BAZ")); + EXPECT_EQ("Foo-Bar-", GetCanonicalHeaderName("fOO-bAR-")); + EXPECT_EQ("-Bar", GetCanonicalHeaderName("-bAR")); + EXPECT_EQ("", GetCanonicalHeaderName("")); + EXPECT_EQ("A-B-C", GetCanonicalHeaderName("a-B-c")); +} + +} // namespace http +} // namespace brillo diff --git a/brillo/http/mock_connection.h b/brillo/http/mock_connection.h new file mode 100644 index 0000000..c57aac1 --- /dev/null +++ b/brillo/http/mock_connection.h @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_MOCK_CONNECTION_H_ +#define LIBCHROMEOS_BRILLO_HTTP_MOCK_CONNECTION_H_ + +#include +#include + +#include +#include +#include + +namespace brillo { +namespace http { + +class MockConnection : public Connection { + public: + using Connection::Connection; + + MOCK_METHOD2(SendHeaders, bool(const HeaderList&, ErrorPtr*)); + MOCK_METHOD2(MockSetRequestData, bool(Stream*, ErrorPtr*)); + MOCK_METHOD1(MockSetResponseData, void(Stream*)); + MOCK_METHOD1(FinishRequest, bool(ErrorPtr*)); + MOCK_METHOD2(FinishRequestAsync, + RequestID(const SuccessCallback&, const ErrorCallback&)); + MOCK_CONST_METHOD0(GetResponseStatusCode, int()); + MOCK_CONST_METHOD0(GetResponseStatusText, std::string()); + MOCK_CONST_METHOD0(GetProtocolVersion, std::string()); + MOCK_CONST_METHOD1(GetResponseHeader, std::string(const std::string&)); + MOCK_CONST_METHOD1(MockExtractDataStream, Stream*(brillo::ErrorPtr*)); + + private: + bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) override { + return MockSetRequestData(stream.get(), error); + } + void SetResponseData(StreamPtr stream) override { + MockSetResponseData(stream.get()); + } + StreamPtr ExtractDataStream(brillo::ErrorPtr* error) override { + return StreamPtr{MockExtractDataStream(error)}; + } + + DISALLOW_COPY_AND_ASSIGN(MockConnection); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_MOCK_CONNECTION_H_ diff --git a/brillo/http/mock_curl_api.h b/brillo/http/mock_curl_api.h new file mode 100644 index 0000000..838ebf2 --- /dev/null +++ b/brillo/http/mock_curl_api.h @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_MOCK_CURL_API_H_ +#define LIBCHROMEOS_BRILLO_HTTP_MOCK_CURL_API_H_ + +#include + +#include +#include + +namespace brillo { +namespace http { + +// This is a mock for CURL interfaces which allows to mock out the CURL's +// low-level C APIs in tests by intercepting the virtual function calls on +// the abstract CurlInterface. +class MockCurlInterface : public CurlInterface { + public: + MockCurlInterface() = default; + + MOCK_METHOD0(EasyInit, CURL*()); + MOCK_METHOD1(EasyCleanup, void(CURL*)); + MOCK_METHOD3(EasySetOptInt, CURLcode(CURL*, CURLoption, int)); + MOCK_METHOD3(EasySetOptStr, CURLcode(CURL*, CURLoption, const std::string&)); + MOCK_METHOD3(EasySetOptPtr, CURLcode(CURL*, CURLoption, void*)); + MOCK_METHOD3(EasySetOptCallback, CURLcode(CURL*, CURLoption, intptr_t)); + MOCK_METHOD3(EasySetOptOffT, CURLcode(CURL*, CURLoption, curl_off_t)); + MOCK_METHOD1(EasyPerform, CURLcode(CURL*)); + MOCK_CONST_METHOD3(EasyGetInfoInt, CURLcode(CURL*, CURLINFO, int*)); + MOCK_CONST_METHOD3(EasyGetInfoDbl, CURLcode(CURL*, CURLINFO, double*)); + MOCK_CONST_METHOD3(EasyGetInfoStr, CURLcode(CURL*, CURLINFO, std::string*)); + MOCK_CONST_METHOD3(EasyGetInfoPtr, CURLcode(CURL*, CURLINFO, void**)); + MOCK_CONST_METHOD1(EasyStrError, std::string(CURLcode)); + MOCK_METHOD0(MultiInit, CURLM*()); + MOCK_METHOD1(MultiCleanup, CURLMcode(CURLM*)); + MOCK_METHOD2(MultiInfoRead, CURLMsg*(CURLM*, int*)); + MOCK_METHOD2(MultiAddHandle, CURLMcode(CURLM*, CURL*)); + MOCK_METHOD2(MultiRemoveHandle, CURLMcode(CURLM*, CURL*)); + MOCK_METHOD3(MultiSetSocketCallback, + CURLMcode(CURLM*, curl_socket_callback, void*)); + MOCK_METHOD3(MultiSetTimerCallback, + CURLMcode(CURLM*, curl_multi_timer_callback, void*)); + MOCK_METHOD3(MultiAssign, CURLMcode(CURLM*, curl_socket_t, void*)); + MOCK_METHOD4(MultiSocketAction, CURLMcode(CURLM*, curl_socket_t, int, int*)); + MOCK_CONST_METHOD1(MultiStrError, std::string(CURLMcode)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockCurlInterface); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_MOCK_CURL_API_H_ diff --git a/brillo/http/mock_transport.h b/brillo/http/mock_transport.h new file mode 100644 index 0000000..bba1708 --- /dev/null +++ b/brillo/http/mock_transport.h @@ -0,0 +1,44 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_HTTP_MOCK_TRANSPORT_H_ +#define LIBCHROMEOS_BRILLO_HTTP_MOCK_TRANSPORT_H_ + +#include +#include + +#include +#include +#include + +namespace brillo { +namespace http { + +class MockTransport : public Transport { + public: + MockTransport() = default; + + MOCK_METHOD6(CreateConnection, + std::shared_ptr(const std::string&, + const std::string&, + const HeaderList&, + const std::string&, + const std::string&, + brillo::ErrorPtr*)); + MOCK_METHOD2(RunCallbackAsync, + void(const tracked_objects::Location&, const base::Closure&)); + MOCK_METHOD3(StartAsyncTransfer, RequestID(Connection*, + const SuccessCallback&, + const ErrorCallback&)); + MOCK_METHOD1(CancelRequest, bool(RequestID)); + MOCK_METHOD1(SetDefaultTimeout, void(base::TimeDelta)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockTransport); +}; + +} // namespace http +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_HTTP_MOCK_TRANSPORT_H_ diff --git a/brillo/key_value_store.cc b/brillo/key_value_store.cc new file mode 100644 index 0000000..2efa50e --- /dev/null +++ b/brillo/key_value_store.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/key_value_store.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using std::map; +using std::string; +using std::vector; + +namespace brillo { + +namespace { + +// Values used for booleans. +const char kTrueValue[] = "true"; +const char kFalseValue[] = "false"; + +// Returns a copy of |key| with leading and trailing whitespace removed. +string TrimKey(const string& key) { + string trimmed_key; + base::TrimWhitespace(key, base::TRIM_ALL, &trimmed_key); + CHECK(!trimmed_key.empty()); + return trimmed_key; +} + +} // namespace + +bool KeyValueStore::Load(const base::FilePath& path) { + string file_data; + if (!base::ReadFileToString(path, &file_data)) + return false; + return LoadFromString(file_data); +} + +bool KeyValueStore::LoadFromString(const std::string& data) { + // Split along '\n', then along '='. + vector lines; + base::SplitStringDontTrim(data, '\n', &lines); + for (auto it = lines.begin(); it != lines.end(); ++it) { + std::string line; + base::TrimWhitespace(*it, base::TRIM_LEADING, &line); + if (line.empty() || line.front() == '#') + continue; + + std::string key; + std::string value; + if (!string_utils::SplitAtFirst(line, "=", &key, &value, false)) + return false; + + base::TrimWhitespace(key, base::TRIM_TRAILING, &key); + if (key.empty()) + return false; + + // Append additional lines to the value as long as we see trailing + // backslashes. + while (!value.empty() && value.back() == '\\') { + ++it; + if (it == lines.end() || it->empty()) + return false; + value.pop_back(); + value += *it; + } + + store_[key] = value; + } + return true; +} + +bool KeyValueStore::Save(const base::FilePath& path) const { + return base::ImportantFileWriter::WriteFileAtomically(path, SaveToString()); +} + +string KeyValueStore::SaveToString() const { + string data; + for (const auto& key_value : store_) + data += key_value.first + "=" + key_value.second + "\n"; + return data; +} + +bool KeyValueStore::GetString(const string& key, string* value) const { + const auto key_value = store_.find(TrimKey(key)); + if (key_value == store_.end()) + return false; + *value = key_value->second; + return true; +} + +void KeyValueStore::SetString(const string& key, const string& value) { + store_[TrimKey(key)] = value; +} + +bool KeyValueStore::GetBoolean(const string& key, bool* value) const { + string string_value; + if (!GetString(key, &string_value)) + return false; + + if (string_value == kTrueValue) { + *value = true; + return true; + } else if (string_value == kFalseValue) { + *value = false; + return true; + } + return false; +} + +void KeyValueStore::SetBoolean(const string& key, bool value) { + SetString(key, value ? kTrueValue : kFalseValue); +} + +std::vector KeyValueStore::GetKeys() const { + return GetMapKeysAsVector(store_); +} + +} // namespace brillo diff --git a/brillo/key_value_store.h b/brillo/key_value_store.h new file mode 100644 index 0000000..0511326 --- /dev/null +++ b/brillo/key_value_store.h @@ -0,0 +1,76 @@ +// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// These functions can parse a blob of data that's formatted as a simple +// key value store. Each key/value pair is stored on its own line and +// separated by the first '=' on the line. + +#ifndef LIBCHROMEOS_BRILLO_KEY_VALUE_STORE_H_ +#define LIBCHROMEOS_BRILLO_KEY_VALUE_STORE_H_ + +#include +#include +#include + +#include +#include + +namespace brillo { + +class BRILLO_EXPORT KeyValueStore { + public: + // Creates an empty KeyValueStore. + KeyValueStore() = default; + virtual ~KeyValueStore() = default; + + // Loads the key=value pairs from the given |path|. Lines starting with '#' + // and empty lines are ignored, and whitespace around keys is trimmed. + // Trailing backslashes may be used to extend values across multiple lines. + // Adds all the read key=values to the store, overriding those already defined + // but persisting the ones that aren't present on the passed file. Returns + // whether reading the file succeeded. + bool Load(const base::FilePath& path); + + // Loads the key=value pairs parsing the text passed in |data|. See Load() for + // details. + // Returns whether the parsing succeeded. + bool LoadFromString(const std::string& data); + + // Saves the current store to the given |path| file. See SaveToString() for + // details on the formate of the created file. + // Returns whether the file creation succeeded. + bool Save(const base::FilePath& path) const; + + // Returns a string with the contents of the store as key=value lines. + // Calling LoadFromString() and then SaveToString() may result in different + // result if the original string contained backslash-terminated lines (i.e. + // these values will be rewritten on single lines), comments or empty lines. + std::string SaveToString() const; + + // Getter for the given key. Returns whether the key was found on the store. + bool GetString(const std::string& key, std::string* value) const; + + // Setter for the given key. It overrides the key if already exists. + void SetString(const std::string& key, const std::string& value); + + // Boolean getter. Returns whether the key was found on the store and if it + // has a valid value ("true" or "false"). + bool GetBoolean(const std::string& key, bool* value) const; + + // Boolean setter. Sets the value as "true" or "false". + void SetBoolean(const std::string& key, bool value); + + // Retrieves the keys for all values currently stored in the map. + std::vector GetKeys() const; + + private: + // The map storing all the key-value pairs. + std::map store_; + + DISALLOW_COPY_AND_ASSIGN(KeyValueStore); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_KEY_VALUE_STORE_H_ diff --git a/brillo/key_value_store_unittest.cc b/brillo/key_value_store_unittest.cc new file mode 100644 index 0000000..cd18e89 --- /dev/null +++ b/brillo/key_value_store_unittest.cc @@ -0,0 +1,192 @@ +// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using base::FilePath; +using base::ReadFileToString; +using std::map; +using std::string; +using std::vector; + +namespace brillo { + +class KeyValueStoreTest : public ::testing::Test { + protected: + // Returns the value from |store_| corresponding to |key|, or an empty string + // if the key is not present. Crashes if the store returns an empty value. + string GetNonemptyStringValue(const string& key) { + string value; + if (store_.GetString(key, &value)) + CHECK(!value.empty()); + return value; + } + + KeyValueStore store_; // KeyValueStore under test. +}; + +TEST_F(KeyValueStoreTest, LoadAndSaveFromFile) { + base::ScopedTempDir temp_dir_; + CHECK(temp_dir_.CreateUniqueTempDir()); + base::FilePath temp_file_ = temp_dir_.path().Append("temp.conf"); + base::FilePath saved_temp_file_ = temp_dir_.path().Append("saved_temp.conf"); + + string blob = "A=B\n# Comment\n"; + ASSERT_EQ(blob.size(), base::WriteFile(temp_file_, blob.data(), blob.size())); + ASSERT_TRUE(store_.Load(temp_file_)); + + string value; + EXPECT_TRUE(store_.GetString("A", &value)); + EXPECT_EQ("B", value); + + ASSERT_TRUE(store_.Save(saved_temp_file_)); + string read_blob; + ASSERT_TRUE(ReadFileToString(FilePath(saved_temp_file_), &read_blob)); + EXPECT_EQ("A=B\n", read_blob); +} + +TEST_F(KeyValueStoreTest, CommentsAreIgnored) { + EXPECT_TRUE(store_.LoadFromString( + "# comment\nA=B\n\n\n#another=comment\n # leading spaces\n")); + EXPECT_EQ("A=B\n", store_.SaveToString()); +} + +TEST_F(KeyValueStoreTest, EmptyTest) { + EXPECT_TRUE(store_.LoadFromString("")); + EXPECT_EQ("", store_.SaveToString()); +} + +TEST_F(KeyValueStoreTest, LoadAndReloadTest) { + EXPECT_TRUE(store_.LoadFromString( + "A=B\nC=\nFOO=BAR=BAZ\nBAR=BAX\nMISSING=NEWLINE")); + + map expected = {{"A", "B"}, + {"C", ""}, + {"FOO", "BAR=BAZ"}, + {"BAR", "BAX"}, + {"MISSING", "NEWLINE"}}; + + // Test expected values. + string value; + for (const auto& it : expected) { + EXPECT_TRUE(store_.GetString(it.first, &value)); + EXPECT_EQ(it.second, value) << "Testing key: " << it.first; + } + + // Save, load and test again. + KeyValueStore new_store; + ASSERT_TRUE(new_store.LoadFromString(store_.SaveToString())); + + for (const auto& it : expected) { + EXPECT_TRUE(new_store.GetString(it.first, &value)) << "key: " << it.first; + EXPECT_EQ(it.second, value) << "key: " << it.first; + } +} + +TEST_F(KeyValueStoreTest, SimpleBooleanTest) { + bool result; + EXPECT_FALSE(store_.GetBoolean("A", &result)); + + store_.SetBoolean("A", true); + EXPECT_TRUE(store_.GetBoolean("A", &result)); + EXPECT_TRUE(result); + + store_.SetBoolean("A", false); + EXPECT_TRUE(store_.GetBoolean("A", &result)); + EXPECT_FALSE(result); +} + +TEST_F(KeyValueStoreTest, BooleanParsingTest) { + string blob = "TRUE=true\nfalse=false\nvar=false\nDONT_SHOUT=TRUE\n"; + EXPECT_TRUE(store_.LoadFromString(blob)); + + map expected = { + {"TRUE", true}, {"false", false}, {"var", false}}; + bool value; + EXPECT_FALSE(store_.GetBoolean("DONT_SHOUT", &value)); + string str_value; + EXPECT_TRUE(store_.GetString("DONT_SHOUT", &str_value)); + + // Test expected values. + for (const auto& it : expected) { + EXPECT_TRUE(store_.GetBoolean(it.first, &value)) << "key: " << it.first; + EXPECT_EQ(it.second, value) << "key: " << it.first; + } +} + +TEST_F(KeyValueStoreTest, TrimWhitespaceAroundKey) { + EXPECT_TRUE(store_.LoadFromString(" a=1\nb =2\n c =3\n")); + + EXPECT_EQ("1", GetNonemptyStringValue("a")); + EXPECT_EQ("2", GetNonemptyStringValue("b")); + EXPECT_EQ("3", GetNonemptyStringValue("c")); + + // Keys should also be trimmed when setting new values. + store_.SetString(" foo ", "4"); + EXPECT_EQ("4", GetNonemptyStringValue("foo")); + + store_.SetBoolean(" bar ", true); + bool value = false; + ASSERT_TRUE(store_.GetBoolean("bar", &value)); + EXPECT_TRUE(value); +} + +TEST_F(KeyValueStoreTest, IgnoreWhitespaceLine) { + EXPECT_TRUE(store_.LoadFromString("a=1\n \t \nb=2")); + + EXPECT_EQ("1", GetNonemptyStringValue("a")); + EXPECT_EQ("2", GetNonemptyStringValue("b")); +} + +TEST_F(KeyValueStoreTest, RejectEmptyKeys) { + EXPECT_FALSE(store_.LoadFromString("=1")); + EXPECT_FALSE(store_.LoadFromString(" =2")); + + // Trying to set an empty (after trimming) key should fail an assert. + EXPECT_DEATH(store_.SetString(" ", "3"), ""); + EXPECT_DEATH(store_.SetBoolean(" ", "4"), ""); +} + +TEST_F(KeyValueStoreTest, RejectBogusLines) { + EXPECT_FALSE(store_.LoadFromString("a=1\nbogus\nb=2")); +} + +TEST_F(KeyValueStoreTest, MultilineValue) { + EXPECT_TRUE(store_.LoadFromString("a=foo\nb=bar\\\n baz \\ \nc=3\n")); + + EXPECT_EQ("foo", GetNonemptyStringValue("a")); + EXPECT_EQ("bar baz \\ ", GetNonemptyStringValue("b")); + EXPECT_EQ("3", GetNonemptyStringValue("c")); +} + +TEST_F(KeyValueStoreTest, UnterminatedMultilineValue) { + EXPECT_FALSE(store_.LoadFromString("a=foo\\")); + EXPECT_FALSE(store_.LoadFromString("a=foo\\\n")); + EXPECT_FALSE(store_.LoadFromString("a=foo\\\n\n# blah\n")); +} + +TEST_F(KeyValueStoreTest, GetKeys) { + map entries = { + {"1", "apple"}, {"2", "banana"}, {"3", "cherry"} + }; + for (const auto& it : entries) { + store_.SetString(it.first, it.second); + } + + vector keys = GetMapKeysAsVector(entries); + EXPECT_EQ(keys, store_.GetKeys()); +} + +} // namespace brillo diff --git a/brillo/location_logging.h b/brillo/location_logging.h new file mode 100644 index 0000000..011d1e2 --- /dev/null +++ b/brillo/location_logging.h @@ -0,0 +1,24 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_LOCATION_LOGGING_H_ +#define LIBCHROMEOS_BRILLO_LOCATION_LOGGING_H_ + +// These macros help to log Location objects in verbose mode. + +#include + +#define VLOG_LOC_STREAM(from_here, verbose_level) \ + logging::LogMessage(from_here.file_name(), from_here.line_number(), \ + -verbose_level).stream() + +#define VLOG_LOC(from_here, verbose_level) \ + LAZY_STREAM(VLOG_LOC_STREAM(from_here, verbose_level), \ + VLOG_IS_ON(verbose_level)) + +#define DVLOG_LOC(from_here, verbose_level) \ + LAZY_STREAM(VLOG_LOC_STREAM(from_here, verbose_level), \ + ::logging::DEBUG_MODE && VLOG_IS_ON(verbose_level)) + +#endif // LIBCHROMEOS_BRILLO_LOCATION_LOGGING_H_ diff --git a/brillo/make_unique_ptr.h b/brillo/make_unique_ptr.h new file mode 100644 index 0000000..35b135e --- /dev/null +++ b/brillo/make_unique_ptr.h @@ -0,0 +1,25 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MAKE_UNIQUE_PTR_H_ +#define LIBCHROMEOS_BRILLO_MAKE_UNIQUE_PTR_H_ + +#include + +namespace brillo { + +// A function to convert T* into unique_ptr +// Doing e.g. make_unique_ptr(new FooBarBaz(arg)) is a shorter notation +// for unique_ptr>(new FooBarBaz(arg)) +// Basically the same as Chromium's make_scoped_ptr(). +// Deliberately not named "make_unique" to avoid conflicting with the similar, +// but more complex and semantically different C++14 function. +template +std::unique_ptr make_unique_ptr(T* ptr) { + return std::unique_ptr(ptr); +} + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MAKE_UNIQUE_PTR_H_ diff --git a/brillo/map_utils.h b/brillo/map_utils.h new file mode 100644 index 0000000..b7f1658 --- /dev/null +++ b/brillo/map_utils.h @@ -0,0 +1,71 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MAP_UTILS_H_ +#define LIBCHROMEOS_BRILLO_MAP_UTILS_H_ + +#include +#include +#include +#include + +namespace brillo { + +// Given an STL map, returns a set containing all keys from the map. +template +inline std::set GetMapKeys(const T& map) { + std::set keys; + for (const auto& pair : map) + keys.insert(keys.end(), pair.first); // Map keys are already sorted. + return keys; +} + +// Given an STL map, returns a vector containing all keys from the map. +// The keys in the vector are sorted. +template +inline std::vector GetMapKeysAsVector(const T& map) { + std::vector keys; + keys.reserve(map.size()); + for (const auto& pair : map) + keys.push_back(pair.first); + return keys; +} + +// Given an STL map, returns a vector containing all values from the map. +template +inline std::vector GetMapValues(const T& map) { + std::vector values; + values.reserve(map.size()); + for (const auto& pair : map) + values.push_back(pair.second); + return values; +} + +// Given an STL map, returns a vector of key-value pairs from the map. +template +inline std::vector> +MapToVector(const T& map) { + std::vector> vector; + vector.reserve(map.size()); + for (const auto& pair : map) + vector.push_back(pair); + return vector; +} + +// Given an STL map, returns the value associated with a given key or a default +// value if the key is not present in the map. +template +inline typename T::mapped_type GetOrDefault( + const T& map, + typename T::key_type key, + const typename T::mapped_type& def) { + typename T::const_iterator it = map.find(key); + if (it == map.end()) + return def; + return it->second; +} + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MAP_UTILS_H_ diff --git a/brillo/map_utils_unittest.cc b/brillo/map_utils_unittest.cc new file mode 100644 index 0000000..19bda1d --- /dev/null +++ b/brillo/map_utils_unittest.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include + +namespace brillo { + +class MapUtilsTest : public ::testing::Test { + public: + void SetUp() override { + map_ = { + {"key1", 1}, {"key2", 2}, {"key3", 3}, {"key4", 4}, {"key5", 5}, + }; + } + + void TearDown() override { map_.clear(); } + + std::map map_; +}; + +TEST_F(MapUtilsTest, GetMapKeys) { + std::set keys = GetMapKeys(map_); + EXPECT_EQ((std::set{"key1", "key2", "key3", "key4", "key5"}), + keys); +} + +TEST_F(MapUtilsTest, GetMapKeysAsVector) { + std::vector keys = GetMapKeysAsVector(map_); + EXPECT_EQ((std::vector{"key1", "key2", "key3", "key4", "key5"}), + keys); +} + +TEST_F(MapUtilsTest, GetMapValues) { + std::vector values = GetMapValues(map_); + EXPECT_EQ((std::vector{1, 2, 3, 4, 5}), values); +} + +TEST_F(MapUtilsTest, MapToVector) { + std::vector> elements = MapToVector(map_); + std::vector> expected{ + {"key1", 1}, {"key2", 2}, {"key3", 3}, {"key4", 4}, {"key5", 5}, + }; + EXPECT_EQ(expected, elements); +} + +TEST_F(MapUtilsTest, Empty) { + std::map empty_map; + EXPECT_TRUE(GetMapKeys(empty_map).empty()); + EXPECT_TRUE(GetMapKeysAsVector(empty_map).empty()); + EXPECT_TRUE(GetMapValues(empty_map).empty()); + EXPECT_TRUE(MapToVector(empty_map).empty()); +} + +} // namespace brillo diff --git a/brillo/message_loops/base_message_loop.cc b/brillo/message_loops/base_message_loop.cc new file mode 100644 index 0000000..a24c64b --- /dev/null +++ b/brillo/message_loops/base_message_loop.cc @@ -0,0 +1,342 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include +#include + +#include + +using base::Closure; + +namespace brillo { + +BaseMessageLoop::BaseMessageLoop(base::MessageLoopForIO* base_loop) + : base_loop_(base_loop), + weak_ptr_factory_(this) {} + +BaseMessageLoop::~BaseMessageLoop() { + for (auto& io_task : io_tasks_) { + DVLOG_LOC(io_task.second.location(), 1) + << "Removing file descriptor watcher task_id " << io_task.first + << " leaked on BaseMessageLoop, scheduled from this location."; + io_task.second.StopWatching(); + } + + // Note all pending canceled delayed tasks when destroying the message loop. + size_t lazily_deleted_tasks = 0; + for (const auto& delayed_task : delayed_tasks_) { + if (delayed_task.second.closure.is_null()) { + lazily_deleted_tasks++; + } else { + DVLOG_LOC(delayed_task.second.location, 1) + << "Removing delayed task_id " << delayed_task.first + << " leaked on BaseMessageLoop, scheduled from this location."; + } + } + if (lazily_deleted_tasks) { + LOG(INFO) << "Leaking " << lazily_deleted_tasks << " canceled tasks."; + } +} + +MessageLoop::TaskId BaseMessageLoop::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure &task, + base::TimeDelta delay) { + TaskId task_id = NextTaskId(); + bool base_scheduled = base_loop_->task_runner()->PostDelayedTask( + from_here, + base::Bind(&BaseMessageLoop::OnRanPostedTask, + weak_ptr_factory_.GetWeakPtr(), + task_id), + delay); + DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id + << " to run in " << delay << "."; + if (!base_scheduled) + return MessageLoop::kTaskIdNull; + + delayed_tasks_.emplace(task_id, + DelayedTask{from_here, task_id, std::move(task)}); + return task_id; +} + +MessageLoop::TaskId BaseMessageLoop::WatchFileDescriptor( + const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const Closure &task) { + // base::MessageLoopForIO CHECKS that "fd >= 0", so we handle that case here. + if (fd < 0) + return MessageLoop::kTaskIdNull; + + base::MessageLoopForIO::Mode base_mode = base::MessageLoopForIO::WATCH_READ; + switch (mode) { + case MessageLoop::kWatchRead: + base_mode = base::MessageLoopForIO::WATCH_READ; + break; + case MessageLoop::kWatchWrite: + base_mode = base::MessageLoopForIO::WATCH_WRITE; + break; + default: + return MessageLoop::kTaskIdNull; + } + + TaskId task_id = NextTaskId(); + auto it_bool = io_tasks_.emplace( + std::piecewise_construct, + std::forward_as_tuple(task_id), + std::forward_as_tuple( + from_here, this, task_id, fd, base_mode, persistent, task)); + // This should always insert a new element. + DCHECK(it_bool.second); + bool scheduled = it_bool.first->second.StartWatching(); + DVLOG_LOC(from_here, 1) + << "Watching fd " << fd << " for " + << (mode == MessageLoop::kWatchRead ? "reading" : "writing") + << (persistent ? " persistently" : " just once") + << " as task_id " << task_id + << (scheduled ? " successfully" : " failed."); + + if (!scheduled) { + io_tasks_.erase(task_id); + return MessageLoop::kTaskIdNull; + } + return task_id; +} + +bool BaseMessageLoop::CancelTask(TaskId task_id) { + if (task_id == kTaskIdNull) + return false; + auto delayed_task_it = delayed_tasks_.find(task_id); + if (delayed_task_it == delayed_tasks_.end()) { + // This might be an IOTask then. + auto io_task_it = io_tasks_.find(task_id); + if (io_task_it == io_tasks_.end()) + return false; + return io_task_it->second.CancelTask(); + } + // A DelayedTask was found for this task_id at this point. + + // Check if the callback was already canceled but we have the entry in + // delayed_tasks_ since it didn't fire yet in the message loop. + if (delayed_task_it->second.closure.is_null()) + return false; + + DVLOG_LOC(delayed_task_it->second.location, 1) + << "Removing task_id " << task_id << " scheduled from this location."; + // We reset to closure to a null Closure to release all the resources + // used by this closure at this point, but we don't remove the task_id from + // delayed_tasks_ since we can't tell base::MessageLoopForIO to not run it. + delayed_task_it->second.closure = Closure(); + + return true; +} + +bool BaseMessageLoop::RunOnce(bool may_block) { + run_once_ = true; + base::RunLoop run_loop; // Uses the base::MessageLoopForIO implicitly. + base_run_loop_ = &run_loop; + if (!may_block) + run_loop.RunUntilIdle(); + else + run_loop.Run(); + base_run_loop_ = nullptr; + // If the flag was reset to false, it means a closure was run. + if (!run_once_) + return true; + + run_once_ = false; + return false; +} + +void BaseMessageLoop::Run() { + base::RunLoop run_loop; // Uses the base::MessageLoopForIO implicitly. + base_run_loop_ = &run_loop; + run_loop.Run(); + base_run_loop_ = nullptr; +} + +void BaseMessageLoop::BreakLoop() { + if (base_run_loop_ == nullptr) { + DVLOG(1) << "Message loop not running, ignoring BreakLoop()."; + return; // Message loop not running, nothing to do. + } + base_run_loop_->Quit(); +} + +Closure BaseMessageLoop::QuitClosure() const { + if (base_run_loop_ == nullptr) + return base::Bind(&base::DoNothing); + return base_run_loop_->QuitClosure(); +} + +MessageLoop::TaskId BaseMessageLoop::NextTaskId() { + TaskId res; + do { + res = ++last_id_; + // We would run out of memory before we run out of task ids. + } while (!res || + delayed_tasks_.find(res) != delayed_tasks_.end() || + io_tasks_.find(res) != io_tasks_.end()); + return res; +} + +void BaseMessageLoop::OnRanPostedTask(MessageLoop::TaskId task_id) { + auto task_it = delayed_tasks_.find(task_id); + DCHECK(task_it != delayed_tasks_.end()); + if (!task_it->second.closure.is_null()) { + DVLOG_LOC(task_it->second.location, 1) + << "Running delayed task_id " << task_id + << " scheduled from this location."; + // Mark the task as canceled while we are running it so CancelTask returns + // false. + Closure closure = std::move(task_it->second.closure); + task_it->second.closure = Closure(); + closure.Run(); + + // If the |run_once_| flag is set, it is because we are instructed to run + // only once callback. + if (run_once_) { + run_once_ = false; + BreakLoop(); + } + } + delayed_tasks_.erase(task_it); +} + +void BaseMessageLoop::OnFileReadyPostedTask(MessageLoop::TaskId task_id) { + auto task_it = io_tasks_.find(task_id); + // Even if this task was canceled while we were waiting in the message loop + // for this method to run, the entry in io_tasks_ should still be present, but + // won't do anything. + DCHECK(task_it != io_tasks_.end()); + task_it->second.OnFileReadyPostedTask(); +} + +BaseMessageLoop::IOTask::IOTask(const tracked_objects::Location& location, + BaseMessageLoop* loop, + MessageLoop::TaskId task_id, + int fd, + base::MessageLoopForIO::Mode base_mode, + bool persistent, + const Closure& task) + : location_(location), loop_(loop), task_id_(task_id), + fd_(fd), base_mode_(base_mode), persistent_(persistent), closure_(task) {} + +bool BaseMessageLoop::IOTask::StartWatching() { + return loop_->base_loop_->WatchFileDescriptor( + fd_, persistent_, base_mode_, &fd_watcher_, this); +} + +void BaseMessageLoop::IOTask::StopWatching() { + // This is safe to call even if we are not watching for it. + fd_watcher_.StopWatchingFileDescriptor(); +} + +void BaseMessageLoop::IOTask::OnFileCanReadWithoutBlocking(int /* fd */) { + OnFileReady(); +} + +void BaseMessageLoop::IOTask::OnFileCanWriteWithoutBlocking(int /* fd */) { + OnFileReady(); +} + +void BaseMessageLoop::IOTask::OnFileReady() { + // When the file descriptor becomes available we stop watching for it and + // schedule a task to run the callback from the main loop. The callback will + // run using the same scheduler use to run other delayed tasks, avoiding + // starvation of the available posted tasks if there are file descriptors + // always available. The new posted task will use the same TaskId as the + // current file descriptor watching task an could be canceled in either state, + // when waiting for the file descriptor or waiting in the main loop. + StopWatching(); + bool base_scheduled = loop_->base_loop_->task_runner()->PostTask( + location_, + base::Bind(&BaseMessageLoop::OnFileReadyPostedTask, + loop_->weak_ptr_factory_.GetWeakPtr(), + task_id_)); + posted_task_pending_ = true; + if (base_scheduled) { + DVLOG_LOC(location_, 1) + << "Dispatching task_id " << task_id_ << " for " + << (base_mode_ == base::MessageLoopForIO::WATCH_READ ? + "reading" : "writing") + << " file descriptor " << fd_ << ", scheduled from this location."; + } else { + // In the rare case that PostTask() fails, we fall back to run it directly. + // This would indicate a bigger problem with the message loop setup. + LOG(ERROR) << "Error on base::MessageLoopForIO::PostTask()."; + OnFileReadyPostedTask(); + } +} + +void BaseMessageLoop::IOTask::OnFileReadyPostedTask() { + // We can't access |this| after running the |closure_| since it could call + // CancelTask on its own task_id, so we copy the members we need now. + BaseMessageLoop* loop_ptr = loop_; + DCHECK(posted_task_pending_ = true); + posted_task_pending_ = false; + + // If this task was already canceled, the closure will be null and there is + // nothing else to do here. This execution doesn't count a step for RunOnce() + // unless we have a callback to run. + if (closure_.is_null()) { + loop_->io_tasks_.erase(task_id_); + return; + } + + DVLOG_LOC(location_, 1) + << "Running task_id " << task_id_ << " for " + << (base_mode_ == base::MessageLoopForIO::WATCH_READ ? + "reading" : "writing") + << " file descriptor " << fd_ << ", scheduled from this location."; + + if (persistent_) { + // In the persistent case we just run the callback. If this callback cancels + // the task id, we can't access |this| anymore, so we re-start watching the + // file descriptor before running the callback. + StartWatching(); + closure_.Run(); + } else { + // This will destroy |this|, the fd_watcher and therefore stop watching this + // file descriptor. + Closure closure_copy = std::move(closure_); + loop_->io_tasks_.erase(task_id_); + // Run the closure from the local copy we just made. + closure_copy.Run(); + } + + if (loop_ptr->run_once_) { + loop_ptr->run_once_ = false; + loop_ptr->BreakLoop(); + } +} + +bool BaseMessageLoop::IOTask::CancelTask() { + if (closure_.is_null()) + return false; + + DVLOG_LOC(location_, 1) + << "Removing task_id " << task_id_ << " scheduled from this location."; + + if (!posted_task_pending_) { + // Destroying the FileDescriptorWatcher implicitly stops watching the file + // descriptor. This will delete our instance. + loop_->io_tasks_.erase(task_id_); + return true; + } + // The IOTask is waiting for the message loop to run its delayed task, so + // it is not watching for the file descriptor. We release the closure + // resources now but keep the IOTask instance alive while we wait for the + // callback to run and delete the IOTask. + closure_ = Closure(); + return true; +} + +} // namespace brillo diff --git a/brillo/message_loops/base_message_loop.h b/brillo/message_loops/base_message_loop.h new file mode 100644 index 0000000..76bd94a --- /dev/null +++ b/brillo/message_loops/base_message_loop.h @@ -0,0 +1,155 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_ +#define LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_ + +// BaseMessageLoop is a brillo::MessageLoop implementation based on +// base::MessageLoopForIO. This allows to mix new code using +// brillo::MessageLoop and legacy code using base::MessageLoopForIO in the +// same thread and share a single main loop. This disadvantage of using this +// class is a less efficient implementation of CancelTask() for delayed tasks +// since base::MessageLoopForIO doesn't provide a way to remove the event. + +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace brillo { + +class BRILLO_EXPORT BaseMessageLoop : public MessageLoop { + public: + explicit BaseMessageLoop(base::MessageLoopForIO* base_loop); + ~BaseMessageLoop() override; + + // MessageLoop overrides. + TaskId PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) override; + using MessageLoop::PostDelayedTask; + TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const base::Closure& task) override; + using MessageLoop::WatchFileDescriptor; + bool CancelTask(TaskId task_id) override; + bool RunOnce(bool may_block) override; + void Run() override; + void BreakLoop() override; + + // Returns a callback that will quit the current message loop. If the message + // loop is not running, an empty (null) callback is returned. + base::Closure QuitClosure() const; + + private: + // Called by base::MessageLoopForIO when is time to call the callback + // scheduled with Post*Task() of id |task_id|, even if it was canceled. + void OnRanPostedTask(MessageLoop::TaskId task_id); + + // Called from the message loop when the IOTask should run the scheduled + // callback. This is a simple wrapper of IOTask::OnFileReadyPostedTask() + // posted from the BaseMessageLoop so it is deleted when the BaseMessageLoop + // goes out of scope since we can't cancel the callback otherwise. + void OnFileReadyPostedTask(MessageLoop::TaskId task_id); + + // Return a new unused task_id. + TaskId NextTaskId(); + + struct DelayedTask { + tracked_objects::Location location; + + MessageLoop::TaskId task_id; + base::Closure closure; + }; + + std::map delayed_tasks_; + + class IOTask : public base::MessageLoopForIO::Watcher { + public: + IOTask(const tracked_objects::Location& location, + BaseMessageLoop* loop, + MessageLoop::TaskId task_id, + int fd, + base::MessageLoopForIO::Mode base_mode, + bool persistent, + const base::Closure& task); + + const tracked_objects::Location& location() const { return location_; } + + // Used to start/stop watching the file descriptor while keeping the + // IOTask entry available. + bool StartWatching(); + void StopWatching(); + + // Called from the message loop as a PostTask() when the file descriptor is + // available, scheduled to run from OnFileReady(). + void OnFileReadyPostedTask(); + + // Cancel the IOTask and returns whether it was actually canceled, with the + // same semantics as MessageLoop::CancelTask(). + bool CancelTask(); + + private: + tracked_objects::Location location_; + BaseMessageLoop* loop_; + + // These are the arguments passed in the constructor, basically forwarding + // all the arguments passed to WatchFileDescriptor() plus the assigned + // TaskId for this task. + MessageLoop::TaskId task_id_; + int fd_; + base::MessageLoopForIO::Mode base_mode_; + bool persistent_; + base::Closure closure_; + + base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_; + + // Tells whether there is a pending call to OnFileReadPostedTask(). + bool posted_task_pending_{false}; + + // base::MessageLoopForIO::Watcher overrides: + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override; + + // Common implementation for both the read and write case. + void OnFileReady(); + + DISALLOW_COPY_AND_ASSIGN(IOTask); + }; + + std::map io_tasks_; + + // Flag to mark that we should run the message loop only one iteration. + bool run_once_{false}; + + // The last used TaskId. While base::MessageLoopForIO doesn't allow to cancel + // delayed tasks, we handle that functionality by not running the callback + // if it fires at a later point. + MessageLoop::TaskId last_id_{kTaskIdNull}; + + // The pointer to the libchrome base::MessageLoopForIO we are wrapping with + // this interface. + base::MessageLoopForIO* base_loop_; + + // The RunLoop instance used to run the main loop from Run(). + base::RunLoop* base_run_loop_{nullptr}; + + // We use a WeakPtrFactory to schedule tasks with the base::MessageLoopForIO + // since we can't cancel the callbacks we have scheduled there once this + // instance is destroyed. + base::WeakPtrFactory weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(BaseMessageLoop); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_ diff --git a/brillo/message_loops/fake_message_loop.cc b/brillo/message_loops/fake_message_loop.cc new file mode 100644 index 0000000..4d0f157 --- /dev/null +++ b/brillo/message_loops/fake_message_loop.cc @@ -0,0 +1,141 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +namespace brillo { + +FakeMessageLoop::FakeMessageLoop(base::SimpleTestClock* clock) + : test_clock_(clock) { +} + +MessageLoop::TaskId FakeMessageLoop::PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + // If no SimpleTestClock was provided, we use the last time we fired a + // callback. In this way, tasks scheduled from a Closure will have the right + // time. + if (test_clock_) + current_time_ = test_clock_->Now(); + MessageLoop::TaskId current_id = ++last_id_; + // FakeMessageLoop is limited to only 2^64 tasks. That should be enough. + CHECK(current_id); + tasks_.emplace(current_id, ScheduledTask{from_here, false, task}); + fire_order_.push(std::make_pair(current_time_ + delay, current_id)); + VLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << current_id + << " to run at " << current_time_ + delay + << " (in " << delay << ")."; + return current_id; +} + +MessageLoop::TaskId FakeMessageLoop::WatchFileDescriptor( + const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const base::Closure& task) { + MessageLoop::TaskId current_id = ++last_id_; + // FakeMessageLoop is limited to only 2^64 tasks. That should be enough. + CHECK(current_id); + tasks_.emplace(current_id, ScheduledTask{from_here, persistent, task}); + fds_watched_.emplace(std::make_pair(fd, mode), current_id); + return current_id; +} + +bool FakeMessageLoop::CancelTask(TaskId task_id) { + if (task_id == MessageLoop::kTaskIdNull) + return false; + bool ret = tasks_.erase(task_id) > 0; + VLOG_IF(1, ret) << "Removing task_id " << task_id; + return ret; +} + +bool FakeMessageLoop::RunOnce(bool may_block) { + if (test_clock_) + current_time_ = test_clock_->Now(); + // Try to fire ready file descriptors first. + for (const auto& fd_mode : fds_ready_) { + const auto& fd_watched = fds_watched_.find(fd_mode); + if (fd_watched == fds_watched_.end()) + continue; + // The fd_watched->second task might have been canceled and we never removed + // it from the fds_watched_, so we fix that now. + const auto& scheduled_task_ref = tasks_.find(fd_watched->second); + if (scheduled_task_ref == tasks_.end()) { + fds_watched_.erase(fd_watched); + continue; + } + VLOG_LOC(scheduled_task_ref->second.location, 1) + << "Running task_id " << fd_watched->second + << " for watching file descriptor " << fd_mode.first << " for " + << (fd_mode.second == MessageLoop::kWatchRead ? "reading" : "writing") + << (scheduled_task_ref->second.persistent ? + " persistently" : " just once") + << " scheduled from this location."; + if (scheduled_task_ref->second.persistent) { + scheduled_task_ref->second.callback.Run(); + } else { + base::Closure callback = std::move(scheduled_task_ref->second.callback); + tasks_.erase(scheduled_task_ref); + fds_watched_.erase(fd_watched); + callback.Run(); + } + return true; + } + + // Try to fire time-based callbacks afterwards. + while (!fire_order_.empty() && + (may_block || fire_order_.top().first <= current_time_)) { + const auto task_ref = fire_order_.top(); + fire_order_.pop(); + // We need to skip tasks in the priority_queue not in the |tasks_| map. + // This is normal if the task was canceled, as there is no efficient way + // to remove a task from the priority_queue. + const auto scheduled_task_ref = tasks_.find(task_ref.second); + if (scheduled_task_ref == tasks_.end()) + continue; + // Advance the clock to the task firing time, if needed. + if (current_time_ < task_ref.first) { + current_time_ = task_ref.first; + if (test_clock_) + test_clock_->SetNow(current_time_); + } + // Move the Closure out of the map before delete it. We need to delete the + // entry from the map before we call the callback, since calling CancelTask + // for the task you are running now should fail and return false. + base::Closure callback = std::move(scheduled_task_ref->second.callback); + VLOG_LOC(scheduled_task_ref->second.location, 1) + << "Running task_id " << task_ref.second + << " at time " << current_time_ << " from this location."; + tasks_.erase(scheduled_task_ref); + + callback.Run(); + return true; + } + return false; +} + +void FakeMessageLoop::SetFileDescriptorReadiness(int fd, + WatchMode mode, + bool ready) { + if (ready) + fds_ready_.emplace(fd, mode); + else + fds_ready_.erase(std::make_pair(fd, mode)); +} + +bool FakeMessageLoop::PendingTasks() { + for (const auto& task : tasks_) { + VLOG_LOC(task.second.location, 1) + << "Pending " << (task.second.persistent ? "persistent " : "") + << "task_id " << task.first << " scheduled from here."; + } + return !tasks_.empty(); +} + +} // namespace brillo diff --git a/brillo/message_loops/fake_message_loop.h b/brillo/message_loops/fake_message_loop.h new file mode 100644 index 0000000..043152b --- /dev/null +++ b/brillo/message_loops/fake_message_loop.h @@ -0,0 +1,99 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_ +#define LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace brillo { + +// The FakeMessageLoop implements a message loop that doesn't block or wait for +// time based tasks to be ready. The tasks are executed in the order they should +// be executed in a real message loop implementation, but the time is advanced +// to the time when the first task should be executed instead of blocking. +// To keep a consistent notion of time for other classes, FakeMessageLoop +// optionally updates a SimpleTestClock instance when it needs to advance the +// clock. +// This message loop implementation is useful for unittests. +class BRILLO_EXPORT FakeMessageLoop : public MessageLoop { + public: + // Create a FakeMessageLoop optionally using a SimpleTestClock to update the + // time when Run() or RunOnce(true) are called and should block. + explicit FakeMessageLoop(base::SimpleTestClock* clock); + ~FakeMessageLoop() override = default; + + TaskId PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) override; + using MessageLoop::PostDelayedTask; + TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const base::Closure& task) override; + using MessageLoop::WatchFileDescriptor; + bool CancelTask(TaskId task_id) override; + bool RunOnce(bool may_block) override; + + // FakeMessageLoop methods: + + // Pretend, for the purpose of the FakeMessageLoop watching for file + // descriptors, that the file descriptor |fd| readiness to perform the + // operation described by |mode| is |ready|. Initially, no file descriptor + // is ready for any operation. + void SetFileDescriptorReadiness(int fd, WatchMode mode, bool ready); + + // Return whether there are peding tasks. Useful to check that no + // callbacks were leaked. + bool PendingTasks(); + + private: + struct ScheduledTask { + tracked_objects::Location location; + bool persistent; + base::Closure callback; + }; + + // The sparse list of scheduled pending callbacks. + std::map tasks_; + + // Using std::greater<> for the priority_queue means that the top() of the + // queue is the lowest (earliest) time, and for the same time, the smallest + // TaskId. This determines the order in which the tasks will be fired. + std::priority_queue< + std::pair, + std::vector>, + std::greater>> fire_order_; + + // The bag of watched (fd, mode) pair associated with the TaskId that's + // watching them. + std::multimap, MessageLoop::TaskId> fds_watched_; + + // The set of (fd, mode) pairs that are faked as ready. + std::set> fds_ready_; + + base::SimpleTestClock* test_clock_ = nullptr; + base::Time current_time_ = base::Time::FromDoubleT(1246996800.); + + MessageLoop::TaskId last_id_ = kTaskIdNull; + + DISALLOW_COPY_AND_ASSIGN(FakeMessageLoop); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_ diff --git a/brillo/message_loops/fake_message_loop_unittest.cc b/brillo/message_loops/fake_message_loop_unittest.cc new file mode 100644 index 0000000..10b551e --- /dev/null +++ b/brillo/message_loops/fake_message_loop_unittest.cc @@ -0,0 +1,116 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +using base::Bind; +using base::Time; +using base::TimeDelta; +using std::vector; + +namespace brillo { + +using TaskId = MessageLoop::TaskId; + +class FakeMessageLoopTest : public ::testing::Test { + protected: + void SetUp() override { + loop_.reset(new FakeMessageLoop(nullptr)); + EXPECT_TRUE(loop_.get()); + } + void TearDown() override { + EXPECT_FALSE(loop_->PendingTasks()); + } + + base::SimpleTestClock clock_; + std::unique_ptr loop_; +}; + +TEST_F(FakeMessageLoopTest, CancelTaskInvalidValuesTest) { + EXPECT_FALSE(loop_->CancelTask(MessageLoop::kTaskIdNull)); + EXPECT_FALSE(loop_->CancelTask(1234)); +} + +TEST_F(FakeMessageLoopTest, PostDelayedTaskRunsInOrder) { + vector order; + loop_->PostDelayedTask(Bind([&order]() { order.push_back(1); }), + TimeDelta::FromSeconds(1)); + loop_->PostDelayedTask(Bind([&order]() { order.push_back(4); }), + TimeDelta::FromSeconds(4)); + loop_->PostDelayedTask(Bind([&order]() { order.push_back(3); }), + TimeDelta::FromSeconds(3)); + loop_->PostDelayedTask(Bind([&order]() { order.push_back(2); }), + TimeDelta::FromSeconds(2)); + // Run until all the tasks are run. + loop_->Run(); + EXPECT_EQ((vector{1, 2, 3, 4}), order); +} + +TEST_F(FakeMessageLoopTest, PostDelayedTaskAdvancesTheTime) { + Time start = Time::FromInternalValue(1000000); + clock_.SetNow(start); + loop_.reset(new FakeMessageLoop(&clock_)); + loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(1)); + loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(2)); + EXPECT_FALSE(loop_->RunOnce(false)); + // If the callback didn't run, the time shouldn't change. + EXPECT_EQ(start, clock_.Now()); + + // If we run only one callback, the time should be set to the time that + // callack ran. + EXPECT_TRUE(loop_->RunOnce(true)); + EXPECT_EQ(start + TimeDelta::FromSeconds(1), clock_.Now()); + + // If the clock is advanced manually, we should be able to run the + // callback without blocking, since the firing time is in the past. + clock_.SetNow(start + TimeDelta::FromSeconds(3)); + EXPECT_TRUE(loop_->RunOnce(false)); + // The time should not change even if the callback is due in the past. + EXPECT_EQ(start + TimeDelta::FromSeconds(3), clock_.Now()); +} + +TEST_F(FakeMessageLoopTest, WatchFileDescriptorWaits) { + int fd = 1234; + // We will simulate this situation. At the beginning, we will watch for a + // file descriptor that won't trigger for 10s. Then we will pretend it is + // ready after 10s and expect its callback to run just once. + int called = 0; + TaskId task_id = loop_->WatchFileDescriptor( + FROM_HERE, fd, MessageLoop::kWatchRead, false, + Bind([&called] { called++; })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + + EXPECT_NE(MessageLoop::kTaskIdNull, + loop_->PostDelayedTask(Bind([this] { this->loop_->BreakLoop(); }), + TimeDelta::FromSeconds(10))); + EXPECT_NE(MessageLoop::kTaskIdNull, + loop_->PostDelayedTask(Bind([this] { this->loop_->BreakLoop(); }), + TimeDelta::FromSeconds(20))); + loop_->Run(); + EXPECT_EQ(0, called); + + loop_->SetFileDescriptorReadiness(fd, MessageLoop::kWatchRead, true); + loop_->Run(); + EXPECT_EQ(1, called); + EXPECT_FALSE(loop_->CancelTask(task_id)); +} + +TEST_F(FakeMessageLoopTest, PendingTasksTest) { + loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(1)); + EXPECT_TRUE(loop_->PendingTasks()); + loop_->Run(); +} + +} // namespace brillo diff --git a/brillo/message_loops/glib_message_loop.cc b/brillo/message_loops/glib_message_loop.cc new file mode 100644 index 0000000..20c271d --- /dev/null +++ b/brillo/message_loops/glib_message_loop.cc @@ -0,0 +1,194 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include + +using base::Closure; + +namespace brillo { + +GlibMessageLoop::GlibMessageLoop() { + loop_ = g_main_loop_new(g_main_context_default(), FALSE); +} + +GlibMessageLoop::~GlibMessageLoop() { + // Cancel all pending tasks when destroying the message loop. + for (const auto& task : tasks_) { + DVLOG_LOC(task.second->location, 1) + << "Removing task_id " << task.second->task_id + << " leaked on GlibMessageLoop, scheduled from this location."; + g_source_remove(task.second->source_id); + } + g_main_loop_unref(loop_); +} + +MessageLoop::TaskId GlibMessageLoop::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure &task, + base::TimeDelta delay) { + TaskId task_id = NextTaskId(); + // Note: While we store persistent = false in the ScheduledTask object, we + // don't check it in OnRanPostedTask() since it is always false for delayed + // tasks. This is only used for WatchFileDescriptor below. + ScheduledTask* scheduled_task = new ScheduledTask{ + this, from_here, task_id, 0, false, std::move(task)}; + DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id + << " to run in " << delay << "."; + scheduled_task->source_id = g_timeout_add_full( + G_PRIORITY_DEFAULT, + delay.InMillisecondsRoundedUp(), + &GlibMessageLoop::OnRanPostedTask, + reinterpret_cast(scheduled_task), + DestroyPostedTask); + tasks_[task_id] = scheduled_task; + return task_id; +} + +MessageLoop::TaskId GlibMessageLoop::WatchFileDescriptor( + const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const Closure &task) { + // Quick check to see if the fd is valid. + if (fcntl(fd, F_GETFD) == -1 && errno == EBADF) + return MessageLoop::kTaskIdNull; + + GIOCondition condition = G_IO_NVAL; + switch (mode) { + case MessageLoop::kWatchRead: + condition = static_cast(G_IO_IN | G_IO_HUP | G_IO_NVAL); + break; + case MessageLoop::kWatchWrite: + condition = static_cast(G_IO_OUT | G_IO_HUP | G_IO_NVAL); + break; + default: + return MessageLoop::kTaskIdNull; + } + + // TODO(deymo): Used g_unix_fd_add_full() instead of g_io_add_watch_full() + // when/if we switch to glib 2.36 or newer so we don't need to create a + // GIOChannel for this. + GIOChannel* io_channel = g_io_channel_unix_new(fd); + if (!io_channel) + return MessageLoop::kTaskIdNull; + GError* error = nullptr; + GIOStatus status = g_io_channel_set_encoding(io_channel, nullptr, &error); + if (status != G_IO_STATUS_NORMAL) { + LOG(ERROR) << "GError(" << error->code << "): " + << (error->message ? error->message : "(unknown)"); + g_error_free(error); + // g_io_channel_set_encoding() documentation states that this should be + // valid in this context (a new io_channel), but enforce the check in + // debug mode. + DCHECK(status == G_IO_STATUS_NORMAL); + return MessageLoop::kTaskIdNull; + } + + TaskId task_id = NextTaskId(); + ScheduledTask* scheduled_task = new ScheduledTask{ + this, from_here, task_id, 0, persistent, std::move(task)}; + scheduled_task->source_id = g_io_add_watch_full( + io_channel, + G_PRIORITY_DEFAULT, + condition, + &GlibMessageLoop::OnWatchedFdReady, + reinterpret_cast(scheduled_task), + DestroyPostedTask); + // g_io_add_watch_full() increases the reference count on the newly created + // io_channel, so we can dereference it now and it will be free'd once the + // source is removed or now if g_io_add_watch_full() failed. + g_io_channel_unref(io_channel); + + DVLOG_LOC(from_here, 1) + << "Watching fd " << fd << " for " + << (mode == MessageLoop::kWatchRead ? "reading" : "writing") + << (persistent ? " persistently" : " just once") + << " as task_id " << task_id + << (scheduled_task->source_id ? " successfully" : " failed."); + + if (!scheduled_task->source_id) { + delete scheduled_task; + return MessageLoop::kTaskIdNull; + } + tasks_[task_id] = scheduled_task; + return task_id; +} + +bool GlibMessageLoop::CancelTask(TaskId task_id) { + if (task_id == kTaskIdNull) + return false; + const auto task = tasks_.find(task_id); + // It is a programmer error to attempt to remove a non-existent source. + if (task == tasks_.end()) + return false; + DVLOG_LOC(task->second->location, 1) + << "Removing task_id " << task_id << " scheduled from this location."; + guint source_id = task->second->source_id; + // We remove here the entry from the tasks_ map, the pointer will be deleted + // by the g_source_remove() call. + tasks_.erase(task); + return g_source_remove(source_id); +} + +bool GlibMessageLoop::RunOnce(bool may_block) { + return g_main_context_iteration(nullptr, may_block); +} + +void GlibMessageLoop::Run() { + g_main_loop_run(loop_); +} + +void GlibMessageLoop::BreakLoop() { + g_main_loop_quit(loop_); +} + +MessageLoop::TaskId GlibMessageLoop::NextTaskId() { + TaskId res; + do { + res = ++last_id_; + // We would run out of memory before we run out of task ids. + } while (!res || tasks_.find(res) != tasks_.end()); + return res; +} + +gboolean GlibMessageLoop::OnRanPostedTask(gpointer user_data) { + ScheduledTask* scheduled_task = reinterpret_cast(user_data); + DVLOG_LOC(scheduled_task->location, 1) + << "Running delayed task_id " << scheduled_task->task_id + << " scheduled from this location."; + // We only need to remove this task_id from the map. DestroyPostedTask will be + // called with this same |user_data| where we can delete the ScheduledTask. + scheduled_task->loop->tasks_.erase(scheduled_task->task_id); + scheduled_task->closure.Run(); + return FALSE; // Removes the source since a callback can only be called once. +} + +gboolean GlibMessageLoop::OnWatchedFdReady(GIOChannel *source, + GIOCondition condition, + gpointer user_data) { + ScheduledTask* scheduled_task = reinterpret_cast(user_data); + DVLOG_LOC(scheduled_task->location, 1) + << "Running task_id " << scheduled_task->task_id + << " for watching a file descriptor, scheduled from this location."; + if (!scheduled_task->persistent) { + // We only need to remove this task_id from the map. DestroyPostedTask will + // be called with this same |user_data| where we can delete the + // ScheduledTask. + scheduled_task->loop->tasks_.erase(scheduled_task->task_id); + } + scheduled_task->closure.Run(); + return scheduled_task->persistent; +} + +void GlibMessageLoop::DestroyPostedTask(gpointer user_data) { + delete reinterpret_cast(user_data); +} + +} // namespace brillo diff --git a/brillo/message_loops/glib_message_loop.h b/brillo/message_loops/glib_message_loop.h new file mode 100644 index 0000000..1bebc23 --- /dev/null +++ b/brillo/message_loops/glib_message_loop.h @@ -0,0 +1,83 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_ +#define LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_ + +#include +#include + +#include +#include +#include + +#include +#include + +namespace brillo { + +class BRILLO_EXPORT GlibMessageLoop : public MessageLoop { + public: + GlibMessageLoop(); + ~GlibMessageLoop() override; + + // MessageLoop overrides. + TaskId PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) override; + using MessageLoop::PostDelayedTask; + TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const base::Closure& task) override; + using MessageLoop::WatchFileDescriptor; + bool CancelTask(TaskId task_id) override; + bool RunOnce(bool may_block) override; + void Run() override; + void BreakLoop() override; + + private: + // Called by the GLib's main loop when is time to call the callback scheduled + // with Post*Task(). The pointer to the callback passed when scheduling it is + // passed to this function as a gpointer on |user_data|. + static gboolean OnRanPostedTask(gpointer user_data); + + // Called by the GLib's main loop when the watched source |source| is + // ready to perform the operation given in |condition| without blocking. + static gboolean OnWatchedFdReady(GIOChannel *source, + GIOCondition condition, + gpointer user_data); + + // Called by the GLib's main loop when the scheduled callback is removed due + // to it being executed or canceled. + static void DestroyPostedTask(gpointer user_data); + + // Return a new unused task_id. + TaskId NextTaskId(); + + GMainLoop* loop_; + + struct ScheduledTask { + // A pointer to this GlibMessageLoop so we can remove the Task from the + // glib callback. + GlibMessageLoop* loop; + tracked_objects::Location location; + + MessageLoop::TaskId task_id; + guint source_id; + bool persistent; + base::Closure closure; + }; + + std::map tasks_; + + MessageLoop::TaskId last_id_ = kTaskIdNull; + + DISALLOW_COPY_AND_ASSIGN(GlibMessageLoop); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_ diff --git a/brillo/message_loops/glib_message_loop_unittest.cc b/brillo/message_loops/glib_message_loop_unittest.cc new file mode 100644 index 0000000..4b72a11 --- /dev/null +++ b/brillo/message_loops/glib_message_loop_unittest.cc @@ -0,0 +1,69 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +using base::Bind; + +namespace brillo { + +using TaskId = MessageLoop::TaskId; + +class GlibMessageLoopTest : public ::testing::Test { + protected: + void SetUp() override { + loop_.reset(new GlibMessageLoop()); + EXPECT_TRUE(loop_.get()); + } + + std::unique_ptr loop_; +}; + +// When you watch a file descriptor for reading, the guaranties are that a +// blocking call to read() on that file descriptor will not block. This should +// include the case when the other end of a pipe is closed or the file is empty. +TEST_F(GlibMessageLoopTest, WatchFileDescriptorTriggersWhenEmpty) { + int fd = HANDLE_EINTR(open("/dev/null", O_RDONLY)); + int called = 0; + TaskId task_id = loop_->WatchFileDescriptor( + FROM_HERE, fd, MessageLoop::kWatchRead, true, + Bind([&called] { called++; })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + EXPECT_NE(0, MessageLoopRunMaxIterations(loop_.get(), 10)); + EXPECT_LT(2, called); + EXPECT_TRUE(loop_->CancelTask(task_id)); +} + +// Test that an invalid file descriptor triggers the callback. +TEST_F(GlibMessageLoopTest, WatchFileDescriptorTriggersWhenInvalid) { + int fd = HANDLE_EINTR(open("/dev/zero", O_RDONLY)); + int called = 0; + TaskId task_id = loop_->WatchFileDescriptor( + FROM_HERE, fd, MessageLoop::kWatchRead, true, + Bind([&called, fd] { + if (!called) + IGNORE_EINTR(close(fd)); + called++; + })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + EXPECT_NE(0, MessageLoopRunMaxIterations(loop_.get(), 10)); + EXPECT_LT(2, called); + EXPECT_TRUE(loop_->CancelTask(task_id)); +} + +} // namespace brillo diff --git a/brillo/message_loops/message_loop.cc b/brillo/message_loops/message_loop.cc new file mode 100644 index 0000000..3d64ccb --- /dev/null +++ b/brillo/message_loops/message_loop.cc @@ -0,0 +1,63 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +namespace brillo { + +namespace { + +// A lazily created thread local storage for quick access to a thread's message +// loop, if one exists. This should be safe and free of static constructors. +base::LazyInstance >::Leaky lazy_tls_ptr = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +const MessageLoop::TaskId MessageLoop::kTaskIdNull = 0; + +MessageLoop* MessageLoop::current() { + DCHECK(lazy_tls_ptr.Pointer()->Get() != nullptr) << + "There isn't a MessageLoop for this thread. You need to initialize it " + "first."; + return lazy_tls_ptr.Pointer()->Get(); +} + +bool MessageLoop::ThreadHasCurrent() { + return lazy_tls_ptr.Pointer()->Get() != nullptr; +} + +void MessageLoop::SetAsCurrent() { + DCHECK(lazy_tls_ptr.Pointer()->Get() == nullptr) << + "There's already a MessageLoop for this thread."; + lazy_tls_ptr.Pointer()->Set(this); +} + +void MessageLoop::ReleaseFromCurrent() { + DCHECK(lazy_tls_ptr.Pointer()->Get() == this) << + "This is not the MessageLoop bound to the current thread."; + lazy_tls_ptr.Pointer()->Set(nullptr); +} + +MessageLoop::~MessageLoop() { + if (lazy_tls_ptr.Pointer()->Get() == this) + lazy_tls_ptr.Pointer()->Set(nullptr); +} + +void MessageLoop::Run() { + // Default implementation is to call RunOnce() blocking until there aren't + // more tasks scheduled. + while (!should_exit_ && RunOnce(true)) {} + should_exit_ = false; +} + +void MessageLoop::BreakLoop() { + should_exit_ = true; +} + +} // namespace brillo diff --git a/brillo/message_loops/message_loop.h b/brillo/message_loops/message_loop.h new file mode 100644 index 0000000..4ffce02 --- /dev/null +++ b/brillo/message_loops/message_loop.h @@ -0,0 +1,135 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_H_ +#define LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_H_ + +#include + +#include +#include +#include +#include + +namespace brillo { + +class BRILLO_EXPORT MessageLoop { + public: + virtual ~MessageLoop(); + + // A unique task identifier used to refer to scheduled callbacks. + using TaskId = uint64_t; + + // The kNullEventId is reserved for an invalid task and will never be used + // to refer to a real task. + static const TaskId kTaskIdNull; + + // Return the MessageLoop for the current thread. It is a fatal error to + // request the current MessageLoop if SetAsCurrent() was not called on the + // current thread. If you really need to, use ThreadHasCurrent() to check if + // there is a current thread. + static MessageLoop* current(); + + // Return whether there is a MessageLoop in the current thread. + static bool ThreadHasCurrent(); + + // Set this message loop as the current thread main loop. Only one message + // loop can be set at a time. Use ReleaseFromCurrent() to release it. + void SetAsCurrent(); + + // Release this instance from the current thread. This instance must have + // been previously set with SetAsCurrent(). + void ReleaseFromCurrent(); + + // Schedule a Closure |task| to be executed after a |delay|. Returns a task + // identifier for the scheduled task that can be used to cancel the task + // before it is fired by passing it to CancelTask(). + // In case of an error scheduling the task, the kTaskIdNull is returned. + // Note that once the call is executed or canceled, the TaskId could be reused + // at a later point. + // This methond can only be called from the same thread running the main loop. + virtual TaskId PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) = 0; + // Variant without the Location for easier usage. + TaskId PostDelayedTask(const base::Closure& task, base::TimeDelta delay) { + return PostDelayedTask(tracked_objects::Location(), task, delay); + } + + // A convenience method to schedule a call with no delay. + // This methond can only be called from the same thread running the main loop. + TaskId PostTask(const base::Closure& task) { + return PostDelayedTask(task, base::TimeDelta()); + } + TaskId PostTask(const tracked_objects::Location& from_here, + const base::Closure& task) { + return PostDelayedTask(from_here, task, base::TimeDelta()); + } + + // Watch mode flag used to watch for file descriptors. + enum WatchMode { + kWatchRead, + kWatchWrite, + }; + + // Watch a file descriptor |fd| for it to be ready to perform the operation + // passed in |mode| without blocking. When that happens, the |task| closure + // will be executed. If |persistent| is true, the file descriptor will + // continue to be watched and |task| will continue to be called until the task + // is canceled with CancelTask(). + // Returns the TaskId describing this task. In case of error, returns + // kTaskIdNull. + virtual TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const base::Closure& task) = 0; + + // Convenience function to call WatchFileDescriptor() without a location. + TaskId WatchFileDescriptor(int fd, + WatchMode mode, + bool persistent, + const base::Closure& task) { + return WatchFileDescriptor( + tracked_objects::Location(), fd, mode, persistent, task); + } + + // Cancel a scheduled task. Returns whether the task was canceled. For + // example, if the callback was already executed (or is being executed) or was + // already canceled this method will fail. Note that the TaskId can be reused + // after it was executed or cancelled. + virtual bool CancelTask(TaskId task_id) = 0; + + // --------------------------------------------------------------------------- + // Methods used to run and stop the message loop. + + // Run one iteration of the message loop, dispatching up to one task. The + // |may_block| tells whether this method is allowed to block waiting for a + // task to be ready to run. Returns whether it ran a task. Note that even + // if |may_block| is true, this method can return false immediately if there + // are no more tasks registered. + virtual bool RunOnce(bool may_block) = 0; + + // Run the main loop until there are no more registered tasks. + virtual void Run(); + + // Quit the running main loop immediately. This method will make the current + // running Run() method to return right after the current task returns back + // to the message loop without processing any other task. + virtual void BreakLoop(); + + protected: + MessageLoop() = default; + + private: + // Tells whether Run() should quit the message loop in the default + // implementation. + bool should_exit_ = false; + + DISALLOW_COPY_AND_ASSIGN(MessageLoop); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_H_ diff --git a/brillo/message_loops/message_loop_unittest.cc b/brillo/message_loops/message_loop_unittest.cc new file mode 100644 index 0000000..20fb6be --- /dev/null +++ b/brillo/message_loops/message_loop_unittest.cc @@ -0,0 +1,422 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +// These are the common tests for all the brillo::MessageLoop implementations +// that should conform to this interface's contracts. For extra +// implementation-specific tests see the particular implementation unittests in +// the *_unittest.cc files. + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using base::Bind; +using base::TimeDelta; + +namespace { +// Helper class to create and close a unidirectional pipe. Used to provide valid +// file descriptors when testing watching for a file descriptor. +class ScopedPipe { + public: + // The internal pipe size. + static const int kPipeSize; + + ScopedPipe() { + int fds[2]; + if (pipe(fds) != 0) { + PLOG(FATAL) << "Creating a pipe()"; + } + reader = fds[0]; + writer = fds[1]; + EXPECT_EQ(kPipeSize, fcntl(writer, F_SETPIPE_SZ, kPipeSize)); + } + ~ScopedPipe() { + if (reader != -1) + close(reader); + if (writer != -1) + close(writer); + } + + // The reader and writer end of the pipe. + int reader{-1}; + int writer{-1}; +}; + +const int ScopedPipe::kPipeSize = 4096; + +class ScopedSocketPair { + public: + ScopedSocketPair() { + int fds[2]; + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) != 0) { + PLOG(FATAL) << "Creating a socketpair()"; + } + left = fds[0]; + right = fds[1]; + } + ~ScopedSocketPair() { + if (left != -1) + close(left); + if (right != -1) + close(right); + } + + // The left and right sockets are bi-directional connected and + // indistinguishable file descriptor. We named them left/right for easier + // reading. + int left{-1}; + int right{-1}; +}; +} // namespace + +namespace brillo { + +using TaskId = MessageLoop::TaskId; + +template +class MessageLoopTest : public ::testing::Test { + protected: + void SetUp() override { + MessageLoopSetUp(); + EXPECT_TRUE(this->loop_.get()); + } + + std::unique_ptr base_loop_; + + std::unique_ptr loop_; + + private: + // These MessageLoopSetUp() methods are used to setup each MessageLoop + // according to its constructor requirements. + void MessageLoopSetUp(); +}; + +template <> +void MessageLoopTest::MessageLoopSetUp() { + loop_.reset(new GlibMessageLoop()); +} + +template <> +void MessageLoopTest::MessageLoopSetUp() { + base_loop_.reset(new base::MessageLoopForIO()); + loop_.reset(new BaseMessageLoop(base::MessageLoopForIO::current())); +} + +// This setups gtest to run each one of the following TYPED_TEST test cases on +// on each implementation. +typedef ::testing::Types< + GlibMessageLoop, + BaseMessageLoop> MessageLoopTypes; +TYPED_TEST_CASE(MessageLoopTest, MessageLoopTypes); + + +TYPED_TEST(MessageLoopTest, CancelTaskInvalidValuesTest) { + EXPECT_FALSE(this->loop_->CancelTask(MessageLoop::kTaskIdNull)); + EXPECT_FALSE(this->loop_->CancelTask(1234)); +} + +TYPED_TEST(MessageLoopTest, PostTaskTest) { + bool called = false; + TaskId task_id = this->loop_->PostTask(FROM_HERE, + Bind([&called]() { called = true; })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + MessageLoopRunMaxIterations(this->loop_.get(), 100); + EXPECT_TRUE(called); +} + +// Tests that we can cancel tasks right after we schedule them. +TYPED_TEST(MessageLoopTest, PostTaskCancelledTest) { + bool called = false; + TaskId task_id = this->loop_->PostTask(FROM_HERE, + Bind([&called]() { called = true; })); + EXPECT_TRUE(this->loop_->CancelTask(task_id)); + MessageLoopRunMaxIterations(this->loop_.get(), 100); + EXPECT_FALSE(called); + // Can't remove a task you already removed. + EXPECT_FALSE(this->loop_->CancelTask(task_id)); +} + +TYPED_TEST(MessageLoopTest, PostDelayedTaskRunsEventuallyTest) { + bool called = false; + TaskId task_id = this->loop_->PostDelayedTask( + FROM_HERE, + Bind([&called]() { called = true; }), + TimeDelta::FromMilliseconds(50)); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + MessageLoopRunUntil(this->loop_.get(), + TimeDelta::FromSeconds(10), + Bind([&called]() { return called; })); + // Check that the main loop finished before the 10 seconds timeout, so it + // finished due to the callback being called and not due to the timeout. + EXPECT_TRUE(called); +} + +// Test that you can call the overloaded version of PostDelayedTask from +// MessageLoop. This is important because only one of the two methods is +// virtual, so you need to unhide the other when overriding the virtual one. +TYPED_TEST(MessageLoopTest, PostDelayedTaskWithoutLocation) { + this->loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta()); + EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); +} + +TYPED_TEST(MessageLoopTest, WatchForInvalidFD) { + bool called = false; + EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor( + FROM_HERE, -1, MessageLoop::kWatchRead, true, + Bind([&called] { called = true; }))); + EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor( + FROM_HERE, -1, MessageLoop::kWatchWrite, true, + Bind([&called] { called = true; }))); + EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100)); + EXPECT_FALSE(called); +} + +TYPED_TEST(MessageLoopTest, CancelWatchedFileDescriptor) { + ScopedPipe pipe; + bool called = false; + TaskId task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, + Bind([&called] { called = true; })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + // The reader end is blocked because we didn't write anything to the writer + // end. + EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100)); + EXPECT_FALSE(called); + EXPECT_TRUE(this->loop_->CancelTask(task_id)); +} + +// When you watch a file descriptor for reading, the guaranties are that a +// blocking call to read() on that file descriptor will not block. This should +// include the case when the other end of a pipe is closed or the file is empty. +TYPED_TEST(MessageLoopTest, WatchFileDescriptorTriggersWhenPipeClosed) { + ScopedPipe pipe; + bool called = false; + EXPECT_EQ(0, HANDLE_EINTR(close(pipe.writer))); + pipe.writer = -1; + TaskId task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, + Bind([&called] { called = true; })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + // The reader end is not blocked because we closed the writer end so a read on + // the reader end would return 0 bytes read. + EXPECT_NE(0, MessageLoopRunMaxIterations(this->loop_.get(), 10)); + EXPECT_TRUE(called); + EXPECT_TRUE(this->loop_->CancelTask(task_id)); +} + +// When a WatchFileDescriptor task is scheduled with |persistent| = true, we +// should keep getting a call whenever the file descriptor is ready. +TYPED_TEST(MessageLoopTest, WatchFileDescriptorPersistently) { + ScopedPipe pipe; + EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1))); + + int called = 0; + TaskId task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, + Bind([&called] { called++; })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + // We let the main loop run for 20 iterations to give it enough iterations to + // verify that our callback was called more than one. We only check that our + // callback is called more than once. + EXPECT_EQ(20, MessageLoopRunMaxIterations(this->loop_.get(), 20)); + EXPECT_LT(1, called); + EXPECT_TRUE(this->loop_->CancelTask(task_id)); +} + +TYPED_TEST(MessageLoopTest, WatchFileDescriptorNonPersistent) { + ScopedPipe pipe; + EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1))); + + int called = 0; + TaskId task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, pipe.reader, MessageLoop::kWatchRead, false, + Bind([&called] { called++; })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + // We let the main loop run for 20 iterations but we just expect it to run + // at least once. The callback should be called exactly once since we + // scheduled it non-persistently. After it ran, we shouldn't be able to cancel + // this task. + EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20)); + EXPECT_EQ(1, called); + EXPECT_FALSE(this->loop_->CancelTask(task_id)); +} + +TYPED_TEST(MessageLoopTest, WatchFileDescriptorForReadAndWriteSimultaneously) { + ScopedSocketPair socks; + EXPECT_EQ(1, HANDLE_EINTR(write(socks.right, "a", 1))); + // socks.left should be able to read this "a" and should also be able to write + // without blocking since the kernel has some buffering for it. + + TaskId read_task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, socks.left, MessageLoop::kWatchRead, true, + Bind([this, &read_task_id] { + EXPECT_TRUE(this->loop_->CancelTask(read_task_id)) + << "task_id" << read_task_id; + })); + EXPECT_NE(MessageLoop::kTaskIdNull, read_task_id); + + TaskId write_task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, socks.left, MessageLoop::kWatchWrite, true, + Bind([this, &write_task_id] { + EXPECT_TRUE(this->loop_->CancelTask(write_task_id)); + })); + EXPECT_NE(MessageLoop::kTaskIdNull, write_task_id); + + EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20)); + + EXPECT_FALSE(this->loop_->CancelTask(read_task_id)); + EXPECT_FALSE(this->loop_->CancelTask(write_task_id)); +} + +// Test that we can cancel the task we are running, and should just fail. +TYPED_TEST(MessageLoopTest, DeleteTaskFromSelf) { + bool cancel_result = true; // We would expect this to be false. + MessageLoop* loop_ptr = this->loop_.get(); + TaskId task_id; + task_id = this->loop_->PostTask( + FROM_HERE, + Bind([&cancel_result, loop_ptr, &task_id]() { + cancel_result = loop_ptr->CancelTask(task_id); + })); + EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); + EXPECT_FALSE(cancel_result); +} + +// Test that we can cancel a non-persistent file descriptor watching callback, +// which should fail. +TYPED_TEST(MessageLoopTest, DeleteNonPersistenIOTaskFromSelf) { + ScopedPipe pipe; + TaskId task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, false /* persistent */, + Bind([this, &task_id] { + EXPECT_FALSE(this->loop_->CancelTask(task_id)); + task_id = MessageLoop::kTaskIdNull; + })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); + EXPECT_EQ(MessageLoop::kTaskIdNull, task_id); +} + +// Test that we can cancel a persistent file descriptor watching callback from +// the same callback. +TYPED_TEST(MessageLoopTest, DeletePersistenIOTaskFromSelf) { + ScopedPipe pipe; + TaskId task_id = this->loop_->WatchFileDescriptor( + FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, true /* persistent */, + Bind([this, &task_id] { + EXPECT_TRUE(this->loop_->CancelTask(task_id)); + task_id = MessageLoop::kTaskIdNull; + })); + EXPECT_NE(MessageLoop::kTaskIdNull, task_id); + EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); + EXPECT_EQ(MessageLoop::kTaskIdNull, task_id); +} + +// Test that we can cancel several persistent file descriptor watching callbacks +// from a scheduled callback. In the BaseMessageLoop implementation, this code +// will cause us to cancel an IOTask that has a pending delayed task, but +// otherwise is a valid test case on all implementations. +TYPED_TEST(MessageLoopTest, DeleteAllPersistenIOTaskFromSelf) { + const int kNumTasks = 5; + ScopedPipe pipes[kNumTasks]; + TaskId task_ids[kNumTasks]; + + for (int i = 0; i < kNumTasks; ++i) { + task_ids[i] = this->loop_->WatchFileDescriptor( + FROM_HERE, pipes[i].writer, MessageLoop::kWatchWrite, + true /* persistent */, + Bind([this, kNumTasks, &task_ids] { + for (int j = 0; j < kNumTasks; ++j) { + // Once we cancel all the tasks, none should run, so this code runs + // only once from one callback. + EXPECT_TRUE(this->loop_->CancelTask(task_ids[j])); + task_ids[j] = MessageLoop::kTaskIdNull; + } + })); + } + MessageLoopRunMaxIterations(this->loop_.get(), 100); + for (int i = 0; i < kNumTasks; ++i) { + EXPECT_EQ(MessageLoop::kTaskIdNull, task_ids[i]); + } +} + +// Test that if there are several tasks watching for file descriptors to be +// available or simply waiting in the message loop are fairly scheduled to run. +// In other words, this test ensures that having a file descriptor always +// available doesn't prevent other file descriptors watching tasks or delayed +// tasks to be dispatched, causing starvation. +TYPED_TEST(MessageLoopTest, AllTasksAreEqual) { + int total_calls = 0; + + // First, schedule a repeating timeout callback to run from the main loop. + int timeout_called = 0; + base::Closure timeout_callback; + MessageLoop::TaskId timeout_task; + timeout_callback = base::Bind( + [this, &timeout_called, &total_calls, &timeout_callback, &timeout_task] { + timeout_called++; + total_calls++; + timeout_task = this->loop_->PostTask(FROM_HERE, Bind(timeout_callback)); + if (total_calls > 100) + this->loop_->BreakLoop(); + }); + timeout_task = this->loop_->PostTask(FROM_HERE, timeout_callback); + + // Second, schedule several file descriptor watchers. + const int kNumTasks = 3; + ScopedPipe pipes[kNumTasks]; + MessageLoop::TaskId tasks[kNumTasks]; + + int reads[kNumTasks] = {}; + auto fd_callback = [this, &pipes, &reads, &total_calls](int i) { + reads[i]++; + total_calls++; + char c; + EXPECT_EQ(1, HANDLE_EINTR(read(pipes[i].reader, &c, 1))); + if (total_calls > 100) + this->loop_->BreakLoop(); + }; + + for (int i = 0; i < kNumTasks; ++i) { + tasks[i] = this->loop_->WatchFileDescriptor( + FROM_HERE, pipes[i].reader, MessageLoop::kWatchRead, + true /* persistent */, + Bind(fd_callback, i)); + // Make enough bytes available on each file descriptor. This should not + // block because we set the size of the file descriptor buffer when + // creating it. + std::vector blob(1000, 'a'); + EXPECT_EQ(blob.size(), + HANDLE_EINTR(write(pipes[i].writer, blob.data(), blob.size()))); + } + this->loop_->Run(); + EXPECT_GT(total_calls, 100); + // We run the loop up 100 times and expect each callback to run at least 10 + // times. A good scheduler should balance these callbacks. + EXPECT_GE(timeout_called, 10); + EXPECT_TRUE(this->loop_->CancelTask(timeout_task)); + for (int i = 0; i < kNumTasks; ++i) { + EXPECT_GE(reads[i], 10) << "Reading from pipes[" << i << "], fd " + << pipes[i].reader; + EXPECT_TRUE(this->loop_->CancelTask(tasks[i])); + } +} + +} // namespace brillo diff --git a/brillo/message_loops/message_loop_utils.cc b/brillo/message_loops/message_loop_utils.cc new file mode 100644 index 0000000..7931447 --- /dev/null +++ b/brillo/message_loops/message_loop_utils.cc @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +namespace brillo { + +void MessageLoopRunUntil( + MessageLoop* loop, + base::TimeDelta timeout, + base::Callback terminate) { + bool timeout_called = false; + MessageLoop::TaskId task_id = loop->PostDelayedTask( + FROM_HERE, + base::Bind([&timeout_called]() { timeout_called = true; }), + timeout); + while (!timeout_called && (terminate.is_null() || !terminate.Run())) + loop->RunOnce(true); + + if (!timeout_called) + loop->CancelTask(task_id); +} + +int MessageLoopRunMaxIterations(MessageLoop* loop, int iterations) { + int result; + for (result = 0; result < iterations && loop->RunOnce(false); result++) {} + return result; +} + +} // namespace brillo diff --git a/brillo/message_loops/message_loop_utils.h b/brillo/message_loops/message_loop_utils.h new file mode 100644 index 0000000..b7338fd --- /dev/null +++ b/brillo/message_loops/message_loop_utils.h @@ -0,0 +1,30 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_ +#define LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_ + +#include +#include + +#include +#include + +namespace brillo { + +// Run the MessageLoop until the condition passed in |terminate| returns true +// or the timeout expires. +BRILLO_EXPORT void MessageLoopRunUntil( + MessageLoop* loop, + base::TimeDelta timeout, + base::Callback terminate); + +// Run the MessageLoop |loop| for up to |iterations| times without blocking. +// Return the number of tasks run. +BRILLO_EXPORT int MessageLoopRunMaxIterations(MessageLoop* loop, + int iterations); + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_ diff --git a/brillo/message_loops/mock_message_loop.h b/brillo/message_loops/mock_message_loop.h new file mode 100644 index 0000000..73fbe8d --- /dev/null +++ b/brillo/message_loops/mock_message_loop.h @@ -0,0 +1,89 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_ +#define LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_ + +#include + +#include +#include +#include + +#include +#include +#include + +namespace brillo { + +// The MockMessageLoop is a mockable MessageLoop that will by default act as a +// FakeMessageLoop. It is possible to set expectations with EXPECT_CALL without +// any action associated and they will call the same methods in the underlying +// FakeMessageLoop implementation. +// This message loop implementation is useful to check interaction with the +// message loop when running unittests. +class BRILLO_EXPORT MockMessageLoop : public MessageLoop { + public: + // Create a FakeMessageLoop optionally using a SimpleTestClock to update the + // time when Run() or RunOnce(true) are called and should block. + explicit MockMessageLoop(base::SimpleTestClock* clock) + : fake_loop_(clock) { + // Redirect all actions to calling the underlying FakeMessageLoop by + // default. For the overloaded methods, we need to disambiguate between the + // different options by specifying the type of the method pointer. + ON_CALL(*this, PostDelayedTask(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + &fake_loop_, + static_cast( + &FakeMessageLoop::PostDelayedTask))); + ON_CALL(*this, WatchFileDescriptor( + ::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + &fake_loop_, + static_cast( + &FakeMessageLoop::WatchFileDescriptor))); + ON_CALL(*this, CancelTask(::testing::_)) + .WillByDefault(::testing::Invoke(&fake_loop_, + &FakeMessageLoop::CancelTask)); + ON_CALL(*this, RunOnce(::testing::_)) + .WillByDefault(::testing::Invoke(&fake_loop_, + &FakeMessageLoop::RunOnce)); + } + ~MockMessageLoop() override = default; + + MOCK_METHOD3(PostDelayedTask, + TaskId(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay)); + using MessageLoop::PostDelayedTask; + MOCK_METHOD5(WatchFileDescriptor, + TaskId(const tracked_objects::Location& from_here, + int fd, + WatchMode mode, + bool persistent, + const base::Closure& task)); + using MessageLoop::WatchFileDescriptor; + MOCK_METHOD1(CancelTask, bool(TaskId task_id)); + MOCK_METHOD1(RunOnce, bool(bool may_block)); + + // Returns the actual FakeMessageLoop instance so default actions can be + // override with other actions or call + FakeMessageLoop* fake_loop() { + return &fake_loop_; + } + + private: + FakeMessageLoop fake_loop_; + + DISALLOW_COPY_AND_ASSIGN(MockMessageLoop); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_ diff --git a/brillo/mime_utils.cc b/brillo/mime_utils.cc new file mode 100644 index 0000000..f194cd2 --- /dev/null +++ b/brillo/mime_utils.cc @@ -0,0 +1,163 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +namespace brillo { + +// *************************************************************************** +// ******************************* MIME types ******************************** +// *************************************************************************** +const char mime::types::kApplication[] = "application"; +const char mime::types::kAudio[] = "audio"; +const char mime::types::kImage[] = "image"; +const char mime::types::kMessage[] = "message"; +const char mime::types::kMultipart[] = "multipart"; +const char mime::types::kText[] = "text"; +const char mime::types::kVideo[] = "video"; + +const char mime::parameters::kCharset[] = "charset"; + +const char mime::image::kJpeg[] = "image/jpeg"; +const char mime::image::kPng[] = "image/png"; +const char mime::image::kBmp[] = "image/bmp"; +const char mime::image::kTiff[] = "image/tiff"; +const char mime::image::kGif[] = "image/gif"; + +const char mime::text::kPlain[] = "text/plain"; +const char mime::text::kHtml[] = "text/html"; +const char mime::text::kXml[] = "text/xml"; + +const char mime::application::kOctet_stream[] = "application/octet-stream"; +const char mime::application::kJson[] = "application/json"; +const char mime::application::kWwwFormUrlEncoded[] = + "application/x-www-form-urlencoded"; +const char mime::application::kProtobuf[] = "application/x-protobuf"; + +const char mime::multipart::kFormData[] = "multipart/form-data"; +const char mime::multipart::kMixed[] = "multipart/mixed"; + +// *************************************************************************** +// **************************** Utility Functions **************************** +// *************************************************************************** +static std::string EncodeParam(const std::string& param) { + // If the string contains one of "tspecials" characters as + // specified in RFC 1521, enclose it in quotes. + if (param.find_first_of("()<>@,;:\\\"/[]?=") != std::string::npos) { + return '"' + param + '"'; + } + return param; +} + +static std::string DecodeParam(const std::string& param) { + if (param.size() > 1 && param.front() == '"' && param.back() == '"') { + return param.substr(1, param.size() - 2); + } + return param; +} + +// *************************************************************************** +// ******************** Main MIME manipulation functions ********************* +// *************************************************************************** + +bool mime::Split(const std::string& mime_string, + std::string* type, + std::string* subtype, + mime::Parameters* parameters) { + std::vector parts = + brillo::string_utils::Split(mime_string, ";"); + if (parts.empty()) + return false; + + if (!mime::Split(parts.front(), type, subtype)) + return false; + + if (parameters) { + parameters->clear(); + parameters->reserve(parts.size() - 1); + for (size_t i = 1; i < parts.size(); i++) { + auto pair = brillo::string_utils::SplitAtFirst(parts[i], "="); + pair.second = DecodeParam(pair.second); + parameters->push_back(pair); + } + } + return true; +} + +bool mime::Split(const std::string& mime_string, + std::string* type, + std::string* subtype) { + std::string mime = mime::RemoveParameters(mime_string); + auto types = brillo::string_utils::SplitAtFirst(mime, "/"); + + if (type) + *type = types.first; + + if (subtype) + *subtype = types.second; + + return !types.first.empty() && !types.second.empty(); +} + +std::string mime::Combine(const std::string& type, + const std::string& subtype, + const mime::Parameters& parameters) { + std::vector parts; + parts.push_back(brillo::string_utils::Join("/", type, subtype)); + for (const auto& pair : parameters) { + parts.push_back( + brillo::string_utils::Join("=", pair.first, EncodeParam(pair.second))); + } + return brillo::string_utils::Join("; ", parts); +} + +std::string mime::GetType(const std::string& mime_string) { + std::string mime = mime::RemoveParameters(mime_string); + return brillo::string_utils::SplitAtFirst(mime, "/").first; +} + +std::string mime::GetSubtype(const std::string& mime_string) { + std::string mime = mime::RemoveParameters(mime_string); + return brillo::string_utils::SplitAtFirst(mime, "/").second; +} + +mime::Parameters mime::GetParameters(const std::string& mime_string) { + std::string type; + std::string subtype; + mime::Parameters parameters; + + if (mime::Split(mime_string, &type, &subtype, ¶meters)) + return parameters; + + return mime::Parameters(); +} + +std::string mime::RemoveParameters(const std::string& mime_string) { + return brillo::string_utils::SplitAtFirst(mime_string, ";").first; +} + +std::string mime::AppendParameter(const std::string& mime_string, + const std::string& paramName, + const std::string& paramValue) { + std::string mime(mime_string); + mime += "; "; + mime += brillo::string_utils::Join("=", paramName, EncodeParam(paramValue)); + return mime; +} + +std::string mime::GetParameterValue(const std::string& mime_string, + const std::string& paramName) { + mime::Parameters params = mime::GetParameters(mime_string); + for (const auto& pair : params) { + if (base::strcasecmp(pair.first.c_str(), paramName.c_str()) == 0) + return pair.second; + } + return std::string(); +} + +} // namespace brillo diff --git a/brillo/mime_utils.h b/brillo/mime_utils.h new file mode 100644 index 0000000..9e05460 --- /dev/null +++ b/brillo/mime_utils.h @@ -0,0 +1,126 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MIME_UTILS_H_ +#define LIBCHROMEOS_BRILLO_MIME_UTILS_H_ + +#include +#include +#include + +#include +#include +#include + +namespace brillo { +namespace mime { + +namespace types { +// Main MIME type categories +BRILLO_EXPORT extern const char kApplication[]; // application +BRILLO_EXPORT extern const char kAudio[]; // audio +BRILLO_EXPORT extern const char kImage[]; // image +BRILLO_EXPORT extern const char kMessage[]; // message +BRILLO_EXPORT extern const char kMultipart[]; // multipart +BRILLO_EXPORT extern const char kText[]; // test +BRILLO_EXPORT extern const char kVideo[]; // video +} // namespace types + +namespace parameters { +// Common MIME parameters +BRILLO_EXPORT extern const char kCharset[]; // charset=... +} // namespace parameters + +namespace image { +// Common image MIME types +BRILLO_EXPORT extern const char kJpeg[]; // image/jpeg +BRILLO_EXPORT extern const char kPng[]; // image/png +BRILLO_EXPORT extern const char kBmp[]; // image/bmp +BRILLO_EXPORT extern const char kTiff[]; // image/tiff +BRILLO_EXPORT extern const char kGif[]; // image/gif +} // namespace image + +namespace text { +// Common text MIME types +BRILLO_EXPORT extern const char kPlain[]; // text/plain +BRILLO_EXPORT extern const char kHtml[]; // text/html +BRILLO_EXPORT extern const char kXml[]; // text/xml +} // namespace text + +namespace application { +// Common application MIME types +// application/octet-stream +BRILLO_EXPORT extern const char kOctet_stream[]; +// application/json +BRILLO_EXPORT extern const char kJson[]; +// application/x-www-form-urlencoded +BRILLO_EXPORT extern const char kWwwFormUrlEncoded[]; +// application/x-protobuf +BRILLO_EXPORT extern const char kProtobuf[]; +} // namespace application + +namespace multipart { +// Common multipart MIME types +// multipart/form-data +BRILLO_EXPORT extern const char kFormData[]; +// multipart/mixed +BRILLO_EXPORT extern const char kMixed[]; +} // namespace multipart + +using Parameters = std::vector>; + +// Combine a MIME type, subtype and parameters into a MIME string. +// e.g. Combine("text", "plain", {{"charset", "utf-8"}}) will give: +// "text/plain; charset=utf-8" +BRILLO_EXPORT std::string Combine( + const std::string& type, + const std::string& subtype, + const Parameters& parameters = {}) WARN_UNUSED_RESULT; + +// Splits a MIME string into type and subtype. +// "text/plain;charset=utf-8" => ("text", "plain") +BRILLO_EXPORT bool Split(const std::string& mime_string, + std::string* type, + std::string* subtype); + +// Splits a MIME string into type, subtype, and parameters. +// "text/plain;charset=utf-8" => ("text", "plain", {{"charset","utf-8"}}) +BRILLO_EXPORT bool Split(const std::string& mime_string, + std::string* type, + std::string* subtype, + Parameters* parameters); + +// Returns the MIME type from MIME string. +// "text/plain;charset=utf-8" => "text" +BRILLO_EXPORT std::string GetType(const std::string& mime_string); + +// Returns the MIME sub-type from MIME string. +// "text/plain;charset=utf-8" => "plain" +BRILLO_EXPORT std::string GetSubtype(const std::string& mime_string); + +// Returns the MIME parameters from MIME string. +// "text/plain;charset=utf-8" => {{"charset","utf-8"}} +BRILLO_EXPORT Parameters GetParameters(const std::string& mime_string); + +// Removes parameters from a MIME string +// "text/plain;charset=utf-8" => "text/plain" +BRILLO_EXPORT std::string RemoveParameters( + const std::string& mime_string) WARN_UNUSED_RESULT; + +// Appends a parameter to a MIME string. +// "text/plain" => "text/plain; charset=utf-8" +BRILLO_EXPORT std::string AppendParameter( + const std::string& mime_string, + const std::string& paramName, + const std::string& paramValue) WARN_UNUSED_RESULT; + +// Returns the value of a parameter on a MIME string (empty string if missing). +// ("text/plain;charset=utf-8","charset") => "utf-8" +BRILLO_EXPORT std::string GetParameterValue(const std::string& mime_string, + const std::string& paramName); + +} // namespace mime +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MIME_UTILS_H_ diff --git a/brillo/mime_utils_unittest.cc b/brillo/mime_utils_unittest.cc new file mode 100644 index 0000000..a7595dc --- /dev/null +++ b/brillo/mime_utils_unittest.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { + +TEST(MimeUtils, Combine) { + std::string mime_string = mime::Combine(mime::types::kText, "xml"); + EXPECT_EQ(mime::text::kXml, mime_string); + EXPECT_EQ( + "application/json; charset=utf-8", + mime::Combine(mime::types::kApplication, "json", {{"charset", "utf-8"}})); +} + +TEST(MimeUtils, Split) { + std::string s1, s2; + EXPECT_TRUE(mime::Split(mime::image::kJpeg, &s1, &s2)); + EXPECT_EQ(mime::types::kImage, s1); + EXPECT_EQ("jpeg", s2); + + mime::Parameters parameters; + EXPECT_TRUE( + mime::Split("application/json;charset=utf-8", &s1, &s2, ¶meters)); + EXPECT_EQ(mime::types::kApplication, s1); + EXPECT_EQ("json", s2); + EXPECT_EQ(mime::application::kJson, mime::Combine(s1, s2)); + EXPECT_EQ(1, parameters.size()); + EXPECT_EQ(mime::parameters::kCharset, parameters.front().first); + EXPECT_EQ("utf-8", parameters.front().second); + EXPECT_EQ("application/json; charset=utf-8", + mime::Combine(s1, s2, parameters)); +} + +TEST(MimeUtils, ExtractParts) { + mime::Parameters parameters; + + EXPECT_EQ(mime::types::kText, mime::GetType(mime::text::kPlain)); + EXPECT_EQ("plain", mime::GetSubtype(mime::text::kPlain)); + + parameters = mime::GetParameters("text/plain; charset=iso-8859-1;foo=bar"); + EXPECT_EQ(2, parameters.size()); + EXPECT_EQ(mime::parameters::kCharset, parameters[0].first); + EXPECT_EQ("iso-8859-1", parameters[0].second); + EXPECT_EQ("foo", parameters[1].first); + EXPECT_EQ("bar", parameters[1].second); +} + +TEST(MimeUtils, AppendRemoveParams) { + std::string mime_string = mime::AppendParameter( + mime::text::kXml, mime::parameters::kCharset, "utf-8"); + EXPECT_EQ("text/xml; charset=utf-8", mime_string); + mime_string = mime::AppendParameter(mime_string, "foo", "bar"); + EXPECT_EQ("text/xml; charset=utf-8; foo=bar", mime_string); + EXPECT_EQ("utf-8", + mime::GetParameterValue(mime_string, mime::parameters::kCharset)); + EXPECT_EQ("bar", mime::GetParameterValue(mime_string, "foo")); + EXPECT_EQ("", mime::GetParameterValue(mime_string, "baz")); + mime_string = mime::RemoveParameters(mime_string); + EXPECT_EQ(mime::text::kXml, mime_string); +} + +} // namespace brillo diff --git a/brillo/minijail/minijail.cc b/brillo/minijail/minijail.cc new file mode 100644 index 0000000..b72f41b --- /dev/null +++ b/brillo/minijail/minijail.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/minijail/minijail.h" + +#include +#include + +using std::vector; + +namespace brillo { + +static base::LazyInstance g_minijail = LAZY_INSTANCE_INITIALIZER; + +Minijail::Minijail() {} + +Minijail::~Minijail() {} + +// static +Minijail* Minijail::GetInstance() { + return g_minijail.Pointer(); +} + +struct minijail* Minijail::New() { + return minijail_new(); +} + +void Minijail::Destroy(struct minijail* jail) { + minijail_destroy(jail); +} + +void Minijail::DropRoot(struct minijail* jail, uid_t uid, gid_t gid) { + minijail_change_uid(jail, uid); + minijail_change_gid(jail, gid); +} + +bool Minijail::DropRoot(struct minijail* jail, + const char* user, + const char* group) { + // |user| and |group| are copied so the only reason either of these + // calls can fail is ENOMEM. + return !minijail_change_user(jail, user) && + !minijail_change_group(jail, group); +} + +void Minijail::EnterNewPidNamespace(struct minijail* jail) { + minijail_namespace_pids(jail); +} + +void Minijail::MountTmp(struct minijail* jail) { + minijail_mount_tmp(jail); +} + +void Minijail::UseSeccompFilter(struct minijail* jail, const char* path) { + minijail_no_new_privs(jail); + minijail_use_seccomp_filter(jail); + minijail_parse_seccomp_filters(jail, path); +} + +void Minijail::UseCapabilities(struct minijail* jail, uint64_t capmask) { + minijail_use_caps(jail, capmask); +} + +void Minijail::Enter(struct minijail* jail) { + minijail_enter(jail); +} + +bool Minijail::Run(struct minijail* jail, vector args, pid_t* pid) { + return minijail_run_pid(jail, args[0], args.data(), pid) == 0; +} + +bool Minijail::RunSync(struct minijail* jail, vector args, int* status) { + pid_t pid; + if (Run(jail, args, &pid) && waitpid(pid, status, 0) == pid) { + return true; + } + + return false; +} + +bool Minijail::RunPipe(struct minijail* jail, + vector args, + pid_t* pid, + int* stdin) { + return minijail_run_pid_pipe(jail, args[0], args.data(), pid, stdin) == 0; +} + +bool Minijail::RunPipes(struct minijail* jail, + vector args, + pid_t* pid, + int* stdin, + int* stdout, + int* stderr) { + return minijail_run_pid_pipes( + jail, args[0], args.data(), pid, stdin, stdout, stderr) == 0; +} + +bool Minijail::RunAndDestroy(struct minijail* jail, + vector args, + pid_t* pid) { + bool res = Run(jail, args, pid); + Destroy(jail); + return res; +} + +bool Minijail::RunSyncAndDestroy(struct minijail* jail, + vector args, + int* status) { + bool res = RunSync(jail, args, status); + Destroy(jail); + return res; +} + +bool Minijail::RunPipeAndDestroy(struct minijail* jail, + vector args, + pid_t* pid, + int* stdin) { + bool res = RunPipe(jail, args, pid, stdin); + Destroy(jail); + return res; +} + +bool Minijail::RunPipesAndDestroy(struct minijail* jail, + vector args, + pid_t* pid, + int* stdin, + int* stdout, + int* stderr) { + bool res = RunPipes(jail, args, pid, stdin, stdout, stderr); + Destroy(jail); + return res; +} + +} // namespace brillo diff --git a/brillo/minijail/minijail.h b/brillo/minijail/minijail.h new file mode 100644 index 0000000..a04268d --- /dev/null +++ b/brillo/minijail/minijail.h @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MINIJAIL_MINIJAIL_H_ +#define LIBCHROMEOS_BRILLO_MINIJAIL_MINIJAIL_H_ + +#include + +extern "C" { +#include +#include +} + +#include + +#include + +namespace brillo { + +// A Minijail abstraction allowing Minijail mocking in tests. +class Minijail { + public: + virtual ~Minijail(); + + // This is a singleton -- use Minijail::GetInstance()->Foo(). + static Minijail* GetInstance(); + + // minijail_new + virtual struct minijail* New(); + // minijail_destroy + virtual void Destroy(struct minijail* jail); + + // minijail_change_uid/minijail_change_gid + virtual void DropRoot(struct minijail* jail, uid_t uid, gid_t gid); + + // minijail_change_user/minijail_change_group + virtual bool DropRoot(struct minijail* jail, + const char* user, + const char* group); + + // minijail_namespace_pids + virtual void EnterNewPidNamespace(struct minijail* jail); + + // minijail_mount_tmp + virtual void MountTmp(struct minijail* jail); + + // minijail_use_seccomp_filter/minijail_no_new_privs/ + // minijail_parse_seccomp_filters + virtual void UseSeccompFilter(struct minijail* jail, const char* path); + + // minijail_use_caps + virtual void UseCapabilities(struct minijail* jail, uint64_t capmask); + + // minijail_enter + virtual void Enter(struct minijail* jail); + + // minijail_run_pid + virtual bool Run(struct minijail* jail, std::vector args, pid_t* pid); + + // minijail_run_pid and waitpid + virtual bool RunSync(struct minijail* jail, + std::vector args, + int* status); + + // minijail_run_pid_pipe + virtual bool RunPipe(struct minijail* jail, + std::vector args, + pid_t* pid, + int* stdin); + + // minijail_run_pid_pipes + virtual bool RunPipes(struct minijail* jail, + std::vector args, + pid_t* pid, + int* stdin, + int* stdout, + int* stderr); + + // Run() and Destroy() + virtual bool RunAndDestroy(struct minijail* jail, + std::vector args, + pid_t* pid); + + // RunSync() and Destroy() + virtual bool RunSyncAndDestroy(struct minijail* jail, + std::vector args, + int* status); + + // RunPipe() and Destroy() + virtual bool RunPipeAndDestroy(struct minijail* jail, + std::vector args, + pid_t* pid, + int* stdin); + + // RunPipes() and Destroy() + virtual bool RunPipesAndDestroy(struct minijail* jail, + std::vector args, + pid_t* pid, + int* stdin, + int* stdout, + int* stderr); + + protected: + Minijail(); + + private: + friend struct base::DefaultLazyInstanceTraits; + + DISALLOW_COPY_AND_ASSIGN(Minijail); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MINIJAIL_MINIJAIL_H_ diff --git a/brillo/minijail/mock_minijail.h b/brillo/minijail/mock_minijail.h new file mode 100644 index 0000000..8eb209f --- /dev/null +++ b/brillo/minijail/mock_minijail.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_MINIJAIL_MOCK_MINIJAIL_H_ +#define LIBCHROMEOS_BRILLO_MINIJAIL_MOCK_MINIJAIL_H_ + +#include + +#include +#include + +#include "brillo/minijail/minijail.h" + +namespace brillo { + +class MockMinijail : public brillo::Minijail { + public: + MockMinijail() {} + virtual ~MockMinijail() {} + + MOCK_METHOD0(New, struct minijail*()); + MOCK_METHOD1(Destroy, void(struct minijail*)); + + MOCK_METHOD3(DropRoot, + bool(struct minijail* jail, + const char* user, + const char* group)); + MOCK_METHOD2(UseSeccompFilter, void(struct minijail* jail, const char* path)); + MOCK_METHOD2(UseCapabilities, void(struct minijail* jail, uint64_t capmask)); + MOCK_METHOD1(Enter, void(struct minijail* jail)); + MOCK_METHOD3(Run, + bool(struct minijail* jail, + std::vector args, + pid_t* pid)); + MOCK_METHOD3(RunSync, + bool(struct minijail* jail, + std::vector args, + int* status)); + MOCK_METHOD3(RunAndDestroy, + bool(struct minijail* jail, + std::vector args, + pid_t* pid)); + MOCK_METHOD3(RunSyncAndDestroy, + bool(struct minijail* jail, + std::vector args, + int* status)); + MOCK_METHOD4(RunPipeAndDestroy, + bool(struct minijail* jail, + std::vector args, + pid_t* pid, + int* stdin)); + MOCK_METHOD6(RunPipesAndDestroy, + bool(struct minijail* jail, + std::vector args, + pid_t* pid, + int* stdin, + int* stdout, + int* stderr)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockMinijail); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_MINIJAIL_MOCK_MINIJAIL_H_ diff --git a/brillo/osrelease_reader.cc b/brillo/osrelease_reader.cc new file mode 100644 index 0000000..6e4bf90 --- /dev/null +++ b/brillo/osrelease_reader.cc @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include + +namespace brillo { + +void OsReleaseReader::Load() { + Load(base::FilePath("/")); +} + +bool OsReleaseReader::GetString(const std::string& key, + std::string* value) const { + CHECK(initialized_) << "OsReleaseReader.Load() must be called first."; + return store_.GetString(key, value); +} + +void OsReleaseReader::LoadTestingOnly(const base::FilePath& root_dir) { + Load(root_dir); +} + +void OsReleaseReader::Load(const base::FilePath& root_dir) { + base::FilePath osrelease = root_dir.Append("etc").Append("os-release"); + if (!store_.Load(osrelease)) { + // /etc/os-release might not be present (cros deploying a new configuration + // or no fields set at all). Just print a debug message and continue. + DLOG(INFO) << "Could not load fields from " << osrelease.value(); + } + + base::FilePath osreleased = root_dir.Append("etc").Append("os-release.d"); + base::FileEnumerator enumerator( + osreleased, false, base::FileEnumerator::FILES); + + for (base::FilePath path = enumerator.Next(); !path.empty(); + path = enumerator.Next()) { + std::string content; + if (!base::ReadFileToString(path, &content)) { + // The only way to fail is if a file exist in /etc/os-release.d but we + // cannot read it. + PLOG(FATAL) << "Could not read " << path.value(); + } + // There might be a trailing new line. Strip it to keep only the first line + // of the file. + content = brillo::string_utils::SplitAtFirst(content, "\n", true).first; + store_.SetString(path.BaseName().value(), content); + } + initialized_ = true; +} + +} // namespace brillo diff --git a/brillo/osrelease_reader.h b/brillo/osrelease_reader.h new file mode 100644 index 0000000..b72f733 --- /dev/null +++ b/brillo/osrelease_reader.h @@ -0,0 +1,54 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Wrapper around /etc/os-release and /etc/os-release.d. +// Standard fields can come from both places depending on how we set them. They +// should always be accessed through this interface. + +#ifndef LIBCHROMEOS_BRILLO_OSRELEASE_READER_H_ +#define LIBCHROMEOS_BRILLO_OSRELEASE_READER_H_ + +#include + +#include +#include +#include + +namespace brillo { + +class BRILLO_EXPORT OsReleaseReader final { + public: + // Create an empty reader + OsReleaseReader() = default; + + // Loads the key=value pairs from either /etc/os-release.d/ or + // /etc/os-release. + void Load(); + + // Same as the private Load method. + // This need to be public so that services can use it in testing mode (for + // autotest tests for example). + // This should not be used in production so suffix it with TestingOnly to + // make it obvious. + void LoadTestingOnly(const base::FilePath& root_dir); + + // Getter for the given key. Returns whether the key was found on the store. + bool GetString(const std::string& key, std::string* value) const; + + private: + // The map storing all the key-value pairs. + KeyValueStore store_; + + // os-release can be lazily loaded if need be. + bool initialized_; + + // Load the data from a given root_dir. + BRILLO_PRIVATE void Load(const base::FilePath& root_dir); + + DISALLOW_COPY_AND_ASSIGN(OsReleaseReader); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_OSRELEASE_READER_H_ diff --git a/brillo/osrelease_reader_unittest.cc b/brillo/osrelease_reader_unittest.cc new file mode 100644 index 0000000..88185a0 --- /dev/null +++ b/brillo/osrelease_reader_unittest.cc @@ -0,0 +1,95 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +using std::string; + +namespace brillo { + +class OsReleaseReaderTest : public ::testing::Test { + public: + void SetUp() override { + CHECK(temp_dir_.CreateUniqueTempDir()); + osreleased_ = temp_dir_.path().Append("etc").Append("os-release.d"); + osrelease_ = temp_dir_.path().Append("etc").Append("os-release"); + base::CreateDirectory(osreleased_); + } + + protected: + base::FilePath temp_file_, osrelease_, osreleased_; + base::ScopedTempDir temp_dir_; + OsReleaseReader store_; // reader under test. +}; + +TEST_F(OsReleaseReaderTest, MissingOsReleaseTest) { + store_.LoadTestingOnly(temp_dir_.path()); +} + +TEST_F(OsReleaseReaderTest, MissingOsReleaseDTest) { + base::DeleteFile(osreleased_, true); + store_.LoadTestingOnly(temp_dir_.path()); +} + +TEST_F(OsReleaseReaderTest, CompleteTest) { + string hello = "hello"; + string ola = "ola"; + string bob = "bob"; + string osreleasecontent = "TEST_KEY=bonjour\nNAME=bob\n"; + + base::WriteFile(osreleased_.Append("TEST_KEY"), hello.data(), hello.size()); + base::WriteFile(osreleased_.Append("GREETINGS"), ola.data(), ola.size()); + base::WriteFile(osrelease_, osreleasecontent.data(), osreleasecontent.size()); + + store_.LoadTestingOnly(temp_dir_.path()); + + string test_key_value; + ASSERT_TRUE(store_.GetString("TEST_KEY", &test_key_value)); + + string greetings_value; + ASSERT_TRUE(store_.GetString("GREETINGS", &greetings_value)); + + string name_value; + ASSERT_TRUE(store_.GetString("NAME", &name_value)); + + string nonexistent_value; + // Getting the string should fail if the key does not exist. + ASSERT_FALSE(store_.GetString("DOES_NOT_EXIST", &nonexistent_value)); + + // hello in chosen (from os-release.d) instead of bonjour from os-release. + ASSERT_EQ(hello, test_key_value); + + // greetings is set to ola. + ASSERT_EQ(ola, greetings_value); + + // Name from os-release is set. + ASSERT_EQ(bob, name_value); +} + +TEST_F(OsReleaseReaderTest, NoNewLine) { + // New lines should be stripped from os-release.d files. + string hello = "hello\n"; + string bonjour = "bonjour\ngarbage"; + + base::WriteFile(osreleased_.Append("HELLO"), hello.data(), hello.size()); + base::WriteFile( + osreleased_.Append("BONJOUR"), bonjour.data(), bonjour.size()); + + store_.LoadTestingOnly(temp_dir_.path()); + + string hello_value; + string bonjour_value; + + ASSERT_TRUE(store_.GetString("HELLO", &hello_value)); + ASSERT_TRUE(store_.GetString("BONJOUR", &bonjour_value)); + + ASSERT_EQ("hello", hello_value); + ASSERT_EQ("bonjour", bonjour_value); +} + +} // namespace brillo diff --git a/brillo/pointer_utils.h b/brillo/pointer_utils.h new file mode 100644 index 0000000..1cee1f8 --- /dev/null +++ b/brillo/pointer_utils.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_POINTER_UTILS_H_ +#define LIBCHROMEOS_BRILLO_POINTER_UTILS_H_ + +#include +#include + +namespace brillo { + +// AdvancePointer() is a helper function to advance void pointer by +// |byte_offset| bytes. Both const and non-const overloads are provided. +inline void* AdvancePointer(void* pointer, ssize_t byte_offset) { + return reinterpret_cast(pointer) + byte_offset; +} +inline const void* AdvancePointer(const void* pointer, ssize_t byte_offset) { + return reinterpret_cast(pointer) + byte_offset; +} + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_POINTER_UTILS_H_ diff --git a/brillo/process.cc b/brillo/process.cc new file mode 100644 index 0000000..6199bc1 --- /dev/null +++ b/brillo/process.cc @@ -0,0 +1,358 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/process.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#define setresuid(_u1, _u2, _u3) setreuid(_u1, _u2) +#define setresgid(_g1, _g2, _g3) setregid(_g1, _g2) +#endif // !__linux__ + +namespace brillo { + +bool ReturnTrue() { + return true; +} + +Process::Process() { +} + +Process::~Process() { +} + +bool Process::ProcessExists(pid_t pid) { + return base::DirectoryExists( + base::FilePath(base::StringPrintf("/proc/%d", pid))); +} + +ProcessImpl::ProcessImpl() + : pid_(0), + uid_(-1), + gid_(-1), + pre_exec_(base::Bind(&ReturnTrue)), + search_path_(false), + inherit_parent_signal_mask_(false) { +} + +ProcessImpl::~ProcessImpl() { + Reset(0); +} + +void ProcessImpl::AddArg(const std::string& arg) { + arguments_.push_back(arg); +} + +void ProcessImpl::RedirectOutput(const std::string& output_file) { + output_file_ = output_file; +} + +void ProcessImpl::RedirectUsingPipe(int child_fd, bool is_input) { + PipeInfo info; + info.is_input_ = is_input; + info.is_bound_ = false; + pipe_map_[child_fd] = info; +} + +void ProcessImpl::BindFd(int parent_fd, int child_fd) { + PipeInfo info; + info.is_bound_ = true; + + // info.child_fd_ is the 'child half' of the pipe, which gets dup2()ed into + // place over child_fd. Since we already have the child we want to dup2() into + // place, we can set info.child_fd_ to parent_fd and leave info.parent_fd_ + // invalid. + info.child_fd_ = parent_fd; + info.parent_fd_ = -1; + pipe_map_[child_fd] = info; +} + +void ProcessImpl::SetUid(uid_t uid) { + uid_ = uid; +} + +void ProcessImpl::SetGid(gid_t gid) { + gid_ = gid; +} + +void ProcessImpl::SetInheritParentSignalMask(bool inherit) { + inherit_parent_signal_mask_ = inherit; +} + +void ProcessImpl::SetPreExecCallback(const PreExecCallback& cb) { + pre_exec_ = cb; +} + +void ProcessImpl::SetSearchPath(bool search_path) { + search_path_ = search_path; +} + +int ProcessImpl::GetPipe(int child_fd) { + PipeMap::iterator i = pipe_map_.find(child_fd); + if (i == pipe_map_.end()) + return -1; + else + return i->second.parent_fd_; +} + +bool ProcessImpl::PopulatePipeMap() { + // Verify all target fds are already open. With this assumption we + // can be sure that the pipe fds created below do not overlap with + // any of the target fds which simplifies how we dup2 to them. Note + // that multi-threaded code could close i->first between this loop + // and the next. + for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { + struct stat stat_buffer; + if (fstat(i->first, &stat_buffer) < 0) { + int saved_errno = errno; + LOG(ERROR) << "Unable to fstat fd " << i->first << ": " << saved_errno; + return false; + } + } + + for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { + if (i->second.is_bound_) { + // already have a parent fd, and the child fd gets dup()ed later. + continue; + } + int pipefds[2]; + if (pipe(pipefds) < 0) { + int saved_errno = errno; + LOG(ERROR) << "pipe call failed with: " << saved_errno; + return false; + } + if (i->second.is_input_) { + // pipe is an input from the prospective of the child. + i->second.parent_fd_ = pipefds[1]; + i->second.child_fd_ = pipefds[0]; + } else { + i->second.parent_fd_ = pipefds[0]; + i->second.child_fd_ = pipefds[1]; + } + } + return true; +} + +bool ProcessImpl::Start() { + // If no arguments are provided, fail. + if (arguments_.empty()) { + return false; + } + scoped_ptr argv(new char*[arguments_.size() + 1]); + + for (size_t i = 0; i < arguments_.size(); ++i) + argv[i] = const_cast(arguments_[i].c_str()); + + argv[arguments_.size()] = nullptr; + + if (!PopulatePipeMap()) { + LOG(ERROR) << "Failing to start because pipe creation failed"; + return false; + } + + pid_t pid = fork(); + int saved_errno = errno; + if (pid < 0) { + LOG(ERROR) << "Fork failed: " << saved_errno; + Reset(0); + return false; + } + + if (pid == 0) { + // Executing inside the child process. + // Close parent's side of the child pipes. dup2 ours into place and + // then close our ends. + for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { + if (i->second.parent_fd_ != -1) + IGNORE_EINTR(close(i->second.parent_fd_)); + HANDLE_EINTR(dup2(i->second.child_fd_, i->first)); + } + // Defer the actual close() of the child fd until afterward; this lets the + // same child fd be bound to multiple fds using BindFd + for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { + IGNORE_EINTR(close(i->second.child_fd_)); + } + if (!output_file_.empty()) { + int output_handle = HANDLE_EINTR(open( + output_file_.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, + 0666)); + if (output_handle < 0) { + PLOG(ERROR) << "Could not create " << output_file_; + // Avoid exit() to avoid atexit handlers from parent. + _exit(kErrorExitStatus); + } + HANDLE_EINTR(dup2(output_handle, STDOUT_FILENO)); + HANDLE_EINTR(dup2(output_handle, STDERR_FILENO)); + // Only close output_handle if it does not happen to be one of + // the two standard file descriptors we are trying to redirect. + if (output_handle != STDOUT_FILENO && output_handle != STDERR_FILENO) { + IGNORE_EINTR(close(output_handle)); + } + } + if (gid_ != static_cast(-1) && setresgid(gid_, gid_, gid_) < 0) { + int saved_errno = errno; + LOG(ERROR) << "Unable to set GID to " << gid_ << ": " << saved_errno; + _exit(kErrorExitStatus); + } + if (uid_ != static_cast(-1) && setresuid(uid_, uid_, uid_) < 0) { + int saved_errno = errno; + LOG(ERROR) << "Unable to set UID to " << uid_ << ": " << saved_errno; + _exit(kErrorExitStatus); + } + if (!pre_exec_.Run()) { + LOG(ERROR) << "Pre-exec callback failed"; + _exit(kErrorExitStatus); + } + // Reset signal mask for the child process if not inheriting signal mask + // from the parent process. + if (!inherit_parent_signal_mask_) { + sigset_t signal_mask; + CHECK_EQ(0, sigemptyset(&signal_mask)); + CHECK_EQ(0, sigprocmask(SIG_SETMASK, &signal_mask, nullptr)); + } + if (search_path_) { + execvp(argv[0], &argv[0]); + } else { + execv(argv[0], &argv[0]); + } + PLOG(ERROR) << "Exec of " << argv[0] << " failed:"; + _exit(kErrorExitStatus); + } else { + // Still executing inside the parent process with known child pid. + arguments_.clear(); + UpdatePid(pid); + // Close our copy of child side pipes. + for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { + IGNORE_EINTR(close(i->second.child_fd_)); + } + } + return true; +} + +int ProcessImpl::Wait() { + int status = 0; + if (pid_ == 0) { + LOG(ERROR) << "Process not running"; + return -1; + } + if (HANDLE_EINTR(waitpid(pid_, &status, 0)) < 0) { + int saved_errno = errno; + LOG(ERROR) << "Problem waiting for pid " << pid_ << ": " << saved_errno; + return -1; + } + pid_t old_pid = pid_; + // Update the pid to 0 - do not Reset as we do not want to try to + // kill the process that has just exited. + UpdatePid(0); + if (!WIFEXITED(status)) { + DCHECK(WIFSIGNALED(status)) << old_pid + << " neither exited, nor died on a signal?"; + LOG(ERROR) << "Process " << old_pid + << " did not exit normally: " << WTERMSIG(status); + return -1; + } + return WEXITSTATUS(status); +} + +int ProcessImpl::Run() { + if (!Start()) { + return -1; + } + return Wait(); +} + +pid_t ProcessImpl::pid() { + return pid_; +} + +bool ProcessImpl::Kill(int signal, int timeout) { + if (pid_ == 0) { + // Passing pid == 0 to kill is committing suicide. Check specifically. + LOG(ERROR) << "Process not running"; + return false; + } + if (kill(pid_, signal) < 0) { + int saved_errno = errno; + LOG(ERROR) << "Unable to send signal to " << pid_ << " error " + << saved_errno; + return false; + } + base::TimeTicks start_signal = base::TimeTicks::Now(); + do { + int status = 0; + pid_t w = waitpid(pid_, &status, WNOHANG); + int saved_errno = errno; + if (w < 0) { + if (saved_errno == ECHILD) + return true; + LOG(ERROR) << "Waitpid returned " << w << ", errno " << saved_errno; + return false; + } + if (w > 0) { + Reset(0); + return true; + } + usleep(100); + } while ((base::TimeTicks::Now() - start_signal).InSecondsF() <= timeout); + LOG(INFO) << "process " << pid_ << " did not exit from signal " << signal + << " in " << timeout << " seconds"; + return false; +} + +void ProcessImpl::UpdatePid(pid_t new_pid) { + pid_ = new_pid; +} + +void ProcessImpl::Reset(pid_t new_pid) { + arguments_.clear(); + // Close our side of all pipes to this child giving the child to + // handle sigpipes and shutdown nicely, though likely it won't + // have time. + for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) + IGNORE_EINTR(close(i->second.parent_fd_)); + pipe_map_.clear(); + if (pid_) + Kill(SIGKILL, 0); + UpdatePid(new_pid); +} + +bool ProcessImpl::ResetPidByFile(const std::string& pid_file) { + std::string contents; + if (!base::ReadFileToString(base::FilePath(pid_file), &contents)) { + LOG(ERROR) << "Could not read pid file" << pid_file; + return false; + } + base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents); + int64_t pid_int64 = 0; + if (!base::StringToInt64(contents, &pid_int64)) { + LOG(ERROR) << "Unexpected pid file contents"; + return false; + } + Reset(pid_int64); + return true; +} + +pid_t ProcessImpl::Release() { + pid_t old_pid = pid_; + pid_ = 0; + return old_pid; +} + +} // namespace brillo diff --git a/brillo/process.h b/brillo/process.h new file mode 100644 index 0000000..568e8e0 --- /dev/null +++ b/brillo/process.h @@ -0,0 +1,200 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_PROCESS_H_ +#define LIBCHROMEOS_BRILLO_PROCESS_H_ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { +// Manages a process. Can create the process, attach to an existing +// process by pid or pid file, and kill the process. Upon destruction +// any managed process is killed with SIGKILL. Use Release() to +// release the process from management. A given system process may +// only be managed by one Process at a time. +class BRILLO_EXPORT Process { + public: + Process(); + virtual ~Process(); + + // Adds |arg| to the executable command-line to be run. The + // executable name itself is the first argument. + virtual void AddArg(const std::string& arg) = 0; + + // Adds |option| and |value| as an option with a string value to the + // command line to be run. + inline void AddStringOption(const std::string& option, + const std::string& value) { + AddArg(option); + AddArg(value); + } + + // Adds |option| and |value| as an option which takes an integer + // value to the command line to be run. + inline void AddIntOption(const std::string& option, int value) { + AddArg(option); + AddArg(base::StringPrintf("%d", value)); + } + + // Redirects stderr and stdout to |output_file|. + virtual void RedirectOutput(const std::string& output_file) = 0; + + // Indicates we want to redirect |child_fd| in the child process's + // file table to a pipe. |child_fd| will be available for reading + // from child process's perspective iff |is_input|. + virtual void RedirectUsingPipe(int child_fd, bool is_input) = 0; + + // Binds the given file descriptor in the parent to the given file + // descriptor in the child. + virtual void BindFd(int parent_fd, int child_fd) = 0; + + // Set the real/effective/saved user ID of the child process. + virtual void SetUid(uid_t uid) = 0; + + // Set the real/effective/saved group ID of the child process. + virtual void SetGid(gid_t gid) = 0; + + // Set a flag |inherit| to indicate if the child process intend to + // inherit signal mask from the parent process. When |inherit| is + // set to true, the child process will inherit signal mask from the + // parent process. This could cause unintended side effect, where all + // the signals to the child process might be blocked if they are set + // in the parent's signal mask. + virtual void SetInheritParentSignalMask(bool inherit) = 0; + + typedef base::Callback PreExecCallback; + + // Set the pre-exec callback. This is called after all setup is complete but + // before we exec() the process. The callback may return false to cause Start + // to return false without starting the process. + virtual void SetPreExecCallback(const PreExecCallback& cb) = 0; + + // Sets whether starting the process should search the system path or not. + // By default the system path will not be searched. + virtual void SetSearchPath(bool search_path) = 0; + + // Gets the pipe file descriptor mapped to the process's |child_fd|. + virtual int GetPipe(int child_fd) = 0; + + // Starts this process, returning true if successful. + virtual bool Start() = 0; + + // Waits for this process to finish. Returns the process's exit + // status if it exited normally, or otherwise returns -1. Note + // that kErrorExitStatus may be returned if an error occurred + // after forking and before execing the child process. + virtual int Wait() = 0; + + // Start and wait for this process to finish. Returns same value as + // Wait(). + virtual int Run() = 0; + + // Returns the pid of this process or else returns 0 if there is no + // corresponding process (either because it has not yet been started + // or has since exited). + virtual pid_t pid() = 0; + + // Sends |signal| to process and wait |timeout| seconds until it + // dies. If process is not a child, returns immediately with a + // value based on whether kill was successful. If the process is a + // child and |timeout| is non-zero, returns true if the process is + // able to be reaped within the given |timeout| in seconds. + virtual bool Kill(int signal, int timeout) = 0; + + // Resets this Process object to refer to the process with |pid|. + // If |pid| is zero, this object no longer refers to a process. + virtual void Reset(pid_t new_pid) = 0; + + // Same as Reset but reads the pid from |pid_file|. Returns false + // only when the file cannot be read/parsed. + virtual bool ResetPidByFile(const std::string& pid_file) = 0; + + // Releases the process so that on destruction, the process is not killed. + virtual pid_t Release() = 0; + + // Returns if |pid| is a currently running process. + static bool ProcessExists(pid_t pid); + + // When returned from Wait or Run, indicates an error may have occurred + // creating the process. + enum { kErrorExitStatus = 127 }; +}; + +class BRILLO_EXPORT ProcessImpl : public Process { + public: + ProcessImpl(); + virtual ~ProcessImpl(); + + virtual void AddArg(const std::string& arg); + virtual void RedirectOutput(const std::string& output_file); + virtual void RedirectUsingPipe(int child_fd, bool is_input); + virtual void BindFd(int parent_fd, int child_fd); + virtual void SetUid(uid_t uid); + virtual void SetGid(gid_t gid); + virtual void SetInheritParentSignalMask(bool inherit); + virtual void SetPreExecCallback(const PreExecCallback& cb); + virtual void SetSearchPath(bool search_path); + virtual int GetPipe(int child_fd); + virtual bool Start(); + virtual int Wait(); + virtual int Run(); + virtual pid_t pid(); + virtual bool Kill(int signal, int timeout); + virtual void Reset(pid_t pid); + virtual bool ResetPidByFile(const std::string& pid_file); + virtual pid_t Release(); + + protected: + struct PipeInfo { + PipeInfo() : parent_fd_(-1), child_fd_(-1), is_input_(false) {} + // Parent (our) side of the pipe to the child process. + int parent_fd_; + // Child's side of the pipe to the parent. + int child_fd_; + // Is this an input or output pipe from child's perspective. + bool is_input_; + // Is this a bound (pre-existing) file descriptor? + bool is_bound_; + }; + typedef std::map PipeMap; + + void UpdatePid(pid_t new_pid); + bool PopulatePipeMap(); + + private: + FRIEND_TEST(ProcessTest, ResetPidByFile); + + // Pid of currently managed process or 0 if no currently managed + // process. pid must not be modified except by calling + // UpdatePid(new_pid). + pid_t pid_; + std::string output_file_; + std::vector arguments_; + // Map of child target file descriptors (first) to information about + // pipes created (second). + PipeMap pipe_map_; + uid_t uid_; + gid_t gid_; + PreExecCallback pre_exec_; + bool search_path_; + // Flag indicating to inherit signal mask from the parent process. It + // is set to false by default, which means by default the child process + // will not inherit signal mask from the parent process. + bool inherit_parent_signal_mask_; +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_PROCESS_H_ diff --git a/brillo/process_information.cc b/brillo/process_information.cc new file mode 100644 index 0000000..6b03c40 --- /dev/null +++ b/brillo/process_information.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/process_information.h" + +namespace brillo { + +ProcessInformation::ProcessInformation() : cmd_line_(), process_id_(-1) { +} +ProcessInformation::~ProcessInformation() { +} + +std::string ProcessInformation::GetCommandLine() { + std::string result; + for (std::vector::iterator cmd_itr = cmd_line_.begin(); + cmd_itr != cmd_line_.end(); + cmd_itr++) { + if (result.length()) { + result.append(" "); + } + result.append((*cmd_itr)); + } + return result; +} + +} // namespace brillo diff --git a/brillo/process_information.h b/brillo/process_information.h new file mode 100644 index 0000000..8bdcf43 --- /dev/null +++ b/brillo/process_information.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_PROCESS_INFORMATION_H_ +#define LIBCHROMEOS_BRILLO_PROCESS_INFORMATION_H_ + +#include +#include +#include + +#include + +namespace brillo { + +// Information for a single running process. Stores its command line, set of +// open files, process id and working directory. +class BRILLO_EXPORT ProcessInformation { + public: + ProcessInformation(); + virtual ~ProcessInformation(); + + std::string GetCommandLine(); + + // Set the command line array. This method DOES swap out the contents of + // |value|. The caller should expect an empty vector on return. + void set_cmd_line(std::vector* value) { + cmd_line_.clear(); + cmd_line_.swap(*value); + } + + const std::vector& get_cmd_line() { return cmd_line_; } + + // Set the command line array. This method DOES swap out the contents of + // |value|. The caller should expect an empty set on return. + void set_open_files(std::set* value) { + open_files_.clear(); + open_files_.swap(*value); + } + + const std::set& get_open_files() { return open_files_; } + + // Set the command line array. This method DOES swap out the contents of + // |value|. The caller should expect an empty string on return. + void set_cwd(std::string* value) { + cwd_.clear(); + cwd_.swap(*value); + } + + const std::string& get_cwd() { return cwd_; } + + void set_process_id(int value) { process_id_ = value; } + + int get_process_id() { return process_id_; } + + private: + std::vector cmd_line_; + std::set open_files_; + std::string cwd_; + int process_id_; +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_PROCESS_INFORMATION_H_ diff --git a/brillo/process_mock.h b/brillo/process_mock.h new file mode 100644 index 0000000..88b60c6 --- /dev/null +++ b/brillo/process_mock.h @@ -0,0 +1,43 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_PROCESS_MOCK_H_ +#define LIBCHROMEOS_BRILLO_PROCESS_MOCK_H_ + +#include + +#include + +#include "brillo/process.h" + +namespace brillo { + +class ProcessMock : public Process { + public: + ProcessMock() {} + virtual ~ProcessMock() {} + + MOCK_METHOD1(AddArg, void(const std::string& arg)); + MOCK_METHOD1(RedirectOutput, void(const std::string& output_file)); + MOCK_METHOD2(RedirectUsingPipe, void(int child_fd, bool is_input)); + MOCK_METHOD2(BindFd, void(int parent_fd, int child_fd)); + MOCK_METHOD1(SetUid, void(uid_t)); + MOCK_METHOD1(SetGid, void(gid_t)); + MOCK_METHOD1(SetInheritParentSignalMask, void(bool)); + MOCK_METHOD1(SetPreExecCallback, void(const PreExecCallback&)); + MOCK_METHOD1(SetSearchPath, void(bool)); + MOCK_METHOD1(GetPipe, int(int child_fd)); + MOCK_METHOD0(Start, bool()); + MOCK_METHOD0(Wait, int()); + MOCK_METHOD0(Run, int()); + MOCK_METHOD0(pid, pid_t()); + MOCK_METHOD2(Kill, bool(int signal, int timeout)); + MOCK_METHOD1(Reset, void(pid_t)); + MOCK_METHOD1(ResetPidByFile, bool(const std::string& pid_file)); + MOCK_METHOD0(Release, pid_t()); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_PROCESS_MOCK_H_ diff --git a/brillo/process_reaper.cc b/brillo/process_reaper.cc new file mode 100644 index 0000000..5ee1195 --- /dev/null +++ b/brillo/process_reaper.cc @@ -0,0 +1,86 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/process_reaper.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { + +ProcessReaper::~ProcessReaper() { + Unregister(); +} + +void ProcessReaper::Register( + AsynchronousSignalHandlerInterface* async_signal_handler) { + CHECK(!async_signal_handler_); + async_signal_handler_ = async_signal_handler; + async_signal_handler->RegisterHandler( + SIGCHLD, + base::Bind(&ProcessReaper::HandleSIGCHLD, base::Unretained(this))); +} + +void ProcessReaper::Unregister() { + if (!async_signal_handler_) + return; + async_signal_handler_->UnregisterHandler(SIGCHLD); + async_signal_handler_ = nullptr; +} + +bool ProcessReaper::WatchForChild(const tracked_objects::Location& from_here, + pid_t pid, + const ChildCallback& callback) { + if (watched_processes_.find(pid) != watched_processes_.end()) + return false; + watched_processes_.emplace(pid, WatchedProcess{from_here, callback}); + return true; +} + +bool ProcessReaper::HandleSIGCHLD(const struct signalfd_siginfo& sigfd_info) { + // One SIGCHLD may correspond to multiple terminated children, so ignore + // sigfd_info and reap any available children. + while (true) { + siginfo_t info; + info.si_pid = 0; + int rc = HANDLE_EINTR(waitid(P_ALL, 0, &info, WNOHANG | WEXITED)); + + if (rc == -1) { + if (errno != ECHILD) { + PLOG(ERROR) << "waitid failed"; + } + break; + } + + if (info.si_pid == 0) { + break; + } + + auto proc = watched_processes_.find(info.si_pid); + if (proc == watched_processes_.end()) { + LOG(INFO) << "Untracked process " << info.si_pid + << " terminated with status " << info.si_status + << " (code = " << info.si_code << ")"; + } else { + DVLOG_LOC(proc->second.location, 1) + << "Process " << info.si_pid << " terminated with status " + << info.si_status << " (code = " << info.si_code << ")"; + ChildCallback callback = std::move(proc->second.callback); + watched_processes_.erase(proc); + callback.Run(info); + } + } + + // Return false to indicate that our handler should not be uninstalled. + return false; +} + +} // namespace brillo diff --git a/brillo/process_reaper.h b/brillo/process_reaper.h new file mode 100644 index 0000000..4d28f4e --- /dev/null +++ b/brillo/process_reaper.h @@ -0,0 +1,67 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_PROCESS_REAPER_H_ +#define LIBCHROMEOS_BRILLO_PROCESS_REAPER_H_ + +#include + +#include + +#include +#include +#include +#include +#include + +namespace brillo { + +class BRILLO_EXPORT ProcessReaper final { + public: + // The callback called when a child exits. + using ChildCallback = base::Callback; + + ProcessReaper() = default; + ~ProcessReaper(); + + // Register the ProcessReaper using either the provided + // brillo::AsynchronousSignalHandlerInterface. You can call Unregister() to + // remove this ProcessReapper or it will be called during shutdown. + // You can only register this ProcessReaper with one signal handler at a time. + void Register(AsynchronousSignalHandlerInterface* async_signal_handler); + + // Unregisters the ProcessReaper from the + // brillo::AsynchronousSignalHandlerInterface passed in Register(). It + // doesn't do anything if not registered. + void Unregister(); + + // Watch for the child process |pid| to finish and call |callback| when the + // selected process exits or the process terminates for other reason. The + // |callback| receives the exit status and exit code of the terminated process + // as a siginfo_t. See wait(2) for details about siginfo_t. + bool WatchForChild(const tracked_objects::Location& from_here, + pid_t pid, + const ChildCallback& callback); + + private: + // SIGCHLD handler for the AsynchronousSignalHandler. Always returns false + // (meaning that the signal handler should not be unregistered). + bool HandleSIGCHLD(const signalfd_siginfo& sigfd_info); + + struct WatchedProcess { + tracked_objects::Location location; + ChildCallback callback; + }; + std::map watched_processes_; + + // The |async_signal_handler_| is owned by the caller and is |nullptr| when + // not registered. + AsynchronousSignalHandlerInterface* async_signal_handler_{nullptr}; + + DISALLOW_COPY_AND_ASSIGN(ProcessReaper); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_PROCESS_REAPER_H_ diff --git a/brillo/process_reaper_unittest.cc b/brillo/process_reaper_unittest.cc new file mode 100644 index 0000000..499b908 --- /dev/null +++ b/brillo/process_reaper_unittest.cc @@ -0,0 +1,121 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +pid_t ForkChildAndExit(int exit_code) { + pid_t pid = fork(); + PCHECK(pid != -1); + if (pid == 0) { + _exit(exit_code); + } + return pid; +} + +pid_t ForkChildAndKill(int sig) { + pid_t pid = fork(); + PCHECK(pid != -1); + if (pid == 0) { + if (raise(sig) != 0) { + PLOG(ERROR) << "raise(" << sig << ")"; + } + _exit(0); // Not reached. This value will cause the test to fail. + } + return pid; +} + +} // namespace + +namespace brillo { + +class ProcessReaperTest : public ::testing::Test { + public: + void SetUp() override { + brillo_loop_.SetAsCurrent(); + async_signal_handler_.Init(); + process_reaper_.Register(&async_signal_handler_); + } + + protected: + base::MessageLoopForIO base_loop_; + brillo::BaseMessageLoop brillo_loop_{&base_loop_}; + brillo::AsynchronousSignalHandler async_signal_handler_; + + // ProcessReaper under test. + ProcessReaper process_reaper_; +}; + +TEST_F(ProcessReaperTest, UnregisterWhenNotRegistered) { + ProcessReaper another_process_reaper_; + another_process_reaper_.Unregister(); +} + +TEST_F(ProcessReaperTest, UnregisterAndReregister) { + process_reaper_.Unregister(); + process_reaper_.Register(&async_signal_handler_); + // This checks that we can unregister the ProcessReaper and then destroy it. + process_reaper_.Unregister(); +} + +TEST_F(ProcessReaperTest, ReapExitedChild) { + pid_t pid = ForkChildAndExit(123); + EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind( + [this](const siginfo_t& info) { + EXPECT_EQ(CLD_EXITED, info.si_code); + EXPECT_EQ(123, info.si_status); + this->brillo_loop_.BreakLoop(); + }))); + brillo_loop_.Run(); +} + +// Test that simultaneous child processes fire their respective callbacks when +// exiting. +TEST_F(ProcessReaperTest, ReapedChildsMatchCallbacks) { + int running_childs = 10; + for (int i = 0; i < running_childs; ++i) { + // Different processes will have different exit values. + int exit_value = 1 + i; + pid_t pid = ForkChildAndExit(exit_value); + EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind( + [this, exit_value, &running_childs](const siginfo_t& info) { + EXPECT_EQ(CLD_EXITED, info.si_code); + EXPECT_EQ(exit_value, info.si_status); + running_childs--; + if (running_childs == 0) + this->brillo_loop_.BreakLoop(); + }))); + } + // This sleep is optional. It helps to have more processes exit before we + // start watching for them in the message loop. + usleep(10 * 1000); + brillo_loop_.Run(); + EXPECT_EQ(0, running_childs); +} + +TEST_F(ProcessReaperTest, ReapKilledChild) { + pid_t pid = ForkChildAndKill(SIGKILL); + EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind( + [this](const siginfo_t& info) { + EXPECT_EQ(CLD_KILLED, info.si_code); + EXPECT_EQ(SIGKILL, info.si_status); + this->brillo_loop_.BreakLoop(); + }))); + brillo_loop_.Run(); +} + +} // namespace brillo diff --git a/brillo/process_unittest.cc b/brillo/process_unittest.cc new file mode 100644 index 0000000..ebf1d4e --- /dev/null +++ b/brillo/process_unittest.cc @@ -0,0 +1,337 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/process.h" + +#include + +#include +#include +#include +#include + +#include "brillo/process_mock.h" +#include "brillo/test_helpers.h" + +using base::FilePath; + +// This test assumes the following standard binaries are installed. +#if defined(__ANDROID__) +# define SYSTEM_PREFIX "/system" +#else +# define SYSTEM_PREFIX "" +#endif + +static const char kBinSh[] = SYSTEM_PREFIX "/bin/sh"; +static const char kBinCat[] = SYSTEM_PREFIX "/bin/cat"; +static const char kBinCp[] = SYSTEM_PREFIX "/bin/cp"; +static const char kBinEcho[] = SYSTEM_PREFIX "/bin/echo"; +static const char kBinFalse[] = SYSTEM_PREFIX "/bin/false"; +static const char kBinSleep[] = SYSTEM_PREFIX "/bin/sleep"; +static const char kBinTrue[] = SYSTEM_PREFIX "/bin/true"; + +namespace brillo { + +// Test that the mock has all the functions of the interface by +// instantiating it. This variable is not used elsewhere. +struct CompileMocks { + ProcessMock process_mock; +}; + +TEST(SimpleProcess, Basic) { + // Log must be cleared before running this test, just as ProcessTest::SetUp. + ClearLog(); + ProcessImpl process; + process.AddArg(kBinEcho); + EXPECT_EQ(0, process.Run()); + EXPECT_EQ("", GetLog()); +} + +TEST(SimpleProcess, NoSearchPath) { + ProcessImpl process; + process.AddArg("echo"); + EXPECT_EQ(127, process.Run()); +} + +TEST(SimpleProcess, SearchPath) { + ProcessImpl process; + process.AddArg("echo"); + process.SetSearchPath(true); + EXPECT_EQ(EXIT_SUCCESS, process.Run()); +} + +TEST(SimpleProcess, BindFd) { + int fds[2]; + char buf[16]; + static const char* kMsg = "hello, world!"; + ProcessImpl process; + EXPECT_EQ(0, pipe(fds)); + process.AddArg(kBinEcho); + process.AddArg(kMsg); + process.BindFd(fds[1], 1); + process.Run(); + memset(buf, 0, sizeof(buf)); + EXPECT_EQ(read(fds[0], buf, sizeof(buf) - 1), strlen(kMsg) + 1); + EXPECT_EQ(std::string(kMsg) + "\n", std::string(buf)); +} + +class ProcessTest : public ::testing::Test { + public: + void SetUp() { + CHECK(temp_dir_.CreateUniqueTempDir()); + output_file_ = temp_dir_.path().Append("fork_out").value(); + process_.RedirectOutput(output_file_); + ClearLog(); + } + + static void SetUpTestCase() { + base::CommandLine::Init(0, nullptr); + ::brillo::InitLog(brillo::kLogToStderr); + ::brillo::LogToString(true); + } + + protected: + void CheckStderrCaptured(); + FilePath GetFdPath(int fd); + + ProcessImpl process_; + std::vector args_; + std::string output_file_; + base::ScopedTempDir temp_dir_; +}; + +TEST_F(ProcessTest, Basic) { + process_.AddArg(kBinEcho); + process_.AddArg("hello world"); + EXPECT_EQ(0, process_.Run()); + ExpectFileEquals("hello world\n", output_file_.c_str()); + EXPECT_EQ("", GetLog()); +} + +TEST_F(ProcessTest, AddStringOption) { + process_.AddArg(kBinEcho); + process_.AddStringOption("--hello", "world"); + EXPECT_EQ(0, process_.Run()); + ExpectFileEquals("--hello world\n", output_file_.c_str()); +} + +TEST_F(ProcessTest, AddIntValue) { + process_.AddArg(kBinEcho); + process_.AddIntOption("--answer", 42); + EXPECT_EQ(0, process_.Run()); + ExpectFileEquals("--answer 42\n", output_file_.c_str()); +} + +TEST_F(ProcessTest, NonZeroReturnValue) { + process_.AddArg(kBinFalse); + EXPECT_EQ(1, process_.Run()); + ExpectFileEquals("", output_file_.c_str()); + EXPECT_EQ("", GetLog()); +} + +TEST_F(ProcessTest, BadOutputFile) { + process_.AddArg(kBinEcho); + process_.RedirectOutput("/bad/path"); + EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); +} + +TEST_F(ProcessTest, BadExecutable) { + process_.AddArg("false"); + EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); +} + +void ProcessTest::CheckStderrCaptured() { + std::string contents; + process_.AddArg(kBinSh); + process_.AddArg("-c"); + process_.AddArg("echo errormessage 1>&2 && exit 1"); + EXPECT_EQ(1, process_.Run()); + EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); + EXPECT_NE(std::string::npos, contents.find("errormessage")); + EXPECT_EQ("", GetLog()); +} + +TEST_F(ProcessTest, StderrCaptured) { + CheckStderrCaptured(); +} + +TEST_F(ProcessTest, StderrCapturedWhenPreviouslyClosed) { + int saved_stderr = dup(STDERR_FILENO); + close(STDERR_FILENO); + CheckStderrCaptured(); + dup2(saved_stderr, STDERR_FILENO); +} + +FilePath ProcessTest::GetFdPath(int fd) { + return FilePath(base::StringPrintf("/proc/self/fd/%d", fd)); +} + +TEST_F(ProcessTest, RedirectStderrUsingPipe) { + std::string contents; + process_.RedirectOutput(""); + process_.AddArg(kBinSh); + process_.AddArg("-c"); + process_.AddArg("echo errormessage >&2 && exit 1"); + process_.RedirectUsingPipe(STDERR_FILENO, false); + EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); + EXPECT_EQ(1, process_.Run()); + int pipe_fd = process_.GetPipe(STDERR_FILENO); + EXPECT_GE(pipe_fd, 0); + EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO)); + EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO)); + EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents)); + EXPECT_NE(std::string::npos, contents.find("errormessage")); + EXPECT_EQ("", GetLog()); +} + +TEST_F(ProcessTest, RedirectStderrUsingPipeWhenPreviouslyClosed) { + int saved_stderr = dup(STDERR_FILENO); + close(STDERR_FILENO); + process_.RedirectOutput(""); + process_.AddArg(kBinCp); + process_.RedirectUsingPipe(STDERR_FILENO, false); + EXPECT_FALSE(process_.Start()); + EXPECT_TRUE(FindLog("Unable to fstat fd 2:")); + dup2(saved_stderr, STDERR_FILENO); +} + +TEST_F(ProcessTest, RedirectStdoutUsingPipe) { + std::string contents; + process_.RedirectOutput(""); + process_.AddArg(kBinEcho); + process_.AddArg("hello world\n"); + process_.RedirectUsingPipe(STDOUT_FILENO, false); + EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO)); + EXPECT_EQ(0, process_.Run()); + int pipe_fd = process_.GetPipe(STDOUT_FILENO); + EXPECT_GE(pipe_fd, 0); + EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); + EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO)); + EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents)); + EXPECT_NE(std::string::npos, contents.find("hello world\n")); + EXPECT_EQ("", GetLog()); +} + +TEST_F(ProcessTest, RedirectStdinUsingPipe) { + std::string contents; + const char kMessage[] = "made it!\n"; + process_.AddArg(kBinCat); + process_.RedirectUsingPipe(STDIN_FILENO, true); + process_.RedirectOutput(output_file_); + EXPECT_TRUE(process_.Start()); + int write_fd = process_.GetPipe(STDIN_FILENO); + EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); + EXPECT_TRUE(base::WriteFile(GetFdPath(write_fd), kMessage, strlen(kMessage))); + close(write_fd); + EXPECT_EQ(0, process_.Wait()); + ExpectFileEquals(kMessage, output_file_.c_str()); +} + +TEST_F(ProcessTest, WithSameUid) { + gid_t uid = geteuid(); + process_.AddArg(kBinEcho); + process_.SetUid(uid); + EXPECT_EQ(0, process_.Run()); +} + +TEST_F(ProcessTest, WithSameGid) { + gid_t gid = getegid(); + process_.AddArg(kBinEcho); + process_.SetGid(gid); + EXPECT_EQ(0, process_.Run()); +} + +TEST_F(ProcessTest, WithIllegalUid) { + ASSERT_NE(0, geteuid()); + process_.AddArg(kBinEcho); + process_.SetUid(0); + EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); + std::string contents; + EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); + EXPECT_NE(std::string::npos, contents.find("Unable to set UID to 0: 1\n")); +} + +TEST_F(ProcessTest, WithIllegalGid) { + ASSERT_NE(0, getegid()); + process_.AddArg(kBinEcho); + process_.SetGid(0); + EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); + std::string contents; + EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); + EXPECT_NE(std::string::npos, contents.find("Unable to set GID to 0: 1\n")); +} + +TEST_F(ProcessTest, NoParams) { + EXPECT_EQ(-1, process_.Run()); +} + +#if !defined(__BIONIC__) // Bionic intercepts the segfault on Android. +TEST_F(ProcessTest, SegFaultHandling) { + process_.AddArg(kBinSh); + process_.AddArg("-c"); + process_.AddArg("kill -SEGV $$"); + EXPECT_EQ(-1, process_.Run()); + EXPECT_TRUE(FindLog("did not exit normally: 11")); +} +#endif + +TEST_F(ProcessTest, KillHandling) { + process_.AddArg(kBinSh); + process_.AddArg("-c"); + process_.AddArg("kill -KILL $$"); + EXPECT_EQ(-1, process_.Run()); + EXPECT_TRUE(FindLog("did not exit normally: 9")); +} + + +TEST_F(ProcessTest, KillNoPid) { + process_.Kill(SIGTERM, 0); + EXPECT_TRUE(FindLog("Process not running")); +} + +TEST_F(ProcessTest, ProcessExists) { + EXPECT_FALSE(Process::ProcessExists(0)); + EXPECT_TRUE(Process::ProcessExists(1)); + EXPECT_TRUE(Process::ProcessExists(getpid())); +} + +TEST_F(ProcessTest, ResetPidByFile) { + FilePath pid_path = temp_dir_.path().Append("pid"); + EXPECT_FALSE(process_.ResetPidByFile(pid_path.value())); + EXPECT_TRUE(base::WriteFile(pid_path, "456\n", 4)); + EXPECT_TRUE(process_.ResetPidByFile(pid_path.value())); + EXPECT_EQ(456, process_.pid()); + // The purpose of this unit test is to check if Process::ResetPidByFile() can + // properly read a pid from a file. We don't really want to kill the process + // with pid 456, so update the pid to 0 to prevent the Process destructor from + // killing any innocent process. + process_.UpdatePid(0); +} + +TEST_F(ProcessTest, KillSleeper) { + process_.AddArg(kBinSleep); + process_.AddArg("10000"); + ASSERT_TRUE(process_.Start()); + pid_t pid = process_.pid(); + ASSERT_GT(pid, 1); + EXPECT_TRUE(process_.Kill(SIGTERM, 1)); + EXPECT_EQ(0, process_.pid()); +} + +TEST_F(ProcessTest, Reset) { + process_.AddArg(kBinFalse); + process_.Reset(0); + process_.AddArg(kBinEcho); + EXPECT_EQ(0, process_.Run()); +} + +bool ReturnFalse() { return false; } + +TEST_F(ProcessTest, PreExecCallback) { + process_.AddArg(kBinTrue); + process_.SetPreExecCallback(base::Bind(&ReturnFalse)); + ASSERT_NE(0, process_.Run()); +} + +} // namespace brillo diff --git a/brillo/secure_blob.cc b/brillo/secure_blob.cc new file mode 100644 index 0000000..9e6d570 --- /dev/null +++ b/brillo/secure_blob.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include // memcpy + +#include + +#include "brillo/secure_blob.h" + +namespace brillo { + +SecureBlob::SecureBlob(const std::string& data) + : SecureBlob(data.begin(), data.end()) {} + +SecureBlob::~SecureBlob() { + clear(); +} + +void SecureBlob::resize(size_type count) { + if (count < size()) { + SecureMemset(data() + count, 0, capacity() - count); + } + Blob::resize(count); +} + +void SecureBlob::resize(size_type count, const value_type& value) { + if (count < size()) { + SecureMemset(data() + count, 0, capacity() - count); + } + Blob::resize(count, value); +} + +void SecureBlob::clear() { + SecureMemset(data(), 0, capacity()); + Blob::clear(); +} + +std::string SecureBlob::to_string() const { + return std::string(data(), data() + size()); +} + +SecureBlob SecureBlob::Combine(const SecureBlob& blob1, + const SecureBlob& blob2) { + SecureBlob result; + result.reserve(blob1.size() + blob2.size()); + result.insert(result.end(), blob1.begin(), blob1.end()); + result.insert(result.end(), blob2.begin(), blob2.end()); + return result; +} + +void* SecureMemset(void* v, int c, size_t n) { + volatile uint8_t* p = reinterpret_cast(v); + while (n--) + *p++ = c; + return v; +} + +int SecureMemcmp(const void* s1, const void* s2, size_t n) { + const uint8_t* us1 = reinterpret_cast(s1); + const uint8_t* us2 = reinterpret_cast(s2); + int result = 0; + + if (0 == n) + return 1; + + /* Code snippet without data-dependent branch due to + * Nate Lawson (nate@root.org) of Root Labs. */ + while (n--) + result |= *us1++ ^ *us2++; + + return result != 0; +} + +} // namespace brillo diff --git a/brillo/secure_blob.h b/brillo/secure_blob.h new file mode 100644 index 0000000..dec9444 --- /dev/null +++ b/brillo/secure_blob.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_SECURE_BLOB_H_ +#define LIBCHROMEOS_BRILLO_SECURE_BLOB_H_ + +#include +#include + +#include + +namespace brillo { + +using Blob = std::vector; + +// SecureBlob erases the contents on destruction. It does not guarantee erasure +// on resize, assign, etc. +class BRILLO_EXPORT SecureBlob : public Blob { + public: + SecureBlob() = default; + using Blob::vector; // Inherit standard constructors from vector. + explicit SecureBlob(const std::string& data); + ~SecureBlob(); + + void resize(size_type count); + void resize(size_type count, const value_type& value); + void clear(); + + std::string to_string() const; + char* char_data() { return reinterpret_cast(data()); } + const char* char_data() const { + return reinterpret_cast(data()); + } + static SecureBlob Combine(const SecureBlob& blob1, const SecureBlob& blob2); +}; + +// Secure memset(). This function is guaranteed to fill in the whole buffer +// and is not subject to compiler optimization as allowed by Sub-clause 5.1.2.3 +// of C Standard [ISO/IEC 9899:2011] which states: +// In the abstract machine, all expressions are evaluated as specified by the +// semantics. An actual implementation need not evaluate part of an expression +// if it can deduce that its value is not used and that no needed side effects +// are produced (including any caused by calling a function or accessing +// a volatile object). +// While memset() can be optimized out in certain situations (since most +// compilers implement this function as intrinsic and know of its side effects), +// this function will not be optimized out. +BRILLO_EXPORT void* SecureMemset(void* v, int c, size_t n); + +// Compare [n] bytes starting at [s1] with [s2] and return 0 if they match, +// 1 if they don't. Time taken to perform the comparison is only dependent on +// [n] and not on the relationship of the match between [s1] and [s2]. +BRILLO_EXPORT int SecureMemcmp(const void* s1, const void* s2, size_t n); + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_SECURE_BLOB_H_ diff --git a/brillo/secure_blob_unittest.cc b/brillo/secure_blob_unittest.cc new file mode 100644 index 0000000..d4fd555 --- /dev/null +++ b/brillo/secure_blob_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Unit tests for SecureBlob. + +#include "brillo/secure_blob.h" + +#include +#include +#include + +#include +#include + +namespace brillo { +using std::string; + +class SecureBlobTest : public ::testing::Test { + public: + SecureBlobTest() {} + virtual ~SecureBlobTest() {} + + static bool FindBlobInBlob(const brillo::Blob& haystack, + const brillo::Blob& needle) { + auto pos = std::search( + haystack.begin(), haystack.end(), needle.begin(), needle.end()); + return (pos != haystack.end()); + } + + static int FindBlobIndexInBlob(const brillo::Blob& haystack, + const brillo::Blob& needle) { + auto pos = std::search( + haystack.begin(), haystack.end(), needle.begin(), needle.end()); + if (pos == haystack.end()) { + return -1; + } + return std::distance(haystack.begin(), pos); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SecureBlobTest); +}; + +TEST_F(SecureBlobTest, AllocationSizeTest) { + // Check that allocating a SecureBlob of a specified size works + SecureBlob blob(32); + + EXPECT_EQ(32, blob.size()); +} + +TEST_F(SecureBlobTest, AllocationCopyTest) { + // Check that allocating a SecureBlob with an iterator works + unsigned char from_data[32]; + std::iota(std::begin(from_data), std::end(from_data), 0); + + SecureBlob blob(std::begin(from_data), std::end(from_data)); + + EXPECT_EQ(sizeof(from_data), blob.size()); + + for (unsigned int i = 0; i < sizeof(from_data); i++) { + EXPECT_EQ(from_data[i], blob[i]); + } +} + +TEST_F(SecureBlobTest, IteratorConstructorTest) { + // Check that allocating a SecureBlob with an iterator works + brillo::Blob from_blob(32); + for (unsigned int i = 0; i < from_blob.size(); i++) { + from_blob[i] = i; + } + + SecureBlob blob(from_blob.begin(), from_blob.end()); + + EXPECT_EQ(from_blob.size(), blob.size()); + EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(from_blob, blob)); +} + +TEST_F(SecureBlobTest, ResizeTest) { + // Check that resizing a SecureBlob wipes the excess memory. The test assumes + // that resize() down by one will not re-allocate the memory, so the last byte + // will still be part of the SecureBlob's allocation + size_t length = 1024; + SecureBlob blob(length); + void* original_data = blob.data(); + for (size_t i = 0; i < length; i++) { + blob[i] = i; + } + + blob.resize(length - 1); + + EXPECT_EQ(original_data, blob.data()); + EXPECT_EQ(length - 1, blob.size()); + EXPECT_EQ(0, blob.data()[length - 1]); +} + +TEST_F(SecureBlobTest, CombineTest) { + SecureBlob blob1(32); + SecureBlob blob2(32); + std::iota(blob1.begin(), blob1.end(), 0); + std::iota(blob2.begin(), blob2.end(), 32); + SecureBlob combined_blob = SecureBlob::Combine(blob1, blob2); + EXPECT_EQ(combined_blob.size(), (blob1.size() + blob2.size())); + EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob1)); + EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob2)); + int blob1_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob1); + int blob2_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob2); + EXPECT_EQ(blob1_index, 0); + EXPECT_EQ(blob2_index, 32); +} + +TEST_F(SecureBlobTest, BlobToStringTest) { + std::string test_string("Test String"); + SecureBlob blob = SecureBlob(test_string.begin(), test_string.end()); + EXPECT_EQ(blob.size(), test_string.length()); + std::string result_string = blob.to_string(); + EXPECT_EQ(test_string.compare(result_string), 0); +} + +} // namespace brillo diff --git a/brillo/streams/fake_stream.cc b/brillo/streams/fake_stream.cc new file mode 100644 index 0000000..db24e95 --- /dev/null +++ b/brillo/streams/fake_stream.cc @@ -0,0 +1,404 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include + +namespace brillo { + +namespace { + +// Gets a delta between the two times, makes sure that the delta is positive. +base::TimeDelta CalculateDelay(const base::Time& now, + const base::Time& delay_until) { + const base::TimeDelta zero_delay; + if (delay_until.is_null() || now >= delay_until) { + return zero_delay; + } + + base::TimeDelta delay = delay_until - now; + if (delay < zero_delay) + delay = zero_delay; + return delay; +} + +// Given the current clock time, and expected delays for read and write +// operations calculates the smaller wait delay of the two and sets the +// resulting operation to |*mode| and the delay to wait for into |*delay|. +void GetMinDelayAndMode(const base::Time& now, + bool read, const base::Time& delay_read_until, + bool write, const base::Time& delay_write_until, + Stream::AccessMode* mode, base::TimeDelta* delay) { + base::TimeDelta read_delay = base::TimeDelta::Max(); + base::TimeDelta write_delay = base::TimeDelta::Max(); + + if (read) + read_delay = CalculateDelay(now, delay_read_until); + if (write) + write_delay = CalculateDelay(now, delay_write_until); + + if (read_delay > write_delay) { + read = false; + } else if (read_delay < write_delay) { + write = false; + } + *mode = stream_utils::MakeAccessMode(read, write); + *delay = std::min(read_delay, write_delay); +} + +} // anonymous namespace + +FakeStream::FakeStream(Stream::AccessMode mode, + base::Clock* clock) + : mode_{mode}, clock_{clock} {} + +void FakeStream::AddReadPacketData(base::TimeDelta delay, + const void* data, + size_t size) { + auto* byte_ptr = static_cast(data); + AddReadPacketData(delay, brillo::Blob{byte_ptr, byte_ptr + size}); +} + +void FakeStream::AddReadPacketData(base::TimeDelta delay, brillo::Blob data) { + InputDataPacket packet; + packet.data = std::move(data); + packet.delay_before = delay; + incoming_queue_.push(std::move(packet)); +} + +void FakeStream::AddReadPacketString(base::TimeDelta delay, + const std::string& data) { + AddReadPacketData(delay, brillo::Blob{data.begin(), data.end()}); +} + +void FakeStream::QueueReadError(base::TimeDelta delay) { + QueueReadErrorWithMessage(delay, std::string{}); +} + +void FakeStream::QueueReadErrorWithMessage(base::TimeDelta delay, + const std::string& message) { + InputDataPacket packet; + packet.data.assign(message.begin(), message.end()); + packet.delay_before = delay; + packet.read_error = true; + incoming_queue_.push(std::move(packet)); +} + +void FakeStream::ClearReadQueue() { + std::queue().swap(incoming_queue_); + delay_input_until_ = base::Time{}; + input_buffer_.clear(); + input_ptr_ = 0; + report_read_error_ = 0; +} + +void FakeStream::ExpectWritePacketSize(base::TimeDelta delay, + size_t data_size) { + OutputDataPacket packet; + packet.expected_size = data_size; + packet.delay_before = delay; + outgoing_queue_.push(std::move(packet)); +} + +void FakeStream::ExpectWritePacketData(base::TimeDelta delay, + const void* data, + size_t size) { + auto* byte_ptr = static_cast(data); + ExpectWritePacketData(delay, brillo::Blob{byte_ptr, byte_ptr + size}); +} + +void FakeStream::ExpectWritePacketData(base::TimeDelta delay, + brillo::Blob data) { + OutputDataPacket packet; + packet.expected_size = data.size(); + packet.data = std::move(data); + packet.delay_before = delay; + outgoing_queue_.push(std::move(packet)); +} + +void FakeStream::ExpectWritePacketString(base::TimeDelta delay, + const std::string& data) { + ExpectWritePacketData(delay, brillo::Blob{data.begin(), data.end()}); +} + +void FakeStream::QueueWriteError(base::TimeDelta delay) { + QueueWriteErrorWithMessage(delay, std::string{}); +} + +void FakeStream::QueueWriteErrorWithMessage(base::TimeDelta delay, + const std::string& message) { + OutputDataPacket packet; + packet.expected_size = 0; + packet.data.assign(message.begin(), message.end()); + packet.delay_before = delay; + packet.write_error = true; + outgoing_queue_.push(std::move(packet)); +} + +void FakeStream::ClearWriteQueue() { + std::queue().swap(outgoing_queue_); + delay_output_until_ = base::Time{}; + output_buffer_.clear(); + expected_output_data_.clear(); + max_output_buffer_size_ = 0; + all_output_data_.clear(); + report_write_error_ = 0; +} + +const brillo::Blob& FakeStream::GetFlushedOutputData() const { + return all_output_data_; +} + +std::string FakeStream::GetFlushedOutputDataAsString() const { + return std::string{all_output_data_.begin(), all_output_data_.end()}; +} + +bool FakeStream::CanRead() const { + return stream_utils::IsReadAccessMode(mode_); +} + +bool FakeStream::CanWrite() const { + return stream_utils::IsWriteAccessMode(mode_); +} + +bool FakeStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); +} + +bool FakeStream::Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) { + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); +} + +bool FakeStream::IsReadBufferEmpty() const { + return input_ptr_ >= input_buffer_.size(); +} + +bool FakeStream::PopReadPacket() { + if (incoming_queue_.empty()) + return false; + const InputDataPacket& packet = incoming_queue_.front(); + input_ptr_ = 0; + input_buffer_ = std::move(packet.data); + delay_input_until_ = clock_->Now() + packet.delay_before; + incoming_queue_.pop(); + report_read_error_ = packet.read_error; + return true; +} + +bool FakeStream::ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) { + if (!CanRead()) + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); + + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + for (;;) { + if (!delay_input_until_.is_null() && clock_->Now() < delay_input_until_) { + *size_read = 0; + if (end_of_stream) + *end_of_stream = false; + break; + } + + if (report_read_error_) { + report_read_error_ = false; + std::string message{input_buffer_.begin(), input_buffer_.end()}; + if (message.empty()) + message = "Simulating read error for tests"; + input_buffer_.clear(); + Error::AddTo(error, FROM_HERE, "fake_stream", "read_error", message); + return false; + } + + if (!IsReadBufferEmpty()) { + size_to_read = std::min(size_to_read, input_buffer_.size() - input_ptr_); + std::memcpy(buffer, input_buffer_.data() + input_ptr_, size_to_read); + input_ptr_ += size_to_read; + *size_read = size_to_read; + if (end_of_stream) + *end_of_stream = false; + break; + } + + if (!PopReadPacket()) { + *size_read = 0; + if (end_of_stream) + *end_of_stream = true; + break; + } + } + return true; +} + +bool FakeStream::IsWriteBufferFull() const { + return output_buffer_.size() >= max_output_buffer_size_; +} + +bool FakeStream::PopWritePacket() { + if (outgoing_queue_.empty()) + return false; + const OutputDataPacket& packet = outgoing_queue_.front(); + expected_output_data_ = std::move(packet.data); + delay_output_until_ = clock_->Now() + packet.delay_before; + max_output_buffer_size_ = packet.expected_size; + report_write_error_ = packet.write_error; + outgoing_queue_.pop(); + return true; +} + +bool FakeStream::WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) { + if (!CanWrite()) + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); + + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + for (;;) { + if (!delay_output_until_.is_null() && clock_->Now() < delay_output_until_) { + *size_written = 0; + return true; + } + + if (report_write_error_) { + report_write_error_ = false; + std::string message{expected_output_data_.begin(), + expected_output_data_.end()}; + if (message.empty()) + message = "Simulating write error for tests"; + output_buffer_.clear(); + max_output_buffer_size_ = 0; + expected_output_data_.clear(); + Error::AddTo(error, FROM_HERE, "fake_stream", "write_error", message); + return false; + } + + if (!IsWriteBufferFull()) { + bool success = true; + size_to_write = std::min(size_to_write, + max_output_buffer_size_ - output_buffer_.size()); + auto byte_ptr = static_cast(buffer); + output_buffer_.insert(output_buffer_.end(), + byte_ptr, byte_ptr + size_to_write); + if (output_buffer_.size() == max_output_buffer_size_) { + if (!expected_output_data_.empty() && + expected_output_data_ != output_buffer_) { + // We expected different data to be written, report an error. + Error::AddTo(error, FROM_HERE, "fake_stream", "data_mismatch", + "Unexpected data written"); + success = false; + } + + all_output_data_.insert(all_output_data_.end(), + output_buffer_.begin(), output_buffer_.end()); + + output_buffer_.clear(); + max_output_buffer_size_ = 0; + expected_output_data_.clear(); + } + *size_written = size_to_write; + return success; + } + + if (!PopWritePacket()) { + // No more data expected. + Error::AddTo(error, FROM_HERE, "fake_stream", "full", + "No more output data expected"); + return false; + } + } +} + +bool FakeStream::FlushBlocking(ErrorPtr* error) { + if (!CanWrite()) + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); + + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + bool success = true; + if (!output_buffer_.empty()) { + if (!expected_output_data_.empty() && + expected_output_data_ != output_buffer_) { + // We expected different data to be written, report an error. + Error::AddTo(error, FROM_HERE, "fake_stream", "data_mismatch", + "Unexpected data written"); + success = false; + } + all_output_data_.insert(all_output_data_.end(), + output_buffer_.begin(), output_buffer_.end()); + + output_buffer_.clear(); + max_output_buffer_size_ = 0; + expected_output_data_.clear(); + } + return success; +} + +bool FakeStream::CloseBlocking(ErrorPtr* error) { + is_open_ = false; + return true; +} + +bool FakeStream::WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) { + bool read_requested = stream_utils::IsReadAccessMode(mode); + bool write_requested = stream_utils::IsWriteAccessMode(mode); + + if ((read_requested && !CanRead()) || (write_requested && !CanWrite())) + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); + + if (read_requested && IsReadBufferEmpty()) + PopReadPacket(); + if (write_requested && IsWriteBufferFull()) + PopWritePacket(); + + base::TimeDelta delay; + GetMinDelayAndMode(clock_->Now(), read_requested, delay_input_until_, + write_requested, delay_output_until_, &mode, &delay); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::Bind(callback, mode), delay); + return true; +} + +bool FakeStream::WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) { + const base::TimeDelta zero_delay; + bool read_requested = stream_utils::IsReadAccessMode(in_mode); + bool write_requested = stream_utils::IsWriteAccessMode(in_mode); + + if ((read_requested && !CanRead()) || (write_requested && !CanWrite())) + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); + + base::TimeDelta delay; + GetMinDelayAndMode(clock_->Now(), read_requested, delay_input_until_, + write_requested, delay_output_until_, out_mode, &delay); + + if (timeout < delay) + return stream_utils::ErrorOperationTimeout(FROM_HERE, error); + + LOG(INFO) << "TEST: Would have blocked for " << delay.InMilliseconds() + << " ms."; + + return true; +} + +} // namespace brillo diff --git a/brillo/streams/fake_stream.h b/brillo/streams/fake_stream.h new file mode 100644 index 0000000..2943a44 --- /dev/null +++ b/brillo/streams/fake_stream.h @@ -0,0 +1,171 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_FAKE_STREAM_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_FAKE_STREAM_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { + +// Fake stream implementation for testing. +// This class allows to provide data for the stream in tests that can be later +// read through the Stream interface. Also, data written into the stream can be +// later inspected and verified. +// +// NOTE: This class provides a fake implementation for streams with separate +// input and output channels. That is, read and write operations do not affect +// each other. Also, the stream implementation is sequential only (no seeking). +// Good examples of a use case for fake stream are: +// - read-only sequential streams (file, memory, pipe, ...) +// - write-only sequential streams (same as above) +// - independent channel read-write streams (sockets, ...) +// +// For more complex read/write stream test scenarios using a real MemoryStream +// or temporary FileStream is probably a better choice. +class FakeStream : public Stream { + public: + // Construct a new instance of the fake stream. + // mode - expected read/write mode supported by the stream. + // clock - the clock to use to get the current time. + FakeStream(Stream::AccessMode mode, + base::Clock* clock); + + // Add data packets to the read queue of the stream. + // Optional |delay| indicates that the data packet should be delayed. + void AddReadPacketData(base::TimeDelta delay, const void* data, size_t size); + void AddReadPacketData(base::TimeDelta delay, brillo::Blob data); + void AddReadPacketString(base::TimeDelta delay, const std::string& data); + + // Schedule a read error by adding a special error packet to the queue. + void QueueReadError(base::TimeDelta delay); + void QueueReadErrorWithMessage(base::TimeDelta delay, + const std::string& message); + + // Resets read queue and clears any input data buffers. + void ClearReadQueue(); + + // Add expectations for output data packets to be written by the stream. + // Optional |delay| indicates that the initial write operation for the data in + // the packet should be delayed. + // ExpectWritePacketSize just limits the size of output packet while + // ExpectWritePacketData also validates that the data matches that of |data|. + void ExpectWritePacketSize(base::TimeDelta delay, size_t data_size); + void ExpectWritePacketData(base::TimeDelta delay, + const void* data, + size_t size); + void ExpectWritePacketData(base::TimeDelta delay, brillo::Blob data); + void ExpectWritePacketString(base::TimeDelta delay, const std::string& data); + + // Schedule a write error by adding a special error packet to the queue. + void QueueWriteError(base::TimeDelta delay); + void QueueWriteErrorWithMessage(base::TimeDelta delay, + const std::string& message); + + // Resets write queue and clears any output data buffers. + void ClearWriteQueue(); + + // Returns the output data accumulated so far by all complete write packets, + // or explicitly flushed. + const brillo::Blob& GetFlushedOutputData() const; + std::string GetFlushedOutputDataAsString() const; + + // Overrides from brillo::Stream. + bool IsOpen() const override { return is_open_; } + bool CanRead() const override; + bool CanWrite() const override; + bool CanSeek() const override { return false; } + bool CanGetSize() const override { return false; } + uint64_t GetSize() const override { return 0; } + bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; + uint64_t GetRemainingSize() const override { return 0; } + uint64_t GetPosition() const override { return 0; } + bool Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) override; + + bool ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) override; + bool WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) override; + bool FlushBlocking(ErrorPtr* error) override; + bool CloseBlocking(ErrorPtr* error) override; + bool WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) override; + bool WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) override; + + private: + // Input data packet to be placed on the read queue. + struct InputDataPacket { + brillo::Blob data; // Data to be read. + base::TimeDelta delay_before; // Possible delay for the first read. + bool read_error{false}; // Set to true if this packet generates an error. + }; + + // Output data packet to be placed on the write queue. + struct OutputDataPacket { + size_t expected_size{0}; // Output packet size + brillo::Blob data; // Possible data to verify the output with. + base::TimeDelta delay_before; // Possible delay for the first write. + bool write_error{false}; // Set to true if this packet generates an error. + }; + + // Check if there is any pending read data in the input buffer. + bool IsReadBufferEmpty() const; + // Pops the next read packet from the queue and sets its data into the + // internal input buffer. + bool PopReadPacket(); + + // Check if the output buffer is full. + bool IsWriteBufferFull() const; + + // Moves the current full output buffer into |all_output_data_|, clears the + // buffer, and pops the information about the next expected output packet + // from the write queue. + bool PopWritePacket(); + + bool is_open_{true}; + Stream::AccessMode mode_; + base::Clock* clock_; + + // Internal data for read operations. + std::queue incoming_queue_; + base::Time delay_input_until_; + brillo::Blob input_buffer_; + size_t input_ptr_{0}; + bool report_read_error_{false}; + + // Internal data for write operations. + std::queue outgoing_queue_; + base::Time delay_output_until_; + brillo::Blob output_buffer_; + brillo::Blob expected_output_data_; + size_t max_output_buffer_size_{0}; + bool report_write_error_{false}; + brillo::Blob all_output_data_; + + DISALLOW_COPY_AND_ASSIGN(FakeStream); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_FAKE_STREAM_H_ diff --git a/brillo/streams/fake_stream_unittest.cc b/brillo/streams/fake_stream_unittest.cc new file mode 100644 index 0000000..2aa18fd --- /dev/null +++ b/brillo/streams/fake_stream_unittest.cc @@ -0,0 +1,510 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include + +using testing::AnyNumber; +using testing::InSequence; +using testing::_; + +namespace brillo { + +class FakeStreamTest : public testing::Test { + public: + void SetUp() override { + mock_loop_.SetAsCurrent(); + // Ignore calls to RunOnce(). + EXPECT_CALL(mock_loop_, RunOnce(true)).Times(AnyNumber()); + } + + void CreateStream(Stream::AccessMode mode) { + stream_.reset(new FakeStream{mode, &clock_}); + } + + // Performs non-blocking read on the stream and returns the read data + // as a string in |out_buffer|. Returns true if the read was successful or + // false when an error occurs. |*eos| is set to true when end of stream is + // reached. + bool ReadString(size_t size_to_read, std::string* out_buffer, bool* eos) { + std::vector data; + data.resize(size_to_read); + size_t size_read = 0; + bool ok = stream_->ReadNonBlocking(data.data(), data.size(), &size_read, + eos, nullptr); + if (ok) { + out_buffer->assign(data.data(), data.data() + size_read); + } else { + out_buffer->clear(); + } + return ok; + } + + // Writes a string to a stream. Returns the number of bytes written or -1 + // in case an error occurred. + int WriteString(const std::string& data) { + size_t written = 0; + if (!stream_->WriteNonBlocking(data.data(), data.size(), &written, nullptr)) + return -1; + return static_cast(written); + } + + protected: + base::SimpleTestClock clock_; + MockMessageLoop mock_loop_{&clock_}; + std::unique_ptr stream_; + const base::TimeDelta zero_delay; +}; + +TEST_F(FakeStreamTest, InitReadOnly) { + CreateStream(Stream::AccessMode::READ); + EXPECT_TRUE(stream_->IsOpen()); + EXPECT_TRUE(stream_->CanRead()); + EXPECT_FALSE(stream_->CanWrite()); + EXPECT_FALSE(stream_->CanSeek()); + EXPECT_FALSE(stream_->CanGetSize()); + EXPECT_EQ(0, stream_->GetSize()); + EXPECT_EQ(0, stream_->GetRemainingSize()); + EXPECT_EQ(0, stream_->GetPosition()); +} + +TEST_F(FakeStreamTest, InitWriteOnly) { + CreateStream(Stream::AccessMode::WRITE); + EXPECT_TRUE(stream_->IsOpen()); + EXPECT_FALSE(stream_->CanRead()); + EXPECT_TRUE(stream_->CanWrite()); + EXPECT_FALSE(stream_->CanSeek()); + EXPECT_FALSE(stream_->CanGetSize()); + EXPECT_EQ(0, stream_->GetSize()); + EXPECT_EQ(0, stream_->GetRemainingSize()); + EXPECT_EQ(0, stream_->GetPosition()); +} + +TEST_F(FakeStreamTest, InitReadWrite) { + CreateStream(Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->IsOpen()); + EXPECT_TRUE(stream_->CanRead()); + EXPECT_TRUE(stream_->CanWrite()); + EXPECT_FALSE(stream_->CanSeek()); + EXPECT_FALSE(stream_->CanGetSize()); + EXPECT_EQ(0, stream_->GetSize()); + EXPECT_EQ(0, stream_->GetRemainingSize()); + EXPECT_EQ(0, stream_->GetPosition()); +} + +TEST_F(FakeStreamTest, ReadEmpty) { + CreateStream(Stream::AccessMode::READ); + std::string data; + bool eos = false; + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_TRUE(eos); + EXPECT_TRUE(data.empty()); +} + +TEST_F(FakeStreamTest, ReadFullPacket) { + CreateStream(Stream::AccessMode::READ); + stream_->AddReadPacketString({}, "foo"); + std::string data; + bool eos = false; + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("foo", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_TRUE(eos); + EXPECT_TRUE(data.empty()); +} + +TEST_F(FakeStreamTest, ReadPartialPacket) { + CreateStream(Stream::AccessMode::READ); + stream_->AddReadPacketString({}, "foobar"); + std::string data; + bool eos = false; + EXPECT_TRUE(ReadString(3, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("foo", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("bar", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_TRUE(eos); + EXPECT_TRUE(data.empty()); +} + +TEST_F(FakeStreamTest, ReadMultiplePackets) { + CreateStream(Stream::AccessMode::READ); + stream_->AddReadPacketString({}, "foobar"); + stream_->AddReadPacketString({}, "baz"); + stream_->AddReadPacketString({}, "quux"); + std::string data; + bool eos = false; + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("foobar", data); + + EXPECT_TRUE(ReadString(2, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("ba", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("z", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("quux", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_TRUE(eos); + EXPECT_TRUE(data.empty()); + + stream_->AddReadPacketString({}, "foo-bar"); + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("foo-bar", data); +} + +TEST_F(FakeStreamTest, ReadPacketsWithDelay) { + CreateStream(Stream::AccessMode::READ); + stream_->AddReadPacketString({}, "foobar"); + stream_->AddReadPacketString(base::TimeDelta::FromSeconds(1), "baz"); + std::string data; + bool eos = false; + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("foobar", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_TRUE(data.empty()); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_TRUE(data.empty()); + + clock_.Advance(base::TimeDelta::FromSeconds(1)); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("baz", data); +} + +TEST_F(FakeStreamTest, ReadPacketsWithError) { + CreateStream(Stream::AccessMode::READ); + stream_->AddReadPacketString({}, "foobar"); + stream_->QueueReadErrorWithMessage(base::TimeDelta::FromSeconds(1), + "Dummy error"); + stream_->AddReadPacketString({}, "baz"); + + std::string data; + bool eos = false; + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("foobar", data); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_TRUE(data.empty()); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_TRUE(data.empty()); + + clock_.Advance(base::TimeDelta::FromSeconds(1)); + + EXPECT_FALSE(ReadString(100, &data, &eos)); + + EXPECT_TRUE(ReadString(100, &data, &eos)); + EXPECT_FALSE(eos); + EXPECT_EQ("baz", data); +} + +TEST_F(FakeStreamTest, WaitForDataRead) { + CreateStream(Stream::AccessMode::READ); + + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(2); + + int call_count = 0; + auto callback = [&call_count](Stream::AccessMode mode) { + call_count++; + EXPECT_EQ(Stream::AccessMode::READ, mode); + }; + + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ, + base::Bind(callback), nullptr)); + mock_loop_.Run(); + EXPECT_EQ(1, call_count); + + stream_->AddReadPacketString({}, "foobar"); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ, + base::Bind(callback), nullptr)); + mock_loop_.Run(); + EXPECT_EQ(2, call_count); + + stream_->ClearReadQueue(); + + auto one_sec_delay = base::TimeDelta::FromSeconds(1); + stream_->AddReadPacketString(one_sec_delay, "baz"); + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ, + base::Bind(callback), nullptr)); + mock_loop_.Run(); + EXPECT_EQ(3, call_count); +} + +TEST_F(FakeStreamTest, ReadAsync) { + CreateStream(Stream::AccessMode::READ); + std::string input_data = "foobar-baz"; + size_t split_pos = input_data.find('-'); + + auto one_sec_delay = base::TimeDelta::FromSeconds(1); + stream_->AddReadPacketString({}, input_data.substr(0, split_pos)); + stream_->AddReadPacketString(one_sec_delay, input_data.substr(split_pos)); + + { + InSequence seq; + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1); + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); + } + + std::vector buffer; + buffer.resize(input_data.size()); + + int success_count = 0; + int error_count = 0; + auto on_success = [&success_count] { success_count++; }; + auto on_failure = [&error_count](const Error* error) { error_count++; }; + + EXPECT_TRUE(stream_->ReadAllAsync(buffer.data(), buffer.size(), + base::Bind(on_success), + base::Bind(on_failure), + nullptr)); + mock_loop_.Run(); + EXPECT_EQ(1, success_count); + EXPECT_EQ(0, error_count); + EXPECT_EQ(input_data, (std::string{buffer.begin(), buffer.end()})); +} + +TEST_F(FakeStreamTest, WriteEmpty) { + CreateStream(Stream::AccessMode::WRITE); + EXPECT_EQ(-1, WriteString("foo")); +} + +TEST_F(FakeStreamTest, WritePartial) { + CreateStream(Stream::AccessMode::WRITE); + stream_->ExpectWritePacketSize({}, 6); + EXPECT_EQ(3, WriteString("foo")); + EXPECT_EQ(3, WriteString("bar")); + EXPECT_EQ(-1, WriteString("baz")); + + EXPECT_EQ("foobar", stream_->GetFlushedOutputDataAsString()); +} + +TEST_F(FakeStreamTest, WriteFullPackets) { + CreateStream(Stream::AccessMode::WRITE); + + stream_->ExpectWritePacketSize({}, 3); + EXPECT_EQ(3, WriteString("foo")); + EXPECT_EQ(-1, WriteString("bar")); + + stream_->ExpectWritePacketSize({}, 3); + EXPECT_EQ(3, WriteString("bar")); + + stream_->ExpectWritePacketSize({}, 3); + EXPECT_EQ(3, WriteString("quux")); + + EXPECT_EQ("foobarquu", stream_->GetFlushedOutputDataAsString()); +} + +TEST_F(FakeStreamTest, WriteAndVerifyData) { + CreateStream(Stream::AccessMode::WRITE); + + stream_->ExpectWritePacketString({}, "foo"); + stream_->ExpectWritePacketString({}, "bar"); + EXPECT_EQ(3, WriteString("foobar")); + EXPECT_EQ(3, WriteString("bar")); + + stream_->ExpectWritePacketString({}, "foo"); + stream_->ExpectWritePacketString({}, "baz"); + EXPECT_EQ(3, WriteString("foobar")); + EXPECT_EQ(-1, WriteString("bar")); + + stream_->ExpectWritePacketString({}, "foobar"); + EXPECT_EQ(3, WriteString("foo")); + EXPECT_EQ(2, WriteString("ba")); + EXPECT_EQ(-1, WriteString("z")); +} + +TEST_F(FakeStreamTest, WriteWithDelay) { + CreateStream(Stream::AccessMode::WRITE); + + const auto delay = base::TimeDelta::FromMilliseconds(500); + + stream_->ExpectWritePacketSize({}, 3); + stream_->ExpectWritePacketSize(delay, 3); + EXPECT_EQ(3, WriteString("foobar")); + + EXPECT_EQ(0, WriteString("bar")); + EXPECT_EQ(0, WriteString("bar")); + clock_.Advance(delay); + EXPECT_EQ(3, WriteString("bar")); + + EXPECT_EQ("foobar", stream_->GetFlushedOutputDataAsString()); +} + +TEST_F(FakeStreamTest, WriteWithError) { + CreateStream(Stream::AccessMode::WRITE); + + const auto delay = base::TimeDelta::FromMilliseconds(500); + + stream_->ExpectWritePacketSize({}, 3); + stream_->QueueWriteError({}); + stream_->ExpectWritePacketSize({}, 3); + stream_->QueueWriteErrorWithMessage(delay, "Dummy message"); + stream_->ExpectWritePacketString({}, "foobar"); + + const std::string data = "foobarbaz"; + EXPECT_EQ(3, WriteString(data)); + EXPECT_EQ(-1, WriteString(data)); // Simulated error #1. + EXPECT_EQ(3, WriteString(data)); + EXPECT_EQ(0, WriteString(data)); // Waiting for data... + clock_.Advance(delay); + EXPECT_EQ(-1, WriteString(data)); // Simulated error #2. + EXPECT_EQ(6, WriteString(data)); + EXPECT_EQ(-1, WriteString(data)); // No more data expected. +} + +TEST_F(FakeStreamTest, WaitForDataWrite) { + CreateStream(Stream::AccessMode::WRITE); + + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(2); + + int call_count = 0; + auto callback = [&call_count](Stream::AccessMode mode) { + call_count++; + EXPECT_EQ(Stream::AccessMode::WRITE, mode); + }; + + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::WRITE, + base::Bind(callback), nullptr)); + mock_loop_.Run(); + EXPECT_EQ(1, call_count); + + stream_->ExpectWritePacketString({}, "foobar"); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::WRITE, + base::Bind(callback), nullptr)); + mock_loop_.Run(); + EXPECT_EQ(2, call_count); + + stream_->ClearWriteQueue(); + + auto one_sec_delay = base::TimeDelta::FromSeconds(1); + stream_->ExpectWritePacketString(one_sec_delay, "baz"); + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::WRITE, + base::Bind(callback), nullptr)); + mock_loop_.Run(); + EXPECT_EQ(3, call_count); +} + +TEST_F(FakeStreamTest, WriteAsync) { + CreateStream(Stream::AccessMode::WRITE); + std::string output_data = "foobar-baz"; + size_t split_pos = output_data.find('-'); + + auto one_sec_delay = base::TimeDelta::FromSeconds(1); + stream_->ExpectWritePacketString({}, output_data.substr(0, split_pos)); + stream_->ExpectWritePacketString(one_sec_delay, + output_data.substr(split_pos)); + + { + InSequence seq; + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1); + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); + } + + int success_count = 0; + int error_count = 0; + auto on_success = [&success_count] { success_count++; }; + auto on_failure = [&error_count](const Error* error) { error_count++; }; + + EXPECT_TRUE(stream_->WriteAllAsync(output_data.data(), output_data.size(), + base::Bind(on_success), + base::Bind(on_failure), + nullptr)); + mock_loop_.Run(); + EXPECT_EQ(1, success_count); + EXPECT_EQ(0, error_count); + EXPECT_EQ(output_data, stream_->GetFlushedOutputDataAsString()); +} + +TEST_F(FakeStreamTest, WaitForDataReadWrite) { + CreateStream(Stream::AccessMode::READ_WRITE); + auto one_sec_delay = base::TimeDelta::FromSeconds(1); + auto two_sec_delay = base::TimeDelta::FromSeconds(2); + + int call_count = 0; + auto callback = [&call_count](Stream::AccessMode mode, + Stream::AccessMode expected_mode) { + call_count++; + EXPECT_EQ(static_cast(expected_mode), static_cast(mode)); + }; + + stream_->AddReadPacketString(one_sec_delay, "foo"); + stream_->ExpectWritePacketString(two_sec_delay, "bar"); + + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, + base::Bind(callback, + Stream::AccessMode::READ), + nullptr)); + mock_loop_.Run(); + EXPECT_EQ(1, call_count); + + // The above step has adjusted the clock by 1 second already. + stream_->ClearReadQueue(); + stream_->AddReadPacketString(two_sec_delay, "foo"); + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, + base::Bind(callback, + Stream::AccessMode::WRITE), + nullptr)); + mock_loop_.Run(); + EXPECT_EQ(2, call_count); + + clock_.Advance(one_sec_delay); + + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, + base::Bind(callback, + Stream::AccessMode::READ_WRITE), + nullptr)); + mock_loop_.Run(); + EXPECT_EQ(3, call_count); + + stream_->ClearReadQueue(); + stream_->ClearWriteQueue(); + stream_->AddReadPacketString(one_sec_delay, "foo"); + stream_->ExpectWritePacketString(one_sec_delay, "bar"); + + EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); + EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, + base::Bind(callback, + Stream::AccessMode::READ_WRITE), + nullptr)); + mock_loop_.Run(); + EXPECT_EQ(4, call_count); +} + +} // namespace brillo diff --git a/brillo/streams/file_stream.cc b/brillo/streams/file_stream.cc new file mode 100644 index 0000000..7b28a5a --- /dev/null +++ b/brillo/streams/file_stream.cc @@ -0,0 +1,548 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace brillo { + +// FileDescriptor is a helper class that serves two purposes: +// 1. It wraps low-level system APIs (as FileDescriptorInterface) to allow +// mocking calls to them in tests. +// 2. It provides file descriptor watching services using FileDescriptorWatcher +// and MessageLoopForIO::Watcher interface. +// The real FileStream uses this class to perform actual file I/O on the +// contained file descriptor. +class FileDescriptor : public FileStream::FileDescriptorInterface { + public: + FileDescriptor(int fd, bool own) : fd_{fd}, own_{own} {} + ~FileDescriptor() override { + if (IsOpen()) { + Close(); + } + } + + // Overrides for FileStream::FileDescriptorInterface methods. + bool IsOpen() const override { return fd_ >= 0; } + + ssize_t Read(void* buf, size_t nbyte) override { + return HANDLE_EINTR(read(fd_, buf, nbyte)); + } + + ssize_t Write(const void* buf, size_t nbyte) override { + return HANDLE_EINTR(write(fd_, buf, nbyte)); + } + + off64_t Seek(off64_t offset, int whence) override { + return lseek64(fd_, offset, whence); + } + + mode_t GetFileMode() const override { + struct stat file_stat; + if (fstat(fd_, &file_stat) < 0) + return 0; + return file_stat.st_mode; + } + + uint64_t GetSize() const override { + struct stat file_stat; + if (fstat(fd_, &file_stat) < 0) + return 0; + return file_stat.st_size; + } + + int Truncate(off64_t length) const override { + return HANDLE_EINTR(ftruncate(fd_, length)); + } + + int Close() override { + int fd = -1; + // The stream may or may not own the file descriptor stored in |fd_|. + // Despite that, we will need to set |fd_| to -1 when Close() finished. + // So, here we set it to -1 first and if we own the old descriptor, close + // it before exiting. + std::swap(fd, fd_); + CancelPendingAsyncOperations(); + return own_ ? IGNORE_EINTR(close(fd)) : 0; + } + + bool WaitForData(Stream::AccessMode mode, + const DataCallback& data_callback, + ErrorPtr* error) override { + if (stream_utils::IsReadAccessMode(mode)) { + CHECK(read_data_callback_.is_null()); + MessageLoop::current()->CancelTask(read_watcher_); + read_watcher_ = MessageLoop::current()->WatchFileDescriptor( + FROM_HERE, + fd_, + MessageLoop::WatchMode::kWatchRead, + false, // persistent + base::Bind(&FileDescriptor::OnFileCanReadWithoutBlocking, + base::Unretained(this))); + if (read_watcher_ == MessageLoop::kTaskIdNull) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kInvalidParameter, + "File descriptor doesn't support watching for reading."); + return false; + } + read_data_callback_ = data_callback; + } + if (stream_utils::IsWriteAccessMode(mode)) { + CHECK(write_data_callback_.is_null()); + MessageLoop::current()->CancelTask(write_watcher_); + write_watcher_ = MessageLoop::current()->WatchFileDescriptor( + FROM_HERE, + fd_, + MessageLoop::WatchMode::kWatchWrite, + false, // persistent + base::Bind(&FileDescriptor::OnFileCanWriteWithoutBlocking, + base::Unretained(this))); + if (write_watcher_ == MessageLoop::kTaskIdNull) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kInvalidParameter, + "File descriptor doesn't support watching for writing."); + return false; + } + write_data_callback_ = data_callback; + } + return true; + } + + int WaitForDataBlocking(Stream::AccessMode in_mode, + base::TimeDelta timeout, + Stream::AccessMode* out_mode) override { + fd_set read_fds; + fd_set write_fds; + fd_set error_fds; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + FD_ZERO(&error_fds); + + if (stream_utils::IsReadAccessMode(in_mode)) + FD_SET(fd_, &read_fds); + + if (stream_utils::IsWriteAccessMode(in_mode)) + FD_SET(fd_, &write_fds); + + FD_SET(fd_, &error_fds); + timeval timeout_val = {}; + if (!timeout.is_max()) { + const timespec ts = timeout.ToTimeSpec(); + TIMESPEC_TO_TIMEVAL(&timeout_val, &ts); + } + int res = HANDLE_EINTR(select(fd_ + 1, &read_fds, &write_fds, &error_fds, + timeout.is_max() ? nullptr : &timeout_val)); + if (res > 0 && out_mode) { + *out_mode = stream_utils::MakeAccessMode(FD_ISSET(fd_, &read_fds), + FD_ISSET(fd_, &write_fds)); + } + return res; + } + + void CancelPendingAsyncOperations() override { + read_data_callback_.Reset(); + if (read_watcher_ != MessageLoop::kTaskIdNull) { + MessageLoop::current()->CancelTask(read_watcher_); + read_watcher_ = MessageLoop::kTaskIdNull; + } + + write_data_callback_.Reset(); + if (write_watcher_ != MessageLoop::kTaskIdNull) { + MessageLoop::current()->CancelTask(write_watcher_); + write_watcher_ = MessageLoop::kTaskIdNull; + } + } + + // Called from the brillo::MessageLoop when the file descriptor is available + // for reading. + void OnFileCanReadWithoutBlocking() { + CHECK(!read_data_callback_.is_null()); + DataCallback cb = read_data_callback_; + read_data_callback_.Reset(); + cb.Run(Stream::AccessMode::READ); + } + + void OnFileCanWriteWithoutBlocking() { + CHECK(!write_data_callback_.is_null()); + DataCallback cb = write_data_callback_; + write_data_callback_.Reset(); + cb.Run(Stream::AccessMode::WRITE); + } + + private: + // The actual file descriptor we are working with. Will contain -1 if the + // file stream has been closed. + int fd_; + + // |own_| is set to true if the file stream owns the file descriptor |fd_| and + // must close it when the stream is closed. This will be false for file + // descriptors that shouldn't be closed (e.g. stdin, stdout, stderr). + bool own_; + + // Stream callbacks to be called when read and/or write operations can be + // performed on the file descriptor without blocking. + DataCallback read_data_callback_; + DataCallback write_data_callback_; + + // MessageLoop tasks monitoring read/write operations on the file descriptor. + MessageLoop::TaskId read_watcher_{MessageLoop::kTaskIdNull}; + MessageLoop::TaskId write_watcher_{MessageLoop::kTaskIdNull}; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptor); +}; + +StreamPtr FileStream::Open(const base::FilePath& path, + AccessMode mode, + Disposition disposition, + ErrorPtr* error) { + StreamPtr stream; + int open_flags = O_CLOEXEC; + switch (mode) { + case AccessMode::READ: + open_flags |= O_RDONLY; + break; + case AccessMode::WRITE: + open_flags |= O_WRONLY; + break; + case AccessMode::READ_WRITE: + open_flags |= O_RDWR; + break; + } + + switch (disposition) { + case Disposition::OPEN_EXISTING: + // Nothing else to do. + break; + case Disposition::CREATE_ALWAYS: + open_flags |= O_CREAT | O_TRUNC; + break; + case Disposition::CREATE_NEW_ONLY: + open_flags |= O_CREAT | O_EXCL; + break; + case Disposition::TRUNCATE_EXISTING: + open_flags |= O_TRUNC; + break; + } + + mode_t creation_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + int fd = HANDLE_EINTR(open(path.value().c_str(), open_flags, creation_mode)); + if (fd < 0) { + brillo::errors::system::AddSystemError(error, FROM_HERE, errno); + return stream; + } + if (HANDLE_EINTR(fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK)) < 0) { + brillo::errors::system::AddSystemError(error, FROM_HERE, errno); + IGNORE_EINTR(close(fd)); + return stream; + } + + std::unique_ptr fd_interface{ + new FileDescriptor{fd, true}}; + + stream.reset(new FileStream{std::move(fd_interface), mode}); + return stream; +} + +StreamPtr FileStream::CreateTemporary(ErrorPtr* error) { + StreamPtr stream; + base::FilePath path; + // The "proper" solution would be here to add O_TMPFILE flag to |open_flags| + // below and pass just the temp directory path to open(), so the actual file + // name isn't even needed. However this is supported only as of Linux kernel + // 3.11 and not all our configurations have that. So, for now just create + // a temp file first and then open it. + if (!base::CreateTemporaryFile(&path)) { + brillo::errors::system::AddSystemError(error, FROM_HERE, errno); + return stream; + } + int open_flags = O_CLOEXEC | O_RDWR | O_CREAT | O_TRUNC; + mode_t creation_mode = S_IRUSR | S_IWUSR; + int fd = HANDLE_EINTR(open(path.value().c_str(), open_flags, creation_mode)); + if (fd < 0) { + brillo::errors::system::AddSystemError(error, FROM_HERE, errno); + return stream; + } + unlink(path.value().c_str()); + + stream = FromFileDescriptor(fd, true, error); + if (!stream) + IGNORE_EINTR(close(fd)); + return stream; +} + +StreamPtr FileStream::FromFileDescriptor(int file_descriptor, + bool own_descriptor, + ErrorPtr* error) { + StreamPtr stream; + if (file_descriptor < 0 || file_descriptor >= FD_SETSIZE) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kInvalidParameter, + "Invalid file descriptor value"); + return stream; + } + + int fd_flags = HANDLE_EINTR(fcntl(file_descriptor, F_GETFL)); + if (fd_flags < 0) { + brillo::errors::system::AddSystemError(error, FROM_HERE, errno); + return stream; + } + int file_access_mode = (fd_flags & O_ACCMODE); + AccessMode access_mode = AccessMode::READ_WRITE; + if (file_access_mode == O_RDONLY) + access_mode = AccessMode::READ; + else if (file_access_mode == O_WRONLY) + access_mode = AccessMode::WRITE; + + // Make sure the file descriptor is set to perform non-blocking operations + // if not enabled already. + if ((fd_flags & O_NONBLOCK) == 0) { + fd_flags |= O_NONBLOCK; + if (HANDLE_EINTR(fcntl(file_descriptor, F_SETFL, fd_flags)) < 0) { + brillo::errors::system::AddSystemError(error, FROM_HERE, errno); + return stream; + } + } + + std::unique_ptr fd_interface{ + new FileDescriptor{file_descriptor, own_descriptor}}; + + stream.reset(new FileStream{std::move(fd_interface), access_mode}); + return stream; +} + +FileStream::FileStream(std::unique_ptr fd_interface, + AccessMode mode) + : fd_interface_(std::move(fd_interface)), + access_mode_(mode) { + switch (fd_interface_->GetFileMode() & S_IFMT) { + case S_IFCHR: // Character device + case S_IFSOCK: // Socket + case S_IFIFO: // FIFO/pipe + // We know that these devices are not seekable and stream size is unknown. + seekable_ = false; + can_get_size_ = false; + break; + + case S_IFBLK: // Block device + case S_IFDIR: // Directory + case S_IFREG: // Normal file + case S_IFLNK: // Symbolic link + default: + // The above devices support seek. Also, if not sure/in doubt, err on the + // side of "allowable". + seekable_ = true; + can_get_size_ = true; + break; + } +} + +bool FileStream::IsOpen() const { + return fd_interface_->IsOpen(); +} + +bool FileStream::CanRead() const { + return IsOpen() && stream_utils::IsReadAccessMode(access_mode_); +} + +bool FileStream::CanWrite() const { + return IsOpen() && stream_utils::IsWriteAccessMode(access_mode_); +} + +bool FileStream::CanSeek() const { + return IsOpen() && seekable_; +} + +bool FileStream::CanGetSize() const { + return IsOpen() && can_get_size_; +} + +uint64_t FileStream::GetSize() const { + return IsOpen() ? fd_interface_->GetSize() : 0; +} + +bool FileStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + if (!stream_utils::CheckInt64Overflow(FROM_HERE, size, 0, error)) + return false; + + if (fd_interface_->Truncate(size) >= 0) + return true; + + errors::system::AddSystemError(error, FROM_HERE, errno); + return false; +} + +uint64_t FileStream::GetRemainingSize() const { + if (!CanGetSize()) + return 0; + uint64_t pos = GetPosition(); + uint64_t size = GetSize(); + return (pos < size) ? (size - pos) : 0; +} + +uint64_t FileStream::GetPosition() const { + if (!CanSeek()) + return 0; + + off64_t pos = fd_interface_->Seek(0, SEEK_CUR); + const off64_t min_pos = 0; + return std::max(min_pos, pos); +} + +bool FileStream::Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + int raw_whence = 0; + switch (whence) { + case Whence::FROM_BEGIN: + raw_whence = SEEK_SET; + break; + case Whence::FROM_CURRENT: + raw_whence = SEEK_CUR; + break; + case Whence::FROM_END: + raw_whence = SEEK_END; + break; + default: + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kInvalidParameter, "Invalid whence"); + return false; + } + off64_t pos = fd_interface_->Seek(offset, raw_whence); + if (pos < 0) { + errors::system::AddSystemError(error, FROM_HERE, errno); + return false; + } + + if (new_position) + *new_position = static_cast(pos); + return true; +} + +bool FileStream::ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + ssize_t read = fd_interface_->Read(buffer, size_to_read); + if (read < 0) { + // If read() fails, check if this is due to no data being currently + // available and we do non-blocking I/O. + if (errno == EWOULDBLOCK || errno == EAGAIN) { + if (end_of_stream) + *end_of_stream = false; + *size_read = 0; + return true; + } + // Otherwise a real problem occurred. + errors::system::AddSystemError(error, FROM_HERE, errno); + return false; + } + if (end_of_stream) + *end_of_stream = (read == 0 && size_to_read != 0); + *size_read = read; + return true; +} + +bool FileStream::WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + ssize_t written = fd_interface_->Write(buffer, size_to_write); + if (written < 0) { + // If write() fails, check if this is due to the fact that no data + // can be presently written and we do non-blocking I/O. + if (errno == EWOULDBLOCK || errno == EAGAIN) { + *size_written = 0; + return true; + } + // Otherwise a real problem occurred. + errors::system::AddSystemError(error, FROM_HERE, errno); + return false; + } + *size_written = written; + return true; +} + +bool FileStream::FlushBlocking(ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + // File descriptors don't have an internal buffer to flush. + return true; +} + +bool FileStream::CloseBlocking(ErrorPtr* error) { + if (!IsOpen()) + return true; + + if (fd_interface_->Close() < 0) { + errors::system::AddSystemError(error, FROM_HERE, errno); + return false; + } + + return true; +} + +bool FileStream::WaitForData( + AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + return fd_interface_->WaitForData(mode, callback, error); +} + +bool FileStream::WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + int ret = fd_interface_->WaitForDataBlocking(in_mode, timeout, out_mode); + if (ret < 0) { + errors::system::AddSystemError(error, FROM_HERE, errno); + return false; + } + if (ret == 0) + return stream_utils::ErrorOperationTimeout(FROM_HERE, error); + + return true; +} + +void FileStream::CancelPendingAsyncOperations() { + if (IsOpen()) { + fd_interface_->CancelPendingAsyncOperations(); + } + Stream::CancelPendingAsyncOperations(); +} + +} // namespace brillo diff --git a/brillo/streams/file_stream.h b/brillo/streams/file_stream.h new file mode 100644 index 0000000..002a850 --- /dev/null +++ b/brillo/streams/file_stream.h @@ -0,0 +1,175 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_FILE_STREAM_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_FILE_STREAM_H_ + +#include +#include +#include +#include + +namespace brillo { + +// FileStream class provides the implementation of brillo::Stream for files +// and file-descriptor-based streams, such as pipes and sockets. +// The FileStream class cannot be instantiated by clients directly. However +// they should use the static factory methods such as: +// - FileStream::Open(): to open a file by name. +// - FileStream::CreateTemporary(): to create a temporary file stream. +// - FileStream::FromFileDescriptor(): to create a stream using an existing +// file descriptor. +class BRILLO_EXPORT FileStream : public Stream { + public: + // See comments for FileStream::Open() for detailed description of this enum. + enum class Disposition { + OPEN_EXISTING, // Open existing file only. Fail if doesn't exist. + CREATE_ALWAYS, // Create empty file, possibly overwriting existing file. + CREATE_NEW_ONLY, // Create new file if doesn't exist already. + TRUNCATE_EXISTING, // Open/truncate existing file. Fail if doesn't exist. + }; + + // Simple interface to wrap native library calls so that they can be mocked + // out for testing. + struct FileDescriptorInterface { + using DataCallback = base::Callback; + + virtual ~FileDescriptorInterface() = default; + + virtual bool IsOpen() const = 0; + virtual ssize_t Read(void* buf, size_t nbyte) = 0; + virtual ssize_t Write(const void* buf, size_t nbyte) = 0; + virtual off64_t Seek(off64_t offset, int whence) = 0; + virtual mode_t GetFileMode() const = 0; + virtual uint64_t GetSize() const = 0; + virtual int Truncate(off64_t length) const = 0; + virtual int Close() = 0; + virtual bool WaitForData(AccessMode mode, + const DataCallback& data_callback, + ErrorPtr* error) = 0; + virtual int WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode) = 0; + virtual void CancelPendingAsyncOperations() = 0; + }; + + // == Construction ========================================================== + + // Opens a file at specified |path| for reading, writing or both as indicated + // by |mode|. The |disposition| specifies how the file must be opened/created: + // - OPEN_EXISTING - opens the existing file and keeps its content intact. + // The seek pointer is at the beginning of the file. + // - CREATE_ALWAYS - creates the file always. If it exists, the file is + // truncated. + // - CREATE_NEW_ONLY - creates a new file only if it doesn't exist. Fails + // otherwise. This can be useful for creating lock files. + // - TRUNCATE_EXISTING - opens existing file and truncates it to zero length. + // Fails if the file doesn't already exist. + // If successful, the open file stream is returned. Otherwise returns the + // stream pointer containing nullptr and fills in the details of the error + // in |error| object, if provided. + static StreamPtr Open(const base::FilePath& path, + AccessMode mode, + Disposition disposition, + ErrorPtr* error); + + // Creates a temporary unnamed file and returns a stream to it. The file will + // be deleted when the stream is destroyed. + static StreamPtr CreateTemporary(ErrorPtr* error); + + // Creates a file stream based on existing file descriptor. The file + // descriptor will be set into non-blocking mode and will be owned by the + // resulting stream (and closed when the stream is destroyed). + // If the function fails, returns a null stream pointer and sets the error + // details to |error| object. Also note that it is the caller's responsibility + // to close the file descriptor if this function fails, since the stream + // hasn't been created yet and didn't take ownership of the file descriptor. + // |own_descriptor| indicates whether the stream must close the underlying + // file descriptor when its CloseBlocking() method is called. This should be + // set to false for file descriptors that shouldn't be closed (e.g. stdin). + static StreamPtr FromFileDescriptor(int file_descriptor, + bool own_descriptor, + ErrorPtr* error); + + // == Stream capabilities =================================================== + bool IsOpen() const override; + bool CanRead() const override; + bool CanWrite() const override; + bool CanSeek() const override; + bool CanGetSize() const override; + + // == Stream size operations ================================================ + uint64_t GetSize() const override; + bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; + uint64_t GetRemainingSize() const override; + + // == Seek operations ======================================================= + uint64_t GetPosition() const override; + bool Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) override; + + // == Read operations ======================================================= + bool ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) override; + + // == Write operations ====================================================== + bool WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) override; + + // == Finalizing/closing streams =========================================== + bool FlushBlocking(ErrorPtr* error) override; + bool CloseBlocking(ErrorPtr* error) override; + + // == Data availability monitoring ========================================== + + // Override for Stream::WaitForData to start watching the associated file + // descriptor for non-blocking read/write operations. + bool WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) override; + + // Runs select() on the file descriptor to wait until we can do non-blocking + // I/O on it. + bool WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) override; + + // Cancels pending asynchronous read/write operations. + void CancelPendingAsyncOperations() override; + + private: + friend class FileStreamTest; + + // Internal constructor used by the factory methods Open(), CreateTemporary(), + // and FromFileDescriptor(). + FileStream(std::unique_ptr fd_interface, + AccessMode mode); + + // Wrapper for the file descriptor. Used in testing to mock out the real + // file system APIs. + std::unique_ptr fd_interface_; + + // The access mode this stream is open with. + AccessMode access_mode_{AccessMode::READ_WRITE}; + + // Set to false for streams that are guaranteed non-seekable. + bool seekable_{true}; + + // Set to false for streams that have unknown size. + bool can_get_size_{false}; + + DISALLOW_COPY_AND_ASSIGN(FileStream); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_FILE_STREAM_H_ diff --git a/brillo/streams/file_stream_unittest.cc b/brillo/streams/file_stream_unittest.cc new file mode 100644 index 0000000..5941621 --- /dev/null +++ b/brillo/streams/file_stream_unittest.cc @@ -0,0 +1,1116 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using testing::InSequence; +using testing::Return; +using testing::ReturnArg; +using testing::SaveArg; +using testing::SetErrnoAndReturn; +using testing::_; + +namespace brillo { + +namespace { + +// gmock action that would return a blocking situation from a read() or write(). +ACTION(ReturnWouldBlock) { + errno = EWOULDBLOCK; + return -1; +} + +// Helper function to read one byte from the stream. +inline int ReadByte(Stream* stream) { + uint8_t byte = 0; + return stream->ReadAllBlocking(&byte, sizeof(byte), nullptr) ? byte : -1; +} + +// Helper function to write one byte from the stream. +inline bool WriteByte(Stream* stream, uint8_t byte) { + return stream->WriteAllBlocking(&byte, sizeof(byte), nullptr); +} + +// Helper function to test file stream workflow on newly created file. +void TestCreateFile(Stream* stream) { + ASSERT_TRUE(stream->IsOpen()); + + // Set up a sample data buffer. + std::vector in_buffer(256); + std::iota(in_buffer.begin(), in_buffer.end(), 0); + + // Initial assumptions about empty file stream. + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(0, stream->GetSize()); + + // Write sample data. + EXPECT_TRUE(stream->WriteAllBlocking(in_buffer.data(), in_buffer.size(), + nullptr)); + EXPECT_EQ(in_buffer.size(), stream->GetPosition()); + EXPECT_EQ(in_buffer.size(), stream->GetSize()); + + // Rewind the stream to the beginning. + uint64_t pos = 0; + EXPECT_TRUE(stream->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(0, pos); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(in_buffer.size(), stream->GetSize()); + + // Read the file contents back. + std::vector out_buffer(256); + EXPECT_TRUE(stream->ReadAllBlocking(out_buffer.data(), out_buffer.size(), + nullptr)); + EXPECT_EQ(out_buffer.size(), stream->GetPosition()); + EXPECT_EQ(out_buffer.size(), stream->GetSize()); + + // Make sure the data read matches those written. + EXPECT_EQ(in_buffer, out_buffer); + + // Random read/write + EXPECT_TRUE(stream->Seek(10, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(10, pos); + + // Since our data buffer contained values from 0 to 255, the byte at position + // 10 will contain the value of 10. + EXPECT_EQ(10, ReadByte(stream)); + EXPECT_EQ(11, ReadByte(stream)); + EXPECT_EQ(12, ReadByte(stream)); + EXPECT_TRUE(stream->Seek(7, Stream::Whence::FROM_CURRENT, nullptr, nullptr)); + EXPECT_EQ(20, ReadByte(stream)); + + EXPECT_EQ(21, stream->GetPosition()); + EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(19, pos); + EXPECT_TRUE(WriteByte(stream, 100)); + EXPECT_EQ(20, ReadByte(stream)); + EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, nullptr, nullptr)); + EXPECT_EQ(100, ReadByte(stream)); + EXPECT_EQ(20, ReadByte(stream)); + EXPECT_TRUE(stream->Seek(-1, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(255, pos); + EXPECT_EQ(255, ReadByte(stream)); + EXPECT_EQ(-1, ReadByte(stream)); +} + +} // anonymous namespace + +// A mock file descriptor wrapper to test low-level file API used by FileStream. +class MockFileDescriptor : public FileStream::FileDescriptorInterface { + public: + MOCK_CONST_METHOD0(IsOpen, bool()); + MOCK_METHOD2(Read, ssize_t(void*, size_t)); + MOCK_METHOD2(Write, ssize_t(const void*, size_t)); + MOCK_METHOD2(Seek, off64_t(off64_t, int)); + MOCK_CONST_METHOD0(GetFileMode, mode_t()); + MOCK_CONST_METHOD0(GetSize, uint64_t()); + MOCK_CONST_METHOD1(Truncate, int(off64_t)); + MOCK_METHOD0(Flush, int()); + MOCK_METHOD0(Close, int()); + MOCK_METHOD3(WaitForData, + bool(Stream::AccessMode, const DataCallback&, ErrorPtr*)); + MOCK_METHOD3(WaitForDataBlocking, + int(Stream::AccessMode, base::TimeDelta, Stream::AccessMode*)); + MOCK_METHOD0(CancelPendingAsyncOperations, void()); +}; + +class FileStreamTest : public testing::Test { + public: + void SetUp() override { + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + } + + MockFileDescriptor& fd_mock() { + return *static_cast(stream_->fd_interface_.get()); + } + + void CreateStream(mode_t file_mode, Stream::AccessMode access_mode) { + std::unique_ptr fd{new MockFileDescriptor{}}; + EXPECT_CALL(*fd, GetFileMode()).WillOnce(Return(file_mode)); + stream_.reset(new FileStream(std::move(fd), access_mode)); + EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(true)); + } + + void ExpectStreamClosed(const ErrorPtr& error) const { + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kStreamClosed, error->GetCode()); + EXPECT_EQ("Stream is closed", error->GetMessage()); + } + + void ExpectStreamOffsetTooLarge(const ErrorPtr& error) const { + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); + EXPECT_EQ("The stream offset value is out of range", error->GetMessage()); + } + + inline static char* IntToPtr(int addr) { + return reinterpret_cast(addr); + } + + inline static const char* IntToConstPtr(int addr) { + return reinterpret_cast(addr); + } + + bool CallWaitForData(Stream::AccessMode mode, ErrorPtr* error) { + return stream_->WaitForData(mode, {}, error); + } + + std::unique_ptr stream_; + + const uint64_t kMaxSize = std::numeric_limits::max(); + const uint64_t kTooLargeSize = std::numeric_limits::max(); + + // Dummy buffer pointer values to make sure that input pointer values + // are delegated to the file interface without a change. + char* const test_read_buffer_ = IntToPtr(12345); + const char* const test_write_buffer_ = IntToConstPtr(67890); +}; + +TEST_F(FileStreamTest, IsOpen) { + EXPECT_TRUE(stream_->IsOpen()); + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_FALSE(stream_->IsOpen()); +} + +TEST_F(FileStreamTest, CanRead) { + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanRead()); + EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false)); + EXPECT_FALSE(stream_->CanRead()); + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_TRUE(stream_->CanRead()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_FALSE(stream_->CanRead()); +} + +TEST_F(FileStreamTest, CanWrite) { + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanWrite()); + EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false)); + EXPECT_FALSE(stream_->CanWrite()); + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_FALSE(stream_->CanWrite()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_TRUE(stream_->CanWrite()); +} + +TEST_F(FileStreamTest, CanSeek) { + CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanSeek()); + CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanSeek()); + CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanSeek()); + + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_TRUE(stream_->CanSeek()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_TRUE(stream_->CanSeek()); +} + +TEST_F(FileStreamTest, CanGetSize) { + CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanGetSize()); + CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanGetSize()); + CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE); + EXPECT_FALSE(stream_->CanGetSize()); + + CreateStream(S_IFREG, Stream::AccessMode::READ); + EXPECT_TRUE(stream_->CanGetSize()); + CreateStream(S_IFREG, Stream::AccessMode::WRITE); + EXPECT_TRUE(stream_->CanGetSize()); +} + +TEST_F(FileStreamTest, GetSize) { + EXPECT_CALL(fd_mock(), GetSize()).WillRepeatedly(Return(12345)); + EXPECT_EQ(12345u, stream_->GetSize()); + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_EQ(0u, stream_->GetSize()); +} + +TEST_F(FileStreamTest, SetSizeBlocking) { + EXPECT_CALL(fd_mock(), Truncate(0)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->SetSizeBlocking(0, nullptr)); + + EXPECT_CALL(fd_mock(), Truncate(123)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->SetSizeBlocking(123, nullptr)); + + EXPECT_CALL(fd_mock(), Truncate(kMaxSize)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->SetSizeBlocking(kMaxSize, nullptr)); +} + +TEST_F(FileStreamTest, SetSizeBlocking_Fail) { + brillo::ErrorPtr error; + + EXPECT_CALL(fd_mock(), Truncate(1235)).WillOnce(SetErrnoAndReturn(EIO, -1)); + EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EIO", error->GetCode()); + + error.reset(); + EXPECT_FALSE(stream_->SetSizeBlocking(kTooLargeSize, &error)); + ExpectStreamOffsetTooLarge(error); + + error.reset(); + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error)); + ExpectStreamClosed(error); +} + +TEST_F(FileStreamTest, GetRemainingSize) { + EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(234)); + EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1234)); + EXPECT_EQ(1000u, stream_->GetRemainingSize()); + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(1234)); + EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1000)); + EXPECT_EQ(0u, stream_->GetRemainingSize()); +} + +TEST_F(FileStreamTest, Seek_Set) { + uint64_t pos = 0; + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(0u, pos); + + EXPECT_CALL(fd_mock(), Seek(123456, SEEK_SET)).WillOnce(Return(123456)); + EXPECT_TRUE(stream_->Seek(123456, Stream::Whence::FROM_BEGIN, &pos, nullptr)); + EXPECT_EQ(123456u, pos); + + EXPECT_CALL(fd_mock(), Seek(kMaxSize, SEEK_SET)) + .WillRepeatedly(Return(kMaxSize)); + EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, &pos, + nullptr)); + EXPECT_EQ(kMaxSize, pos); + EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, nullptr, + nullptr)); +} + +TEST_F(FileStreamTest, Seek_Cur) { + uint64_t pos = 0; + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(100)); + EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(100u, pos); + + EXPECT_CALL(fd_mock(), Seek(234, SEEK_CUR)).WillOnce(Return(1234)); + EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(1234u, pos); + + EXPECT_CALL(fd_mock(), Seek(-100, SEEK_CUR)).WillOnce(Return(900)); + EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_CURRENT, &pos, nullptr)); + EXPECT_EQ(900u, pos); +} + +TEST_F(FileStreamTest, Seek_End) { + uint64_t pos = 0; + + EXPECT_CALL(fd_mock(), Seek(0, SEEK_END)).WillOnce(Return(1000)); + EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(1000u, pos); + + EXPECT_CALL(fd_mock(), Seek(234, SEEK_END)).WillOnce(Return(10234)); + EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(10234u, pos); + + EXPECT_CALL(fd_mock(), Seek(-100, SEEK_END)).WillOnce(Return(9900)); + EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_END, &pos, nullptr)); + EXPECT_EQ(9900u, pos); +} + +TEST_F(FileStreamTest, Seek_Fail) { + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET)) + .WillOnce(SetErrnoAndReturn(EPIPE, -1)); + EXPECT_FALSE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, nullptr, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EPIPE", error->GetCode()); +} + +TEST_F(FileStreamTest, ReadAsync) { + size_t read_size = 0; + bool failed = false; + auto success_callback = [&read_size](size_t size) { read_size = size; }; + auto error_callback = [&failed](const Error* error) { failed = true; }; + FileStream::FileDescriptorInterface::DataCallback data_callback; + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)) + .WillOnce(ReturnWouldBlock()); + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_->ReadAsync(test_read_buffer_, 100, + base::Bind(success_callback), + base::Bind(error_callback), + nullptr)); + EXPECT_EQ(0u, read_size); + EXPECT_FALSE(failed); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(83)); + data_callback.Run(Stream::AccessMode::READ); + EXPECT_EQ(83u, read_size); + EXPECT_FALSE(failed); +} + +TEST_F(FileStreamTest, ReadNonBlocking) { + size_t size = 0; + bool eos = false; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) + .WillRepeatedly(ReturnArg<1>()); + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, + nullptr)); + EXPECT_EQ(100u, size); + EXPECT_FALSE(eos); + + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 0, &size, &eos, + nullptr)); + EXPECT_EQ(0u, size); + EXPECT_FALSE(eos); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, + nullptr)); + EXPECT_EQ(0u, size); + EXPECT_TRUE(eos); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, + nullptr)); + EXPECT_EQ(0u, size); + EXPECT_FALSE(eos); +} + +TEST_F(FileStreamTest, ReadNonBlocking_Fail) { + size_t size = 0; + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EACCES, -1)); + EXPECT_FALSE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, nullptr, + &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EACCES", error->GetCode()); +} + +TEST_F(FileStreamTest, ReadBlocking) { + size_t size = 0; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); + EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 100, &size, nullptr)); + EXPECT_EQ(20u, size); + + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)).WillOnce(Return(45)); + } + EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 80, &size, nullptr)); + EXPECT_EQ(45u, size); + + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 50)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 50, &size, nullptr)); + EXPECT_EQ(0u, size); +} + +TEST_F(FileStreamTest, ReadBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(SetErrnoAndReturn(EBADF, -1)); + } + brillo::ErrorPtr error; + size_t size = 0; + EXPECT_FALSE(stream_->ReadBlocking(test_read_buffer_, 80, &size, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EBADF", error->GetCode()); +} + +TEST_F(FileStreamTest, ReadAllBlocking) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) + .WillOnce(Return(45)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) + .WillOnce(Return(35)); + } + EXPECT_TRUE(stream_->ReadAllBlocking(test_read_buffer_, 100, nullptr)); +} + +TEST_F(FileStreamTest, ReadAllBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); + EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) + .WillOnce(Return(0)); + } + brillo::ErrorPtr error; + EXPECT_FALSE(stream_->ReadAllBlocking(test_read_buffer_, 100, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); + EXPECT_EQ("Reading past the end of stream", error->GetMessage()); +} + +TEST_F(FileStreamTest, WriteAsync) { + size_t write_size = 0; + bool failed = false; + auto success_callback = [&write_size](size_t size) { write_size = size; }; + auto error_callback = [&failed](const Error* error) { failed = true; }; + FileStream::FileDescriptorInterface::DataCallback data_callback; + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)) + .WillOnce(ReturnWouldBlock()); + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_->WriteAsync(test_write_buffer_, 100, + base::Bind(success_callback), + base::Bind(error_callback), + nullptr)); + EXPECT_EQ(0u, write_size); + EXPECT_FALSE(failed); + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(87)); + data_callback.Run(Stream::AccessMode::WRITE); + EXPECT_EQ(87u, write_size); + EXPECT_FALSE(failed); +} + +TEST_F(FileStreamTest, WriteNonBlocking) { + size_t size = 0; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) + .WillRepeatedly(ReturnArg<1>()); + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + nullptr)); + EXPECT_EQ(100u, size); + + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 0, &size, nullptr)); + EXPECT_EQ(0u, size); + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)).WillOnce(Return(0)); + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + nullptr)); + EXPECT_EQ(0u, size); + + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + nullptr)); + EXPECT_EQ(0u, size); +} + +TEST_F(FileStreamTest, WriteNonBlocking_Fail) { + size_t size = 0; + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) + .WillOnce(SetErrnoAndReturn(EACCES, -1)); + EXPECT_FALSE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, + &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EACCES", error->GetCode()); +} + +TEST_F(FileStreamTest, WriteBlocking) { + size_t size = 0; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20)); + EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 100, &size, nullptr)); + EXPECT_EQ(20u, size); + + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)).WillOnce(Return(45)); + } + EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 80, &size, nullptr)); + EXPECT_EQ(45u, size); + + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(0)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(1)); + } + EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 50, &size, nullptr)); + EXPECT_EQ(1u, size); +} + +TEST_F(FileStreamTest, WriteBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(SetErrnoAndReturn(EBADF, -1)); + } + brillo::ErrorPtr error; + size_t size = 0; + EXPECT_FALSE(stream_->WriteBlocking(test_write_buffer_, 80, &size, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EBADF", error->GetCode()); +} + +TEST_F(FileStreamTest, WriteAllBlocking) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80)) + .WillOnce(Return(45)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) + .WillOnce(Return(35)); + } + EXPECT_TRUE(stream_->WriteAllBlocking(test_write_buffer_, 100, nullptr)); +} + +TEST_F(FileStreamTest, WriteAllBlocking_Fail) { + { + InSequence seq; + EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) + .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(SetErrnoAndReturn(EBADF, -1)); + } + brillo::ErrorPtr error; + EXPECT_FALSE(stream_->WriteAllBlocking(test_write_buffer_, 80, &error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EBADF", error->GetCode()); +} + +TEST_F(FileStreamTest, WaitForDataBlocking_Timeout) { + EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(0)); + brillo::ErrorPtr error; + EXPECT_FALSE(stream_->WaitForDataBlocking(Stream::AccessMode::WRITE, {}, + nullptr, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kTimeout, error->GetCode()); +} + +TEST_F(FileStreamTest, FlushBlocking) { + EXPECT_TRUE(stream_->FlushBlocking(nullptr)); +} + +TEST_F(FileStreamTest, CloseBlocking) { + EXPECT_CALL(fd_mock(), Close()).WillOnce(Return(0)); + EXPECT_TRUE(stream_->CloseBlocking(nullptr)); + + EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); + EXPECT_TRUE(stream_->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, CloseBlocking_Fail) { + brillo::ErrorPtr error; + EXPECT_CALL(fd_mock(), Close()).WillOnce(SetErrnoAndReturn(EFBIG, -1)); + EXPECT_FALSE(stream_->CloseBlocking(&error)); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EFBIG", error->GetCode()); +} + +TEST_F(FileStreamTest, WaitForData) { + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ, nullptr)); + + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(CallWaitForData(Stream::AccessMode::WRITE, nullptr)); + + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr)); + + EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _)) + .WillOnce(Return(false)); + EXPECT_FALSE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr)); +} + +TEST_F(FileStreamTest, CreateTemporary) { + StreamPtr stream = FileStream::CreateTemporary(nullptr); + ASSERT_NE(nullptr, stream.get()); + TestCreateFile(stream.get()); +} + +TEST_F(FileStreamTest, OpenRead) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + std::vector buffer(1024 * 1024); + base::RandBytes(buffer.data(), buffer.size()); + int file_size = buffer.size(); // Stupid base::WriteFile taking "int" size. + ASSERT_EQ(file_size, base::WriteFile(path, buffer.data(), file_size)); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ, + FileStream::Disposition::OPEN_EXISTING, + nullptr); + ASSERT_NE(nullptr, stream.get()); + ASSERT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_FALSE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(buffer.size(), stream->GetSize()); + + std::vector buffer2(buffer.size()); + EXPECT_TRUE(stream->ReadAllBlocking(buffer2.data(), buffer2.size(), nullptr)); + EXPECT_EQ(buffer2, buffer); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, OpenWrite) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + std::vector buffer(1024 * 1024); + base::RandBytes(buffer.data(), buffer.size()); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::WRITE, + FileStream::Disposition::CREATE_ALWAYS, + nullptr); + ASSERT_NE(nullptr, stream.get()); + ASSERT_TRUE(stream->IsOpen()); + EXPECT_FALSE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + + EXPECT_TRUE(stream->WriteAllBlocking(buffer.data(), buffer.size(), nullptr)); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + + std::vector buffer2(buffer.size()); + int file_size = buffer2.size(); // Stupid base::ReadFile taking "int" size. + ASSERT_EQ(file_size, base::ReadFile(path, buffer2.data(), file_size)); + EXPECT_EQ(buffer2, buffer); +} + +TEST_F(FileStreamTest, Open_OpenExisting) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::OPEN_EXISTING, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(data.size(), stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_OpenExisting_Fail) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + + ErrorPtr error; + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::OPEN_EXISTING, + &error); + ASSERT_EQ(nullptr, stream.get()); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("ENOENT", error->GetCode()); +} + +TEST_F(FileStreamTest, Open_CreateAlways_New) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_ALWAYS, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_CreateAlways_Existing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_ALWAYS, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_CreateNewOnly_New) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_NEW_ONLY, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, Open_CreateNewOnly_Existing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + ErrorPtr error; + StreamPtr stream = FileStream::Open(path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::CREATE_NEW_ONLY, + &error); + ASSERT_EQ(nullptr, stream.get()); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("EEXIST", error->GetCode()); +} + +TEST_F(FileStreamTest, Open_TruncateExisting_New) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + + ErrorPtr error; + StreamPtr stream = FileStream::Open( + path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::TRUNCATE_EXISTING, + &error); + ASSERT_EQ(nullptr, stream.get()); + EXPECT_EQ(errors::system::kDomain, error->GetDomain()); + EXPECT_EQ("ENOENT", error->GetCode()); +} + +TEST_F(FileStreamTest, Open_TruncateExisting_Existing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); + std::string data{"Lorem ipsum dolor sit amet ..."}; + int data_size = data.size(); // I hate ints for data size... + ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); + + StreamPtr stream = FileStream::Open( + path, + Stream::AccessMode::READ_WRITE, + FileStream::Disposition::TRUNCATE_EXISTING, + nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_TRUE(stream->CanSeek()); + EXPECT_TRUE(stream->CanGetSize()); + EXPECT_EQ(0u, stream->GetPosition()); + EXPECT_EQ(0u, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_StdIn) { + StreamPtr stream = + FileStream::FromFileDescriptor(STDIN_FILENO, false, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); +} + +TEST_F(FileStreamTest, FromFileDescriptor_StdOut) { + StreamPtr stream = + FileStream::FromFileDescriptor(STDOUT_FILENO, false, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); +} + +TEST_F(FileStreamTest, FromFileDescriptor_StdErr) { + StreamPtr stream = + FileStream::FromFileDescriptor(STDERR_FILENO, false, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); +} + +TEST_F(FileStreamTest, FromFileDescriptor_ReadNonBlocking) { + int fds[2] = {-1, -1}; + ASSERT_EQ(0, pipe(fds)); + + StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_TRUE(stream->CanRead()); + EXPECT_FALSE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); + + char buf[10]; + size_t read = 0; + bool eos = true; + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(0, read); + EXPECT_FALSE(eos); + + std::string data{"foo_bar"}; + EXPECT_TRUE(base::WriteFileDescriptor(fds[1], data.data(), data.size())); + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(data.size(), read); + EXPECT_FALSE(eos); + EXPECT_EQ(data, (std::string{buf, read})); + + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(0, read); + EXPECT_FALSE(eos); + + close(fds[1]); + + EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); + EXPECT_EQ(0, read); + EXPECT_TRUE(eos); + + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_WriteNonBlocking) { + int fds[2] = {-1, -1}; + ASSERT_EQ(0, pipe(fds)); + + StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr); + ASSERT_NE(nullptr, stream.get()); + EXPECT_TRUE(stream->IsOpen()); + EXPECT_FALSE(stream->CanRead()); + EXPECT_TRUE(stream->CanWrite()); + EXPECT_FALSE(stream->CanSeek()); + EXPECT_FALSE(stream->CanGetSize()); + + // Pipe buffer is generally 64K, so 128K should be more than enough. + std::vector buffer(128 * 1024); + base::RandBytes(buffer.data(), buffer.size()); + size_t written = 0; + size_t total_size = 0; + + // Fill the output buffer of the pipe until we can no longer write any data + // to it. + do { + ASSERT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, + nullptr)); + total_size += written; + } while (written == buffer.size()); + + EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, + nullptr)); + EXPECT_EQ(0, written); + + std::vector out_buffer(total_size); + EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size())); + + EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, + nullptr)); + EXPECT_GT(written, 0); + out_buffer.resize(written); + EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size())); + EXPECT_TRUE(std::equal(out_buffer.begin(), out_buffer.end(), buffer.begin())); + + close(fds[0]); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_ReadAsync) { + int fds[2] = {-1, -1}; + bool succeeded = false; + bool failed = false; + char buffer[100]; + base::MessageLoopForIO base_loop; + BaseMessageLoop brillo_loop{&base_loop}; + brillo_loop.SetAsCurrent(); + + auto success_callback = [&succeeded, &buffer](size_t size) { + std::string data{buffer, buffer + size}; + ASSERT_EQ("abracadabra", data); + succeeded = true; + }; + + auto error_callback = [&failed](const Error* error) { + failed = true; + }; + + auto write_data_callback = [](int write_fd) { + std::string data{"abracadabra"}; + EXPECT_TRUE(base::WriteFileDescriptor(write_fd, data.data(), data.size())); + }; + + ASSERT_EQ(0, pipe(fds)); + + StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr); + + // Write to the pipe with a bit of delay. + brillo_loop.PostDelayedTask( + FROM_HERE, + base::Bind(write_data_callback, fds[1]), + base::TimeDelta::FromMilliseconds(10)); + + EXPECT_TRUE(stream->ReadAsync(buffer, 100, base::Bind(success_callback), + base::Bind(error_callback), nullptr)); + + auto end_condition = [&failed, &succeeded] { return failed || succeeded; }; + MessageLoopRunUntil(&brillo_loop, + base::TimeDelta::FromSeconds(1), + base::Bind(end_condition)); + + EXPECT_TRUE(succeeded); + EXPECT_FALSE(failed); + + close(fds[1]); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +TEST_F(FileStreamTest, FromFileDescriptor_WriteAsync) { + int fds[2] = {-1, -1}; + bool succeeded = false; + bool failed = false; + const std::string data{"abracadabra"}; + base::MessageLoopForIO base_loop; + BaseMessageLoop brillo_loop{&base_loop}; + brillo_loop.SetAsCurrent(); + + ASSERT_EQ(0, pipe(fds)); + + auto success_callback = [&succeeded, &data](int read_fd, size_t size) { + char buffer[100]; + EXPECT_TRUE(base::ReadFromFD(read_fd, buffer, data.size())); + EXPECT_EQ(data, (std::string{buffer, buffer + data.size()})); + succeeded = true; + }; + + auto error_callback = [&failed](const Error* error) { + failed = true; + }; + + StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr); + + EXPECT_TRUE(stream->WriteAsync(data.data(), data.size(), + base::Bind(success_callback, fds[0]), + base::Bind(error_callback), nullptr)); + + auto end_condition = [&failed, &succeeded] { return failed || succeeded; }; + MessageLoopRunUntil(&brillo_loop, + base::TimeDelta::FromSeconds(1), + base::Bind(end_condition)); + + EXPECT_TRUE(succeeded); + EXPECT_FALSE(failed); + + close(fds[0]); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); +} + +} // namespace brillo diff --git a/brillo/streams/input_stream_set.cc b/brillo/streams/input_stream_set.cc new file mode 100644 index 0000000..913aaa0 --- /dev/null +++ b/brillo/streams/input_stream_set.cc @@ -0,0 +1,205 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include + +namespace brillo { + +InputStreamSet::InputStreamSet( + std::vector source_streams, + std::vector owned_source_streams, + uint64_t initial_stream_size) + : source_streams_{std::move(source_streams)}, + owned_source_streams_{std::move(owned_source_streams)}, + initial_stream_size_{initial_stream_size} {} + +StreamPtr InputStreamSet::Create(std::vector source_streams, + std::vector owned_source_streams, + ErrorPtr* error) { + StreamPtr stream; + + if (source_streams.empty()) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kInvalidParameter, + "Source stream list is empty"); + return stream; + } + + // Make sure we have only readable streams. + for (Stream* src_stream : source_streams) { + if (!src_stream->CanRead()) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kInvalidParameter, + "The stream list must contain only readable streams"); + return stream; + } + } + + // We are using remaining size here because the multiplexed stream is not + // seekable and the bytes already read are essentially "lost" as far as this + // stream is concerned. + uint64_t initial_stream_size = 0; + for (const Stream* stream : source_streams) + initial_stream_size += stream->GetRemainingSize(); + + stream.reset(new InputStreamSet{std::move(source_streams), + std::move(owned_source_streams), + initial_stream_size}); + return stream; +} + +StreamPtr InputStreamSet::Create(std::vector source_streams, + ErrorPtr* error) { + return Create(std::move(source_streams), {}, error); +} + +StreamPtr InputStreamSet::Create(std::vector owned_source_streams, + ErrorPtr* error) { + std::vector source_streams; + source_streams.reserve(owned_source_streams.size()); + for (const StreamPtr& stream : owned_source_streams) + source_streams.push_back(stream.get()); + return Create(std::move(source_streams), std::move(owned_source_streams), + error); +} + +bool InputStreamSet::IsOpen() const { + return !closed_; +} + +bool InputStreamSet::CanGetSize() const { + bool can_get_size = IsOpen(); + for (const Stream* stream : source_streams_) { + if (!stream->CanGetSize()) { + can_get_size = false; + break; + } + } + return can_get_size; +} + +uint64_t InputStreamSet::GetSize() const { + return initial_stream_size_; +} + +bool InputStreamSet::SetSizeBlocking(uint64_t size, ErrorPtr* error) { + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); +} + +uint64_t InputStreamSet::GetRemainingSize() const { + uint64_t size = 0; + for (const Stream* stream : source_streams_) + size += stream->GetRemainingSize(); + return size; +} + +bool InputStreamSet::Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) { + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); +} + +bool InputStreamSet::ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + while (!source_streams_.empty()) { + Stream* stream = source_streams_.front(); + bool eos = false; + if (!stream->ReadNonBlocking(buffer, size_to_read, size_read, &eos, error)) + return false; + + if (*size_read > 0 || !eos) { + if (end_of_stream) + *end_of_stream = false; + return true; + } + + source_streams_.erase(source_streams_.begin()); + } + *size_read = 0; + if (end_of_stream) + *end_of_stream = true; + return true; +} + +bool InputStreamSet::WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) { + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); +} + +bool InputStreamSet::CloseBlocking(ErrorPtr* error) { + bool success = true; + // We want to close only the owned streams. + for (StreamPtr& stream_ptr : owned_source_streams_) { + if (!stream_ptr->CloseBlocking(error)) + success = false; // Keep going for other streams... + } + owned_source_streams_.clear(); + source_streams_.clear(); + initial_stream_size_ = 0; + closed_ = true; + return success; +} + +bool InputStreamSet::WaitForData( + AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + if (stream_utils::IsWriteAccessMode(mode)) + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); + + if (!source_streams_.empty()) { + Stream* stream = source_streams_.front(); + return stream->WaitForData(mode, callback, error); + } + + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, mode)); + return true; +} + +bool InputStreamSet::WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) { + if (!IsOpen()) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + + if (stream_utils::IsWriteAccessMode(in_mode)) + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); + + if (!source_streams_.empty()) { + Stream* stream = source_streams_.front(); + return stream->WaitForDataBlocking(in_mode, timeout, out_mode, error); + } + + if (out_mode) + *out_mode = in_mode; + return true; +} + +void InputStreamSet::CancelPendingAsyncOperations() { + if (IsOpen() && !source_streams_.empty()) { + Stream* stream = source_streams_.front(); + stream->CancelPendingAsyncOperations(); + } + Stream::CancelPendingAsyncOperations(); +} + +} // namespace brillo diff --git a/brillo/streams/input_stream_set.h b/brillo/streams/input_stream_set.h new file mode 100644 index 0000000..fda255f --- /dev/null +++ b/brillo/streams/input_stream_set.h @@ -0,0 +1,132 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_INPUT_STREAM_SET_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_INPUT_STREAM_SET_H_ + +#include + +#include +#include +#include + +namespace brillo { + +// Multiplexer stream allows to bundle a bunch of secondary streams in one +// logical stream and simulate a read operation across data concatenated from +// all those source streams. +// +// When created on a set of source streams like stream1, stream2, stream3, etc., +// reading from the multiplexer stream will read all the data from stream1 until +// end-of-stream is reached, then keep reading from stream2, stream3 and so on. +// +// InputStreamSet has an option of owning the underlying source streams +// or just referencing them. Owned streams are passed to InputStreamSet +// with exclusive ownership transfer (using StreamPtr) and those streams will +// be closed/destroyed when InputStreamSet is closed/destroyed. +// Referenced source streams' life time is maintained elsewhere and they must +// be valid for the duration of InputStreamSet's life. Closing the +// muliplexer stream does not close the referenced streams. +class BRILLO_EXPORT InputStreamSet : public Stream { + public: + // == Construction ========================================================== + + // Generic method that constructs a multiplexer stream on a list of source + // streams. |source_streams| is the list of all source stream references + // in the order they need to be read from. |owned_source_streams| is a list + // of source stream instances that the multiplexer stream will own. + // Note that the streams from |owned_source_streams| should still be + // referenced in |source_streams| if you need their data to be read from. + // |owned_source_streams| could be empty (in which case none of the source + // streams are not owned), or contain fewer items than in |source_streams|. + static StreamPtr Create(std::vector source_streams, + std::vector owned_source_streams, + ErrorPtr* error); + + // Simple helper method to create a multiplexer stream with a list of + // referenced streams. None of the streams will be owned. + // Effectively calls Create(source_streams, {}, error); + static StreamPtr Create(std::vector source_streams, ErrorPtr* error); + + // Simple helper method to create a multiplexer stream with a list of + // referenced streams. None of the streams will be owned. + // Effectively calls Create(source_streams, owned_source_streams, error) + // with |source_streams| containing pointers to the streams from + // |owned_source_streams| list. + static StreamPtr Create(std::vector owned_source_streams, + ErrorPtr* error); + + // == Stream capabilities =================================================== + bool IsOpen() const override; + bool CanRead() const override { return true; } + bool CanWrite() const override { return false; } + bool CanSeek() const override { return false; } + bool CanGetSize() const override; + + // == Stream size operations ================================================ + uint64_t GetSize() const override; + bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; + uint64_t GetRemainingSize() const override; + + // == Seek operations ======================================================= + uint64_t GetPosition() const override { return 0; } + bool Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) override; + + // == Read operations ======================================================= + bool ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) override; + + // == Write operations ====================================================== + bool WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) override; + + // == Finalizing/closing streams =========================================== + bool FlushBlocking(ErrorPtr* error) override { return true; } + bool CloseBlocking(ErrorPtr* error) override; + + // == Data availability monitoring ========================================== + bool WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) override; + + bool WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) override; + + void CancelPendingAsyncOperations() override; + + private: + friend class InputStreamSetTest; + + // Internal constructor used by the Create() factory methods. + InputStreamSet(std::vector source_streams, + std::vector owned_source_streams, + uint64_t initial_stream_size); + + // List of streams to read data from. + std::vector source_streams_; + + // List of source streams this stream owns. Owned source streams will be + // closed when InputStreamSet::CloseBlocking() is called and will be + // destroyed when this stream is destroyed. + std::vector owned_source_streams_; + + uint64_t initial_stream_size_{0}; + bool closed_{false}; + + DISALLOW_COPY_AND_ASSIGN(InputStreamSet); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_INPUT_STREAM_SET_H_ diff --git a/brillo/streams/input_stream_set_unittest.cc b/brillo/streams/input_stream_set_unittest.cc new file mode 100644 index 0000000..3268d96 --- /dev/null +++ b/brillo/streams/input_stream_set_unittest.cc @@ -0,0 +1,170 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include + +using testing::An; +using testing::DoAll; +using testing::InSequence; +using testing::Return; +using testing::SetArgPointee; +using testing::StrictMock; +using testing::_; + +namespace brillo { + +class InputStreamSetTest : public testing::Test { + public: + void SetUp() override { + itf1_.reset(new StrictMock{}); + itf2_.reset(new StrictMock{}); + stream_.reset(new InputStreamSet({itf1_.get(), itf2_.get()}, {}, 100)); + } + + void TearDown() override { + stream_.reset(); + itf2_.reset(); + itf1_.reset(); + } + + std::unique_ptr> itf1_; + std::unique_ptr> itf2_; + std::unique_ptr stream_; + + inline static void* IntToPtr(int addr) { + return reinterpret_cast(addr); + } +}; + +TEST_F(InputStreamSetTest, InitialFalseAssumptions) { + // Methods that should just succeed/fail without calling underlying streams. + EXPECT_TRUE(stream_->CanRead()); + EXPECT_FALSE(stream_->CanWrite()); + EXPECT_FALSE(stream_->CanSeek()); + EXPECT_EQ(100, stream_->GetSize()); + EXPECT_FALSE(stream_->SetSizeBlocking(0, nullptr)); + EXPECT_FALSE(stream_->GetPosition()); + EXPECT_FALSE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, nullptr, nullptr)); + char buffer[100]; + size_t size = 0; + EXPECT_FALSE(stream_->WriteAsync(buffer, sizeof(buffer), {}, {}, nullptr)); + EXPECT_FALSE(stream_->WriteAllAsync(buffer, sizeof(buffer), {}, {}, nullptr)); + EXPECT_FALSE(stream_->WriteNonBlocking(buffer, sizeof(buffer), &size, + nullptr)); + EXPECT_FALSE(stream_->WriteBlocking(buffer, sizeof(buffer), &size, nullptr)); + EXPECT_FALSE(stream_->WriteAllBlocking(buffer, sizeof(buffer), nullptr)); + EXPECT_TRUE(stream_->FlushBlocking(nullptr)); + EXPECT_TRUE(stream_->CloseBlocking(nullptr)); +} + +TEST_F(InputStreamSetTest, InitialTrueAssumptions) { + // Methods that redirect calls to underlying streams. + EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true)); + EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(true)); + EXPECT_TRUE(stream_->CanGetSize()); + + // Reading from the first stream fails, so the second one shouldn't be used. + EXPECT_CALL(*itf1_, ReadNonBlocking(_, _, _, _, _)) + .WillOnce(Return(false)); + EXPECT_CALL(*itf2_, ReadNonBlocking(_, _, _, _, _)).Times(0); + char buffer[100]; + size_t size = 0; + EXPECT_FALSE(stream_->ReadBlocking(buffer, sizeof(buffer), &size, nullptr)); +} + +TEST_F(InputStreamSetTest, CanGetSize) { + EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true)); + EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(true)); + EXPECT_TRUE(stream_->CanGetSize()); + + EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(false)); + EXPECT_FALSE(stream_->CanGetSize()); + + EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true)); + EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(false)); + EXPECT_FALSE(stream_->CanGetSize()); +} + +TEST_F(InputStreamSetTest, GetRemainingSize) { + EXPECT_CALL(*itf1_, GetRemainingSize()).WillOnce(Return(10)); + EXPECT_CALL(*itf2_, GetRemainingSize()).WillOnce(Return(32)); + EXPECT_EQ(42, stream_->GetRemainingSize()); +} + +TEST_F(InputStreamSetTest, ReadNonBlocking) { + size_t read = 0; + bool eos = false; + + InSequence s; + EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(10), + SetArgPointee<3>(false), + Return(true))); + EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos, + nullptr)); + EXPECT_EQ(10, read); + EXPECT_FALSE(eos); + + EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), SetArgPointee<3>(true), Return(true))); + EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100 , _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(100), + SetArgPointee<3>(false), + Return(true))); + EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos, + nullptr)); + EXPECT_EQ(100, read); + EXPECT_FALSE(eos); + + EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), SetArgPointee<3>(true), Return(true))); + EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos, + nullptr)); + EXPECT_EQ(0, read); + EXPECT_TRUE(eos); +} + +TEST_F(InputStreamSetTest, ReadBlocking) { + size_t read = 0; + + InSequence s; + EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(10), + SetArgPointee<3>(false), + Return(true))); + EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr)); + EXPECT_EQ(10, read); + + EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(true), + Return(true))); + EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(false), + Return(true))); + EXPECT_CALL(*itf2_, WaitForDataBlocking(Stream::AccessMode::READ, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(100), + SetArgPointee<3>(false), + Return(true))); + EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr)); + EXPECT_EQ(100, read); + + EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(true), + Return(true))); + EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr)); + EXPECT_EQ(0, read); +} + +} // namespace brillo diff --git a/brillo/streams/memory_containers.cc b/brillo/streams/memory_containers.cc new file mode 100644 index 0000000..6e277b8 --- /dev/null +++ b/brillo/streams/memory_containers.cc @@ -0,0 +1,129 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +namespace brillo { +namespace data_container { + +namespace { + +bool ErrorStreamReadOnly(const tracked_objects::Location& location, + ErrorPtr* error) { + Error::AddTo(error, + location, + errors::stream::kDomain, + errors::stream::kOperationNotSupported, + "Stream is read-only"); + return false; +} + +} // anonymous namespace + +void ContiguousBufferBase::CopyMemoryBlock(void* dest, + const void* src, + size_t size) const { + memcpy(dest, src, size); +} + +bool ContiguousBufferBase::Read(void* buffer, + size_t size_to_read, + size_t offset, + size_t* size_read, + ErrorPtr* error) { + size_t buf_size = GetSize(); + if (offset < buf_size) { + size_t remaining = buf_size - offset; + if (size_to_read >= remaining) { + size_to_read = remaining; + } + const void* src_buffer = GetReadOnlyBuffer(offset, error); + if (!src_buffer) + return false; + + CopyMemoryBlock(buffer, src_buffer, size_to_read); + } else { + size_to_read = 0; + } + if (size_read) + *size_read = size_to_read; + return true; +} + +bool ContiguousBufferBase::Write(const void* buffer, + size_t size_to_write, + size_t offset, + size_t* size_written, + ErrorPtr* error) { + if (size_to_write) { + size_t new_size = offset + size_to_write; + if (GetSize() < new_size && !Resize(new_size, error)) + return false; + void* ptr = GetBuffer(offset, error); + if (!ptr) + return false; + CopyMemoryBlock(ptr, buffer, size_to_write); + if (size_written) + *size_written = size_to_write; + } + return true; +} + +bool ContiguousReadOnlyBufferBase::Write(const void* buffer, + size_t size_to_write, + size_t offset, + size_t* size_written, + ErrorPtr* error) { + return ErrorStreamReadOnly(FROM_HERE, error); +} + +bool ContiguousReadOnlyBufferBase::Resize(size_t new_size, ErrorPtr* error) { + return ErrorStreamReadOnly(FROM_HERE, error); +} + +void* ContiguousReadOnlyBufferBase::GetBuffer(size_t offset, ErrorPtr* error) { + ErrorStreamReadOnly(FROM_HERE, error); + return nullptr; +} + +ByteBuffer::ByteBuffer(size_t reserve_size) + : VectorPtr(new std::vector()) { + vector_ptr_->reserve(reserve_size); +} + +ByteBuffer::~ByteBuffer() { + delete vector_ptr_; +} + +StringPtr::StringPtr(std::string* string) : string_ptr_(string) {} + +bool StringPtr::Resize(size_t new_size, ErrorPtr* error) { + string_ptr_->resize(new_size); + return true; +} + +const void* StringPtr::GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const { + return string_ptr_->data() + offset; +} + +void* StringPtr::GetBuffer(size_t offset, ErrorPtr* error) { + return &(*string_ptr_)[offset]; +} + +ReadOnlyStringRef::ReadOnlyStringRef(const std::string& string) + : string_ref_(string) {} + +const void* ReadOnlyStringRef::GetReadOnlyBuffer(size_t offset, + ErrorPtr* error) const { + return string_ref_.data() + offset; +} + +ReadOnlyStringCopy::ReadOnlyStringCopy(std::string string) + : ReadOnlyStringRef(string_copy_), string_copy_(std::move(string)) {} + +} // namespace data_container +} // namespace brillo diff --git a/brillo/streams/memory_containers.h b/brillo/streams/memory_containers.h new file mode 100644 index 0000000..498401e --- /dev/null +++ b/brillo/streams/memory_containers.h @@ -0,0 +1,284 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_MEMORY_CONTAINERS_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_MEMORY_CONTAINERS_H_ + +#include +#include + +#include +#include +#include + +namespace brillo { +namespace data_container { + +// MemoryStream class relies on helper classes defined below to support data +// storage in various types of containers. +// A particular implementation of container type (e.g. based on raw memory +// buffers, std::vector, std::string or others) need to implement the container +// interface provided by data_container::DataContainerInterface. +// Low-level functionality such as reading data from and writing data to the +// container, getting and changing the buffer size, and so on, must be provided. +// Not all methods must be provided. For example, for read-only containers, only +// read operations can be provided. +class BRILLO_EXPORT DataContainerInterface { + public: + DataContainerInterface() = default; + virtual ~DataContainerInterface() = default; + + // Read the data from the container into |buffer|. Up to |size_to_read| bytes + // must be read at a time. The container can return fewer bytes. The actual + // size of data read is provided in |size_read|. + // If the read operation fails, the function must return false and provide + // additional information about the error in |error| object. + virtual bool Read(void* buffer, + size_t size_to_read, + size_t offset, + size_t* size_read, + ErrorPtr* error) = 0; + + // Writes |size_to_write| bytes of data from |buffer| into the container. + // The container may accept fewer bytes of data. The actual size of data + // written is provided in |size_written|. + // If the read operation fails, the function must return false and provide + // additional information about the error in |error| object. + virtual bool Write(const void* buffer, + size_t size_to_write, + size_t offset, + size_t* size_written, + ErrorPtr* error) = 0; + // Resizes the container to the new size specified in |new_size|. + virtual bool Resize(size_t new_size, ErrorPtr* error) = 0; + // Returns the current size of the container. + virtual size_t GetSize() const = 0; + // Returns true if the container is read-only. + virtual bool IsReadOnly() const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(DataContainerInterface); +}; + +// ContiguousBufferBase is a helper base class for memory containers that +// employ contiguous memory for all of their data. This class provides the +// default implementation for Read() and Write() functions and requires the +// implementations to provide GetBuffer() and/or ReadOnlyBuffer() functions. +class BRILLO_EXPORT ContiguousBufferBase : public DataContainerInterface { + public: + ContiguousBufferBase() = default; + // Implementation of DataContainerInterface::Read(). + bool Read(void* buffer, + size_t size_to_read, + size_t offset, + size_t* size_read, + ErrorPtr* error) override; + // Implementation of DataContainerInterface::Write(). + bool Write(const void* buffer, + size_t size_to_write, + size_t offset, + size_t* size_written, + ErrorPtr* error) override; + + // Overload to provide the pointer to the read-only data for the container at + // the specified |offset|. In case of an error, this function must return + // nullptr and provide error details in |error| object if provided. + virtual const void* GetReadOnlyBuffer(size_t offset, + ErrorPtr* error) const = 0; + // Overload to provide the pointer to the read/write data for the container at + // the specified |offset|. In case of an error, this function must return + // nullptr and provide error details in |error| object if provided. + virtual void* GetBuffer(size_t offset, ErrorPtr* error) = 0; + + protected: + // Wrapper around memcpy which can be mocked out in tests. + virtual void CopyMemoryBlock(void* dest, const void* src, size_t size) const; + + private: + DISALLOW_COPY_AND_ASSIGN(ContiguousBufferBase); +}; + +// ContiguousReadOnlyBufferBase is a specialization of ContiguousBufferBase for +// read-only containers. +class BRILLO_EXPORT ContiguousReadOnlyBufferBase : public ContiguousBufferBase { + public: + ContiguousReadOnlyBufferBase() = default; + // Fails with an error "operation_not_supported" (Stream is read-only) error. + bool Write(const void* buffer, + size_t size_to_write, + size_t offset, + size_t* size_written, + ErrorPtr* error) override; + // Fails with an error "operation_not_supported" (Stream is read-only) error. + bool Resize(size_t new_size, ErrorPtr* error) override; + // Fails with an error "operation_not_supported" (Stream is read-only) error. + bool IsReadOnly() const override { return true; } + // Fails with an error "operation_not_supported" (Stream is read-only) error. + void* GetBuffer(size_t offset, ErrorPtr* error) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ContiguousReadOnlyBufferBase); +}; + +// ReadOnlyBuffer implements a read-only container based on raw memory block. +class BRILLO_EXPORT ReadOnlyBuffer : public ContiguousReadOnlyBufferBase { + public: + // Constructs the container based at the pointer to memory |buffer| and its + // |size|. The pointer to the memory must be valid throughout life-time of + // the stream using this container. + ReadOnlyBuffer(const void* buffer, size_t size) + : buffer_(buffer), size_(size) {} + + // Returns the pointer to data at |offset|. + const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { + return reinterpret_cast(buffer_) + offset; + } + // Returns the size of the container. + size_t GetSize() const override { return size_; } + + private: + // Raw memory pointer to the data block and its size. + const void* buffer_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(ReadOnlyBuffer); +}; + +// VectorPtr is a read/write container based on a vector pointer. +// This is a template class to allow usage of both vector and +// vector without duplicating the implementation. +template +class VectorPtr : public ContiguousBufferBase { + public: + static_assert(sizeof(T) == 1, "Only char/byte is supported"); + explicit VectorPtr(std::vector* vector) : vector_ptr_(vector) {} + + bool Resize(size_t new_size, ErrorPtr* error) override { + vector_ptr_->resize(new_size); + return true; + } + size_t GetSize() const override { return vector_ptr_->size(); } + bool IsReadOnly() const override { return false; } + const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { + return reinterpret_cast(vector_ptr_->data()) + offset; + } + void* GetBuffer(size_t offset, ErrorPtr* error) override { + return reinterpret_cast(vector_ptr_->data()) + offset; + } + + protected: + std::vector* vector_ptr_; + + private: + DISALLOW_COPY_AND_ASSIGN(VectorPtr); +}; + +// ReadOnlyVectorRef is a read-only container based on a vector reference. +// This is a template class to allow usage of both vector and +// vector without duplicating the implementation. +template +class ReadOnlyVectorRef : public ContiguousReadOnlyBufferBase { + public: + static_assert(sizeof(T) == 1, "Only char/byte is supported"); + explicit ReadOnlyVectorRef(const std::vector& vector) + : vector_ref_(vector) {} + + const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { + return reinterpret_cast(vector_ref_.data()) + offset; + } + size_t GetSize() const override { return vector_ref_.size(); } + + protected: + const std::vector& vector_ref_; + + private: + DISALLOW_COPY_AND_ASSIGN(ReadOnlyVectorRef); +}; + +// ReadOnlyVectorCopy is a read-only container based on a copy of vector. +// This container actually owns the data stored in the vector. +// This is a template class to allow usage of both vector and +// vector without duplicating the implementation. +template +class ReadOnlyVectorCopy : public ContiguousReadOnlyBufferBase { + public: + static_assert(sizeof(T) == 1, "Only char/byte is supported"); + explicit ReadOnlyVectorCopy(std::vector vector) + : vector_copy_(std::move(vector)) {} + + ReadOnlyVectorCopy(const T* buffer, size_t size) + : vector_copy_(buffer, buffer + size) {} + + const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { + return reinterpret_cast(vector_copy_.data()) + offset; + } + size_t GetSize() const override { return vector_copy_.size(); } + + protected: + std::vector vector_copy_; + + private: + DISALLOW_COPY_AND_ASSIGN(ReadOnlyVectorCopy); +}; + +// ByteBuffer is a read/write container that manages the data and underlying +// storage. +class BRILLO_EXPORT ByteBuffer : public VectorPtr { + public: + explicit ByteBuffer(size_t reserve_size); + ~ByteBuffer() override; + + private: + DISALLOW_COPY_AND_ASSIGN(ByteBuffer); +}; + +// StringPtr is a read/write container based on external std::string storage. +class BRILLO_EXPORT StringPtr : public ContiguousBufferBase { + public: + explicit StringPtr(std::string* string); + + bool Resize(size_t new_size, ErrorPtr* error) override; + size_t GetSize() const override { return string_ptr_->size(); } + bool IsReadOnly() const override { return false; } + const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override; + void* GetBuffer(size_t offset, ErrorPtr* error) override; + + protected: + std::string* string_ptr_; + + private: + DISALLOW_COPY_AND_ASSIGN(StringPtr); +}; + +// ReadOnlyStringRef is a read-only container based on external std::string. +class BRILLO_EXPORT ReadOnlyStringRef : public ContiguousReadOnlyBufferBase { + public: + explicit ReadOnlyStringRef(const std::string& string); + const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override; + size_t GetSize() const override { return string_ref_.size(); } + + protected: + const std::string& string_ref_; + + private: + DISALLOW_COPY_AND_ASSIGN(ReadOnlyStringRef); +}; + +// ReadOnlyStringCopy is a read-only container based on a copy of a std::string. +// This container actually owns the data stored in the string. +class BRILLO_EXPORT ReadOnlyStringCopy : public ReadOnlyStringRef { + public: + explicit ReadOnlyStringCopy(std::string string); + + protected: + std::string string_copy_; + + private: + DISALLOW_COPY_AND_ASSIGN(ReadOnlyStringCopy); +}; + +} // namespace data_container +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_MEMORY_CONTAINERS_H_ diff --git a/brillo/streams/memory_containers_unittest.cc b/brillo/streams/memory_containers_unittest.cc new file mode 100644 index 0000000..2f0bf38 --- /dev/null +++ b/brillo/streams/memory_containers_unittest.cc @@ -0,0 +1,214 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include +#include +#include +#include + +using testing::DoAll; +using testing::Invoke; +using testing::InSequence; +using testing::Return; +using testing::WithArgs; +using testing::_; + +namespace brillo { + +namespace { +class MockContiguousBuffer : public data_container::ContiguousBufferBase { + public: + MockContiguousBuffer() = default; + + MOCK_METHOD2(Resize, bool(size_t, ErrorPtr*)); + MOCK_CONST_METHOD0(GetSize, size_t()); + MOCK_CONST_METHOD0(IsReadOnly, bool()); + + MOCK_CONST_METHOD2(GetReadOnlyBuffer, const void*(size_t, ErrorPtr*)); + MOCK_METHOD2(GetBuffer, void*(size_t, ErrorPtr*)); + + MOCK_CONST_METHOD3(CopyMemoryBlock, void(void*, const void*, size_t)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockContiguousBuffer); +}; +} // anonymous namespace + +class MemoryContainerTest : public testing::Test { + public: + inline static void* IntToPtr(int addr) { + return reinterpret_cast(addr); + } + + inline static const void* IntToConstPtr(int addr) { + return reinterpret_cast(addr); + } + + // Dummy buffer pointer values used as external data source/destination for + // read/write operations. + void* const test_read_buffer_ = IntToPtr(12345); + const void* const test_write_buffer_ = IntToConstPtr(67890); + + // Dummy buffer pointer values used for internal buffer owned by the + // memory buffer container class. + const void* const const_buffer_ = IntToConstPtr(123); + void* const buffer_ = IntToPtr(456); + + MockContiguousBuffer container_; +}; + +TEST_F(MemoryContainerTest, Read_WithinBuffer) { + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, GetReadOnlyBuffer(10, _)) + .WillOnce(Return(const_buffer_)); + EXPECT_CALL(container_, + CopyMemoryBlock(test_read_buffer_, const_buffer_, 50)).Times(1); + } + size_t read = 0; + ErrorPtr error; + EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 10, &read, &error)); + EXPECT_EQ(50, read); + EXPECT_EQ(nullptr, error.get()); +} + +TEST_F(MemoryContainerTest, Read_PastEndOfBuffer) { + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, GetReadOnlyBuffer(80, _)) + .WillOnce(Return(const_buffer_)); + EXPECT_CALL(container_, + CopyMemoryBlock(test_read_buffer_, const_buffer_, 20)).Times(1); + } + size_t read = 0; + EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 80, &read, nullptr)); + EXPECT_EQ(20, read); +} + +TEST_F(MemoryContainerTest, Read_OutsideBuffer) { + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + size_t read = 0; + EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 100, &read, nullptr)); + EXPECT_EQ(0, read); +} + +TEST_F(MemoryContainerTest, Read_Error) { + auto OnReadError = [](ErrorPtr* error) { + Error::AddTo(error, FROM_HERE, "domain", "read_error", "read error"); + }; + + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, GetReadOnlyBuffer(0, _)) + .WillOnce(DoAll(WithArgs<1>(Invoke(OnReadError)), Return(nullptr))); + } + size_t read = 0; + ErrorPtr error; + EXPECT_FALSE(container_.Read(test_read_buffer_, 10, 0, &read, &error)); + EXPECT_EQ(0, read); + EXPECT_NE(nullptr, error.get()); + EXPECT_EQ("domain", error->GetDomain()); + EXPECT_EQ("read_error", error->GetCode()); + EXPECT_EQ("read error", error->GetMessage()); +} + +TEST_F(MemoryContainerTest, Write_WithinBuffer) { + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, GetBuffer(10, _)) + .WillOnce(Return(buffer_)); + EXPECT_CALL(container_, + CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1); + } + size_t written = 0; + ErrorPtr error; + EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 10, &written, &error)); + EXPECT_EQ(50, written); + EXPECT_EQ(nullptr, error.get()); +} + +TEST_F(MemoryContainerTest, Write_PastEndOfBuffer) { + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, Resize(130, _)).WillOnce(Return(true)); + EXPECT_CALL(container_, GetBuffer(80, _)) + .WillOnce(Return(buffer_)); + EXPECT_CALL(container_, + CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1); + } + size_t written = 0; + EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 80, &written, nullptr)); + EXPECT_EQ(50, written); +} + +TEST_F(MemoryContainerTest, Write_OutsideBuffer) { + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, Resize(160, _)).WillOnce(Return(true)); + EXPECT_CALL(container_, GetBuffer(110, _)) + .WillOnce(Return(buffer_)); + EXPECT_CALL(container_, + CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1); + } + size_t written = 0; + EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 110, &written, nullptr)); + EXPECT_EQ(50, written); +} + +TEST_F(MemoryContainerTest, Write_Error_Resize) { + auto OnWriteError = [](ErrorPtr* error) { + Error::AddTo(error, FROM_HERE, "domain", "write_error", "resize error"); + }; + + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, Resize(160, _)) + .WillOnce(DoAll(WithArgs<1>(Invoke(OnWriteError)), Return(false))); + } + size_t written = 0; + ErrorPtr error; + EXPECT_FALSE(container_.Write(test_write_buffer_, 50, 110, &written, &error)); + EXPECT_EQ(0, written); + EXPECT_NE(nullptr, error.get()); + EXPECT_EQ("domain", error->GetDomain()); + EXPECT_EQ("write_error", error->GetCode()); + EXPECT_EQ("resize error", error->GetMessage()); +} + +TEST_F(MemoryContainerTest, Write_Error) { + auto OnWriteError = [](ErrorPtr* error) { + Error::AddTo(error, FROM_HERE, "domain", "write_error", "write error"); + }; + + { + InSequence s; + EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); + EXPECT_CALL(container_, Resize(160, _)).WillOnce(Return(true)); + EXPECT_CALL(container_, GetBuffer(110, _)) + .WillOnce(DoAll(WithArgs<1>(Invoke(OnWriteError)), Return(nullptr))); + } + size_t written = 0; + ErrorPtr error; + EXPECT_FALSE(container_.Write(test_write_buffer_, 50, 110, &written, &error)); + EXPECT_EQ(0, written); + EXPECT_NE(nullptr, error.get()); + EXPECT_EQ("domain", error->GetDomain()); + EXPECT_EQ("write_error", error->GetCode()); + EXPECT_EQ("write error", error->GetMessage()); +} + +} // namespace brillo + diff --git a/brillo/streams/memory_stream.cc b/brillo/streams/memory_stream.cc new file mode 100644 index 0000000..c9712b0 --- /dev/null +++ b/brillo/streams/memory_stream.cc @@ -0,0 +1,201 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include + +namespace brillo { + +MemoryStream::MemoryStream( + std::unique_ptr container, + size_t stream_position) + : container_{std::move(container)}, stream_position_{stream_position} {} + +StreamPtr MemoryStream::OpenRef(const void* buffer, + size_t size, + ErrorPtr* error) { + std::unique_ptr container{ + new data_container::ReadOnlyBuffer{buffer, size}}; + return CreateEx(std::move(container), 0, error); +} + +StreamPtr MemoryStream::OpenCopyOf(const void* buffer, + size_t size, + ErrorPtr* error) { + std::unique_ptr> container{ + new data_container::ReadOnlyVectorCopy{ + reinterpret_cast(buffer), size}}; + return CreateEx(std::move(container), 0, error); +} + +StreamPtr MemoryStream::OpenRef(const std::string& buffer, ErrorPtr* error) { + std::unique_ptr container{ + new data_container::ReadOnlyStringRef{buffer}}; + return CreateEx(std::move(container), 0, error); +} + +StreamPtr MemoryStream::OpenCopyOf(std::string buffer, ErrorPtr* error) { + std::unique_ptr container{ + new data_container::ReadOnlyStringCopy{std::move(buffer)}}; + return CreateEx(std::move(container), 0, error); +} + +StreamPtr MemoryStream::OpenRef(const char* buffer, ErrorPtr* error) { + return OpenRef(buffer, std::strlen(buffer), error); +} + +StreamPtr MemoryStream::OpenCopyOf(const char* buffer, ErrorPtr* error) { + return OpenCopyOf(buffer, std::strlen(buffer), error); +} + +StreamPtr MemoryStream::Create(size_t reserve_size, ErrorPtr* error) { + std::unique_ptr container{ + new data_container::ByteBuffer{reserve_size}}; + return CreateEx(std::move(container), 0, error); +} + +StreamPtr MemoryStream::CreateRef(std::string* buffer, ErrorPtr* error) { + std::unique_ptr container{ + new data_container::StringPtr{buffer}}; + return CreateEx(std::move(container), 0, error); +} + +StreamPtr MemoryStream::CreateRefForAppend(std::string* buffer, + ErrorPtr* error) { + std::unique_ptr container{ + new data_container::StringPtr{buffer}}; + return CreateEx(std::move(container), buffer->size(), error); +} + +StreamPtr MemoryStream::CreateEx( + std::unique_ptr container, + size_t stream_position, + ErrorPtr* error) { + ignore_result(error); // Unused. + return StreamPtr{new MemoryStream(std::move(container), stream_position)}; +} + +bool MemoryStream::IsOpen() const { return container_ != nullptr; } +bool MemoryStream::CanRead() const { return IsOpen(); } + +bool MemoryStream::CanWrite() const { + return IsOpen() && !container_->IsReadOnly(); +} + +bool MemoryStream::CanSeek() const { return IsOpen(); } +bool MemoryStream::CanGetSize() const { return IsOpen(); } + +uint64_t MemoryStream::GetSize() const { + return IsOpen() ? container_->GetSize() : 0; +} + +bool MemoryStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { + if (!CheckContainer(error)) + return false; + return container_->Resize(size, error); +} + +uint64_t MemoryStream::GetRemainingSize() const { + uint64_t pos = GetPosition(); + uint64_t size = GetSize(); + return (pos < size) ? size - pos : 0; +} + +uint64_t MemoryStream::GetPosition() const { + return IsOpen() ? stream_position_ : 0; +} + +bool MemoryStream::Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) { + uint64_t pos = 0; + if (!CheckContainer(error) || + !stream_utils::CalculateStreamPosition(FROM_HERE, offset, whence, + stream_position_, GetSize(), &pos, + error)) { + return false; + } + if (pos > static_cast(std::numeric_limits::max())) { + // This can only be the case on 32 bit systems. + brillo::Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kInvalidParameter, + "Stream pointer position is outside allowed limits"); + return false; + } + + stream_position_ = static_cast(pos); + if (new_position) + *new_position = stream_position_; + return true; +} + +bool MemoryStream::ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) { + if (!CheckContainer(error)) + return false; + size_t read = 0; + if (!container_->Read(buffer, size_to_read, stream_position_, &read, error)) + return false; + stream_position_ += read; + *size_read = read; + if (end_of_stream) + *end_of_stream = (read == 0) && (size_to_read != 0); + return true; +} + +bool MemoryStream::WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) { + if (!CheckContainer(error)) + return false; + if (!container_->Write(buffer, size_to_write, stream_position_, size_written, + error)) { + return false; + } + stream_position_ += *size_written; + return true; +} + +bool MemoryStream::FlushBlocking(ErrorPtr* error) { + return CheckContainer(error); +} + +bool MemoryStream::CloseBlocking(ErrorPtr* error) { + ignore_result(error); // Unused. + container_.reset(); + return true; +} + +bool MemoryStream::CheckContainer(ErrorPtr* error) const { + return container_ || stream_utils::ErrorStreamClosed(FROM_HERE, error); +} + +bool MemoryStream::WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) { + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, mode)); + return true; +} + +bool MemoryStream::WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) { + if (out_mode) + *out_mode = in_mode; + return true; +} + +} // namespace brillo diff --git a/brillo/streams/memory_stream.h b/brillo/streams/memory_stream.h new file mode 100644 index 0000000..b9f5e4b --- /dev/null +++ b/brillo/streams/memory_stream.h @@ -0,0 +1,212 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_MEMORY_STREAM_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_MEMORY_STREAM_H_ + +#include +#include + +#include +#include +#include +#include +#include + +namespace brillo { + +// MemoryStream is a brillo::Stream implementation for memory buffer. A number +// of memory containers are supported, such as raw memory pointers, data stored +// in std::vector and std::string. +// MemoryStream offers support for constant read-only memory buffers as well as +// for writable buffers that can grow when needed. +// A memory stream is created by using the OpenNNN and CreateNNN factory methods +// to construct a read-only and writable streams respectively. +// The following factory methods overloads are provided: +// - OpenRef - overloads for constructing the stream on a constant read-only +// memory buffer that is not owned by the stream. The buffer +// pointer/reference must remain valid throughout the lifetime +// of the constructed stream object. The benefit of this is that +// no data copying is performed and the underlying container can +// be manipulated outside of the stream. +// - OpenCopyOf - overloads to construct a stream that copies the data from the +// memory buffer and maintains the copied data until the stream +// is closed or destroyed. This makes it possible to construct +// a read-only streams on transient data or for cases where +// it is not possible or necessary to maintain the lifetime of +// the underlying memory buffer. +// - Create - creates a new internal memory buffer that can be written to +// or read from using the stream I/O interface. +// - CreateRef - constructs a read/write stream on a reference of data +// container such as std::vector or std::string which must +// remain valid throughout the lifetime of the memory stream. +// The data already stored in the container is maintained, +// however the stream pointer is set to the beginning of the +// data when the stream is created. +// - CreateRefForAppend - similar to CreateRef except that it automatically +// positions the stream seek pointer at the end of the data, +// which makes it possible to append more data to the existing +// container. +class BRILLO_EXPORT MemoryStream : public Stream { + public: + // == Construction ========================================================== + + // Constructs a read-only stream on a generic memory buffer. The data + // pointed to by |buffer| will be copied and owned by the stream object. + static StreamPtr OpenCopyOf(const void* buffer, size_t size, ErrorPtr* error); + static StreamPtr OpenCopyOf(std::string buffer, ErrorPtr* error); + static StreamPtr OpenCopyOf(const char* buffer, ErrorPtr* error); + // Only vectors of char and uint8_t are supported. + template + inline static StreamPtr OpenCopyOf(std::vector buffer, ErrorPtr* error) { + std::unique_ptr> container{ + new data_container::ReadOnlyVectorCopy{std::move(buffer)}}; + return CreateEx(std::move(container), 0, error); + } + + // Constructs a read-only stream on a generic memory buffer which is owned + // by the caller. + // ***WARNING***: The |buffer| pointer must be valid for as long as the stream + // object is alive. The stream does not do any additional lifetime management + // for the data pointed to by |buffer| and destroying that buffer before + // the stream is closed will lead to unexpected behavior. + static StreamPtr OpenRef(const void* buffer, size_t size, ErrorPtr* error); + static StreamPtr OpenRef(const std::string& buffer, ErrorPtr* error); + static StreamPtr OpenRef(const char* buffer, ErrorPtr* error); + // Only vectors of char and uint8_t are supported. + template + inline static StreamPtr OpenRef(const std::vector& buffer, + ErrorPtr* error) { + std::unique_ptr> container{ + new data_container::ReadOnlyVectorRef{buffer}}; + return CreateEx(std::move(container), 0, error); + } + + ///------------------------------------------------------------------------ + // Creates new stream for reading/writing. This method creates an internal + // memory buffer and maintains it until the stream is closed. |reserve_size| + // parameter is a hint of the buffer size to pre-allocate. This does not + // affect the memory buffer reported size. The buffer can grow past that + // amount if needed. + static StreamPtr Create(size_t reserve_size, ErrorPtr* error); + + inline static StreamPtr Create(ErrorPtr* error) { return Create(0, error); } + + // Creates new stream for reading/writing stored in a string. The string + // |buffer| must remain valid during the lifetime of the stream. + // The stream pointer will be at the beginning of the string and the string's + // content is preserved. + static StreamPtr CreateRef(std::string* buffer, ErrorPtr* error); + + // Creates new stream for reading/writing stored in a vector. The vector + // |buffer| must remain valid during the lifetime of the stream. + // The stream pointer will be at the beginning of the data and the vector's + // content is preserved. + // Only vectors of char and uint8_t are supported. + template + static StreamPtr CreateRef(std::vector* buffer, ErrorPtr* error) { + std::unique_ptr> container{ + new data_container::VectorPtr{buffer}}; + return CreateEx(std::move(container), 0, error); + } + + // Creates new stream for reading/writing stored in a string. The string + // |buffer| must remain valid during the lifetime of the stream. + // The stream pointer will be at the end of the string and the string's + // content is preserved. + static StreamPtr CreateRefForAppend(std::string* buffer, ErrorPtr* error); + + // Creates new stream for reading/writing stored in a vector. The vector + // |buffer| must remain valid during the lifetime of the stream. + // The stream pointer will be at the end of the data and the vector's + // content is preserved. + // Only vectors of char and uint8_t are supported. + template + static StreamPtr CreateRefForAppend(std::vector* buffer, ErrorPtr* error) { + std::unique_ptr> container{ + new data_container::VectorPtr{buffer}}; + return CreateEx(std::move(container), buffer->size() * sizeof(T), error); + } + + ///------------------------------------------------------------------------ + // Generic stream creation on a data container. Takes an arbitrary |container| + // and constructs a stream using it. The container determines the traits of + // the stream (e.g. whether it is read-only, what operations are supported + // and so on). |stream_position| is the current stream pointer position at + // creation time. + static StreamPtr CreateEx( + std::unique_ptr container, + size_t stream_position, + ErrorPtr* error); + + // == Stream capabilities =================================================== + bool IsOpen() const override; + bool CanRead() const override; + bool CanWrite() const override; + bool CanSeek() const override; + bool CanGetSize() const override; + + // == Stream size operations ================================================ + uint64_t GetSize() const override; + bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; + uint64_t GetRemainingSize() const override; + + // == Seek operations ======================================================= + uint64_t GetPosition() const override; + bool Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) override; + + // == Read operations ======================================================= + bool ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) override; + + // == Write operations ====================================================== + bool WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) override; + + // == Finalizing/closing streams =========================================== + bool FlushBlocking(ErrorPtr* error) override; + bool CloseBlocking(ErrorPtr* error) override; + + // == Data availability monitoring ========================================== + bool WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) override; + + bool WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) override; + + private: + friend class MemoryStreamTest; + + // Private constructor used by MemoryStream::OpenNNNN() and + // MemoryStream::CreateNNNN() factory methods. + MemoryStream( + std::unique_ptr container, + size_t stream_position); + + // Checks if the stream has a valid container. + bool CheckContainer(ErrorPtr* error) const; + + // Data container the stream is using to write and/or read data. + std::unique_ptr container_; + + // The current stream pointer position. + size_t stream_position_{0}; + + DISALLOW_COPY_AND_ASSIGN(MemoryStream); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_MEMORY_STREAM_H_ diff --git a/brillo/streams/memory_stream_unittest.cc b/brillo/streams/memory_stream_unittest.cc new file mode 100644 index 0000000..75278f7 --- /dev/null +++ b/brillo/streams/memory_stream_unittest.cc @@ -0,0 +1,382 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; +using testing::_; + +namespace brillo { + +namespace { + +int ReadByte(Stream* stream, brillo::ErrorPtr* error) { + uint8_t byte = 0; + return stream->ReadAllBlocking(&byte, sizeof(byte), error) ? byte : -1; +} + +class MockMemoryContainer : public data_container::DataContainerInterface { + public: + MockMemoryContainer() = default; + + MOCK_METHOD5(Read, bool(void*, size_t, size_t, size_t*, ErrorPtr*)); + MOCK_METHOD5(Write, bool(const void*, size_t, size_t, size_t*, ErrorPtr*)); + MOCK_METHOD2(Resize, bool(size_t, ErrorPtr*)); + MOCK_CONST_METHOD0(GetSize, size_t()); + MOCK_CONST_METHOD0(IsReadOnly, bool()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockMemoryContainer); +}; + +} // anonymous namespace + +class MemoryStreamTest : public testing::Test { + public: + void SetUp() override { + std::unique_ptr container{new MockMemoryContainer{}}; + stream_.reset(new MemoryStream{std::move(container), 0}); + } + + MockMemoryContainer& container_mock() { + return *static_cast(stream_->container_.get()); + } + + inline static void* IntToPtr(int addr) { + return reinterpret_cast(addr); + } + + inline static const void* IntToConstPtr(int addr) { + return reinterpret_cast(addr); + } + + std::unique_ptr stream_; + // Dummy buffer pointer values to make sure that input pointer values + // are delegated to the stream interface without a change. + void* const test_read_buffer_ = IntToPtr(12345); + const void* const test_write_buffer_ = IntToConstPtr(67890); + // We limit the size of memory streams to be the maximum size of either of + // size_t (on 32 bit platforms) or the size of signed 64 bit integer. + const size_t kSizeMax = + std::min(std::numeric_limits::max(), + std::numeric_limits::max()); +}; + +TEST_F(MemoryStreamTest, CanRead) { + EXPECT_TRUE(stream_->CanRead()); +} + +TEST_F(MemoryStreamTest, CanWrite) { + EXPECT_CALL(container_mock(), IsReadOnly()) + .WillOnce(Return(true)) + .WillOnce(Return(false)); + + EXPECT_FALSE(stream_->CanWrite()); + EXPECT_TRUE(stream_->CanWrite()); +} + +TEST_F(MemoryStreamTest, CanSeek) { + EXPECT_TRUE(stream_->CanSeek()); +} + +TEST_F(MemoryStreamTest, GetSize) { + EXPECT_CALL(container_mock(), GetSize()) + .WillOnce(Return(0)) + .WillOnce(Return(1234)) + .WillOnce(Return(kSizeMax)); + + EXPECT_EQ(0, stream_->GetSize()); + EXPECT_EQ(1234, stream_->GetSize()); + EXPECT_EQ(kSizeMax, stream_->GetSize()); +} + +TEST_F(MemoryStreamTest, SetSizeBlocking) { + EXPECT_CALL(container_mock(), Resize(0, _)).WillOnce(Return(true)); + + ErrorPtr error; + EXPECT_TRUE(stream_->SetSizeBlocking(0, &error)); + EXPECT_EQ(nullptr, error.get()); + + EXPECT_CALL(container_mock(), Resize(kSizeMax, nullptr)) + .WillOnce(Return(true)); + + EXPECT_TRUE(stream_->SetSizeBlocking(kSizeMax, nullptr)); +} + +TEST_F(MemoryStreamTest, SeekAndGetPosition) { + EXPECT_EQ(0, stream_->GetPosition()); + + EXPECT_CALL(container_mock(), GetSize()).WillRepeatedly(Return(200)); + + ErrorPtr error; + uint64_t new_pos = 0; + EXPECT_TRUE(stream_->Seek(2, Stream::Whence::FROM_BEGIN, &new_pos, &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_EQ(2, new_pos); + EXPECT_EQ(2, stream_->GetPosition()); + EXPECT_TRUE(stream_->Seek(2, Stream::Whence::FROM_CURRENT, &new_pos, &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_EQ(4, new_pos); + EXPECT_EQ(4, stream_->GetPosition()); + + EXPECT_TRUE(stream_->Seek(-2, Stream::Whence::FROM_END, nullptr, nullptr)); + EXPECT_EQ(198, stream_->GetPosition()); + + EXPECT_CALL(container_mock(), GetSize()).WillOnce(Return(kSizeMax)); + EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_END, nullptr, nullptr)); + EXPECT_EQ(kSizeMax, stream_->GetPosition()); +} + +TEST_F(MemoryStreamTest, ReadNonBlocking) { + size_t read = 0; + bool eos = false; + + EXPECT_CALL(container_mock(), Read(test_read_buffer_, 10, 0, _, nullptr)) + .WillOnce(DoAll(SetArgPointee<3>(5), Return(true))); + + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 10, &read, &eos, + nullptr)); + EXPECT_EQ(5, read); + EXPECT_EQ(5, stream_->GetPosition()); + EXPECT_FALSE(eos); + + EXPECT_CALL(container_mock(), Read(test_read_buffer_, 100, 5, _, nullptr)) + .WillOnce(DoAll(SetArgPointee<3>(100), Return(true))); + + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &read, &eos, + nullptr)); + EXPECT_EQ(100, read); + EXPECT_EQ(105, stream_->GetPosition()); + EXPECT_FALSE(eos); + + EXPECT_CALL(container_mock(), Read(test_read_buffer_, 10, 105, _, nullptr)) + .WillOnce(DoAll(SetArgPointee<3>(0), Return(true))); + + EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 10, &read, &eos, + nullptr)); + EXPECT_EQ(0, read); + EXPECT_EQ(105, stream_->GetPosition()); + EXPECT_TRUE(eos); +} + +TEST_F(MemoryStreamTest, WriteNonBlocking) { + size_t written = 0; + + EXPECT_CALL(container_mock(), Write(test_write_buffer_, 10, 0, _, nullptr)) + .WillOnce(DoAll(SetArgPointee<3>(5), Return(true))); + + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 10, &written, + nullptr)); + EXPECT_EQ(5, written); + EXPECT_EQ(5, stream_->GetPosition()); + + EXPECT_CALL(container_mock(), Write(test_write_buffer_, 100, 5, _, nullptr)) + .WillOnce(DoAll(SetArgPointee<3>(100), Return(true))); + + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &written, + nullptr)); + EXPECT_EQ(100, written); + EXPECT_EQ(105, stream_->GetPosition()); + + EXPECT_CALL(container_mock(), Write(test_write_buffer_, 10, 105, _, nullptr)) + .WillOnce(DoAll(SetArgPointee<3>(10), Return(true))); + + EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 10, &written, + nullptr)); + EXPECT_EQ(115, stream_->GetPosition()); +} + +////////////////////////////////////////////////////////////////////////////// +// Factory method tests. +TEST(MemoryStream, OpenBinary) { + char buffer[] = {1, 2, 3}; + StreamPtr stream = MemoryStream::OpenRef(buffer, sizeof(buffer), nullptr); + buffer[0] = 5; + EXPECT_EQ(3, stream->GetSize()); + EXPECT_EQ(5, ReadByte(stream.get(), nullptr)); + EXPECT_EQ(2, ReadByte(stream.get(), nullptr)); + EXPECT_EQ(3, ReadByte(stream.get(), nullptr)); + brillo::ErrorPtr error; + EXPECT_EQ(-1, ReadByte(stream.get(), &error)); + EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); + EXPECT_EQ("Reading past the end of stream", error->GetMessage()); +} + +TEST(MemoryStream, OpenBinaryCopy) { + char buffer[] = {1, 2, 3}; + StreamPtr stream = MemoryStream::OpenCopyOf(buffer, sizeof(buffer), nullptr); + buffer[0] = 5; + EXPECT_EQ(3, stream->GetSize()); + EXPECT_EQ(1, ReadByte(stream.get(), nullptr)); + EXPECT_EQ(2, ReadByte(stream.get(), nullptr)); + EXPECT_EQ(3, ReadByte(stream.get(), nullptr)); + brillo::ErrorPtr error; + EXPECT_EQ(-1, ReadByte(stream.get(), &error)); + EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); + EXPECT_EQ("Reading past the end of stream", error->GetMessage()); +} + +TEST(MemoryStream, OpenString) { + std::string str("abcd"); + StreamPtr stream = MemoryStream::OpenRef(str, nullptr); + str[0] = 'A'; + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ('A', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); + EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); +} + +TEST(MemoryStream, OpenStringCopy) { + std::string str("abcd"); + StreamPtr stream = MemoryStream::OpenCopyOf(str, nullptr); + str[0] = 'A'; + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ('a', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); + EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); +} + +TEST(MemoryStream, OpenCharBuf) { + char str[] = "abcd"; + StreamPtr stream = MemoryStream::OpenRef(str, nullptr); + str[0] = 'A'; + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ('A', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); + EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); +} + +TEST(MemoryStream, OpenCharBufCopy) { + char str[] = "abcd"; + StreamPtr stream = MemoryStream::OpenCopyOf(str, nullptr); + str[0] = 'A'; + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ('a', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); + EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); +} + +TEST(MemoryStream, OpenVector) { + std::vector data = {'a', 'b', 'c', 'd'}; + StreamPtr stream = MemoryStream::OpenRef(data, nullptr); + data[0] = 'A'; + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(4, stream->GetRemainingSize()); + EXPECT_EQ('A', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); + EXPECT_EQ(4, stream->GetPosition()); + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ(0, stream->GetRemainingSize()); +} + +TEST(MemoryStream, OpenVectorCopy) { + std::vector data = {'a', 'b', 'c', 'd'}; + StreamPtr stream = MemoryStream::OpenCopyOf(data, nullptr); + data[0] = 'A'; + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(4, stream->GetRemainingSize()); + EXPECT_EQ('a', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); + EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); + EXPECT_EQ(4, stream->GetPosition()); + EXPECT_EQ(4, stream->GetSize()); + EXPECT_EQ(0, stream->GetRemainingSize()); +} + +TEST(MemoryStream, CreateVector) { + std::vector buffer; + StreamPtr stream = MemoryStream::CreateRef(&buffer, nullptr); + EXPECT_TRUE(buffer.empty()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(0, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + + buffer.resize(5); + std::iota(buffer.begin(), buffer.end(), 0); + stream = MemoryStream::CreateRef(&buffer, nullptr); + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(5, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + + stream = MemoryStream::CreateRefForAppend(&buffer, nullptr); + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(5, stream->GetPosition()); + EXPECT_EQ(5, stream->GetSize()); + EXPECT_TRUE(stream->WriteAllBlocking("abcde", 5, nullptr)); + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(10, stream->GetPosition()); + EXPECT_EQ(10, stream->GetSize()); + EXPECT_TRUE(stream->SetPosition(0, nullptr)); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(10, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + + EXPECT_EQ(10, buffer.size()); + EXPECT_EQ((std::vector{0, 1, 2, 3, 4, 'a', 'b', 'c', 'd', 'e'}), + buffer); + + stream = MemoryStream::OpenRef(buffer, nullptr); + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(10, stream->GetSize()); +} + +TEST(MemoryStream, CreateString) { + std::string buffer; + StreamPtr stream = MemoryStream::CreateRef(&buffer, nullptr); + EXPECT_TRUE(buffer.empty()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(0, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + + buffer = "abc"; + stream = MemoryStream::CreateRef(&buffer, nullptr); + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(3, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + + stream = MemoryStream::CreateRefForAppend(&buffer, nullptr); + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(3, stream->GetPosition()); + EXPECT_EQ(3, stream->GetSize()); + EXPECT_TRUE(stream->WriteAllBlocking("d_1234", 6, nullptr)); + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(9, stream->GetPosition()); + EXPECT_EQ(9, stream->GetSize()); + EXPECT_TRUE(stream->SetPosition(0, nullptr)); + EXPECT_EQ(0, stream->GetPosition()); + EXPECT_EQ(9, stream->GetSize()); + EXPECT_TRUE(stream->CloseBlocking(nullptr)); + EXPECT_EQ(9, buffer.size()); + EXPECT_EQ("abcd_1234", buffer); +} + +} // namespace brillo diff --git a/brillo/streams/mock_stream.h b/brillo/streams/mock_stream.h new file mode 100644 index 0000000..4289cc0 --- /dev/null +++ b/brillo/streams/mock_stream.h @@ -0,0 +1,75 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_MOCK_STREAM_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_MOCK_STREAM_H_ + +#include + +#include + +namespace brillo { + +// Mock Stream implementation for testing. +class MockStream : public Stream { + public: + MockStream() = default; + + MOCK_CONST_METHOD0(IsOpen, bool()); + MOCK_CONST_METHOD0(CanRead, bool()); + MOCK_CONST_METHOD0(CanWrite, bool()); + MOCK_CONST_METHOD0(CanSeek, bool()); + MOCK_CONST_METHOD0(CanGetSize, bool()); + + MOCK_CONST_METHOD0(GetSize, uint64_t()); + MOCK_METHOD2(SetSizeBlocking, bool(uint64_t, ErrorPtr*)); + MOCK_CONST_METHOD0(GetRemainingSize, uint64_t()); + + MOCK_CONST_METHOD0(GetPosition, uint64_t()); + MOCK_METHOD4(Seek, bool(int64_t, Whence, uint64_t*, ErrorPtr*)); + + MOCK_METHOD5(ReadAsync, bool(void*, + size_t, + const base::Callback&, + const ErrorCallback&, + ErrorPtr*)); + MOCK_METHOD5(ReadAllAsync, bool(void*, + size_t, + const base::Closure&, + const ErrorCallback&, + ErrorPtr*)); + MOCK_METHOD5(ReadNonBlocking, bool(void*, size_t, size_t*, bool*, ErrorPtr*)); + MOCK_METHOD4(ReadBlocking, bool(void*, size_t, size_t*, ErrorPtr*)); + MOCK_METHOD3(ReadAllBlocking, bool(void*, size_t, ErrorPtr*)); + + MOCK_METHOD5(WriteAsync, bool(const void*, + size_t, + const base::Callback&, + const ErrorCallback&, + ErrorPtr*)); + MOCK_METHOD5(WriteAllAsync, bool(const void*, + size_t, + const base::Closure&, + const ErrorCallback&, + ErrorPtr*)); + MOCK_METHOD4(WriteNonBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); + MOCK_METHOD4(WriteBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); + MOCK_METHOD3(WriteAllBlocking, bool(const void*, size_t, ErrorPtr*)); + + MOCK_METHOD1(FlushBlocking, bool(ErrorPtr*)); + MOCK_METHOD1(CloseBlocking, bool(ErrorPtr*)); + + MOCK_METHOD3(WaitForData, bool(AccessMode, + const base::Callback&, + ErrorPtr*)); + MOCK_METHOD4(WaitForDataBlocking, + bool(AccessMode, base::TimeDelta, AccessMode*, ErrorPtr*)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockStream); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_MOCK_STREAM_H_ diff --git a/brillo/streams/openssl_stream_bio.cc b/brillo/streams/openssl_stream_bio.cc new file mode 100644 index 0000000..54a5536 --- /dev/null +++ b/brillo/streams/openssl_stream_bio.cc @@ -0,0 +1,101 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include + +namespace brillo { + +namespace { + +// Internal functions for implementing OpenSSL BIO on brillo::Stream. +int stream_write(BIO* bio, const char* buf, int size) { + brillo::Stream* stream = static_cast(bio->ptr); + size_t written = 0; + BIO_clear_retry_flags(bio); + if (!stream->WriteNonBlocking(buf, size, &written, nullptr)) + return -1; + + if (written == 0) { + // Socket's output buffer is full, try again later. + BIO_set_retry_write(bio); + return -1; + } + return base::checked_cast(written); +} + +int stream_read(BIO* bio, char* buf, int size) { + brillo::Stream* stream = static_cast(bio->ptr); + size_t read = 0; + BIO_clear_retry_flags(bio); + bool eos = false; + if (!stream->ReadNonBlocking(buf, size, &read, &eos, nullptr)) + return -1; + + if (read == 0 && !eos) { + // If no data is available on the socket and it is still not closed, + // ask OpenSSL to try again later. + BIO_set_retry_read(bio); + return -1; + } + return base::checked_cast(read); +} + +// NOLINTNEXTLINE(runtime/int) +long stream_ctrl(BIO* bio, int cmd, long num, void* ptr) { + if (cmd == BIO_CTRL_FLUSH) { + brillo::Stream* stream = static_cast(bio->ptr); + return stream->FlushBlocking(nullptr) ? 1 : 0; + } + return 0; +} + +int stream_new(BIO* bio) { + bio->shutdown = 0; // By default do not close underlying stream on shutdown. + bio->init = 0; + bio->num = -1; // not used. + return 1; +} + +int stream_free(BIO* bio) { + if (!bio) + return 0; + + if (bio->init) { + bio->ptr = nullptr; + bio->init = 0; + } + return 1; +} + +// BIO_METHOD structure describing the BIO built on top of brillo::Stream. +BIO_METHOD stream_method = { + 0x7F | BIO_TYPE_SOURCE_SINK, // type: 0x7F is an arbitrary unused type ID. + "stream", // name + stream_write, // write function + stream_read, // read function + nullptr, // puts function, not implemented + nullptr, // gets function, not implemented + stream_ctrl, // control function + stream_new, // creation + stream_free, // free + nullptr, // callback function, not used +}; + +} // anonymous namespace + +BIO* BIO_new_stream(brillo::Stream* stream) { + BIO* bio = BIO_new(&stream_method); + if (bio) { + bio->ptr = stream; + bio->init = 1; + } + return bio; +} + +} // namespace brillo diff --git a/brillo/streams/openssl_stream_bio.h b/brillo/streams/openssl_stream_bio.h new file mode 100644 index 0000000..3cb0989 --- /dev/null +++ b/brillo/streams/openssl_stream_bio.h @@ -0,0 +1,27 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_OPENSSL_STREAM_BIO_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_OPENSSL_STREAM_BIO_H_ + +#include + +// Forward-declare BIO as an alias to OpenSSL's internal bio_st structure. +using BIO = struct bio_st; + +namespace brillo { + +class Stream; + +// Creates a new BIO that uses the brillo::Stream as the back-end storage. +// The created BIO does *NOT* own the |stream| and the stream must out-live +// the BIO. +// At the moment, only BIO_read and BIO_write operations are supported as well +// as BIO_flush. More functionality could be added to this when/if needed. +// The returned BIO performs *NON-BLOCKING* IO on the underlying stream. +BRILLO_EXPORT BIO* BIO_new_stream(brillo::Stream* stream); + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_OPENSSL_STREAM_BIO_H_ diff --git a/brillo/streams/openssl_stream_bio_unittests.cc b/brillo/streams/openssl_stream_bio_unittests.cc new file mode 100644 index 0000000..a80710d --- /dev/null +++ b/brillo/streams/openssl_stream_bio_unittests.cc @@ -0,0 +1,125 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include + +#include +#include +#include + +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; +using testing::StrictMock; +using testing::_; + +namespace brillo { + +class StreamBIOTest : public testing::Test { + public: + void SetUp() override { + stream_.reset(new StrictMock{}); + bio_ = BIO_new_stream(stream_.get()); + } + + void TearDown() override { + BIO_free(bio_); + bio_ = nullptr; + stream_.reset(); + } + + std::unique_ptr> stream_; + BIO* bio_{nullptr}; +}; + +TEST_F(StreamBIOTest, ReadFull) { + char buffer[10]; + EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(10), + SetArgPointee<3>(false), + Return(true))); + EXPECT_EQ(10, BIO_read(bio_, buffer, sizeof(buffer))); +} + +TEST_F(StreamBIOTest, ReadPartial) { + char buffer[10]; + EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), + SetArgPointee<3>(false), + Return(true))); + EXPECT_EQ(3, BIO_read(bio_, buffer, sizeof(buffer))); +} + +TEST_F(StreamBIOTest, ReadWouldBlock) { + char buffer[10]; + EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(false), + Return(true))); + EXPECT_EQ(-1, BIO_read(bio_, buffer, sizeof(buffer))); + EXPECT_TRUE(BIO_should_retry(bio_)); +} + +TEST_F(StreamBIOTest, ReadEndOfStream) { + char buffer[10]; + EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(true), + Return(true))); + EXPECT_EQ(0, BIO_read(bio_, buffer, sizeof(buffer))); + EXPECT_FALSE(BIO_should_retry(bio_)); +} + +TEST_F(StreamBIOTest, ReadError) { + char buffer[10]; + EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) + .WillOnce(Return(false)); + EXPECT_EQ(-1, BIO_read(bio_, buffer, sizeof(buffer))); + EXPECT_FALSE(BIO_should_retry(bio_)); +} + +TEST_F(StreamBIOTest, WriteFull) { + char buffer[10] = {}; + EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(10), Return(true))); + EXPECT_EQ(10, BIO_write(bio_, buffer, sizeof(buffer))); +} + +TEST_F(StreamBIOTest, WritePartial) { + char buffer[10] = {}; + EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), Return(true))); + EXPECT_EQ(3, BIO_write(bio_, buffer, sizeof(buffer))); +} + +TEST_F(StreamBIOTest, WriteWouldBlock) { + char buffer[10] = {}; + EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_EQ(-1, BIO_write(bio_, buffer, sizeof(buffer))); + EXPECT_TRUE(BIO_should_retry(bio_)); +} + +TEST_F(StreamBIOTest, WriteError) { + char buffer[10] = {}; + EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) + .WillOnce(Return(false)); + EXPECT_EQ(-1, BIO_write(bio_, buffer, sizeof(buffer))); + EXPECT_FALSE(BIO_should_retry(bio_)); +} + +TEST_F(StreamBIOTest, FlushSuccess) { + EXPECT_CALL(*stream_, FlushBlocking(_)).WillOnce(Return(true)); + EXPECT_EQ(1, BIO_flush(bio_)); +} + +TEST_F(StreamBIOTest, FlushError) { + EXPECT_CALL(*stream_, FlushBlocking(_)).WillOnce(Return(false)); + EXPECT_EQ(0, BIO_flush(bio_)); +} + +} // namespace brillo diff --git a/brillo/streams/stream.cc b/brillo/streams/stream.cc new file mode 100644 index 0000000..cd9b0a8 --- /dev/null +++ b/brillo/streams/stream.cc @@ -0,0 +1,392 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include + +namespace brillo { + +bool Stream::TruncateBlocking(ErrorPtr* error) { + return SetSizeBlocking(GetPosition(), error); +} + +bool Stream::SetPosition(uint64_t position, ErrorPtr* error) { + if (!stream_utils::CheckInt64Overflow(FROM_HERE, position, 0, error)) + return false; + return Seek(position, Whence::FROM_BEGIN, nullptr, error); +} + +bool Stream::ReadAsync(void* buffer, + size_t size_to_read, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error) { + if (is_async_read_pending_) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kOperationNotSupported, + "Another asynchronous operation is still pending"); + return false; + } + + auto callback = base::Bind(&Stream::IgnoreEOSCallback, success_callback); + // If we can read some data right away non-blocking we should still run the + // callback from the main loop, so we pass true here for force_async_callback. + return ReadAsyncImpl(buffer, size_to_read, callback, error_callback, error, + true); +} + +bool Stream::ReadAllAsync(void* buffer, + size_t size_to_read, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error) { + if (is_async_read_pending_) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kOperationNotSupported, + "Another asynchronous operation is still pending"); + return false; + } + + auto callback = base::Bind(&Stream::ReadAllAsyncCallback, + weak_ptr_factory_.GetWeakPtr(), buffer, + size_to_read, success_callback, error_callback); + return ReadAsyncImpl(buffer, size_to_read, callback, error_callback, error, + true); +} + +bool Stream::ReadBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + ErrorPtr* error) { + for (;;) { + bool eos = false; + if (!ReadNonBlocking(buffer, size_to_read, size_read, &eos, error)) + return false; + + if (*size_read > 0 || eos) + break; + + if (!WaitForDataBlocking(AccessMode::READ, base::TimeDelta::Max(), nullptr, + error)) { + return false; + } + } + return true; +} + +bool Stream::ReadAllBlocking(void* buffer, + size_t size_to_read, + ErrorPtr* error) { + while (size_to_read > 0) { + size_t size_read = 0; + if (!ReadBlocking(buffer, size_to_read, &size_read, error)) + return false; + + if (size_read == 0) + return stream_utils::ErrorReadPastEndOfStream(FROM_HERE, error); + + size_to_read -= size_read; + buffer = AdvancePointer(buffer, size_read); + } + return true; +} + +bool Stream::WriteAsync(const void* buffer, + size_t size_to_write, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error) { + if (is_async_write_pending_) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kOperationNotSupported, + "Another asynchronous operation is still pending"); + return false; + } + // If we can read some data right away non-blocking we should still run the + // callback from the main loop, so we pass true here for force_async_callback. + return WriteAsyncImpl(buffer, size_to_write, success_callback, error_callback, + error, true); +} + +bool Stream::WriteAllAsync(const void* buffer, + size_t size_to_write, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error) { + if (is_async_write_pending_) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kOperationNotSupported, + "Another asynchronous operation is still pending"); + return false; + } + + auto callback = base::Bind(&Stream::WriteAllAsyncCallback, + weak_ptr_factory_.GetWeakPtr(), buffer, + size_to_write, success_callback, error_callback); + return WriteAsyncImpl(buffer, size_to_write, callback, error_callback, error, + true); +} + +bool Stream::WriteBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) { + for (;;) { + if (!WriteNonBlocking(buffer, size_to_write, size_written, error)) + return false; + + if (*size_written > 0 || size_to_write == 0) + break; + + if (!WaitForDataBlocking(AccessMode::WRITE, base::TimeDelta::Max(), nullptr, + error)) { + return false; + } + } + return true; +} + +bool Stream::WriteAllBlocking(const void* buffer, + size_t size_to_write, + ErrorPtr* error) { + while (size_to_write > 0) { + size_t size_written = 0; + if (!WriteBlocking(buffer, size_to_write, &size_written, error)) + return false; + + if (size_written == 0) { + Error::AddTo(error, FROM_HERE, errors::stream::kDomain, + errors::stream::kPartialData, + "Failed to write all the data"); + return false; + } + size_to_write -= size_written; + buffer = AdvancePointer(buffer, size_written); + } + return true; +} + +bool Stream::FlushAsync(const base::Closure& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* /* error */) { + auto callback = base::Bind(&Stream::FlushAsyncCallback, + weak_ptr_factory_.GetWeakPtr(), + success_callback, error_callback); + MessageLoop::current()->PostTask(FROM_HERE, callback); + return true; +} + +void Stream::IgnoreEOSCallback( + const base::Callback& success_callback, + size_t bytes, + bool eos) { + success_callback.Run(bytes); +} + +bool Stream::ReadAsyncImpl( + void* buffer, + size_t size_to_read, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error, + bool force_async_callback) { + CHECK(!is_async_read_pending_); + // We set this value to true early in the function so calling others will + // prevent us from calling WaitForData() to make calls to + // ReadAsync() fail while we run WaitForData(). + is_async_read_pending_ = true; + + size_t read = 0; + bool eos = false; + if (!ReadNonBlocking(buffer, size_to_read, &read, &eos, error)) + return false; + + if (read > 0 || eos) { + if (force_async_callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&Stream::OnReadAsyncDone, weak_ptr_factory_.GetWeakPtr(), + success_callback, read, eos)); + } else { + is_async_read_pending_ = false; + success_callback.Run(read, eos); + } + return true; + } + + is_async_read_pending_ = WaitForData( + AccessMode::READ, + base::Bind(&Stream::OnReadAvailable, weak_ptr_factory_.GetWeakPtr(), + buffer, size_to_read, success_callback, error_callback), + error); + return is_async_read_pending_; +} + +void Stream::OnReadAsyncDone( + const base::Callback& success_callback, + size_t bytes_read, + bool eos) { + is_async_read_pending_ = false; + success_callback.Run(bytes_read, eos); +} + +void Stream::OnReadAvailable( + void* buffer, + size_t size_to_read, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + AccessMode mode) { + CHECK(stream_utils::IsReadAccessMode(mode)); + CHECK(is_async_read_pending_); + is_async_read_pending_ = false; + ErrorPtr error; + // Just reschedule the read operation but don't need to run the callback from + // the main loop since we are already running on a callback. + if (!ReadAsyncImpl(buffer, size_to_read, success_callback, error_callback, + &error, false)) { + error_callback.Run(error.get()); + } +} + +bool Stream::WriteAsyncImpl( + const void* buffer, + size_t size_to_write, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error, + bool force_async_callback) { + CHECK(!is_async_write_pending_); + // We set this value to true early in the function so calling others will + // prevent us from calling WaitForData() to make calls to + // ReadAsync() fail while we run WaitForData(). + is_async_write_pending_ = true; + + size_t written = 0; + if (!WriteNonBlocking(buffer, size_to_write, &written, error)) + return false; + + if (written > 0) { + if (force_async_callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&Stream::OnWriteAsyncDone, weak_ptr_factory_.GetWeakPtr(), + success_callback, written)); + } else { + is_async_write_pending_ = false; + success_callback.Run(written); + } + return true; + } + is_async_write_pending_ = WaitForData( + AccessMode::WRITE, + base::Bind(&Stream::OnWriteAvailable, weak_ptr_factory_.GetWeakPtr(), + buffer, size_to_write, success_callback, error_callback), + error); + return is_async_write_pending_; +} + +void Stream::OnWriteAsyncDone( + const base::Callback& success_callback, + size_t size_written) { + is_async_write_pending_ = false; + success_callback.Run(size_written); +} + +void Stream::OnWriteAvailable( + const void* buffer, + size_t size, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + AccessMode mode) { + CHECK(stream_utils::IsWriteAccessMode(mode)); + CHECK(is_async_write_pending_); + is_async_write_pending_ = false; + ErrorPtr error; + // Just reschedule the read operation but don't need to run the callback from + // the main loop since we are already running on a callback. + if (!WriteAsyncImpl(buffer, size, success_callback, error_callback, &error, + false)) { + error_callback.Run(error.get()); + } +} + +void Stream::ReadAllAsyncCallback(void* buffer, + size_t size_to_read, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + size_t size_read, + bool eos) { + ErrorPtr error; + size_to_read -= size_read; + if (size_to_read != 0 && eos) { + stream_utils::ErrorReadPastEndOfStream(FROM_HERE, &error); + error_callback.Run(error.get()); + return; + } + + if (size_to_read) { + buffer = AdvancePointer(buffer, size_read); + auto callback = base::Bind(&Stream::ReadAllAsyncCallback, + weak_ptr_factory_.GetWeakPtr(), buffer, + size_to_read, success_callback, error_callback); + if (!ReadAsyncImpl(buffer, size_to_read, callback, error_callback, &error, + false)) { + error_callback.Run(error.get()); + } + } else { + success_callback.Run(); + } +} + +void Stream::WriteAllAsyncCallback(const void* buffer, + size_t size_to_write, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + size_t size_written) { + ErrorPtr error; + if (size_to_write != 0 && size_written == 0) { + Error::AddTo(&error, FROM_HERE, errors::stream::kDomain, + errors::stream::kPartialData, "Failed to write all the data"); + error_callback.Run(error.get()); + return; + } + size_to_write -= size_written; + if (size_to_write) { + buffer = AdvancePointer(buffer, size_written); + auto callback = base::Bind(&Stream::WriteAllAsyncCallback, + weak_ptr_factory_.GetWeakPtr(), buffer, + size_to_write, success_callback, error_callback); + if (!WriteAsyncImpl(buffer, size_to_write, callback, error_callback, &error, + false)) { + error_callback.Run(error.get()); + } + } else { + success_callback.Run(); + } +} + +void Stream::FlushAsyncCallback(const base::Closure& success_callback, + const ErrorCallback& error_callback) { + ErrorPtr error; + if (FlushBlocking(&error)) { + success_callback.Run(); + } else { + error_callback.Run(error.get()); + } +} + +void Stream::CancelPendingAsyncOperations() { + weak_ptr_factory_.InvalidateWeakPtrs(); + is_async_read_pending_ = false; + is_async_write_pending_ = false; +} + +} // namespace brillo diff --git a/brillo/streams/stream.h b/brillo/streams/stream.h new file mode 100644 index 0000000..6614cb3 --- /dev/null +++ b/brillo/streams/stream.h @@ -0,0 +1,506 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_STREAM_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_STREAM_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace brillo { + +// Stream is a base class that specific stream storage implementations must +// derive from to provide I/O facilities. +// The stream class provides general streaming I/O primitives to read, write and +// seek within a stream. It has methods for asynchronous (callback-based) as +// well as synchronous (both blocking and non-blocking) operations. +// The Stream class is abstract and cannot be created by itself. +// In order to construct a stream, you must use one of the derived classes' +// factory methods which return a stream smart pointer (StreamPtr): +// +// StreamPtr input_stream = FileStream::Open(path, AccessMode::READ); +// StreamPtr output_stream = MemoryStream::Create(); +// uint8_t buf[1000]; +// size_t read = 0; +// while (input_stream->ReadBlocking(buf, sizeof(buf), &read, nullptr)) { +// if (read == 0) break; +// output_stream->WriteAllBlocking(buf, read, nullptr); +// } +// +// NOTE ABOUT ASYNCHRONOUS OPERATIONS: Asynchronous I/O relies on a MessageLoop +// instance to be present on the current thread. Using Stream::ReadAsync(), +// Stream::WriteAsync() and similar will call MessageLoop::current() to access +// the current message loop and abort if there isn't one for the current thread. +// Also, only one outstanding asynchronous operation of particular kind (reading +// or writing) at a time is supported. Trying to call ReadAsync() while another +// asynchronous read operation is pending will fail with an error +// ("operation_not_supported"). +// +// NOTE ABOUT READING FROM/WRITING TO STREAMS: In many cases underlying streams +// use buffered I/O. Using all read/write methods other than ReadAllAsync(), +// ReadAllBlocking(), WriteAllAsync(), WriteAllBlocking() will return +// immediately if there is any data available in the underlying buffer. That is, +// trying to read 1000 bytes while the internal buffer contains only 100 will +// return immediately with just those 100 bytes and no blocking or other I/O +// traffic will be incurred. This guarantee is important for efficient and +// correct implementation of duplex communication over pipes and sockets. +// +// NOTE TO IMPLEMENTERS: When creating new stream types, you must derive +// from this class and provide the implementation for its pure virtual methods. +// For operations that do not apply to your stream, make sure the corresponding +// methods return "false" and set the error to "operation_not_supported". +// You should use stream_utils::ErrorOperationNotSupported() for this. Also +// Make sure the stream capabilities functions like CanRead(), etc return +// correct values: +// +// bool MyReadOnlyStream::CanRead() const { return true; } +// bool MyReadOnlyStream::CanWrite() const { return false; } +// bool MyReadOnlyStream::WriteBlocking(const void* buffer, +// size_t size_to_write, +// size_t* size_written, +// ErrorPtr* error) { +// return stream_utils::ErrorOperationNotSupported(error); +// } +// +// The class should also provide a static factory methods to create/open +// a new stream: +// +// static StreamPtr MyReadOnlyStream::Open(..., ErrorPtr* error) { +// auto my_stream = std::make_unique(...); +// if (!my_stream->Initialize(..., error)) +// my_stream.reset(); +// } +// return my_stream; +// } +// +class BRILLO_EXPORT Stream { + public: + // When seeking in streams, whence specifies the origin of the seek operation. + enum class Whence { FROM_BEGIN, FROM_CURRENT, FROM_END }; + // Stream access mode for open operations (used in derived classes). + enum class AccessMode { READ, WRITE, READ_WRITE }; + + // Standard error callback for asynchronous operations. + using ErrorCallback = base::Callback; + + virtual ~Stream() = default; + + // == Stream capabilities =================================================== + + // Returns true while stream is open. Closing the last reference to the stream + // will make this method return false. + virtual bool IsOpen() const = 0; + + // Called to determine if read operations are supported on the stream (stream + // is readable). This method does not check if there is actually any data to + // read, only the fact that the stream is open in read mode and can be read + // from in general. + // If CanRead() returns false, it is guaranteed that the stream can't be + // read from. However, if it returns true, there is no guarantee that the + // subsequent read operation will actually succeed (for example, the stream + // position could be at the end of the data stream, or the access mode of + // the stream is unknown beforehand). + virtual bool CanRead() const = 0; + + // Called to determine if write operations are supported on the stream (stream + // is writable). + // If CanWrite() returns false, it is guaranteed that the stream can't be + // written to. However, if it returns true, the subsequent write operation + // is not guaranteed to succeed (e.g. the output media could be out of free + // space or a transport error could occur). + virtual bool CanWrite() const = 0; + + // Called to determine if random access I/O operations are supported on + // the stream. Sequential streams should return false. + // If CanSeek() returns false, it is guaranteed that the stream can't use + // Seek(). However, if it returns true, it might be possible to seek, but this + // is not guaranteed since the actual underlying stream capabilities might + // not be known. + // Note that non-seekable streams might still maintain the current stream + // position and GetPosition method might still be used even if CanSeek() + // returns false. However SetPosition() will almost always fail in such + // a case. + virtual bool CanSeek() const = 0; + + // Called to determine if the size of the stream is known. Size of some + // sequential streams (e.g. based on pipes) is unknown beforehand, so this + // method can be used to check how reliable a call to GetSize() is. + virtual bool CanGetSize() const = 0; + + // == Stream size operations ================================================ + + // Returns the size of stream data. + // If the stream size is unavailable/unknown, it returns 0. + virtual uint64_t GetSize() const = 0; + + // Resizes the stream storage to |size|. Stream must be writable and support + // this operation. + virtual bool SetSizeBlocking(uint64_t size, ErrorPtr* error) = 0; + + // Truncates the stream at the current stream pointer. + // Calls SetSizeBlocking(GetPosition(), ...). + bool TruncateBlocking(ErrorPtr* error); + + // Returns the amount of data remaining in the stream. If the size of the + // stream is unknown, or if the stream pointer is at or past the end of the + // stream, the function returns 0. + virtual uint64_t GetRemainingSize() const = 0; + + // == Seek operations ======================================================= + + // Gets the position of the stream I/O pointer from the beginning of the + // stream. If the stream position is unavailable/unknown, it returns 0. + virtual uint64_t GetPosition() const = 0; + + // Moves the stream pointer to the specified position, relative to the + // beginning of the stream. This calls Seek(position, Whence::FROM_BEGIN), + // however it also provides proper |position| validation to ensure that + // it doesn't overflow the range of signed int64_t used by Seek. + bool SetPosition(uint64_t position, ErrorPtr* error); + + // Moves the stream pointer by |offset| bytes relative to |whence|. + // When successful, returns true and sets the new pointer position from the + // beginning of the stream to |new_position|. If |new_position| is nullptr, + // new stream position is not returned. + // On error, returns false and specifies additional details in |error| if it + // is not nullptr. + virtual bool Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) = 0; + + // == Read operations ======================================================= + + // -- Asynchronous ---------------------------------------------------------- + + // Reads up to |size_to_read| bytes from the stream asynchronously. It is not + // guaranteed that all requested data will be read. It is not an error for + // this function to read fewer bytes than requested. If the function reads + // zero bytes, it means that the end of stream is reached. + // Upon successful read, the |success_callback| will be invoked with the + // actual number of bytes read. + // If an error occurs during the asynchronous operation, the |error_callback| + // is invoked with the error details. The error object pointer passed in as a + // parameter to the |error_callback| is valid only for the duration of that + // callback. + // If this function successfully schedules an asynchronous operation, it + // returns true. If it fails immediately, it will return false and set the + // error details to |error| object and will not call the success or error + // callbacks. + // The |buffer| must be at least |size_to_read| in size and must remain + // valid for the duration of the asynchronous operation (until either + // |success_callback| or |error_callback| is called). + // Only one asynchronous operation at a time is allowed on the stream (read + // and/or write) + // Uses ReadNonBlocking() and MonitorDataAvailable(). + virtual bool ReadAsync(void* buffer, + size_t size_to_read, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error); + + // Similar to ReadAsync() operation above but reads exactly |size_to_read| + // bytes from the stream into the |buffer|. Attempt to read past the end of + // the stream is considered an error in this case and will trigger the + // |error_callback|. The rest of restrictions and conditions of ReadAsync() + // method applies to ReadAllAsync() as well. + // Uses ReadNonBlocking() and MonitorDataAvailable(). + virtual bool ReadAllAsync(void* buffer, + size_t size_to_read, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error); + + // -- Synchronous non-blocking ---------------------------------------------- + + // Reads up to |size_to_read| bytes from the stream without blocking. + // The |buffer| must be at least |size_to_read| in size. It is not an error + // for this function to return without reading all (or any) the data. + // The actual amount of data read (which could be 0 bytes) is returned in + // |size_read|. + // On error, the function returns false and specifies additional error details + // in |error|. + // If end of stream is reached or if no data is currently available to be read + // without blocking, |size_read| will contain 0 and the function will still + // return true (success). In case of end-of-stream scenario, |end_of_stream| + // will also be set to true to indicate that no more data is available. + virtual bool ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) = 0; + + // -- Synchronous blocking -------------------------------------------------- + + // Reads up to |size_to_read| bytes from the stream. This function will block + // until at least one byte is read or the end of stream is reached or until + // the stream is closed. + // The |buffer| must be at least |size_to_read| in size. It is not an error + // for this function to return without reading all the data. The actual amount + // of data read (which could be 0 bytes) is returned in |size_read|. + // On error, the function returns false and specifies additional error details + // in |error|. In this case, the state of the stream pointer is undefined, + // since some bytes might have been read successfully (and the pointer moved) + // before the error has occurred and |size_read| is not updated. + // If end of stream is reached, |size_read| will contain 0 and the function + // will still return true (success). + virtual bool ReadBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + ErrorPtr* error); + + // Reads exactly |size_to_read| bytes to |buffer|. Returns false on error + // (reading fewer than requested bytes is treated as an error as well). + // Calls ReadAllBlocking() repeatedly until all the data is read. + virtual bool ReadAllBlocking(void* buffer, + size_t size_to_read, + ErrorPtr* error); + + // == Write operations ====================================================== + + // -- Asynchronous ---------------------------------------------------------- + + // Writes up to |size_to_write| bytes from |buffer| to the stream + // asynchronously. It is not guaranteed that all requested data will be + // written. It is not an error for this function to write fewer bytes than + // requested. + // Upon successful write, the |success_callback| will be invoked with the + // actual number of bytes written. + // If an error occurs during the asynchronous operation, the |error_callback| + // is invoked with the error details. The error object pointer is valid only + // for the duration of the error callback. + // If this function successfully schedules an asynchronous operation, it + // returns true. If it fails immediately, it will return false and set the + // error details to |error| object and will not call the success or error + // callbacks. + // The |buffer| must be at least |size_to_write| in size and must remain + // valid for the duration of the asynchronous operation (until either + // |success_callback| or |error_callback| is called). + // Only one asynchronous operation at a time is allowed on the stream (read + // and/or write). + // Uses WriteNonBlocking() and MonitorDataAvailable(). + virtual bool WriteAsync(const void* buffer, + size_t size_to_write, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error); + + // Similar to WriteAsync() operation above but writes exactly |size_to_write| + // bytes from |buffet| to the stream. When all the data is written + // successfully, the |success_callback| is invoked. + // The rest of restrictions and conditions of WriteAsync() method applies to + // WriteAllAsync() as well. + // Uses WriteNonBlocking() and MonitorDataAvailable(). + virtual bool WriteAllAsync(const void* buffer, + size_t size_to_write, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error); + + // -- Synchronous non-blocking ---------------------------------------------- + + // Writes up to |size_to_write| bytes to the stream. The |buffer| must be at + // least |size_to_write| in size. It is not an error for this function to + // return without writing all the data requested (or any data at all). + // The actual amount of data written is returned in |size_written|. + // On error, the function returns false and specifies additional error details + // in |error|. + virtual bool WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) = 0; + + // -- Synchronous blocking -------------------------------------------------- + + // Writes up to |size_to_write| bytes to the stream. The |buffer| must be at + // least |size_to_write| in size. It is not an error for this function to + // return without writing all the data requested. The actual amount of data + // written is returned in |size_written|. + // On error, the function returns false and specifies additional error details + // in |error|. + virtual bool WriteBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error); + + // Writes exactly |size_to_write| bytes to |buffer|. Returns false on error + // (writing fewer than requested bytes is treated as an error as well). + // Calls WriteBlocking() repeatedly until all the data is written. + virtual bool WriteAllBlocking(const void* buffer, + size_t size_to_write, + ErrorPtr* error); + + // == Finalizing/closing streams =========================================== + + // Flushes all the user-space data from cache output buffers to storage + // medium. For read-only streams this is a no-op, however it is still valid + // to call this method on read-only streams. + // If an error occurs, the function returns false and specifies additional + // error details in |error|. + virtual bool FlushBlocking(ErrorPtr* error) = 0; + + // Flushes all the user-space data from the cache output buffer + // asynchronously. When all the data is successfully flushed, the + // |success_callback| is invoked. If an error occurs while flushing, partial + // data might be flushed and |error_callback| is invoked. If there's an error + // scheduling the flush operation, it returns false and neither callback will + // be called. + virtual bool FlushAsync(const base::Closure& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error); + + // Closes the underlying stream. The stream is also automatically closed + // when the stream object is destroyed, but since closing a stream is + // an operation that may fail, in situations when it is important to detect + // the failure to close the stream, CloseBlocking() should be used explicitly + // before destroying the stream object. + virtual bool CloseBlocking(ErrorPtr* error) = 0; + + // == Data availability monitoring ========================================== + + // Overloaded by derived classes to provide stream monitoring for read/write + // data availability for the stream. Calls |callback| when data can be read + // and/or written without blocking. + // |mode| specifies the type of operation to monitor for (read, write, both). + virtual bool WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) = 0; + + // Helper function for implementing blocking I/O. Blocks until the + // non-blocking operation specified by |in_mode| can be performed. + // If |out_mode| is not nullptr, it receives the actual operation that can be + // performed. For example, watching a stream for READ_WRITE while only + // READ can be performed, |out_mode| would contain READ even though |in_mode| + // was set to READ_WRITE. + // |timeout| is the maximum amount of time to wait. Set it to TimeDelta::Max() + // to wait indefinitely. + virtual bool WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) = 0; + + // Cancels pending asynchronous read/write operations. + virtual void CancelPendingAsyncOperations(); + + protected: + Stream() = default; + + private: + // Simple wrapper to call the externally exposed |success_callback| that only + // receives a size_t. + BRILLO_PRIVATE static void IgnoreEOSCallback( + const base::Callback& success_callback, + size_t read, + bool eos); + + // The internal implementation of ReadAsync() and ReadAllAsync(). + // Calls ReadNonBlocking and if there's no data available waits for it calling + // WaitForData(). The extra |force_async_callback| tell whether the success + // callback should be called from the main loop instead of directly from this + // method. This method only calls WaitForData() if ReadNonBlocking() returns a + // situation in which it would block (bytes_read = 0 and eos = false), + // preventing us from calling WaitForData() on streams that don't support such + // feature. + BRILLO_PRIVATE bool ReadAsyncImpl( + void* buffer, + size_t size_to_read, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error, + bool force_async_callback); + + // Called from the main loop when the ReadAsyncImpl finished right away + // without waiting for data. We use this callback to call the + // |sucess_callback| but invalidate the callback if the Stream is destroyed + // while this call is waiting in the main loop. + BRILLO_PRIVATE void OnReadAsyncDone( + const base::Callback& success_callback, + size_t bytes_read, + bool eos); + + // Called from WaitForData() when read operations can be performed + // without blocking (the type of operation is provided in |mode|). + BRILLO_PRIVATE void OnReadAvailable( + void* buffer, + size_t size_to_read, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + AccessMode mode); + + // The internal implementation of WriteAsync() and WriteAllAsync(). + // Calls WriteNonBlocking and if the write would block for it to not block + // calling WaitForData(). The extra |force_async_callback| tell whether the + // success callback should be called from the main loop instead of directly + // from this method. This method only calls WaitForData() if + // WriteNonBlocking() returns a situation in which it would block + // (size_written = 0 and eos = false), preventing us from calling + // WaitForData() on streams that don't support such feature. + BRILLO_PRIVATE bool WriteAsyncImpl( + const void* buffer, + size_t size_to_write, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + ErrorPtr* error, + bool force_async_callback); + + // Called from the main loop when the WriteAsyncImpl finished right away + // without waiting for data. We use this callback to call the + // |sucess_callback| but invalidate the callback if the Stream is destroyed + // while this call is waiting in the main loop. + BRILLO_PRIVATE void OnWriteAsyncDone( + const base::Callback& success_callback, + size_t size_written); + + // Called from WaitForData() when write operations can be performed + // without blocking (the type of operation is provided in |mode|). + BRILLO_PRIVATE void OnWriteAvailable( + const void* buffer, + size_t size, + const base::Callback& success_callback, + const ErrorCallback& error_callback, + AccessMode mode); + + // Helper callbacks to implement ReadAllAsync/WriteAllAsync. + BRILLO_PRIVATE void ReadAllAsyncCallback( + void* buffer, + size_t size_to_read, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + size_t size_read, + bool eos); + BRILLO_PRIVATE void WriteAllAsyncCallback( + const void* buffer, + size_t size_to_write, + const base::Closure& success_callback, + const ErrorCallback& error_callback, + size_t size_written); + + // Helper callbacks to implement FlushAsync(). + BRILLO_PRIVATE void FlushAsyncCallback( + const base::Closure& success_callback, + const ErrorCallback& error_callback); + + // Data members for asynchronous read operations. + bool is_async_read_pending_{false}; + + // Data members for asynchronous write operations. + bool is_async_write_pending_{false}; + + base::WeakPtrFactory weak_ptr_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(Stream); +}; + +// A smart pointer to the stream used to pass the stream object around. +using StreamPtr = std::unique_ptr; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_STREAM_H_ diff --git a/brillo/streams/stream_errors.cc b/brillo/streams/stream_errors.cc new file mode 100644 index 0000000..e7c3dcd --- /dev/null +++ b/brillo/streams/stream_errors.cc @@ -0,0 +1,21 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +namespace brillo { +namespace errors { +namespace stream { + +const char kDomain[] = "stream.io"; + +const char kStreamClosed[] = "stream_closed"; +const char kOperationNotSupported[] = "operation_not_supported"; +const char kPartialData[] = "partial_data"; +const char kInvalidParameter[] = "invalid_parameter"; +const char kTimeout[] = "time_out"; + +} // namespace stream +} // namespace errors +} // namespace brillo diff --git a/brillo/streams/stream_errors.h b/brillo/streams/stream_errors.h new file mode 100644 index 0000000..564d097 --- /dev/null +++ b/brillo/streams/stream_errors.h @@ -0,0 +1,27 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_STREAM_ERRORS_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_STREAM_ERRORS_H_ + +#include + +namespace brillo { +namespace errors { +namespace stream { + +// Error domain for generic stream-based errors. +BRILLO_EXPORT extern const char kDomain[]; + +BRILLO_EXPORT extern const char kStreamClosed[]; +BRILLO_EXPORT extern const char kOperationNotSupported[]; +BRILLO_EXPORT extern const char kPartialData[]; +BRILLO_EXPORT extern const char kInvalidParameter[]; +BRILLO_EXPORT extern const char kTimeout[]; + +} // namespace stream +} // namespace errors +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_STREAM_ERRORS_H_ diff --git a/brillo/streams/stream_unittest.cc b/brillo/streams/stream_unittest.cc new file mode 100644 index 0000000..d19b9ac --- /dev/null +++ b/brillo/streams/stream_unittest.cc @@ -0,0 +1,481 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include + +#include +#include +#include + +using testing::DoAll; +using testing::InSequence; +using testing::Return; +using testing::SaveArg; +using testing::SetArgPointee; +using testing::_; + +namespace brillo { + +using AccessMode = Stream::AccessMode; +using Whence = Stream::Whence; + +// To verify "non-trivial" methods implemented in Stream, mock out the +// "trivial" methods to make sure the ones we are interested in testing +// actually end up calling the expected methods with right parameters. +class MockStreamImpl : public Stream { + public: + MockStreamImpl() = default; + + MOCK_CONST_METHOD0(IsOpen, bool()); + MOCK_CONST_METHOD0(CanRead, bool()); + MOCK_CONST_METHOD0(CanWrite, bool()); + MOCK_CONST_METHOD0(CanSeek, bool()); + MOCK_CONST_METHOD0(CanGetSize, bool()); + + MOCK_CONST_METHOD0(GetSize, uint64_t()); + MOCK_METHOD2(SetSizeBlocking, bool(uint64_t, ErrorPtr*)); + MOCK_CONST_METHOD0(GetRemainingSize, uint64_t()); + + MOCK_CONST_METHOD0(GetPosition, uint64_t()); + MOCK_METHOD4(Seek, bool(int64_t, Whence, uint64_t*, ErrorPtr*)); + + // Omitted: ReadAsync + // Omitted: ReadAllAsync + MOCK_METHOD5(ReadNonBlocking, bool(void*, size_t, size_t*, bool*, ErrorPtr*)); + // Omitted: ReadBlocking + // Omitted: ReadAllBlocking + + // Omitted: WriteAsync + // Omitted: WriteAllAsync + MOCK_METHOD4(WriteNonBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); + // Omitted: WriteBlocking + // Omitted: WriteAllBlocking + + MOCK_METHOD1(FlushBlocking, bool(ErrorPtr*)); + MOCK_METHOD1(CloseBlocking, bool(ErrorPtr*)); + + MOCK_METHOD3(WaitForData, bool(AccessMode, + const base::Callback&, + ErrorPtr*)); + MOCK_METHOD4(WaitForDataBlocking, + bool(AccessMode, base::TimeDelta, AccessMode*, ErrorPtr*)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockStreamImpl); +}; + +TEST(Stream, TruncateBlocking) { + MockStreamImpl stream_mock; + EXPECT_CALL(stream_mock, GetPosition()).WillOnce(Return(123)); + EXPECT_CALL(stream_mock, SetSizeBlocking(123, _)).WillOnce(Return(true)); + EXPECT_TRUE(stream_mock.TruncateBlocking(nullptr)); +} + +TEST(Stream, SetPosition) { + MockStreamImpl stream_mock; + EXPECT_CALL(stream_mock, Seek(12345, Whence::FROM_BEGIN, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(stream_mock.SetPosition(12345, nullptr)); + + // Test too large an offset (that doesn't fit in signed 64 bit value). + ErrorPtr error; + uint64_t max_offset = std::numeric_limits::max(); + EXPECT_CALL(stream_mock, Seek(max_offset, _, _, _)) + .WillOnce(Return(true)); + EXPECT_TRUE(stream_mock.SetPosition(max_offset, nullptr)); + + EXPECT_FALSE(stream_mock.SetPosition(max_offset + 1, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); +} + +TEST(Stream, ReadAsync) { + size_t read_size = 0; + bool succeeded = false; + bool failed = false; + auto success_callback = [&read_size, &succeeded](size_t size) { + read_size = size; + succeeded = true; + }; + auto error_callback = [&failed](const Error* error) { failed = true; }; + + MockStreamImpl stream_mock; + base::Callback data_callback; + char buf[10]; + + // This sets up an initial non blocking read that would block, so ReadAsync() + // should wait for more data. + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_mock.ReadAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), nullptr)); + EXPECT_EQ(0u, read_size); + EXPECT_FALSE(succeeded); + EXPECT_FALSE(failed); + + // Since the previous call is waiting for the data to be available, we can't + // schedule another read. + ErrorPtr error; + EXPECT_FALSE(stream_mock.ReadAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); + EXPECT_EQ("Another asynchronous operation is still pending", + error->GetMessage()); + + // Making the data available via data_callback should not schedule the + // success callback from the main loop and run it directly instead. + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(7), + SetArgPointee<3>(false), + Return(true))); + data_callback.Run(AccessMode::READ); + EXPECT_EQ(7u, read_size); + EXPECT_FALSE(failed); +} + +TEST(Stream, ReadAsync_DontWaitForData) { + bool succeeded = false; + bool failed = false; + auto success_callback = [&succeeded](size_t size) { succeeded = true; }; + auto error_callback = [&failed](const Error* error) { failed = true; }; + + MockStreamImpl stream_mock; + char buf[10]; + FakeMessageLoop fake_loop_{nullptr}; + fake_loop_.SetAsCurrent(); + + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<2>(5), SetArgPointee<3>(false), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(_, _, _)).Times(0); + EXPECT_TRUE(stream_mock.ReadAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), nullptr)); + // Even if ReadNonBlocking() returned some data without waiting, the + // |success_callback| should not run yet. + EXPECT_TRUE(fake_loop_.PendingTasks()); + EXPECT_FALSE(succeeded); + EXPECT_FALSE(failed); + + // Since the previous callback is still waiting in the main loop, we can't + // schedule another read yet. + ErrorPtr error; + EXPECT_FALSE(stream_mock.ReadAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); + EXPECT_EQ("Another asynchronous operation is still pending", + error->GetMessage()); + + fake_loop_.Run(); + EXPECT_TRUE(succeeded); + EXPECT_FALSE(failed); +} + +TEST(Stream, ReadAllAsync) { + bool succeeded = false; + bool failed = false; + auto success_callback = [&succeeded]() { succeeded = true; }; + auto error_callback = [&failed](const Error* error) { failed = true; }; + + MockStreamImpl stream_mock; + base::Callback data_callback; + char buf[10]; + + // This sets up an initial non blocking read that would block, so + // ReadAllAsync() should wait for more data. + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_mock.ReadAllAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), + nullptr)); + EXPECT_FALSE(succeeded); + EXPECT_FALSE(failed); + testing::Mock::VerifyAndClearExpectations(&stream_mock); + + // ReadAllAsync() will try to read non blocking until the read would block + // before it waits for the data to be available again. + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(7), + SetArgPointee<3>(false), + Return(true))); + EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + data_callback.Run(AccessMode::READ); + EXPECT_FALSE(succeeded); + EXPECT_FALSE(failed); + testing::Mock::VerifyAndClearExpectations(&stream_mock); + + EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), + SetArgPointee<3>(true), + Return(true))); + data_callback.Run(AccessMode::READ); + EXPECT_TRUE(succeeded); + EXPECT_FALSE(failed); +} + +TEST(Stream, ReadAllAsync_EOS) { + bool succeeded = false; + bool failed = false; + auto success_callback = [&succeeded]() { succeeded = true; }; + auto error_callback = [&failed](const Error* error) { + ASSERT_EQ(errors::stream::kDomain, error->GetDomain()); + ASSERT_EQ(errors::stream::kPartialData, error->GetCode()); + failed = true; + }; + + MockStreamImpl stream_mock; + base::Callback data_callback; + char buf[10]; + + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_mock.ReadAllAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), + nullptr)); + + // ReadAsyncAll() should finish and fail once ReadNonBlocking() returns an + // end-of-stream condition. + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(7), + SetArgPointee<3>(true), + Return(true))); + data_callback.Run(AccessMode::READ); + EXPECT_FALSE(succeeded); + EXPECT_TRUE(failed); +} + +TEST(Stream, ReadBlocking) { + MockStreamImpl stream_mock; + char buf[1024]; + size_t read = 0; + + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(24), + SetArgPointee<3>(false), + Return(true))); + EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); + EXPECT_EQ(24, read); + + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(true), + Return(true))); + EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); + EXPECT_EQ(0, read); + + { + InSequence seq; + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(false), + Return(true))); + EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(false), + Return(true))); + EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(124), + SetArgPointee<3>(false), + Return(true))); + } + EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); + EXPECT_EQ(124, read); + + { + InSequence seq; + EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), + SetArgPointee<3>(false), + Return(true))); + EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _)) + .WillOnce(Return(false)); + } + EXPECT_FALSE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); +} + +TEST(Stream, ReadAllBlocking) { + class MockReadBlocking : public MockStreamImpl { + public: + MOCK_METHOD4(ReadBlocking, bool(void*, size_t, size_t*, ErrorPtr*)); + } stream_mock; + + char buf[1024]; + + EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); + EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true))); + EXPECT_TRUE(stream_mock.ReadAllBlocking(buf, sizeof(buf), nullptr)); + + ErrorPtr error; + EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); + EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_FALSE(stream_mock.ReadAllBlocking(buf, sizeof(buf), &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); +} + +TEST(Stream, WriteAsync) { + size_t write_size = 0; + bool failed = false; + auto success_callback = [&write_size](size_t size) { write_size = size; }; + auto error_callback = [&failed](const Error* error) { failed = true; }; + + MockStreamImpl stream_mock; + InSequence s; + base::Callback data_callback; + char buf[10] = {}; + + // WriteNonBlocking returns a blocking situation (size_written = 0) so the + // WaitForData() is run. + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_mock.WriteAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), nullptr)); + EXPECT_EQ(0u, write_size); + EXPECT_FALSE(failed); + + ErrorPtr error; + EXPECT_FALSE(stream_mock.WriteAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); + EXPECT_EQ("Another asynchronous operation is still pending", + error->GetMessage()); + + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(7), Return(true))); + data_callback.Run(AccessMode::WRITE); + EXPECT_EQ(7u, write_size); + EXPECT_FALSE(failed); +} + +TEST(Stream, WriteAllAsync) { + bool succeeded = false; + bool failed = false; + auto success_callback = [&succeeded]() { succeeded = true; }; + auto error_callback = [&failed](const Error* error) { failed = true; }; + + MockStreamImpl stream_mock; + base::Callback data_callback; + char buf[10] = {}; + + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + EXPECT_TRUE(stream_mock.WriteAllAsync(buf, sizeof(buf), + base::Bind(success_callback), + base::Bind(error_callback), + nullptr)); + testing::Mock::VerifyAndClearExpectations(&stream_mock); + EXPECT_FALSE(succeeded); + EXPECT_FALSE(failed); + + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(7), Return(true))); + EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _)) + .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); + data_callback.Run(AccessMode::WRITE); + testing::Mock::VerifyAndClearExpectations(&stream_mock); + EXPECT_FALSE(succeeded); + EXPECT_FALSE(failed); + + EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), Return(true))); + data_callback.Run(AccessMode::WRITE); + EXPECT_TRUE(succeeded); + EXPECT_FALSE(failed); +} + +TEST(Stream, WriteBlocking) { + MockStreamImpl stream_mock; + char buf[1024]; + size_t written = 0; + + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); + EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr)); + EXPECT_EQ(24, written); + + { + InSequence seq; + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(124), Return(true))); + } + EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr)); + EXPECT_EQ(124, written); + + { + InSequence seq; + EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); + EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _)) + .WillOnce(Return(false)); + } + EXPECT_FALSE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr)); +} + +TEST(Stream, WriteAllBlocking) { + class MockWritelocking : public MockStreamImpl { + public: + MOCK_METHOD4(WriteBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); + } stream_mock; + + char buf[1024]; + + EXPECT_CALL(stream_mock, WriteBlocking(buf, 1024, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); + EXPECT_CALL(stream_mock, WriteBlocking(buf + 24, 1000, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true))); + EXPECT_TRUE(stream_mock.WriteAllBlocking(buf, sizeof(buf), nullptr)); +} + +} // namespace brillo diff --git a/brillo/streams/stream_utils.cc b/brillo/streams/stream_utils.cc new file mode 100644 index 0000000..5f3be24 --- /dev/null +++ b/brillo/streams/stream_utils.cc @@ -0,0 +1,216 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include + +namespace brillo { +namespace stream_utils { + +namespace { + +// Status of asynchronous CopyData operation. +struct CopyDataState { + brillo::StreamPtr in_stream; + brillo::StreamPtr out_stream; + std::vector buffer; + uint64_t remaining_to_copy; + uint64_t size_copied; + CopyDataSuccessCallback success_callback; + CopyDataErrorCallback error_callback; +}; + +// Async CopyData I/O error callback. +void OnCopyDataError(const std::shared_ptr& state, + const brillo::Error* error) { + state->error_callback.Run(std::move(state->in_stream), + std::move(state->out_stream), error); +} + +// Forward declaration. +void PerformRead(const std::shared_ptr& state); + +// Callback from read operation for CopyData. Writes the read data to the output +// stream and invokes PerformRead when done to restart the copy cycle. +void PerformWrite(const std::shared_ptr& state, size_t size) { + if (size == 0) { + state->success_callback.Run(std::move(state->in_stream), + std::move(state->out_stream), + state->size_copied); + return; + } + state->size_copied += size; + CHECK_GE(state->remaining_to_copy, size); + state->remaining_to_copy -= size; + + brillo::ErrorPtr error; + bool success = state->out_stream->WriteAllAsync( + state->buffer.data(), size, base::Bind(&PerformRead, state), + base::Bind(&OnCopyDataError, state), &error); + + if (!success) + OnCopyDataError(state, error.get()); +} + +// Performs the read part of asynchronous CopyData operation. Reads the data +// from input stream and invokes PerformWrite when done to write the data to +// the output stream. +void PerformRead(const std::shared_ptr& state) { + brillo::ErrorPtr error; + const uint64_t buffer_size = state->buffer.size(); + // |buffer_size| is guaranteed to fit in size_t, so |size_to_read| value will + // also not overflow size_t, so the static_cast below is safe. + size_t size_to_read = + static_cast(std::min(buffer_size, state->remaining_to_copy)); + if (size_to_read == 0) + return PerformWrite(state, 0); // Nothing more to read. Finish operation. + bool success = state->in_stream->ReadAsync( + state->buffer.data(), size_to_read, base::Bind(PerformWrite, state), + base::Bind(OnCopyDataError, state), &error); + + if (!success) + OnCopyDataError(state, error.get()); +} + +} // anonymous namespace + +bool ErrorStreamClosed(const tracked_objects::Location& location, + ErrorPtr* error) { + Error::AddTo(error, + location, + errors::stream::kDomain, + errors::stream::kStreamClosed, + "Stream is closed"); + return false; +} + +bool ErrorOperationNotSupported(const tracked_objects::Location& location, + ErrorPtr* error) { + Error::AddTo(error, + location, + errors::stream::kDomain, + errors::stream::kOperationNotSupported, + "Stream operation not supported"); + return false; +} + +bool ErrorReadPastEndOfStream(const tracked_objects::Location& location, + ErrorPtr* error) { + Error::AddTo(error, + location, + errors::stream::kDomain, + errors::stream::kPartialData, + "Reading past the end of stream"); + return false; +} + +bool ErrorOperationTimeout(const tracked_objects::Location& location, + ErrorPtr* error) { + Error::AddTo(error, + location, + errors::stream::kDomain, + errors::stream::kTimeout, + "Operation timed out"); + return false; +} + +bool CheckInt64Overflow(const tracked_objects::Location& location, + uint64_t position, + int64_t offset, + ErrorPtr* error) { + if (offset < 0) { + // Subtracting the offset. Make sure we do not underflow. + uint64_t unsigned_offset = static_cast(-offset); + if (position >= unsigned_offset) + return true; + } else { + // Adding the offset. Make sure we do not overflow unsigned 64 bits first. + if (position <= std::numeric_limits::max() - offset) { + // We definitely will not overflow the unsigned 64 bit integer. + // Now check that we end up within the limits of signed 64 bit integer. + uint64_t new_position = position + offset; + uint64_t max = std::numeric_limits::max(); + if (new_position <= max) + return true; + } + } + Error::AddTo(error, + location, + errors::stream::kDomain, + errors::stream::kInvalidParameter, + "The stream offset value is out of range"); + return false; +} + +bool CalculateStreamPosition(const tracked_objects::Location& location, + int64_t offset, + Stream::Whence whence, + uint64_t current_position, + uint64_t stream_size, + uint64_t* new_position, + ErrorPtr* error) { + uint64_t pos = 0; + switch (whence) { + case Stream::Whence::FROM_BEGIN: + pos = 0; + break; + + case Stream::Whence::FROM_CURRENT: + pos = current_position; + break; + + case Stream::Whence::FROM_END: + pos = stream_size; + break; + + default: + Error::AddTo(error, + location, + errors::stream::kDomain, + errors::stream::kInvalidParameter, + "Invalid stream position whence"); + return false; + } + + if (!CheckInt64Overflow(location, pos, offset, error)) + return false; + + *new_position = static_cast(pos + offset); + return true; +} + +void CopyData(StreamPtr in_stream, + StreamPtr out_stream, + const CopyDataSuccessCallback& success_callback, + const CopyDataErrorCallback& error_callback) { + CopyData(std::move(in_stream), std::move(out_stream), + std::numeric_limits::max(), 4096, success_callback, + error_callback); +} + +void CopyData(StreamPtr in_stream, + StreamPtr out_stream, + uint64_t max_size_to_copy, + size_t buffer_size, + const CopyDataSuccessCallback& success_callback, + const CopyDataErrorCallback& error_callback) { + auto state = std::make_shared(); + state->in_stream = std::move(in_stream); + state->out_stream = std::move(out_stream); + state->buffer.resize(buffer_size); + state->remaining_to_copy = max_size_to_copy; + state->size_copied = 0; + state->success_callback = success_callback; + state->error_callback = error_callback; + brillo::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&PerformRead, state)); +} + +} // namespace stream_utils +} // namespace brillo diff --git a/brillo/streams/stream_utils.h b/brillo/streams/stream_utils.h new file mode 100644 index 0000000..84792ce --- /dev/null +++ b/brillo/streams/stream_utils.h @@ -0,0 +1,114 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_STREAM_UTILS_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_STREAM_UTILS_H_ + +#include +#include +#include + +namespace brillo { +namespace stream_utils { + +// Generates "Stream closed" error and returns false. +BRILLO_EXPORT bool ErrorStreamClosed( + const tracked_objects::Location& location, ErrorPtr* error); + +// Generates "Not supported" error and returns false. +BRILLO_EXPORT bool ErrorOperationNotSupported( + const tracked_objects::Location& location, ErrorPtr* error); + +// Generates "Read past end of stream" error and returns false. +BRILLO_EXPORT bool ErrorReadPastEndOfStream( + const tracked_objects::Location& location, ErrorPtr* error); + +// Generates "Operation time out" error and returns false. +BRILLO_EXPORT bool ErrorOperationTimeout( + const tracked_objects::Location& location, ErrorPtr* error); + +// Checks if |position| + |offset| fit within the constraint of positive +// signed int64_t type. We use uint64_t for absolute stream pointer positions, +// however many implementations, including file-descriptor-based I/O do not +// support the full extent of unsigned 64 bit numbers. So we restrict the file +// positions to what can fit in the signed 64 bit value (that is, we support +// "only" up to 9 exabytes, instead of the possible 18). +// The |location| parameter will be used to report the origin of the error +// if one is generated/triggered. +BRILLO_EXPORT bool CheckInt64Overflow( + const tracked_objects::Location& location, + uint64_t position, + int64_t offset, + ErrorPtr* error); + +// Helper function to calculate the stream position based on the current +// stream position and offset. Returns true and the new calculated stream +// position in |new_position| if successful. In case of invalid stream +// position (negative values or out of range of signed 64 bit values), returns +// false and "invalid_parameter" |error|. +// The |location| parameter will be used to report the origin of the error +// if one is generated/triggered. +BRILLO_EXPORT bool CalculateStreamPosition( + const tracked_objects::Location& location, + int64_t offset, + Stream::Whence whence, + uint64_t current_position, + uint64_t stream_size, + uint64_t* new_position, + ErrorPtr* error); + +// Checks if |mode| allows read access. +inline bool IsReadAccessMode(Stream::AccessMode mode) { + return mode == Stream::AccessMode::READ || + mode == Stream::AccessMode::READ_WRITE; +} + +// Checks if |mode| allows write access. +inline bool IsWriteAccessMode(Stream::AccessMode mode) { + return mode == Stream::AccessMode::WRITE || + mode == Stream::AccessMode::READ_WRITE; +} + +// Make the access mode based on read/write rights requested. +inline Stream::AccessMode MakeAccessMode(bool read, bool write) { + CHECK(read || write); // Either read or write (or both) must be specified. + if (read && write) + return Stream::AccessMode::READ_WRITE; + return write ? Stream::AccessMode::WRITE : Stream::AccessMode::READ; +} + +using CopyDataSuccessCallback = + base::Callback; +using CopyDataErrorCallback = + base::Callback; + +// Asynchronously copies data from input stream to output stream until all the +// data from the input stream is read. The function takes ownership of both +// streams for the duration of the operation and then gives them back when +// either the |success_callback| or |error_callback| is called. +// |success_callback| also provides the number of bytes actually copied. +// This variant of CopyData uses internal buffer of 4 KiB for the operation. +BRILLO_EXPORT void CopyData(StreamPtr in_stream, + StreamPtr out_stream, + const CopyDataSuccessCallback& success_callback, + const CopyDataErrorCallback& error_callback); + +// Asynchronously copies data from input stream to output stream until the +// maximum amount of data specified in |max_size_to_copy| is copied or the end +// of the input stream is encountered. The function takes ownership of both +// streams for the duration of the operation and then gives them back when +// either the |success_callback| or |error_callback| is called. +// |success_callback| also provides the number of bytes actually copied. +// |buffer_size| specifies the size of the read buffer to use for the operation. +BRILLO_EXPORT void CopyData(StreamPtr in_stream, + StreamPtr out_stream, + uint64_t max_size_to_copy, + size_t buffer_size, + const CopyDataSuccessCallback& success_callback, + const CopyDataErrorCallback& error_callback); + +} // namespace stream_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_STREAM_UTILS_H_ diff --git a/brillo/streams/stream_utils_unittest.cc b/brillo/streams/stream_utils_unittest.cc new file mode 100644 index 0000000..8145027 --- /dev/null +++ b/brillo/streams/stream_utils_unittest.cc @@ -0,0 +1,300 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using testing::DoAll; +using testing::InSequence; +using testing::Return; +using testing::StrictMock; +using testing::_; + +ACTION_TEMPLATE(InvokeAsyncCallback, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(size)) { + brillo::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(std::get(args), size)); + return true; +} + +ACTION_TEMPLATE(InvokeAsyncCallback, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + brillo::MessageLoop::current()->PostTask(FROM_HERE, std::get(args)); + return true; +} + +ACTION_TEMPLATE(InvokeAsyncErrorCallback, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(code)) { + brillo::ErrorPtr error; + brillo::Error::AddTo(&error, FROM_HERE, "test", code, "message"); + brillo::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(std::get(args), base::Owned(error.release()))); + return true; +} + +namespace brillo { + +TEST(StreamUtils, ErrorStreamClosed) { + ErrorPtr error; + EXPECT_FALSE(stream_utils::ErrorStreamClosed(FROM_HERE, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kStreamClosed, error->GetCode()); + EXPECT_EQ("Stream is closed", error->GetMessage()); +} + +TEST(StreamUtils, ErrorOperationNotSupported) { + ErrorPtr error; + EXPECT_FALSE(stream_utils::ErrorOperationNotSupported(FROM_HERE, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); + EXPECT_EQ("Stream operation not supported", error->GetMessage()); +} + +TEST(StreamUtils, ErrorReadPastEndOfStream) { + ErrorPtr error; + EXPECT_FALSE(stream_utils::ErrorReadPastEndOfStream(FROM_HERE, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); + EXPECT_EQ("Reading past the end of stream", error->GetMessage()); +} + +TEST(StreamUtils, CheckInt64Overflow) { + const int64_t max_int64 = std::numeric_limits::max(); + const uint64_t max_uint64 = std::numeric_limits::max(); + EXPECT_TRUE(stream_utils::CheckInt64Overflow(FROM_HERE, 0, 0, nullptr)); + EXPECT_TRUE(stream_utils::CheckInt64Overflow( + FROM_HERE, 0, max_int64, nullptr)); + EXPECT_TRUE(stream_utils::CheckInt64Overflow( + FROM_HERE, max_int64, 0, nullptr)); + EXPECT_TRUE(stream_utils::CheckInt64Overflow(FROM_HERE, 100, -90, nullptr)); + EXPECT_TRUE(stream_utils::CheckInt64Overflow( + FROM_HERE, 1000, -1000, nullptr)); + + ErrorPtr error; + EXPECT_FALSE(stream_utils::CheckInt64Overflow(FROM_HERE, 100, -101, &error)); + EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); + EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); + EXPECT_EQ("The stream offset value is out of range", error->GetMessage()); + + EXPECT_FALSE(stream_utils::CheckInt64Overflow( + FROM_HERE, max_int64, 1, nullptr)); + EXPECT_FALSE(stream_utils::CheckInt64Overflow( + FROM_HERE, max_uint64, 0, nullptr)); + EXPECT_FALSE(stream_utils::CheckInt64Overflow( + FROM_HERE, max_uint64, max_int64, nullptr)); +} + +TEST(StreamUtils, CalculateStreamPosition) { + using Whence = Stream::Whence; + const uint64_t current_pos = 1234; + const uint64_t end_pos = 2000; + uint64_t pos = 0; + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, 0, Whence::FROM_BEGIN, current_pos, end_pos, &pos, nullptr)); + EXPECT_EQ(0u, pos); + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, 0, Whence::FROM_CURRENT, current_pos, end_pos, &pos, nullptr)); + EXPECT_EQ(current_pos, pos); + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, 0, Whence::FROM_END, current_pos, end_pos, &pos, nullptr)); + EXPECT_EQ(end_pos, pos); + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, 10, Whence::FROM_BEGIN, current_pos, end_pos, &pos, nullptr)); + EXPECT_EQ(10u, pos); + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, 10, Whence::FROM_CURRENT, current_pos, end_pos, &pos, + nullptr)); + EXPECT_EQ(current_pos + 10, pos); + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, 10, Whence::FROM_END, current_pos, end_pos, &pos, nullptr)); + EXPECT_EQ(end_pos + 10, pos); + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, -10, Whence::FROM_CURRENT, current_pos, end_pos, &pos, + nullptr)); + EXPECT_EQ(current_pos - 10, pos); + + EXPECT_TRUE(stream_utils::CalculateStreamPosition( + FROM_HERE, -10, Whence::FROM_END, current_pos, end_pos, &pos, nullptr)); + EXPECT_EQ(end_pos - 10, pos); + + ErrorPtr error; + EXPECT_FALSE(stream_utils::CalculateStreamPosition( + FROM_HERE, -1, Whence::FROM_BEGIN, current_pos, end_pos, &pos, &error)); + EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); + EXPECT_EQ("The stream offset value is out of range", error->GetMessage()); + + EXPECT_FALSE(stream_utils::CalculateStreamPosition( + FROM_HERE, -1001, Whence::FROM_CURRENT, 1000, end_pos, &pos, nullptr)); + + const uint64_t max_int64 = std::numeric_limits::max(); + EXPECT_FALSE(stream_utils::CalculateStreamPosition( + FROM_HERE, 1, Whence::FROM_CURRENT, max_int64, end_pos, &pos, nullptr)); +} + +class CopyStreamDataTest : public testing::Test { + public: + void SetUp() override { + fake_loop_.SetAsCurrent(); + in_stream_.reset(new StrictMock{}); + out_stream_.reset(new StrictMock{}); + } + + FakeMessageLoop fake_loop_{nullptr}; + std::unique_ptr> in_stream_; + std::unique_ptr> out_stream_; + bool succeeded_{false}; + bool failed_{false}; + + void OnSuccess(uint64_t expected, + StreamPtr in_stream, + StreamPtr out_stream, + uint64_t copied) { + EXPECT_EQ(expected, copied); + succeeded_ = true; + } + + void OnError(const std::string& expected_error, + StreamPtr in_stream, + StreamPtr out_stream, + const Error* error) { + EXPECT_EQ(expected_error, error->GetCode()); + failed_ = true; + } + + void ExpectSuccess() { + EXPECT_TRUE(succeeded_); + EXPECT_FALSE(failed_); + } + + void ExpectFailure() { + EXPECT_FALSE(succeeded_); + EXPECT_TRUE(failed_); + } +}; + +TEST_F(CopyStreamDataTest, CopyAllAtOnce) { + { + InSequence seq; + EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(100)); + EXPECT_CALL(*out_stream_, WriteAllAsync(_, 100, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>()); + } + stream_utils::CopyData( + std::move(in_stream_), std::move(out_stream_), 100, 4096, + base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100), + base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); + fake_loop_.Run(); + ExpectSuccess(); +} + +TEST_F(CopyStreamDataTest, CopyInBlocks) { + { + InSequence seq; + EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(60)); + EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>()); + EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(40)); + EXPECT_CALL(*out_stream_, WriteAllAsync(_, 40, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>()); + } + stream_utils::CopyData( + std::move(in_stream_), std::move(out_stream_), 100, 4096, + base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100), + base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); + fake_loop_.Run(); + ExpectSuccess(); +} + +TEST_F(CopyStreamDataTest, CopyTillEndOfStream) { + { + InSequence seq; + EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(60)); + EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>()); + EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(0)); + } + stream_utils::CopyData( + std::move(in_stream_), std::move(out_stream_), 100, 4096, + base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 60), + base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); + fake_loop_.Run(); + ExpectSuccess(); +} + +TEST_F(CopyStreamDataTest, CopyInSmallBlocks) { + { + InSequence seq; + EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(60)); + EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>()); + EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(40)); + EXPECT_CALL(*out_stream_, WriteAllAsync(_, 40, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>()); + } + stream_utils::CopyData( + std::move(in_stream_), std::move(out_stream_), 100, 60, + base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100), + base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); + fake_loop_.Run(); + ExpectSuccess(); +} + +TEST_F(CopyStreamDataTest, ErrorRead) { + { + InSequence seq; + EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _)) + .WillOnce(InvokeAsyncErrorCallback<3>("read")); + } + stream_utils::CopyData( + std::move(in_stream_), std::move(out_stream_), 100, 60, + base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 0), + base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "read")); + fake_loop_.Run(); + ExpectFailure(); +} + +TEST_F(CopyStreamDataTest, ErrorWrite) { + { + InSequence seq; + EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _)) + .WillOnce(InvokeAsyncCallback<2>(60)); + EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) + .WillOnce(InvokeAsyncErrorCallback<3>("write")); + } + stream_utils::CopyData( + std::move(in_stream_), std::move(out_stream_), 100, 60, + base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 0), + base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), + "write")); + fake_loop_.Run(); + ExpectFailure(); +} + +} // namespace brillo diff --git a/brillo/streams/tls_stream.cc b/brillo/streams/tls_stream.cc new file mode 100644 index 0000000..70d1e13 --- /dev/null +++ b/brillo/streams/tls_stream.cc @@ -0,0 +1,545 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +// SSL info callback which is called by OpenSSL when we enable logging level of +// at least 3. This logs the information about the internal TLS handshake. +void TlsInfoCallback(const SSL* ssl, int where, int ret) { + std::string reason; + std::vector info; + if (where & SSL_CB_LOOP) + info.push_back("loop"); + if (where & SSL_CB_EXIT) + info.push_back("exit"); + if (where & SSL_CB_READ) + info.push_back("read"); + if (where & SSL_CB_WRITE) + info.push_back("write"); + if (where & SSL_CB_ALERT) { + info.push_back("alert"); + reason = ", reason: "; + reason += SSL_alert_type_string_long(ret); + reason += "/"; + reason += SSL_alert_desc_string_long(ret); + } + if (where & SSL_CB_HANDSHAKE_START) + info.push_back("handshake_start"); + if (where & SSL_CB_HANDSHAKE_DONE) + info.push_back("handshake_done"); + + VLOG(3) << "TLS progress info: " << brillo::string_utils::Join(",", info) + << ", with status: " << ret << reason; +} + +// Static variable to store the index of TlsStream private data in SSL context +// used to store custom data for OnCertVerifyResults(). +int ssl_ctx_private_data_index = -1; + +// Default trusted certificate store location. +const char kCACertificatePath[] = +#ifdef __ANDROID__ + "/system/etc/security/cacerts"; +#else + "/usr/share/chromeos-ca-certificates"; +#endif + +} // anonymous namespace + +namespace brillo { + +// Helper implementation of TLS stream used to hide most of OpenSSL inner +// workings from the users of brillo::TlsStream. +class TlsStream::TlsStreamImpl { + public: + TlsStreamImpl(); + ~TlsStreamImpl(); + + bool Init(StreamPtr socket, + const std::string& host, + const base::Closure& success_callback, + const Stream::ErrorCallback& error_callback, + ErrorPtr* error); + + bool ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error); + + bool WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error); + + bool Flush(ErrorPtr* error); + bool Close(ErrorPtr* error); + bool WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error); + bool WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error); + void CancelPendingAsyncOperations(); + + private: + bool ReportError(ErrorPtr* error, + const tracked_objects::Location& location, + const std::string& message); + void DoHandshake(const base::Closure& success_callback, + const Stream::ErrorCallback& error_callback); + void RetryHandshake(const base::Closure& success_callback, + const Stream::ErrorCallback& error_callback, + Stream::AccessMode mode); + + int OnCertVerifyResults(int ok, X509_STORE_CTX* ctx); + static int OnCertVerifyResultsStatic(int ok, X509_STORE_CTX* ctx); + + StreamPtr socket_; + std::unique_ptr ctx_{nullptr, SSL_CTX_free}; + std::unique_ptr ssl_{nullptr, SSL_free}; + BIO* stream_bio_{nullptr}; + bool need_more_read_{false}; + bool need_more_write_{false}; + + base::WeakPtrFactory weak_ptr_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(TlsStreamImpl); +}; + +TlsStream::TlsStreamImpl::TlsStreamImpl() { + SSL_load_error_strings(); + SSL_library_init(); + if (ssl_ctx_private_data_index < 0) { + ssl_ctx_private_data_index = + SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + } +} + +TlsStream::TlsStreamImpl::~TlsStreamImpl() { + ssl_.reset(); + ctx_.reset(); +} + +bool TlsStream::TlsStreamImpl::ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) { + const size_t max_int = std::numeric_limits::max(); + int size_int = static_cast(std::min(size_to_read, max_int)); + int ret = SSL_read(ssl_.get(), buffer, size_int); + if (ret > 0) { + *size_read = static_cast(ret); + if (end_of_stream) + *end_of_stream = false; + return true; + } + + int err = SSL_get_error(ssl_.get(), ret); + if (err == SSL_ERROR_ZERO_RETURN) { + *size_read = 0; + if (end_of_stream) + *end_of_stream = true; + return true; + } + + if (err == SSL_ERROR_WANT_READ) { + need_more_read_ = true; + } else if (err == SSL_ERROR_WANT_WRITE) { + // Writes might be required for SSL_read() because of possible TLS + // re-negotiations which can happen at any time. + need_more_write_ = true; + } else { + return ReportError(error, FROM_HERE, "Error reading from TLS socket"); + } + *size_read = 0; + if (end_of_stream) + *end_of_stream = false; + return true; +} + +bool TlsStream::TlsStreamImpl::WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) { + const size_t max_int = std::numeric_limits::max(); + int size_int = static_cast(std::min(size_to_write, max_int)); + int ret = SSL_write(ssl_.get(), buffer, size_int); + if (ret > 0) { + *size_written = static_cast(ret); + return true; + } + + int err = SSL_get_error(ssl_.get(), ret); + if (err == SSL_ERROR_WANT_READ) { + // Reads might be required for SSL_write() because of possible TLS + // re-negotiations which can happen at any time. + need_more_read_ = true; + } else if (err == SSL_ERROR_WANT_WRITE) { + need_more_write_ = true; + } else { + return ReportError(error, FROM_HERE, "Error writing to TLS socket"); + } + *size_written = 0; + return true; +} + +bool TlsStream::TlsStreamImpl::Flush(ErrorPtr* error) { + return socket_->FlushBlocking(error); +} + +bool TlsStream::TlsStreamImpl::Close(ErrorPtr* error) { + // 2 seconds should be plenty here. + const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(2); + // The retry count of 4 below is just arbitrary, to ensure we don't get stuck + // here forever. We should rarely need to repeat SSL_shutdown anyway. + for (int retry_count = 0; retry_count < 4; retry_count++) { + int ret = SSL_shutdown(ssl_.get()); + // We really don't care for bi-directional shutdown here. + // Just make sure we only send the "close notify" alert to the remote peer. + if (ret >= 0) + break; + + int err = SSL_get_error(ssl_.get(), ret); + if (err == SSL_ERROR_WANT_READ) { + if (!socket_->WaitForDataBlocking(AccessMode::READ, kTimeout, nullptr, + error)) { + break; + } + } else if (err == SSL_ERROR_WANT_WRITE) { + if (!socket_->WaitForDataBlocking(AccessMode::WRITE, kTimeout, nullptr, + error)) { + break; + } + } else { + LOG(ERROR) << "SSL_shutdown returned error #" << err; + ReportError(error, FROM_HERE, "Failed to shut down TLS socket"); + break; + } + } + return socket_->CloseBlocking(error); +} + +bool TlsStream::TlsStreamImpl::WaitForData( + AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) { + bool is_read = stream_utils::IsReadAccessMode(mode); + bool is_write = stream_utils::IsWriteAccessMode(mode); + is_read |= need_more_read_; + is_write |= need_more_write_; + need_more_read_ = false; + need_more_write_ = false; + if (is_read && SSL_pending(ssl_.get()) > 0) { + callback.Run(AccessMode::READ); + return true; + } + mode = stream_utils::MakeAccessMode(is_read, is_write); + return socket_->WaitForData(mode, callback, error); +} + +bool TlsStream::TlsStreamImpl::WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) { + bool is_read = stream_utils::IsReadAccessMode(in_mode); + bool is_write = stream_utils::IsWriteAccessMode(in_mode); + is_read |= need_more_read_; + is_write |= need_more_write_; + need_more_read_ = need_more_write_ = false; + if (is_read && SSL_pending(ssl_.get()) > 0) { + if (out_mode) + *out_mode = AccessMode::READ; + return true; + } + in_mode = stream_utils::MakeAccessMode(is_read, is_write); + return socket_->WaitForDataBlocking(in_mode, timeout, out_mode, error); +} + +void TlsStream::TlsStreamImpl::CancelPendingAsyncOperations() { + socket_->CancelPendingAsyncOperations(); + weak_ptr_factory_.InvalidateWeakPtrs(); +} + +bool TlsStream::TlsStreamImpl::ReportError( + ErrorPtr* error, + const tracked_objects::Location& location, + const std::string& message) { + const char* file = nullptr; + int line = 0; + const char* data = 0; + int flags = 0; + while (auto errnum = ERR_get_error_line_data(&file, &line, &data, &flags)) { + char buf[256]; + ERR_error_string_n(errnum, buf, sizeof(buf)); + tracked_objects::Location ssl_location{"Unknown", file, line, nullptr}; + std::string ssl_message = buf; + if (flags & ERR_TXT_STRING) { + ssl_message += ": "; + ssl_message += data; + } + Error::AddTo(error, ssl_location, "openssl", std::to_string(errnum), + ssl_message); + } + Error::AddTo(error, location, "tls_stream", "failed", message); + return false; +} + +int TlsStream::TlsStreamImpl::OnCertVerifyResults(int ok, X509_STORE_CTX* ctx) { + // OpenSSL already performs a comprehensive check of the certificate chain + // (using X509_verify_cert() function) and calls back with the result of its + // verification. + // |ok| is set to 1 if the verification passed and 0 if an error was detected. + // Here we can perform some additional checks if we need to, or simply log + // the issues found. + + // For now, just log an error if it occurred. + if (!ok) { + LOG(ERROR) << "Server certificate validation failed: " + << X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)); + } + return ok; +} + +int TlsStream::TlsStreamImpl::OnCertVerifyResultsStatic(int ok, + X509_STORE_CTX* ctx) { + // Obtain the pointer to the instance of TlsStream::TlsStreamImpl from the + // SSL CTX object referenced by |ctx|. + SSL* ssl = static_cast(X509_STORE_CTX_get_ex_data( + ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + SSL_CTX* ssl_ctx = ssl ? SSL_get_SSL_CTX(ssl) : nullptr; + TlsStream::TlsStreamImpl* self = nullptr; + if (ssl_ctx) { + self = static_cast(SSL_CTX_get_ex_data( + ssl_ctx, ssl_ctx_private_data_index)); + } + return self ? self->OnCertVerifyResults(ok, ctx) : ok; +} + +bool TlsStream::TlsStreamImpl::Init(StreamPtr socket, + const std::string& host, + const base::Closure& success_callback, + const Stream::ErrorCallback& error_callback, + ErrorPtr* error) { + ctx_.reset(SSL_CTX_new(TLSv1_2_client_method())); + if (!ctx_) + return ReportError(error, FROM_HERE, "Cannot create SSL_CTX"); + + // Top cipher suites supported by both Google GFEs and OpenSSL (in server + // preferred order). + int res = SSL_CTX_set_cipher_list(ctx_.get(), + "ECDHE-ECDSA-AES128-GCM-SHA256:" + "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES128-GCM-SHA256:" + "ECDHE-RSA-AES256-GCM-SHA384"); + if (res != 1) + return ReportError(error, FROM_HERE, "Cannot set the cipher list"); + + res = SSL_CTX_load_verify_locations(ctx_.get(), nullptr, kCACertificatePath); + if (res != 1) { + return ReportError(error, FROM_HERE, + "Failed to specify trusted certificate location"); + } + + // Store a pointer to "this" into SSL_CTX instance. + SSL_CTX_set_ex_data(ctx_.get(), ssl_ctx_private_data_index, this); + + // Ask OpenSSL to validate the server host from the certificate to match + // the expected host name we are given: + X509_VERIFY_PARAM* param = SSL_CTX_get0_param(ctx_.get()); + X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size()); + + SSL_CTX_set_verify(ctx_.get(), SSL_VERIFY_PEER, + &TlsStreamImpl::OnCertVerifyResultsStatic); + + socket_ = std::move(socket); + ssl_.reset(SSL_new(ctx_.get())); + + // Enable TLS progress callback if VLOG level is >=3. + if (VLOG_IS_ON(3)) + SSL_set_info_callback(ssl_.get(), TlsInfoCallback); + + stream_bio_ = BIO_new_stream(socket_.get()); + SSL_set_bio(ssl_.get(), stream_bio_, stream_bio_); + SSL_set_connect_state(ssl_.get()); + + // We might have no message loop (e.g. we are in unit tests). + if (MessageLoop::ThreadHasCurrent()) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&TlsStreamImpl::DoHandshake, + weak_ptr_factory_.GetWeakPtr(), + success_callback, + error_callback)); + } else { + DoHandshake(success_callback, error_callback); + } + return true; +} + +void TlsStream::TlsStreamImpl::RetryHandshake( + const base::Closure& success_callback, + const Stream::ErrorCallback& error_callback, + Stream::AccessMode mode) { + VLOG(1) << "Retrying TLS handshake"; + DoHandshake(success_callback, error_callback); +} + +void TlsStream::TlsStreamImpl::DoHandshake( + const base::Closure& success_callback, + const Stream::ErrorCallback& error_callback) { + VLOG(1) << "Begin TLS handshake"; + int res = SSL_do_handshake(ssl_.get()); + if (res == 1) { + VLOG(1) << "Handshake successful"; + success_callback.Run(); + return; + } + ErrorPtr error; + int err = SSL_get_error(ssl_.get(), res); + if (err == SSL_ERROR_WANT_READ) { + VLOG(1) << "Waiting for read data..."; + bool ok = socket_->WaitForData( + Stream::AccessMode::READ, + base::Bind(&TlsStreamImpl::RetryHandshake, + weak_ptr_factory_.GetWeakPtr(), + success_callback, error_callback), + &error); + if (ok) + return; + } else if (err == SSL_ERROR_WANT_WRITE) { + VLOG(1) << "Waiting for write data..."; + bool ok = socket_->WaitForData( + Stream::AccessMode::WRITE, + base::Bind(&TlsStreamImpl::RetryHandshake, + weak_ptr_factory_.GetWeakPtr(), + success_callback, error_callback), + &error); + if (ok) + return; + } else { + ReportError(&error, FROM_HERE, "TLS handshake failed."); + } + error_callback.Run(error.get()); +} + +///////////////////////////////////////////////////////////////////////////// +TlsStream::TlsStream(std::unique_ptr impl) + : impl_{std::move(impl)} {} + +TlsStream::~TlsStream() { + if (impl_) { + impl_->Close(nullptr); + } +} + +void TlsStream::Connect(StreamPtr socket, + const std::string& host, + const base::Callback& success_callback, + const Stream::ErrorCallback& error_callback) { + std::unique_ptr impl{new TlsStreamImpl}; + std::unique_ptr stream{new TlsStream{std::move(impl)}}; + + TlsStreamImpl* pimpl = stream->impl_.get(); + ErrorPtr error; + bool success = pimpl->Init(std::move(socket), host, + base::Bind(success_callback, + base::Passed(std::move(stream))), + error_callback, &error); + + if (!success) + error_callback.Run(error.get()); +} + +bool TlsStream::IsOpen() const { + return impl_ ? true : false; +} + +bool TlsStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); +} + +bool TlsStream::Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) { + return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); +} + +bool TlsStream::ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) { + if (!impl_) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + return impl_->ReadNonBlocking(buffer, size_to_read, size_read, end_of_stream, + error); +} + +bool TlsStream::WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) { + if (!impl_) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + return impl_->WriteNonBlocking(buffer, size_to_write, size_written, error); +} + +bool TlsStream::FlushBlocking(ErrorPtr* error) { + if (!impl_) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + return impl_->Flush(error); +} + +bool TlsStream::CloseBlocking(ErrorPtr* error) { + if (impl_ && !impl_->Close(error)) + return false; + impl_.reset(); + return true; +} + +bool TlsStream::WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) { + if (!impl_) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + return impl_->WaitForData(mode, callback, error); +} + +bool TlsStream::WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) { + if (!impl_) + return stream_utils::ErrorStreamClosed(FROM_HERE, error); + return impl_->WaitForDataBlocking(in_mode, timeout, out_mode, error); +} + +void TlsStream::CancelPendingAsyncOperations() { + if (impl_) + impl_->CancelPendingAsyncOperations(); + Stream::CancelPendingAsyncOperations(); +} + +} // namespace brillo diff --git a/brillo/streams/tls_stream.h b/brillo/streams/tls_stream.h new file mode 100644 index 0000000..5513eb5 --- /dev/null +++ b/brillo/streams/tls_stream.h @@ -0,0 +1,84 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STREAMS_TLS_STREAM_H_ +#define LIBCHROMEOS_BRILLO_STREAMS_TLS_STREAM_H_ + +#include +#include + +#include +#include +#include +#include + +namespace brillo { + +// This class provides client-side TLS stream that performs handshake with the +// server and established a secure communication channel which can be used +// by performing read/write operations on this stream. Both synchronous and +// asynchronous I/O is supported. +// The underlying socket stream must already be created and connected to the +// destination server and passed in TlsStream::Connect() method as |socket|. +class BRILLO_EXPORT TlsStream : public Stream { + public: + ~TlsStream() override; + + // Perform a TLS handshake and establish secure connection over |socket|. + // Calls |callback| when successful and passes the instance of TlsStream + // as an argument. In case of an error, |error_callback| is called. + // |host| must specify the expected remote host (server) name. + static void Connect( + StreamPtr socket, + const std::string& host, + const base::Callback& success_callback, + const Stream::ErrorCallback& error_callback); + + // Overrides from Stream: + bool IsOpen() const override; + bool CanRead() const override { return true; } + bool CanWrite() const override { return true; } + bool CanSeek() const override { return false; } + bool CanGetSize() const override { return false; } + uint64_t GetSize() const override { return 0; } + bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; + uint64_t GetRemainingSize() const override { return 0; } + uint64_t GetPosition() const override { return 0; } + bool Seek(int64_t offset, + Whence whence, + uint64_t* new_position, + ErrorPtr* error) override; + bool ReadNonBlocking(void* buffer, + size_t size_to_read, + size_t* size_read, + bool* end_of_stream, + ErrorPtr* error) override; + bool WriteNonBlocking(const void* buffer, + size_t size_to_write, + size_t* size_written, + ErrorPtr* error) override; + bool FlushBlocking(ErrorPtr* error) override; + bool CloseBlocking(ErrorPtr* error) override; + bool WaitForData(AccessMode mode, + const base::Callback& callback, + ErrorPtr* error) override; + bool WaitForDataBlocking(AccessMode in_mode, + base::TimeDelta timeout, + AccessMode* out_mode, + ErrorPtr* error) override; + void CancelPendingAsyncOperations() override; + + private: + class TlsStreamImpl; + + // Private constructor called from TlsStream::Connect() factory method. + explicit TlsStream(std::unique_ptr impl); + + std::unique_ptr impl_; + DISALLOW_COPY_AND_ASSIGN(TlsStream); +}; + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STREAMS_TLS_STREAM_H_ diff --git a/brillo/strings/string_utils.cc b/brillo/strings/string_utils.cc new file mode 100644 index 0000000..8279a0e --- /dev/null +++ b/brillo/strings/string_utils.cc @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include + +namespace brillo { +namespace string_utils { + +std::vector Split(const std::string& str, + const std::string& delimiter, + bool trim_whitespaces, + bool purge_empty_strings) { + std::vector tokens; + if (str.empty()) + return tokens; + + for (std::string::size_type i = 0;;) { + const std::string::size_type pos = + delimiter.empty() ? (i + 1) : str.find(delimiter, i); + std::string tmp_str{str.substr(i, pos - i)}; + if (trim_whitespaces) + base::TrimWhitespaceASCII(tmp_str, base::TRIM_ALL, &tmp_str); + if (!tmp_str.empty() || !purge_empty_strings) + tokens.emplace_back(std::move(tmp_str)); + if (pos >= str.size()) + break; + i = pos + delimiter.size(); + } + return tokens; +} + +bool SplitAtFirst(const std::string& str, + const std::string& delimiter, + std::string* left_part, + std::string* right_part, + bool trim_whitespaces) { + bool delimiter_found = false; + std::string::size_type pos = str.find(delimiter); + if (pos != std::string::npos) { + *left_part = str.substr(0, pos); + *right_part = str.substr(pos + delimiter.size()); + delimiter_found = true; + } else { + *left_part = str; + right_part->clear(); + } + + if (trim_whitespaces) { + base::TrimWhitespaceASCII(*left_part, base::TRIM_ALL, left_part); + base::TrimWhitespaceASCII(*right_part, base::TRIM_ALL, right_part); + } + + return delimiter_found; +} + +std::pair SplitAtFirst(const std::string& str, + const std::string& delimiter, + bool trim_whitespaces) { + std::pair pair; + SplitAtFirst(str, delimiter, &pair.first, &pair.second, trim_whitespaces); + return pair; +} + +std::string ToString(double value) { + return base::StringPrintf("%g", value); +} + +std::string ToString(bool value) { + return value ? "true" : "false"; +} + +std::string GetBytesAsString(const std::vector& buffer) { + return std::string(buffer.begin(), buffer.end()); +} + +std::vector GetStringAsBytes(const std::string& str) { + return std::vector(str.begin(), str.end()); +} + +} // namespace string_utils +} // namespace brillo diff --git a/brillo/strings/string_utils.h b/brillo/strings/string_utils.h new file mode 100644 index 0000000..f467bf9 --- /dev/null +++ b/brillo/strings/string_utils.h @@ -0,0 +1,131 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_STRINGS_STRING_UTILS_H_ +#define LIBCHROMEOS_BRILLO_STRINGS_STRING_UTILS_H_ + +#include +#include +#include + +#include + +namespace brillo { +namespace string_utils { + +// Treats the string as a delimited list of substrings and returns the array +// of original elements of the list. +// |trim_whitespaces| causes each element to have all whitespaces trimmed off. +// |purge_empty_strings| specifies whether empty elements from the original +// string should be omitted. +BRILLO_EXPORT std::vector Split(const std::string& str, + const std::string& delimiter, + bool trim_whitespaces, + bool purge_empty_strings); +// Splits the string, trims all whitespaces, omits empty string parts. +inline std::vector Split(const std::string& str, + const std::string& delimiter) { + return Split(str, delimiter, true, true); +} +// Splits the string, omits empty string parts. +inline std::vector Split(const std::string& str, + const std::string& delimiter, + bool trim_whitespaces) { + return Split(str, delimiter, trim_whitespaces, true); +} + +// Splits the string into two pieces at the first position of the specified +// delimiter. +BRILLO_EXPORT std::pair SplitAtFirst( + const std::string& str, + const std::string& delimiter, + bool trim_whitespaces); +// Splits the string into two pieces at the first position of the specified +// delimiter. Both parts have all whitespaces trimmed off. +inline std::pair SplitAtFirst( + const std::string& str, + const std::string& delimiter) { + return SplitAtFirst(str, delimiter, true); +} + +// The following overload returns false if the delimiter was not found in the +// source string. In this case, |left_part| will be set to |str| and +// |right_part| will be empty. +BRILLO_EXPORT bool SplitAtFirst(const std::string& str, + const std::string& delimiter, + std::string* left_part, + std::string* right_part, + bool trim_whitespaces); +// Always trims the white spaces in the split parts. +inline bool SplitAtFirst(const std::string& str, + const std::string& delimiter, + std::string* left_part, + std::string* right_part) { + return SplitAtFirst(str, delimiter, left_part, right_part, true); +} + +// Joins strings into a single string separated by |delimiter|. +template +std::string JoinRange(const std::string& delimiter, + InputIterator first, + InputIterator last) { + std::string result; + if (first == last) + return result; + result = *first; + for (++first; first != last; ++first) { + result += delimiter; + result += *first; + } + return result; +} + +template +std::string Join(const std::string& delimiter, const Container& strings) { + using std::begin; + using std::end; + return JoinRange(delimiter, begin(strings), end(strings)); +} + +inline std::string Join(const std::string& delimiter, + std::initializer_list strings) { + return JoinRange(delimiter, strings.begin(), strings.end()); +} + +inline std::string Join(const std::string& delimiter, + const std::string& str1, + const std::string& str2) { + return str1 + delimiter + str2; +} + +// string_utils::ToString() is a helper function to convert any scalar type +// to a string. In most cases, it redirects the call to std::to_string with +// two exceptions: for std::string itself and for double and bool. +template +inline std::string ToString(T value) { + return std::to_string(value); +} +// Having the following overload is handy for templates where the type +// of template parameter isn't known and could be a string itself. +inline std::string ToString(std::string value) { + return value; +} +// We overload this for double because std::to_string(double) uses %f to +// format the value and I would like to use a shorter %g format instead. +BRILLO_EXPORT std::string ToString(double value); +// And the bool to be converted as true/false instead of 1/0. +BRILLO_EXPORT std::string ToString(bool value); + +// Converts a byte-array into a string. This method doesn't perform any +// data re-encoding. It just takes every byte from the buffer and appends it +// to the string as a character. +BRILLO_EXPORT std::string GetBytesAsString(const std::vector& buf); + +// Converts a string into a byte-array. Opposite of GetBytesAsString(). +BRILLO_EXPORT std::vector GetStringAsBytes(const std::string& str); + +} // namespace string_utils +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_STRINGS_STRING_UTILS_H_ diff --git a/brillo/strings/string_utils_unittest.cc b/brillo/strings/string_utils_unittest.cc new file mode 100644 index 0000000..c554e74 --- /dev/null +++ b/brillo/strings/string_utils_unittest.cc @@ -0,0 +1,163 @@ +// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include + +namespace brillo { + +TEST(StringUtils, Split) { + std::vector parts; + + parts = string_utils::Split("", ",", false, false); + EXPECT_EQ(0, parts.size()); + + parts = string_utils::Split("abc", ",", false, false); + EXPECT_EQ(1, parts.size()); + EXPECT_EQ("abc", parts[0]); + + parts = string_utils::Split(",a,bc , d, ,e, ", ",", true, true); + EXPECT_EQ(4, parts.size()); + EXPECT_EQ("a", parts[0]); + EXPECT_EQ("bc", parts[1]); + EXPECT_EQ("d", parts[2]); + EXPECT_EQ("e", parts[3]); + + parts = string_utils::Split(",a,bc , d, ,e, ", ",", false, true); + EXPECT_EQ(6, parts.size()); + EXPECT_EQ("a", parts[0]); + EXPECT_EQ("bc ", parts[1]); + EXPECT_EQ(" d", parts[2]); + EXPECT_EQ(" ", parts[3]); + EXPECT_EQ("e", parts[4]); + EXPECT_EQ(" ", parts[5]); + + parts = string_utils::Split(",a,bc , d, ,e, ", ",", true, false); + EXPECT_EQ(7, parts.size()); + EXPECT_EQ("", parts[0]); + EXPECT_EQ("a", parts[1]); + EXPECT_EQ("bc", parts[2]); + EXPECT_EQ("d", parts[3]); + EXPECT_EQ("", parts[4]); + EXPECT_EQ("e", parts[5]); + EXPECT_EQ("", parts[6]); + + parts = string_utils::Split(",a,bc , d, ,e, ", ",", false, false); + EXPECT_EQ(7, parts.size()); + EXPECT_EQ("", parts[0]); + EXPECT_EQ("a", parts[1]); + EXPECT_EQ("bc ", parts[2]); + EXPECT_EQ(" d", parts[3]); + EXPECT_EQ(" ", parts[4]); + EXPECT_EQ("e", parts[5]); + EXPECT_EQ(" ", parts[6]); + + parts = string_utils::Split("abc:=xyz", ":=", false, false); + EXPECT_EQ(2, parts.size()); + EXPECT_EQ("abc", parts[0]); + EXPECT_EQ("xyz", parts[1]); + + parts = string_utils::Split("abc", "", false, false); + EXPECT_EQ(3, parts.size()); + EXPECT_EQ("a", parts[0]); + EXPECT_EQ("b", parts[1]); + EXPECT_EQ("c", parts[2]); +} + +TEST(StringUtils, SplitAtFirst) { + std::pair pair; + + pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ":", true); + EXPECT_EQ("123", pair.first); + EXPECT_EQ("4 : 56 : 789", pair.second); + + pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ":", false); + EXPECT_EQ(" 123 ", pair.first); + EXPECT_EQ(" 4 : 56 : 789 ", pair.second); + + pair = string_utils::SplitAtFirst("", "="); + EXPECT_EQ("", pair.first); + EXPECT_EQ("", pair.second); + + pair = string_utils::SplitAtFirst("=", "="); + EXPECT_EQ("", pair.first); + EXPECT_EQ("", pair.second); + + pair = string_utils::SplitAtFirst("a=", "="); + EXPECT_EQ("a", pair.first); + EXPECT_EQ("", pair.second); + + pair = string_utils::SplitAtFirst("abc=", "="); + EXPECT_EQ("abc", pair.first); + EXPECT_EQ("", pair.second); + + pair = string_utils::SplitAtFirst("=a", "="); + EXPECT_EQ("", pair.first); + EXPECT_EQ("a", pair.second); + + pair = string_utils::SplitAtFirst("=abc=", "="); + EXPECT_EQ("", pair.first); + EXPECT_EQ("abc=", pair.second); + + pair = string_utils::SplitAtFirst("abc", "="); + EXPECT_EQ("abc", pair.first); + EXPECT_EQ("", pair.second); + + pair = string_utils::SplitAtFirst("abc:=xyz", ":="); + EXPECT_EQ("abc", pair.first); + EXPECT_EQ("xyz", pair.second); + + pair = string_utils::SplitAtFirst("abc", ""); + EXPECT_EQ("", pair.first); + EXPECT_EQ("abc", pair.second); +} + +TEST(StringUtils, Join_String) { + EXPECT_EQ("", string_utils::Join(",", {})); + EXPECT_EQ("abc", string_utils::Join(",", {"abc"})); + EXPECT_EQ("abc,,xyz", string_utils::Join(",", {"abc", "", "xyz"})); + EXPECT_EQ("abc,defg", string_utils::Join(",", {"abc", "defg"})); + EXPECT_EQ("1 : 2 : 3", string_utils::Join(" : ", {"1", "2", "3"})); + EXPECT_EQ("1:2", string_utils::Join(":", std::set{"1", "2"})); + EXPECT_EQ("1:2", string_utils::Join(":", std::vector{"1", "2"})); + EXPECT_EQ("1:2", string_utils::Join(":", std::list{"1", "2"})); + EXPECT_EQ("123", string_utils::Join("", {"1", "2", "3"})); +} + +TEST(StringUtils, Join_Pair) { + EXPECT_EQ("ab,cd", string_utils::Join(",", "ab", "cd")); + EXPECT_EQ("key = value", string_utils::Join(" = ", "key", "value")); +} + +TEST(StringUtils, GetBytesAsString) { + EXPECT_EQ("abc", string_utils::GetBytesAsString({'a', 'b', 'c'})); + EXPECT_TRUE(string_utils::GetBytesAsString({}).empty()); + auto str = string_utils::GetBytesAsString({0xFF, 0x00, 0x01, 0x7F, 0x80}); + ASSERT_EQ(5, str.size()); + EXPECT_EQ('\xFF', str[0]); + EXPECT_EQ('\x00', str[1]); + EXPECT_EQ('\x01', str[2]); + EXPECT_EQ('\x7F', str[3]); + EXPECT_EQ('\x80', str[4]); +} + +TEST(StringUtils, GetStringAsBytes) { + EXPECT_EQ((std::vector{'a', 'b', 'c'}), + string_utils::GetStringAsBytes("abc")); + EXPECT_TRUE(string_utils::GetStringAsBytes("").empty()); + auto buf = string_utils::GetStringAsBytes(std::string{"\x80\0\1\xFF", 4}); + ASSERT_EQ(4, buf.size()); + EXPECT_EQ(128, buf[0]); + EXPECT_EQ(0, buf[1]); + EXPECT_EQ(1, buf[2]); + EXPECT_EQ(255, buf[3]); +} + +} // namespace brillo diff --git a/brillo/syslog_logging.cc b/brillo/syslog_logging.cc new file mode 100644 index 0000000..045c6e3 --- /dev/null +++ b/brillo/syslog_logging.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/syslog_logging.h" + +#include + +#include + +// syslog.h and base/logging.h both try to #define LOG_INFO and LOG_WARNING. +// We need to #undef at least these two before including base/logging.h. The +// others are included to be consistent. +namespace { +const int kSyslogDebug = LOG_DEBUG; +const int kSyslogInfo = LOG_INFO; +const int kSyslogWarning = LOG_WARNING; +const int kSyslogError = LOG_ERR; +const int kSyslogCritical = LOG_CRIT; + +#undef LOG_INFO +#undef LOG_WARNING +#undef LOG_ERR +#undef LOG_CRIT +} // namespace + +#include + +static std::string s_ident; +static std::string s_accumulated; +static bool s_accumulate; +static bool s_log_to_syslog; +static bool s_log_to_stderr; +static bool s_log_header; + +static bool HandleMessage(int severity, + const char* file, + int line, + size_t message_start, + const std::string& message) { + switch (severity) { + case logging::LOG_INFO: + severity = kSyslogInfo; + break; + + case logging::LOG_WARNING: + severity = kSyslogWarning; + break; + + case logging::LOG_ERROR: + severity = kSyslogError; + break; + + case logging::LOG_FATAL: + severity = kSyslogCritical; + break; + + default: + severity = kSyslogDebug; + break; + } + + const char* str; + if (s_log_header) { + str = message.c_str(); + } else { + str = message.c_str() + message_start; + } + + if (s_log_to_syslog) + syslog(severity, "%s", str); + if (s_accumulate) + s_accumulated.append(str); + return !s_log_to_stderr && severity != kSyslogCritical; +} + +namespace brillo { +void SetLogFlags(int log_flags) { + s_log_to_syslog = (log_flags & kLogToSyslog) != 0; + s_log_to_stderr = (log_flags & kLogToStderr) != 0; + s_log_header = (log_flags & kLogHeader) != 0; +} +int GetLogFlags() { + int flags = 0; + flags |= (s_log_to_syslog) ? kLogToSyslog : 0; + flags |= (s_log_to_stderr) ? kLogToStderr : 0; + flags |= (s_log_header) ? kLogHeader : 0; + return flags; +} +void InitLog(int init_flags) { + logging::LoggingSettings settings; + settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; + logging::InitLogging(settings); + + const bool kOptionPID = false; + const bool kOptionTID = false; + const bool kOptionTimestamp = false; + const bool kOptionTickcount = false; + logging::SetLogItems( + kOptionPID, kOptionTID, kOptionTimestamp, kOptionTickcount); + logging::SetLogMessageHandler(HandleMessage); + SetLogFlags(init_flags); +} +void OpenLog(const char* ident, bool log_pid) { + s_ident = ident; + openlog(s_ident.c_str(), log_pid ? LOG_PID : 0, LOG_USER); +} +void LogToString(bool enabled) { + s_accumulate = enabled; +} +std::string GetLog() { + return s_accumulated; +} +void ClearLog() { + s_accumulated.clear(); +} +bool FindLog(const char* string) { + return s_accumulated.find(string) != std::string::npos; +} +} // namespace brillo diff --git a/brillo/syslog_logging.h b/brillo/syslog_logging.h new file mode 100644 index 0000000..d940b42 --- /dev/null +++ b/brillo/syslog_logging.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_SYSLOG_LOGGING_H_ +#define LIBCHROMEOS_BRILLO_SYSLOG_LOGGING_H_ + +#include + +#include + +namespace brillo { + +enum InitFlags { + kLogToSyslog = 1, + kLogToStderr = 2, + kLogHeader = 4, +}; + +// Initialize logging subsystem. |init_flags| is a bitfield, with bits defined +// in InitFlags above. +BRILLO_EXPORT void InitLog(int init_flags); +// Gets the current logging flags. +BRILLO_EXPORT int GetLogFlags(); +// Sets the current logging flags. +BRILLO_EXPORT void SetLogFlags(int log_flags); +// Convenience function for configuring syslog via openlog. Users +// could call openlog directly except for naming collisions between +// base/logging.h and syslog.h. Similarly users cannot pass the +// normal parameters so we pick a representative set. |log_pid| +// causes pid to be logged with |ident|. +BRILLO_EXPORT void OpenLog(const char* ident, bool log_pid); +// Start accumulating the logs to a string. This is inefficient, so +// do not set to true if large numbers of log messages are coming. +// Accumulated logs are only ever cleared when the clear function ings +// called. +BRILLO_EXPORT void LogToString(bool enabled); +// Get the accumulated logs as a string. +BRILLO_EXPORT std::string GetLog(); +// Clear the accumulated logs. +BRILLO_EXPORT void ClearLog(); +// Returns true if the accumulated log contains the given string. Useful +// for testing. +BRILLO_EXPORT bool FindLog(const char* string); + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_SYSLOG_LOGGING_H_ diff --git a/brillo/syslog_logging_unittest.cc b/brillo/syslog_logging_unittest.cc new file mode 100644 index 0000000..e852e50 --- /dev/null +++ b/brillo/syslog_logging_unittest.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +namespace brillo { + +class SyslogLoggingDeathTest : public ::testing::Test { + public: + SyslogLoggingDeathTest() {} + virtual ~SyslogLoggingDeathTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(SyslogLoggingDeathTest); +}; + +TEST_F(SyslogLoggingDeathTest, FatalLoggingIsFatal) { + int old_flags = GetLogFlags(); + SetLogFlags(kLogToStderr); + EXPECT_DEATH({ LOG(FATAL) << "First Fatality!"; }, "First Fatality!"); + // No flags == don't log to syslog, stderr, or accumulated string. + SetLogFlags(0); + // Still a fatal log message + EXPECT_DEATH({ LOG(FATAL) << "Second Fatality!"; }, "Second Fatality!"); + SetLogFlags(old_flags); +} + +} // namespace brillo diff --git a/brillo/test_helpers.h b/brillo/test_helpers.h new file mode 100644 index 0000000..f035b66 --- /dev/null +++ b/brillo/test_helpers.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_TEST_HELPERS_H_ +#define LIBCHROMEOS_BRILLO_TEST_HELPERS_H_ + +#include "gtest/gtest.h" + +#include + +#include +#include +#include +#include + +#include "brillo/syslog_logging.h" + +inline void ExpectFileEquals(const char* golden, const char* file_path) { + std::string contents; + EXPECT_TRUE(base::ReadFileToString(base::FilePath(file_path), &contents)); + EXPECT_EQ(golden, contents); +} + +inline void SetUpTests(int* argc, char** argv, bool log_to_stderr) { + base::CommandLine::Init(*argc, argv); + ::brillo::InitLog(log_to_stderr ? brillo::kLogToStderr : 0); + ::brillo::LogToString(true); + ::testing::InitGoogleTest(argc, argv); +} + +#endif // LIBCHROMEOS_BRILLO_TEST_HELPERS_H_ diff --git a/brillo/type_name_undecorate.cc b/brillo/type_name_undecorate.cc new file mode 100644 index 0000000..9a1d6a1 --- /dev/null +++ b/brillo/type_name_undecorate.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#ifdef __GNUG__ +#include +#include +#include +#endif // __GNUG__ + +namespace brillo { + +std::string UndecorateTypeName(const char* type_name) { +#ifdef __GNUG__ + // Under g++ use abi::__cxa_demangle() to undecorate the type name. + int status = 0; + + std::unique_ptr res{ + abi::__cxa_demangle(type_name, nullptr, nullptr, &status), + std::free + }; + + return (status == 0) ? res.get() : type_name; +#else + // If not compiled with g++, do nothing... + // E.g. MSVC's type_info::name() already contains undecorated name. + return type_name; +#endif +} + +} // namespace brillo diff --git a/brillo/type_name_undecorate.h b/brillo/type_name_undecorate.h new file mode 100644 index 0000000..2da657c --- /dev/null +++ b/brillo/type_name_undecorate.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_TYPE_NAME_UNDECORATE_H_ +#define LIBCHROMEOS_BRILLO_TYPE_NAME_UNDECORATE_H_ + +#include +#include + +#include + +namespace brillo { + +// Use brillo::UndecorateTypeName() to obtain human-readable type from +// the decorated/mangled type name returned by std::type_info::name(). +BRILLO_EXPORT std::string UndecorateTypeName(const char* type_name); + +// A template helper function that returns the undecorated type name for type T. +template +inline std::string GetUndecoratedTypeName() { + return UndecorateTypeName(typeid(T).name()); +} + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_TYPE_NAME_UNDECORATE_H_ diff --git a/brillo/url_utils.cc b/brillo/url_utils.cc new file mode 100644 index 0000000..eba7db8 --- /dev/null +++ b/brillo/url_utils.cc @@ -0,0 +1,166 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace { +// Given a URL string, determine where the query string starts and ends. +// URLs have schema, domain and path (along with possible user name, password +// and port number which are of no interest for us here) which could optionally +// have a query string that is separated from the path by '?'. Finally, the URL +// could also have a '#'-separated URL fragment which is usually used by the +// browser as a bookmark element. So, for example: +// http://server.com/path/to/object?k=v&foo=bar#fragment +// Here: +// http://server.com/path/to/object - is the URL of the object, +// ?k=v&foo=bar - URL query string +// #fragment - URL fragment string +// If |exclude_fragment| is true, the function returns the start character and +// the length of the query string alone. If it is false, the query string length +// will include both the query string and the fragment. +bool GetQueryStringPos(const std::string& url, + bool exclude_fragment, + size_t* query_pos, + size_t* query_len) { + size_t query_start = url.find_first_of("?#"); + if (query_start == std::string::npos) { + *query_pos = url.size(); + if (query_len) + *query_len = 0; + return false; + } + + *query_pos = query_start; + if (query_len) { + size_t query_end = url.size(); + + if (exclude_fragment) { + if (url[query_start] == '?') { + size_t pos_fragment = url.find('#', query_start); + if (pos_fragment != std::string::npos) + query_end = pos_fragment; + } else { + query_end = query_start; + } + } + *query_len = query_end - query_start; + } + return true; +} +} // anonymous namespace + +namespace brillo { + +std::string url::TrimOffQueryString(std::string* url) { + size_t query_pos; + if (!GetQueryStringPos(*url, false, &query_pos, nullptr)) + return std::string(); + std::string query_string = url->substr(query_pos); + url->resize(query_pos); + return query_string; +} + +std::string url::Combine(const std::string& url, const std::string& subpath) { + return CombineMultiple(url, {subpath}); +} + +std::string url::CombineMultiple(const std::string& url, + const std::vector& parts) { + std::string result = url; + if (!parts.empty()) { + std::string query_string = TrimOffQueryString(&result); + for (const auto& part : parts) { + if (!part.empty()) { + if (!result.empty() && result.back() != '/') + result += '/'; + size_t non_slash_pos = part.find_first_not_of('/'); + if (non_slash_pos != std::string::npos) + result += part.substr(non_slash_pos); + } + } + result += query_string; + } + return result; +} + +std::string url::GetQueryString(const std::string& url, bool remove_fragment) { + std::string query_string; + size_t query_pos, query_len; + if (GetQueryStringPos(url, remove_fragment, &query_pos, &query_len)) { + query_string = url.substr(query_pos, query_len); + } + return query_string; +} + +data_encoding::WebParamList url::GetQueryStringParameters( + const std::string& url) { + // Extract the query string and remove the leading '?'. + std::string query_string = GetQueryString(url, true); + if (!query_string.empty() && query_string.front() == '?') + query_string.erase(query_string.begin()); + return data_encoding::WebParamsDecode(query_string); +} + +std::string url::GetQueryStringValue(const std::string& url, + const std::string& name) { + return GetQueryStringValue(GetQueryStringParameters(url), name); +} + +std::string url::GetQueryStringValue(const data_encoding::WebParamList& params, + const std::string& name) { + for (const auto& pair : params) { + if (name.compare(pair.first) == 0) + return pair.second; + } + return std::string(); +} + +std::string url::RemoveQueryString(const std::string& url, + bool remove_fragment_too) { + size_t query_pos, query_len; + if (!GetQueryStringPos(url, !remove_fragment_too, &query_pos, &query_len)) + return url; + std::string result = url.substr(0, query_pos); + size_t fragment_pos = query_pos + query_len; + if (fragment_pos < url.size()) { + result += url.substr(fragment_pos); + } + return result; +} + +std::string url::AppendQueryParam(const std::string& url, + const std::string& name, + const std::string& value) { + return AppendQueryParams(url, {{name, value}}); +} + +std::string url::AppendQueryParams(const std::string& url, + const data_encoding::WebParamList& params) { + if (params.empty()) + return url; + size_t query_pos, query_len; + GetQueryStringPos(url, true, &query_pos, &query_len); + size_t fragment_pos = query_pos + query_len; + std::string result = url.substr(0, fragment_pos); + if (query_len == 0) { + result += '?'; + } else if (query_len > 1) { + result += '&'; + } + result += data_encoding::WebParamsEncode(params); + if (fragment_pos < url.size()) { + result += url.substr(fragment_pos); + } + return result; +} + +bool url::HasQueryString(const std::string& url) { + size_t query_pos, query_len; + GetQueryStringPos(url, true, &query_pos, &query_len); + return (query_len > 0); +} + +} // namespace brillo diff --git a/brillo/url_utils.h b/brillo/url_utils.h new file mode 100644 index 0000000..80dea5a --- /dev/null +++ b/brillo/url_utils.h @@ -0,0 +1,85 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_URL_UTILS_H_ +#define LIBCHROMEOS_BRILLO_URL_UTILS_H_ + +#include +#include + +#include +#include +#include +#include + +namespace brillo { + +namespace url { + +// Appends a subpath to url and delimiting then with '/' if the path doesn't +// end with it already. Also handles URLs with query parameters/fragment. +BRILLO_EXPORT std::string Combine( + const std::string& url, + const std::string& subpath) WARN_UNUSED_RESULT; +BRILLO_EXPORT std::string CombineMultiple( + const std::string& url, + const std::vector& parts) WARN_UNUSED_RESULT; + +// Removes the query string/fragment from |url| and returns the query string. +// This method actually modifies |url|. So, if you call it on this: +// http://www.test.org/?foo=bar +// it will modify |url| to "http://www.test.org/" and return "?foo=bar" +BRILLO_EXPORT std::string TrimOffQueryString(std::string* url); + +// Returns the query string, if available. +// For example, for the following URL: +// http://server.com/path/to/object?k=v&foo=bar#fragment +// Here: +// http://server.com/path/to/object - is the URL of the object, +// ?k=v&foo=bar - URL query string +// #fragment - URL fragment string +// If |remove_fragment| is true, the function returns the query string without +// the fragment. Otherwise the fragment is included as part of the result. +BRILLO_EXPORT std::string GetQueryString(const std::string& url, + bool remove_fragment); + +// Parses the query string into a set of key-value pairs. +BRILLO_EXPORT data_encoding::WebParamList GetQueryStringParameters( + const std::string& url); + +// Returns a value of the specified query parameter, or empty string if missing. +BRILLO_EXPORT std::string GetQueryStringValue( + const std::string& url, + const std::string& name); +BRILLO_EXPORT std::string GetQueryStringValue( + const data_encoding::WebParamList& params, + const std::string& name); + +// Removes the query string and/or a fragment part from URL. +// If |remove_fragment| is specified, the fragment is also removed. +// For example: +// http://server.com/path/to/object?k=v&foo=bar#fragment +// true -> http://server.com/path/to/object +// false -> http://server.com/path/to/object#fragment +BRILLO_EXPORT std::string RemoveQueryString( + const std::string& url, + bool remove_fragment) WARN_UNUSED_RESULT; + +// Appends a single query parameter to the URL. +BRILLO_EXPORT std::string AppendQueryParam( + const std::string& url, + const std::string& name, + const std::string& value) WARN_UNUSED_RESULT; +// Appends a list of query parameters to the URL. +BRILLO_EXPORT std::string AppendQueryParams( + const std::string& url, + const data_encoding::WebParamList& params) WARN_UNUSED_RESULT; + +// Checks if the URL has query parameters. +BRILLO_EXPORT bool HasQueryString(const std::string& url); + +} // namespace url +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_URL_UTILS_H_ diff --git a/brillo/url_utils_unittest.cc b/brillo/url_utils_unittest.cc new file mode 100644 index 0000000..a2603cb --- /dev/null +++ b/brillo/url_utils_unittest.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace brillo { + +TEST(UrlUtils, Combine) { + EXPECT_EQ("http://sample.org/path", + url::Combine("http://sample.org", "path")); + EXPECT_EQ("http://sample.org/path", + url::Combine("http://sample.org/", "path")); + EXPECT_EQ("path1/path2", url::Combine("", "path1/path2")); + EXPECT_EQ("path1/path2", url::Combine("path1", "path2")); + EXPECT_EQ("http://sample.org", url::Combine("http://sample.org", "")); + EXPECT_EQ("http://sample.org/path", + url::Combine("http://sample.org/", "/path")); + EXPECT_EQ("http://sample.org/path", + url::Combine("http://sample.org", "//////path")); + EXPECT_EQ("http://sample.org/", url::Combine("http://sample.org", "///")); + EXPECT_EQ("http://sample.org/obj/path1/path2", + url::Combine("http://sample.org/obj", "path1/path2")); + EXPECT_EQ("http://sample.org/obj/path1/path2#tag", + url::Combine("http://sample.org/obj#tag", "path1/path2")); + EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1&k2=v2", + url::Combine("http://sample.org/obj?k1=v1&k2=v2", "path1/path2")); + EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1#k2=v2", + url::Combine("http://sample.org/obj/?k1=v1#k2=v2", "path1/path2")); + EXPECT_EQ("http://sample.org/obj/path1/path2#tag?", + url::Combine("http://sample.org/obj#tag?", "path1/path2")); + EXPECT_EQ("path1/path2", url::CombineMultiple("", {"path1", "path2"})); + EXPECT_EQ("http://sample.org/obj/part1/part2", + url::CombineMultiple("http://sample.org", + {"obj", "", "/part1/", "part2"})); +} + +TEST(UrlUtils, GetQueryString) { + EXPECT_EQ("", url::GetQueryString("http://sample.org", false)); + EXPECT_EQ("", url::GetQueryString("http://sample.org", true)); + EXPECT_EQ("", url::GetQueryString("", false)); + EXPECT_EQ("", url::GetQueryString("", true)); + + EXPECT_EQ("?q=v&b=2#tag?2", + url::GetQueryString("http://s.com/?q=v&b=2#tag?2", false)); + EXPECT_EQ("?q=v&b=2", + url::GetQueryString("http://s.com/?q=v&b=2#tag?2", true)); + + EXPECT_EQ("#tag?a=2", url::GetQueryString("http://s.com/#tag?a=2", false)); + EXPECT_EQ("", url::GetQueryString("http://s.com/#tag?a=2", true)); + + EXPECT_EQ("?a=2&b=2", url::GetQueryString("?a=2&b=2", false)); + EXPECT_EQ("?a=2&b=2", url::GetQueryString("?a=2&b=2", true)); + + EXPECT_EQ("#s#?d#?f?#s?#d", url::GetQueryString("#s#?d#?f?#s?#d", false)); + EXPECT_EQ("", url::GetQueryString("#s#?d#?f?#s?#d", true)); +} + +TEST(UrlUtils, GetQueryStringParameters) { + auto params = url::GetQueryStringParameters( + "http://sample.org/path?k=v&&%3Dkey%3D=val%26&r#blah"); + + EXPECT_EQ(3, params.size()); + EXPECT_EQ("k", params[0].first); + EXPECT_EQ("v", params[0].second); + EXPECT_EQ("=key=", params[1].first); + EXPECT_EQ("val&", params[1].second); + EXPECT_EQ("r", params[2].first); + EXPECT_EQ("", params[2].second); +} + +TEST(UrlUtils, GetQueryStringValue) { + std::string url = "http://url?key1=val1&&key2=val2"; + EXPECT_EQ("val1", url::GetQueryStringValue(url, "key1")); + EXPECT_EQ("val2", url::GetQueryStringValue(url, "key2")); + EXPECT_EQ("", url::GetQueryStringValue(url, "key3")); + + auto params = url::GetQueryStringParameters(url); + EXPECT_EQ("val1", url::GetQueryStringValue(params, "key1")); + EXPECT_EQ("val2", url::GetQueryStringValue(params, "key2")); + EXPECT_EQ("", url::GetQueryStringValue(params, "key3")); +} + +TEST(UrlUtils, TrimOffQueryString) { + std::string url = "http://url?key1=val1&key2=val2#fragment"; + std::string query = url::TrimOffQueryString(&url); + EXPECT_EQ("http://url", url); + EXPECT_EQ("?key1=val1&key2=val2#fragment", query); + + url = "http://url#fragment"; + query = url::TrimOffQueryString(&url); + EXPECT_EQ("http://url", url); + EXPECT_EQ("#fragment", query); + + url = "http://url"; + query = url::TrimOffQueryString(&url); + EXPECT_EQ("http://url", url); + EXPECT_EQ("", query); +} + +TEST(UrlUtils, RemoveQueryString) { + std::string url = "http://url?key1=val1&key2=val2#fragment"; + EXPECT_EQ("http://url", url::RemoveQueryString(url, true)); + EXPECT_EQ("http://url#fragment", url::RemoveQueryString(url, false)); +} + +TEST(UrlUtils, AppendQueryParam) { + std::string url = "http://server.com/path"; + url = url::AppendQueryParam(url, "param", "value"); + EXPECT_EQ("http://server.com/path?param=value", url); + url = url::AppendQueryParam(url, "param2", "v"); + EXPECT_EQ("http://server.com/path?param=value¶m2=v", url); + + url = "http://server.com/path#fragment"; + url = url::AppendQueryParam(url, "param", "value"); + EXPECT_EQ("http://server.com/path?param=value#fragment", url); + url = url::AppendQueryParam(url, "param2", "v"); + EXPECT_EQ("http://server.com/path?param=value¶m2=v#fragment", url); + + url = url::AppendQueryParam("http://server.com/path?", "param", "value"); + EXPECT_EQ("http://server.com/path?param=value", url); +} + +TEST(UrlUtils, AppendQueryParams) { + std::string url = "http://server.com/path"; + url = url::AppendQueryParams(url, {}); + EXPECT_EQ("http://server.com/path", url); + url = url::AppendQueryParams(url, {{"param", "value"}, {"q", "="}}); + EXPECT_EQ("http://server.com/path?param=value&q=%3D", url); + url += "#fr?"; + url = url::AppendQueryParams(url, {{"p", "1"}, {"s&", "\n"}}); + EXPECT_EQ("http://server.com/path?param=value&q=%3D&p=1&s%26=%0A#fr?", url); +} + +TEST(UrlUtils, HasQueryString) { + EXPECT_FALSE(url::HasQueryString("http://server.com/path")); + EXPECT_FALSE(url::HasQueryString("http://server.com/path#blah?v=1")); + EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1#blah")); + EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1")); + EXPECT_FALSE(url::HasQueryString("")); + EXPECT_TRUE(url::HasQueryString("?ss")); +} + +} // namespace brillo diff --git a/brillo/userdb_utils.cc b/brillo/userdb_utils.cc new file mode 100644 index 0000000..55c964c --- /dev/null +++ b/brillo/userdb_utils.cc @@ -0,0 +1,56 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "brillo/userdb_utils.h" + +#include +#include +#include +#include + +#include + +#include + +namespace brillo { +namespace userdb { + +bool GetUserInfo(const std::string& user, uid_t* uid, gid_t* gid) { + ssize_t buf_len = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buf_len < 0) + buf_len = 16384; // 16K should be enough?... + passwd pwd_buf; + passwd* pwd = nullptr; + std::vector buf(buf_len); + if (getpwnam_r(user.c_str(), &pwd_buf, buf.data(), buf_len, &pwd) || !pwd) { + PLOG(ERROR) << "Unable to find user " << user; + return false; + } + + if (uid) + *uid = pwd->pw_uid; + if (gid) + *gid = pwd->pw_gid; + return true; +} + +bool GetGroupInfo(const std::string& group, gid_t* gid) { + ssize_t buf_len = sysconf(_SC_GETGR_R_SIZE_MAX); + if (buf_len < 0) + buf_len = 16384; // 16K should be enough?... + struct group grp_buf; + struct group* grp = nullptr; + std::vector buf(buf_len); + if (getgrnam_r(group.c_str(), &grp_buf, buf.data(), buf_len, &grp) || !grp) { + PLOG(ERROR) << "Unable to find group " << group; + return false; + } + + if (gid) + *gid = grp->gr_gid; + return true; +} + +} // namespace userdb +} // namespace brillo diff --git a/brillo/userdb_utils.h b/brillo/userdb_utils.h new file mode 100644 index 0000000..4377119 --- /dev/null +++ b/brillo/userdb_utils.h @@ -0,0 +1,32 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_USERDB_UTILS_H_ +#define LIBCHROMEOS_BRILLO_USERDB_UTILS_H_ + +#include + +#include + +#include +#include +#include + +namespace brillo { +namespace userdb { + +// Looks up the UID and GID corresponding to |user|. Returns true on success. +// Passing nullptr for |uid| or |gid| causes them to be ignored. +BRILLO_EXPORT bool GetUserInfo( + const std::string& user, uid_t* uid, gid_t* gid) WARN_UNUSED_RESULT; + +// Looks up the GID corresponding to |group|. Returns true on success. +// Passing nullptr for |gid| causes it to be ignored. +BRILLO_EXPORT bool GetGroupInfo( + const std::string& group, gid_t* gid) WARN_UNUSED_RESULT; + +} // namespace userdb +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_USERDB_UTILS_H_ diff --git a/brillo/variant_dictionary.h b/brillo/variant_dictionary.h new file mode 100644 index 0000000..c934455 --- /dev/null +++ b/brillo/variant_dictionary.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBCHROMEOS_BRILLO_VARIANT_DICTIONARY_H_ +#define LIBCHROMEOS_BRILLO_VARIANT_DICTIONARY_H_ + +#include +#include + +#include +#include + +namespace brillo { + +using VariantDictionary = std::map; + +// GetVariantValueOrDefault tries to retrieve the named key from the dictionary +// and convert it to the type T. If the value does not exist, or the type +// conversion fails, the default value of type T is returned. +template +const T GetVariantValueOrDefault(const VariantDictionary& dictionary, + const std::string& key) { + VariantDictionary::const_iterator it = dictionary.find(key); + if (it == dictionary.end()) { + return T(); + } + return it->second.TryGet(); +} + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_VARIANT_DICTIONARY_H_ diff --git a/brillo/variant_dictionary_unittest.cc b/brillo/variant_dictionary_unittest.cc new file mode 100644 index 0000000..73ead2c --- /dev/null +++ b/brillo/variant_dictionary_unittest.cc @@ -0,0 +1,26 @@ +// Copyright 2015 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include +#include +#include + +using brillo::VariantDictionary; +using brillo::GetVariantValueOrDefault; + +TEST(VariantDictionary, GetVariantValueOrDefault) { + VariantDictionary dictionary; + dictionary.emplace("a", 1); + dictionary.emplace("b", "string"); + + // Test values that are present in the VariantDictionary. + EXPECT_EQ(1, GetVariantValueOrDefault(dictionary, "a")); + EXPECT_EQ("string", GetVariantValueOrDefault(dictionary, "b")); + + // Test that missing keys result in defaults. + EXPECT_EQ("", GetVariantValueOrDefault(dictionary, "missing")); + EXPECT_EQ(0, GetVariantValueOrDefault(dictionary, "missing")); +} diff --git a/chromeos/any.cc b/chromeos/any.cc deleted file mode 100644 index 4cedff6..0000000 --- a/chromeos/any.cc +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { - -Any::Any() { -} - -Any::Any(const Any& rhs) : data_buffer_(rhs.data_buffer_) { -} - -// NOLINTNEXTLINE(build/c++11) -Any::Any(Any&& rhs) : data_buffer_(std::move(rhs.data_buffer_)) { -} - -Any::~Any() { -} - -Any& Any::operator=(const Any& rhs) { - data_buffer_ = rhs.data_buffer_; - return *this; -} - -// NOLINTNEXTLINE(build/c++11) -Any& Any::operator=(Any&& rhs) { - data_buffer_ = std::move(rhs.data_buffer_); - return *this; -} - -bool Any::operator==(const Any& rhs) const { - // Make sure both objects contain data of the same type. - if (GetType() != rhs.GetType()) - return false; - - if (IsEmpty()) - return true; - - return data_buffer_.GetDataPtr()->CompareEqual(rhs.data_buffer_.GetDataPtr()); -} - -const std::type_info& Any::GetType() const { - if (!IsEmpty()) - return data_buffer_.GetDataPtr()->GetType(); - - struct NullType {}; // Special helper type representing an empty variant. - return typeid(NullType); -} - -void Any::Swap(Any& other) { - std::swap(data_buffer_, other.data_buffer_); -} - -bool Any::IsEmpty() const { - return data_buffer_.IsEmpty(); -} - -void Any::Clear() { - data_buffer_.Clear(); -} - -bool Any::IsConvertibleToInteger() const { - return !IsEmpty() && data_buffer_.GetDataPtr()->IsConvertibleToInteger(); -} - -intmax_t Any::GetAsInteger() const { - CHECK(!IsEmpty()) << "Must not be called on an empty Any"; - return data_buffer_.GetDataPtr()->GetAsInteger(); -} - -void Any::AppendToDBusMessageWriter(dbus::MessageWriter* writer) const { - CHECK(!IsEmpty()) << "Must not be called on an empty Any"; - data_buffer_.GetDataPtr()->AppendToDBusMessage(writer); -} - -} // namespace chromeos diff --git a/chromeos/any.h b/chromeos/any.h deleted file mode 100644 index 68ff996..0000000 --- a/chromeos/any.h +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This is an implementation of a "true" variant class in C++. -// The chromeos::Any class can hold any C++ type, but both the setter and -// getter sites need to know the actual type of data. -// Note that C-style arrays when stored in Any are reduced to simple -// data pointers. Any will not copy a contents of the array. -// const int data[] = [1,2,3]; -// Any v(data); // stores const int*, effectively "Any v(&data[0]);" - -// chromeos::Any is a value type. Which means, the data is copied into it -// and Any owns it. The owned object (stored by value) will be destroyed -// when Any is cleared or reassigned. The contained value type must be -// copy-constructible. You can also store pointers and references to objects. -// Storing pointers is trivial. In order to store a reference, you can -// use helper functions std::ref() and std::cref() to create non-const and -// const references respectively. In such a case, the type of contained data -// will be std::reference_wrapper. See 'References' unit tests in -// any_unittest.cc for examples. - -#ifndef LIBCHROMEOS_CHROMEOS_ANY_H_ -#define LIBCHROMEOS_CHROMEOS_ANY_H_ - -#include - -#include - -#include -#include - -namespace dbus { -class MessageWriter; -} // namespace dbus - -namespace chromeos { - -class CHROMEOS_EXPORT Any final { - public: - Any(); // Do not inline to hide internal_details::Buffer from export table. - // Standard copy/move constructors. This is a value-class container - // that must be copy-constructible and movable. The copy constructors - // should not be marked as explicit. - Any(const Any& rhs); - Any(Any&& rhs); // NOLINT(build/c++11) - // Typed constructor that stores a value of type T in the Any. - template - inline Any(T value) { // NOLINT(runtime/explicit) - data_buffer_.Assign(std::move(value)); - } - - // Not declaring the destructor as virtual since this is a sealed class - // and there is no need to introduce a virtual table to it. - ~Any(); - - // Assignment operators. - Any& operator=(const Any& rhs); - Any& operator=(Any&& rhs); // NOLINT(build/c++11) - template - inline Any& operator=(T value) { - data_buffer_.Assign(std::move(value)); - return *this; - } - - // Compares the contents of two Any objects for equality. Note that the - // contained type must be equality-comparable (must have operator== defined). - // If operator==() is not available for contained type, comparison operation - // always returns false (as if the data were different). - bool operator==(const Any& rhs) const; - inline bool operator!=(const Any& rhs) const { return !operator==(rhs); } - - // Checks if the given type DestType can be obtained from the Any. - // For example, to check if Any has a 'double' value in it: - // any.IsTypeCompatible() - template - bool IsTypeCompatible() const { - // Make sure the requested type DestType conforms to the storage - // requirements of Any. We always store the data by value, which means we - // strip away any references as well as cv-qualifiers. So, if the user - // stores "const int&", we actually store just an "int". - // When calling IsTypeCompatible, we need to do a similar "type cleansing" - // to make sure the requested type matches the type of data actually stored, - // so this "canonical" type is used for type checking below. - using CanonicalDestType = typename std::decay::type; - const std::type_info& ContainedTypeId = GetType(); - if (typeid(CanonicalDestType) == ContainedTypeId) - return true; - - if (!std::is_pointer::value) - return false; - - // If asking for a const pointer from a variant containing non-const - // pointer, still satisfy the request. So, we need to remove the pointer - // specification first, then strip the const/volatile qualifiers, then - // re-add the pointer back, so "const int*" would become "int*". - using NonPointer = typename std::remove_pointer::type; - using CanonicalDestTypeNoConst = typename std::add_pointer< - typename std::remove_const::type>::type; - using CanonicalDestTypeNoVolatile = typename std::add_pointer< - typename std::remove_volatile::type>::type; - using CanonicalDestTypeNoConstOrVolatile = typename std::add_pointer< - typename std::remove_cv::type>::type; - - return typeid(CanonicalDestTypeNoConst) == ContainedTypeId || - typeid(CanonicalDestTypeNoVolatile) == ContainedTypeId || - typeid(CanonicalDestTypeNoConstOrVolatile) == ContainedTypeId; - } - - // Returns immutable data contained in Any. - // Aborts if Any doesn't contain a value of type T, or trivially - // convertible to/compatible with it. - template - const T& Get() const { - CHECK(IsTypeCompatible()) - << "Requesting value of type '" << GetUndecoratedTypeName() - << "' from variant containing '" << UndecorateTypeName(GetType().name()) - << "'"; - return data_buffer_.GetData(); - } - - // Returns a copy of data in Any and returns true when that data is - // compatible with T. Returns false if contained data is incompatible. - template - bool GetValue(T* value) const { - if (!IsTypeCompatible()) { - return false; - } - *value = Get(); - return true; - } - - // Returns a pointer to mutable value of type T contained within Any. - // No data copying is made, the data pointed to is still owned by Any. - // If Any doesn't contain a value of type T, or trivially - // convertible/compatible to/with it, then it returns nullptr. - template - T* GetPtr() { - if (!IsTypeCompatible()) - return nullptr; - return &(data_buffer_.GetData()); - } - - // Returns a copy of the data contained in Any. - // If the Any doesn't contain a compatible value, the provided default - // |def_val| is returned instead. - template - T TryGet(typename std::decay::type const& def_val) const { - if (!IsTypeCompatible()) - return def_val; - return data_buffer_.GetData(); - } - - // A convenience specialization of the above function where the default - // value of type T is returned in case the underlying Get() fails. - template - T TryGet() const { - return TryGet(typename std::decay::type()); - } - - // Returns the type information about the contained data. For most cases, - // instead of using this function, you should be calling IsTypeCompatible<>(). - const std::type_info& GetType() const; - // Swaps the value of this object with that of |other|. - void Swap(Any& other); - // Checks if Any is empty, that is, not containing a value of any type. - bool IsEmpty() const; - // Clears the Any and destroys any contained object. Makes it empty. - void Clear(); - // Checks if Any contains a type convertible to integer. - // Any type that match std::is_integral and std::is_enum is accepted. - // That includes signed and unsigned char, short, int, long, etc as well as - // 'bool' and enumerated types. - // For 'integer' type, you can call GetAsInteger to do implicit type - // conversion to intmax_t. - bool IsConvertibleToInteger() const; - // For integral types and enums contained in the Any, get the integer value - // of data. This is a useful function to obtain an integer value when - // any can possibly have unspecified integer, such as 'short', 'unsigned long' - // and so on. - intmax_t GetAsInteger() const; - // Writes the contained data to D-Bus message writer, if the appropriate - // serialization method for contained data of the given type is provided - // (an appropriate specialization of AppendValueToWriter() is available). - // Returns false if the Any is empty or if there is no serialization method - // defined for the contained data. - void AppendToDBusMessageWriter(dbus::MessageWriter* writer) const; - - private: - // The data buffer for contained object. - internal_details::Buffer data_buffer_; -}; - -} // namespace chromeos - -namespace std { - -// Specialize std::swap() algorithm for chromeos::Any class. -inline void swap(chromeos::Any& lhs, chromeos::Any& rhs) { - lhs.Swap(rhs); -} - -} // namespace std - -#endif // LIBCHROMEOS_CHROMEOS_ANY_H_ diff --git a/chromeos/any_internal_impl.h b/chromeos/any_internal_impl.h deleted file mode 100644 index dc4265a..0000000 --- a/chromeos/any_internal_impl.h +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Internal implementation of chromeos::Any class. - -#ifndef LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_ -#define LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_ - -#include -#include -#include - -#include -#include -#include - -namespace chromeos { - -namespace internal_details { - -// An extension to std::is_convertible to allow conversion from an enum to -// an integral type which std::is_convertible does not indicate as supported. -template -struct IsConvertible - : public std::integral_constant< - bool, - std::is_convertible::value || - (std::is_enum::value && std::is_integral::value)> {}; - -// TryConvert is a helper function that does a safe compile-time conditional -// type cast between data types that may not be always convertible. -// From and To are the source and destination types. -// The function returns true if conversion was possible/successful. -template -inline typename std::enable_if::value, bool>::type -TryConvert(const From& in, To* out) { - *out = static_cast(in); - return true; -} -template -inline typename std::enable_if::value, bool>::type -TryConvert(const From& in, To* out) { - return false; -} - -////////////////////////////////////////////////////////////////////////////// -// Provide a way to compare values of unspecified types without compiler errors -// when no operator==() is provided for a given type. This is important to -// allow Any class to have operator==(), yet still allowing arbitrary types -// (not necessarily comparable) to be placed inside Any without resulting in -// compile-time error. -// -// We achieve this in two ways. First, we provide a IsEqualityComparable -// class that can be used in compile-time conditions to determine if there is -// operator==() defined that takes values of type T (or which can be implicitly -// converted to type T). Secondly, this allows us to specialize a helper -// compare function EqCompare(v1, v2) to use operator==() for types that -// are comparable, and just return false for those that are not. -// -// IsEqualityComparableHelper is a helper class for implementing an -// an STL-compatible IsEqualityComparable containing a Boolean member |value| -// which evaluates to true for comparable types and false otherwise. -template -struct IsEqualityComparableHelper { - struct IntWrapper { - // A special structure that provides a constructor that takes an int. - // This way, an int argument passed to a function will be favored over - // IntWrapper when both overloads are provided. - // Also this constructor must NOT be explicit. - // NOLINTNEXTLINE(runtime/explicit) - IntWrapper(int dummy) {} // do nothing - }; - - // Here is an obscure trick to determine if a type U has operator==(). - // We are providing two function prototypes for TriggerFunction. One that - // takes an argument of type IntWrapper (which is implicitly convertible from - // an int), and returns an std::false_type. This is a fall-back mechanism. - template - static std::false_type TriggerFunction(IntWrapper dummy); - - // The second overload of TriggerFunction takes an int (explicitly) and - // returns std::true_type. If both overloads are available, this one will be - // chosen when referencing it as TriggerFunction(0), since it is a better - // (more specific) match. - // - // However this overload is available only for types that support operator==. - // This is achieved by employing SFINAE mechanism inside a template function - // overload that refers to operator==() for two values of types U&. This is - // used inside decltype(), so no actual code is executed. If the types - // are not comparable, reference to "==" would fail and the compiler will - // simply ignore this overload due to SFIANE. - // - // The final little trick used here is the reliance on operator comma inside - // the decltype() expression. The result of the expression is always - // std::true_type(). The expression on the left of comma is just evaluated and - // discarded. If it evaluates successfully (i.e. the type has operator==), the - // return value of the function is set to be std::true_value. If it fails, - // the whole function prototype is discarded and is not available in the - // IsEqualityComparableHelper class. - // - // Here we use std::declval() to make sure we have operator==() that takes - // lvalue references to type U which is not necessarily default-constructible. - template - static decltype((std::declval() == std::declval()), std::true_type()) - TriggerFunction(int dummy); - - // Finally, use the return type of the overload of TriggerFunction that - // matches the argument (int) to be aliased to type |type|. If T is - // comparable, there will be two overloads and the more specific (int) will - // be chosen which returns std::true_value. If the type is non-comparable, - // there will be only one version of TriggerFunction available which - // returns std::false_value. - using type = decltype(TriggerFunction(0)); -}; - -// IsEqualityComparable is simply a class that derives from either -// std::true_value, if type T is comparable, or from std::false_value, if the -// type is non-comparable. We just use |type| alias from -// IsEqualityComparableHelper as the base class. -template -struct IsEqualityComparable : IsEqualityComparableHelper::type {}; - -// EqCompare() overload for non-comparable types. Always returns false. -template -inline typename std::enable_if::value, bool>::type -EqCompare(const T& v1, const T& v2) { - return false; -} - -// EqCompare overload for comparable types. Calls operator==(v1, v2) to compare. -template -inline typename std::enable_if::value, bool>::type -EqCompare(const T& v1, const T& v2) { - return (v1 == v2); -} - -////////////////////////////////////////////////////////////////////////////// - -class Buffer; // Forward declaration of data buffer container. - -// Abstract base class for contained variant data. -struct Data { - virtual ~Data() {} - // Returns the type information for the contained data. - virtual const std::type_info& GetType() const = 0; - // Copies the contained data to the output |buffer|. - virtual void CopyTo(Buffer* buffer) const = 0; - // Moves the contained data to the output |buffer|. - virtual void MoveTo(Buffer* buffer) = 0; - // Checks if the contained data is an integer type (not necessarily an 'int'). - virtual bool IsConvertibleToInteger() const = 0; - // Gets the contained integral value as an integer. - virtual intmax_t GetAsInteger() const = 0; - // Writes the contained value to the D-Bus message buffer. - virtual void AppendToDBusMessage(dbus::MessageWriter* writer) const = 0; - // Compares if the two data containers have objects of the same value. - virtual bool CompareEqual(const Data* other_data) const = 0; -}; - -// Concrete implementation of variant data of type T. -template -struct TypedData : public Data { - explicit TypedData(const T& value) : value_(value) {} - // NOLINTNEXTLINE(build/c++11) - explicit TypedData(T&& value) : value_(std::move(value)) {} - - const std::type_info& GetType() const override { return typeid(T); } - void CopyTo(Buffer* buffer) const override; - void MoveTo(Buffer* buffer) override; - bool IsConvertibleToInteger() const override { - return std::is_integral::value || std::is_enum::value; - } - intmax_t GetAsInteger() const override { - intmax_t int_val = 0; - bool converted = TryConvert(value_, &int_val); - CHECK(converted) << "Unable to convert value of type '" - << GetUndecoratedTypeName() << "' to integer"; - return int_val; - } - - template - static typename std::enable_if::value>::type - AppendValueHelper(dbus::MessageWriter* writer, const U& value) { - chromeos::dbus_utils::AppendValueToWriterAsVariant(writer, value); - } - template - static typename std::enable_if::value>::type - AppendValueHelper(dbus::MessageWriter* writer, const U& value) { - LOG(FATAL) << "Type '" << GetUndecoratedTypeName() - << "' is not supported by D-Bus"; - } - - void AppendToDBusMessage(dbus::MessageWriter* writer) const override { - return AppendValueHelper(writer, value_); - } - - bool CompareEqual(const Data* other_data) const override { - return EqCompare(value_, - static_cast*>(other_data)->value_); - } - - // Special methods to copy/move data of the same type - // without reallocating the buffer. - void FastAssign(const T& source) { value_ = source; } - // NOLINTNEXTLINE(build/c++11) - void FastAssign(T&& source) { value_ = std::move(source); } - - T value_; -}; - -// Buffer class that stores the contained variant data. -// To improve performance and reduce memory fragmentation, small variants -// are stored in pre-allocated memory buffers that are part of the Any class. -// If the memory requirements are larger than the set limit or the type is -// non-trivially copyable, then the contained class is allocated in a separate -// memory block and the pointer to that memory is contained within this memory -// buffer class. -class Buffer final { - public: - enum StorageType { kExternal, kContained }; - Buffer() : external_ptr_(nullptr), storage_(kExternal) {} - ~Buffer() { Clear(); } - - Buffer(const Buffer& rhs) : Buffer() { rhs.CopyTo(this); } - // NOLINTNEXTLINE(build/c++11) - Buffer(Buffer&& rhs) : Buffer() { rhs.MoveTo(this); } - Buffer& operator=(const Buffer& rhs) { - rhs.CopyTo(this); - return *this; - } - // NOLINTNEXTLINE(build/c++11) - Buffer& operator=(Buffer&& rhs) { - rhs.MoveTo(this); - return *this; - } - - // Returns the underlying pointer to contained data. Uses either the pointer - // or the raw data depending on |storage_| type. - inline Data* GetDataPtr() { - return (storage_ == kExternal) ? external_ptr_ - : reinterpret_cast(contained_buffer_); - } - inline const Data* GetDataPtr() const { - return (storage_ == kExternal) - ? external_ptr_ - : reinterpret_cast(contained_buffer_); - } - - // Destroys the contained object (and frees memory if needed). - void Clear() { - Data* data = GetDataPtr(); - if (storage_ == kExternal) { - delete data; - } else { - // Call the destructor manually, since the object was constructed inline - // in the pre-allocated buffer. We still need to call the destructor - // to free any associated resources, but we can't call delete |data| here. - data->~Data(); - } - external_ptr_ = nullptr; - storage_ = kExternal; - } - - // Stores a value of type T. - template - void Assign(T&& value) { // NOLINT(build/c++11) - using Type = typename std::decay::type; - using DataType = TypedData; - Data* ptr = GetDataPtr(); - if (ptr && ptr->GetType() == typeid(Type)) { - // We assign the data to the variant container, which already - // has the data of the same type. Do fast copy/move with no memory - // reallocation. - DataType* typed_ptr = static_cast(ptr); - // NOLINTNEXTLINE(build/c++11) - typed_ptr->FastAssign(std::forward(value)); - } else { - Clear(); - // TODO(avakulenko): [see crbug.com/379833] - // Unfortunately, GCC doesn't support std::is_trivially_copyable yet, - // so using std::is_trivial instead, which is a bit more restrictive. - // Once GCC has support for is_trivially_copyable, update the following. - if (!std::is_trivial::value || - sizeof(DataType) > sizeof(contained_buffer_)) { - // If it is too big or not trivially copyable, allocate it separately. - // NOLINTNEXTLINE(build/c++11) - external_ptr_ = new DataType(std::forward(value)); - storage_ = kExternal; - } else { - // Otherwise just use the pre-allocated buffer. - DataType* address = reinterpret_cast(contained_buffer_); - // Make sure we still call the copy/move constructor. - // Call the constructor manually by using placement 'new'. - // NOLINTNEXTLINE(build/c++11) - new (address) DataType(std::forward(value)); - storage_ = kContained; - } - } - } - - // Helper methods to retrieve a reference to contained data. - // These assume that type checking has already been performed by Any - // so the type cast is valid and will succeed. - template - const T& GetData() const { - using DataType = internal_details::TypedData::type>; - return static_cast(GetDataPtr())->value_; - } - template - T& GetData() { - using DataType = internal_details::TypedData::type>; - return static_cast(GetDataPtr())->value_; - } - - // Returns true if the buffer has no contained data. - bool IsEmpty() const { - return (storage_ == kExternal && external_ptr_ == nullptr); - } - - // Copies the data from the current buffer into the |destination|. - void CopyTo(Buffer* destination) const { - if (IsEmpty()) { - destination->Clear(); - } else { - GetDataPtr()->CopyTo(destination); - } - } - - // Moves the data from the current buffer into the |destination|. - void MoveTo(Buffer* destination) { - if (IsEmpty()) { - destination->Clear(); - } else { - if (storage_ == kExternal) { - destination->Clear(); - destination->storage_ = kExternal; - destination->external_ptr_ = external_ptr_; - external_ptr_ = nullptr; - } else { - GetDataPtr()->MoveTo(destination); - } - } - } - - union { - // |external_ptr_| is a pointer to a larger object allocated in - // a separate memory block. - Data* external_ptr_; - // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects. - // Pre-allocate enough memory to store objects as big as "double". - unsigned char contained_buffer_[sizeof(TypedData)]; - }; - // Depending on a value of |storage_|, either |external_ptr_| or - // |contained_buffer_| above is used to get a pointer to memory containing - // the variant data. - StorageType storage_; // Declare after the union to eliminate member padding. -}; - -template -void TypedData::CopyTo(Buffer* buffer) const { - buffer->Assign(value_); -} -template -void TypedData::MoveTo(Buffer* buffer) { - buffer->Assign(std::move(value_)); -} - -} // namespace internal_details - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_ diff --git a/chromeos/any_internal_impl_unittest.cc b/chromeos/any_internal_impl_unittest.cc deleted file mode 100644 index da27020..0000000 --- a/chromeos/any_internal_impl_unittest.cc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -using chromeos::internal_details::Buffer; - -TEST(Buffer, Empty) { - Buffer buffer; - EXPECT_TRUE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kExternal, buffer.storage_); - EXPECT_EQ(nullptr, buffer.GetDataPtr()); -} - -TEST(Buffer, Store_Int) { - Buffer buffer; - buffer.Assign(2); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kContained, buffer.storage_); - EXPECT_EQ(typeid(int), buffer.GetDataPtr()->GetType()); -} - -TEST(Buffer, Store_Double) { - Buffer buffer; - buffer.Assign(2.3); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kContained, buffer.storage_); - EXPECT_EQ(typeid(double), buffer.GetDataPtr()->GetType()); -} - -TEST(Buffer, Store_Pointers) { - Buffer buffer; - // nullptr - buffer.Assign(nullptr); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kContained, buffer.storage_); - EXPECT_EQ(typeid(std::nullptr_t), buffer.GetDataPtr()->GetType()); - - // char * - buffer.Assign("abcd"); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kContained, buffer.storage_); - EXPECT_EQ(typeid(const char*), buffer.GetDataPtr()->GetType()); - - // pointer to non-trivial object - class NonTrivial { - public: - virtual ~NonTrivial() {} - } non_trivial; - buffer.Assign(&non_trivial); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kContained, buffer.storage_); - EXPECT_EQ(typeid(NonTrivial*), buffer.GetDataPtr()->GetType()); -} - -TEST(Buffer, Store_NonTrivialObjects) { - class NonTrivial { - public: - virtual ~NonTrivial() {} - } non_trivial; - Buffer buffer; - buffer.Assign(non_trivial); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kExternal, buffer.storage_); - EXPECT_EQ(typeid(NonTrivial), buffer.GetDataPtr()->GetType()); -} - -TEST(Buffer, Store_Objects) { - Buffer buffer; - - struct Small { - double d; - } small = {}; - buffer.Assign(small); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kContained, buffer.storage_); - EXPECT_EQ(typeid(Small), buffer.GetDataPtr()->GetType()); - - struct Large { - char c[10]; - } large = {}; - buffer.Assign(large); - EXPECT_FALSE(buffer.IsEmpty()); - EXPECT_EQ(Buffer::kExternal, buffer.storage_); - EXPECT_EQ(typeid(Large), buffer.GetDataPtr()->GetType()); -} - -TEST(Buffer, Copy) { - Buffer buffer1; - Buffer buffer2; - - buffer1.Assign(30); - buffer1.CopyTo(&buffer2); - EXPECT_FALSE(buffer1.IsEmpty()); - EXPECT_FALSE(buffer2.IsEmpty()); - EXPECT_EQ(typeid(int), buffer1.GetDataPtr()->GetType()); - EXPECT_EQ(typeid(int), buffer2.GetDataPtr()->GetType()); - EXPECT_EQ(30, buffer1.GetData()); - EXPECT_EQ(30, buffer2.GetData()); - - buffer1.Assign(std::string("abc")); - buffer1.CopyTo(&buffer2); - EXPECT_FALSE(buffer1.IsEmpty()); - EXPECT_FALSE(buffer2.IsEmpty()); - EXPECT_EQ(typeid(std::string), buffer1.GetDataPtr()->GetType()); - EXPECT_EQ(typeid(std::string), buffer2.GetDataPtr()->GetType()); - EXPECT_EQ("abc", buffer1.GetData()); - EXPECT_EQ("abc", buffer2.GetData()); -} - -TEST(Buffer, Move) { - // Move operations essentially leave the source object in a state that is - // guaranteed to be safe for reuse or destruction. There is no other explicit - // guarantees on the exact state of the source after move (e.g. that the - // source Any will be Empty after the move is complete). - Buffer buffer1; - Buffer buffer2; - - buffer1.Assign(30); - buffer1.MoveTo(&buffer2); - // Contained types aren't flushed, so the source Any doesn't become empty. - // The contained value is just moved, but for scalars this just copies - // the data and any retains the actual type. - EXPECT_FALSE(buffer1.IsEmpty()); - EXPECT_FALSE(buffer2.IsEmpty()); - EXPECT_EQ(typeid(int), buffer2.GetDataPtr()->GetType()); - EXPECT_EQ(30, buffer2.GetData()); - - buffer1.Assign(std::string("abc")); - buffer1.MoveTo(&buffer2); - // External types are moved by just moving the pointer value from src to dest. - // This will make the source object effectively "Empty". - EXPECT_TRUE(buffer1.IsEmpty()); - EXPECT_FALSE(buffer2.IsEmpty()); - EXPECT_EQ(typeid(std::string), buffer2.GetDataPtr()->GetType()); - EXPECT_EQ("abc", buffer2.GetData()); -} diff --git a/chromeos/any_unittest.cc b/chromeos/any_unittest.cc deleted file mode 100644 index a1114d9..0000000 --- a/chromeos/any_unittest.cc +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include - -#include -#include - -using chromeos::Any; - -TEST(Any, Empty) { - Any val; - EXPECT_TRUE(val.IsEmpty()); - - Any val2 = val; - EXPECT_TRUE(val.IsEmpty()); - EXPECT_TRUE(val2.IsEmpty()); - - Any val3 = std::move(val); - EXPECT_TRUE(val.IsEmpty()); - EXPECT_TRUE(val3.IsEmpty()); -} - -TEST(Any, SimpleTypes) { - Any val(20); - EXPECT_FALSE(val.IsEmpty()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_EQ(20, val.Get()); - - Any val2(3.1415926); - EXPECT_FALSE(val2.IsEmpty()); - EXPECT_TRUE(val2.IsTypeCompatible()); - EXPECT_FALSE(val2.IsTypeCompatible()); - EXPECT_DOUBLE_EQ(3.1415926, val2.Get()); - - Any val3(std::string("blah")); - EXPECT_TRUE(val3.IsTypeCompatible()); - EXPECT_EQ("blah", val3.Get()); -} - -TEST(Any, Clear) { - Any val('x'); - EXPECT_FALSE(val.IsEmpty()); - EXPECT_EQ('x', val.Get()); - - val.Clear(); - EXPECT_TRUE(val.IsEmpty()); -} - -TEST(Any, Assignments) { - Any val(20); - EXPECT_EQ(20, val.Get()); - - val = 3.1415926; - EXPECT_FALSE(val.IsEmpty()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_DOUBLE_EQ(3.1415926, val.Get()); - - val = std::string("blah"); - EXPECT_EQ("blah", val.Get()); - - Any val2; - EXPECT_TRUE(val2.IsEmpty()); - val2 = val; - EXPECT_FALSE(val.IsEmpty()); - EXPECT_FALSE(val2.IsEmpty()); - EXPECT_EQ("blah", val.Get()); - EXPECT_EQ("blah", val2.Get()); - val.Clear(); - EXPECT_TRUE(val.IsEmpty()); - EXPECT_EQ("blah", val2.Get()); - val2.Clear(); - EXPECT_TRUE(val2.IsEmpty()); - - val = std::vector{100, 20, 3}; - auto v = val.Get>(); - EXPECT_EQ(100, v[0]); - EXPECT_EQ(20, v[1]); - EXPECT_EQ(3, v[2]); - - val2 = std::move(val); - EXPECT_TRUE(val.IsEmpty()); - EXPECT_TRUE(val2.IsTypeCompatible>()); - EXPECT_EQ(3, val2.Get>().size()); - - val = val2; - EXPECT_TRUE(val.IsTypeCompatible>()); - EXPECT_TRUE(val2.IsTypeCompatible>()); - EXPECT_EQ(3, val.Get>().size()); - EXPECT_EQ(3, val2.Get>().size()); -} - -TEST(Any, Enums) { - enum class Dummy { foo, bar, baz }; - Any val(Dummy::bar); - EXPECT_FALSE(val.IsEmpty()); - EXPECT_TRUE(val.IsConvertibleToInteger()); - EXPECT_EQ(Dummy::bar, val.Get()); - EXPECT_EQ(1, val.GetAsInteger()); - - val = Dummy::baz; - EXPECT_EQ(2, val.GetAsInteger()); - - val = Dummy::foo; - EXPECT_EQ(0, val.GetAsInteger()); -} - -TEST(Any, Integers) { - Any val(14); - EXPECT_TRUE(val.IsConvertibleToInteger()); - EXPECT_EQ(14, val.Get()); - EXPECT_EQ(14, val.GetAsInteger()); - - val = '\x40'; - EXPECT_TRUE(val.IsConvertibleToInteger()); - EXPECT_EQ(64, val.Get()); - EXPECT_EQ(64, val.GetAsInteger()); - - val = static_cast(65535); - EXPECT_TRUE(val.IsConvertibleToInteger()); - EXPECT_EQ(65535, val.Get()); - EXPECT_EQ(65535, val.GetAsInteger()); - - val = static_cast(0xFFFFFFFFFFFFFFFFULL); - EXPECT_TRUE(val.IsConvertibleToInteger()); - EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, val.Get()); - EXPECT_EQ(-1, val.GetAsInteger()); - - val = "abc"; - EXPECT_FALSE(val.IsConvertibleToInteger()); - - int a = 5; - val = &a; - EXPECT_FALSE(val.IsConvertibleToInteger()); -} - -TEST(Any, Pointers) { - Any val("abc"); // const char* - EXPECT_FALSE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_FALSE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_STREQ("abc", val.Get()); - - int a = 10; - val = &a; - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_EQ(10, *val.Get()); - *val.Get() = 3; - EXPECT_EQ(3, a); -} - -TEST(Any, Arrays) { - // The following test are here to validate the array-to-pointer decay rules. - // Since Any does not store the contents of a C-style array, just a pointer - // to the data, putting array data into Any could be dangerous. - // Make sure the array's lifetime exceeds that of an Any containing the - // pointer to the array data. - // If you want to store the array with data, use corresponding value types - // such as std::vector or a struct containing C-style array as a member. - - int int_array[] = {1, 2, 3}; // int* - Any val = int_array; - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_EQ(3, val.Get()[2]); - - const int const_int_array[] = {10, 20, 30}; // const int* - val = const_int_array; - EXPECT_FALSE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_FALSE(val.IsTypeCompatible()); - EXPECT_TRUE(val.IsTypeCompatible()); - EXPECT_EQ(30, val.Get()[2]); -} - -TEST(Any, References) { - // Passing references to object via Any might be error-prone or the - // semantics could be unfamiliar to other developers. In many cases, - // using pointers instead of references are more conventional and easier - // to understand. Even though the cases of passing references are quite - // explicit on both storing and retrieving ends, you might want to - // use pointers instead anyway. - - int a = 5; - Any val(std::ref(a)); // int& - EXPECT_EQ(5, val.Get>().get()); - val.Get>().get() = 7; - EXPECT_EQ(7, val.Get>().get()); - EXPECT_EQ(7, a); - - Any val2(std::cref(a)); // const int& - EXPECT_EQ(7, val2.Get>().get()); - - a = 10; - EXPECT_EQ(10, val.Get>().get()); - EXPECT_EQ(10, val2.Get>().get()); -} - -TEST(Any, CustomTypes) { - struct Person { - std::string name; - int age; - }; - Any val(Person{"Jack", 40}); - Any val2 = val; - EXPECT_EQ("Jack", val.Get().name); - val.GetPtr()->name = "Joe"; - val.GetPtr()->age /= 2; - EXPECT_EQ("Joe", val.Get().name); - EXPECT_EQ(20, val.Get().age); - EXPECT_EQ("Jack", val2.Get().name); - EXPECT_EQ(40, val2.Get().age); -} - -TEST(Any, Swap) { - Any val(12); - Any val2(2.7); - EXPECT_EQ(12, val.Get()); - EXPECT_EQ(2.7, val2.Get()); - - val.Swap(val2); - EXPECT_EQ(2.7, val.Get()); - EXPECT_EQ(12, val2.Get()); - - std::swap(val, val2); - EXPECT_EQ(12, val.Get()); - EXPECT_EQ(2.7, val2.Get()); -} - -TEST(Any, TypeMismatch) { - Any val(12); - EXPECT_DEATH(val.Get(), - "Requesting value of type 'double' from variant containing " - "'int'"); - - val = std::string("123"); - EXPECT_DEATH(val.GetAsInteger(), - "Unable to convert value of type 'std::string' to integer"); - - Any empty; - EXPECT_DEATH(empty.GetAsInteger(), "Must not be called on an empty Any"); -} - -TEST(Any, TryGet) { - Any val(12); - Any empty; - EXPECT_EQ("dummy", val.TryGet("dummy")); - EXPECT_EQ(12, val.TryGet(17)); - EXPECT_EQ(17, empty.TryGet(17)); -} - -TEST(Any, Compare_Int) { - Any int1{12}; - Any int2{12}; - Any int3{20}; - EXPECT_EQ(int1, int2); - EXPECT_NE(int2, int3); -} - -TEST(Any, Compare_String) { - Any str1{std::string{"foo"}}; - Any str2{std::string{"foo"}}; - Any str3{std::string{"bar"}}; - EXPECT_EQ(str1, str2); - EXPECT_NE(str2, str3); -} - -TEST(Any, Compare_Array) { - Any vec1{std::vector{1, 2}}; - Any vec2{std::vector{1, 2}}; - Any vec3{std::vector{1, 2, 3}}; - EXPECT_EQ(vec1, vec2); - EXPECT_NE(vec2, vec3); -} - -TEST(Any, Compare_Empty) { - Any empty1; - Any empty2; - Any int1{1}; - EXPECT_EQ(empty1, empty2); - EXPECT_NE(int1, empty1); - EXPECT_NE(empty2, int1); -} - -TEST(Any, Compare_NonComparable) { - struct Person { - std::string name; - int age; - }; - Any person1(Person{"Jack", 40}); - Any person2 = person1; - Any person3(Person{"Jill", 20}); - EXPECT_NE(person1, person2); - EXPECT_NE(person1, person3); - EXPECT_NE(person2, person3); -} diff --git a/chromeos/asynchronous_signal_handler.cc b/chromeos/asynchronous_signal_handler.cc deleted file mode 100644 index 4d188c7..0000000 --- a/chromeos/asynchronous_signal_handler.cc +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/asynchronous_signal_handler.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace { -const int kInvalidDescriptor = -1; -} // namespace - -namespace chromeos { - -AsynchronousSignalHandler::AsynchronousSignalHandler() - : descriptor_(kInvalidDescriptor) { - CHECK_EQ(sigemptyset(&signal_mask_), 0) << "Failed to initialize signal mask"; - CHECK_EQ(sigemptyset(&saved_signal_mask_), 0) - << "Failed to initialize signal mask"; -} - -AsynchronousSignalHandler::~AsynchronousSignalHandler() { - if (descriptor_ != kInvalidDescriptor) { - MessageLoop::current()->CancelTask(fd_watcher_task_); - - if (IGNORE_EINTR(close(descriptor_)) != 0) - PLOG(WARNING) << "Failed to close file descriptor"; - - descriptor_ = kInvalidDescriptor; - CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, nullptr)); - } -} - -void AsynchronousSignalHandler::Init() { - CHECK_EQ(kInvalidDescriptor, descriptor_); - CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, &saved_signal_mask_)); - descriptor_ = - signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK); - CHECK_NE(kInvalidDescriptor, descriptor_); - fd_watcher_task_ = MessageLoop::current()->WatchFileDescriptor( - FROM_HERE, - descriptor_, - MessageLoop::WatchMode::kWatchRead, - true, - base::Bind(&AsynchronousSignalHandler::OnFileCanReadWithoutBlocking, - base::Unretained(this))); - CHECK(fd_watcher_task_ != MessageLoop::kTaskIdNull) - << "Watching shutdown pipe failed."; -} - -void AsynchronousSignalHandler::RegisterHandler(int signal, - const SignalHandler& callback) { - registered_callbacks_[signal] = callback; - CHECK_EQ(0, sigaddset(&signal_mask_, signal)); - UpdateSignals(); -} - -void AsynchronousSignalHandler::UnregisterHandler(int signal) { - Callbacks::iterator callback_it = registered_callbacks_.find(signal); - if (callback_it != registered_callbacks_.end()) { - registered_callbacks_.erase(callback_it); - ResetSignal(signal); - } -} - -void AsynchronousSignalHandler::OnFileCanReadWithoutBlocking() { - struct signalfd_siginfo info; - while (base::ReadFromFD(descriptor_, - reinterpret_cast(&info), sizeof(info))) { - int signal = info.ssi_signo; - Callbacks::iterator callback_it = registered_callbacks_.find(signal); - if (callback_it == registered_callbacks_.end()) { - LOG(WARNING) << "Unable to find a signal handler for signal: " << signal; - // Can happen if a signal has been called multiple time, and the callback - // asked to be unregistered the first time. - continue; - } - const SignalHandler& callback = callback_it->second; - bool must_unregister = callback.Run(info); - if (must_unregister) { - UnregisterHandler(signal); - } - } -} - -void AsynchronousSignalHandler::ResetSignal(int signal) { - CHECK_EQ(0, sigdelset(&signal_mask_, signal)); - UpdateSignals(); -} - -void AsynchronousSignalHandler::UpdateSignals() { - if (descriptor_ != kInvalidDescriptor) { - CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, nullptr)); - CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, nullptr)); - CHECK_EQ(descriptor_, - signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK)); - } -} - -} // namespace chromeos diff --git a/chromeos/asynchronous_signal_handler.h b/chromeos/asynchronous_signal_handler.h deleted file mode 100644 index 4127c11..0000000 --- a/chromeos/asynchronous_signal_handler.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_H_ -#define LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_H_ - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { -// Sets up signal handlers for registered signals, and converts signal receipt -// into a write on a pipe. Watches that pipe for data and, when some appears, -// execute the associated callback. -class CHROMEOS_EXPORT AsynchronousSignalHandler final : - public AsynchronousSignalHandlerInterface { - public: - AsynchronousSignalHandler(); - ~AsynchronousSignalHandler() override; - - using AsynchronousSignalHandlerInterface::SignalHandler; - - // Initialize the handler. - void Init(); - - // AsynchronousSignalHandlerInterface overrides. - void RegisterHandler(int signal, const SignalHandler& callback) override; - void UnregisterHandler(int signal) override; - - private: - // Called from the main loop when we can read from |descriptor_|, indicated - // that a signal was processed. - void OnFileCanReadWithoutBlocking(); - - // Controller used to manage watching of signalling pipe. - MessageLoop::TaskId fd_watcher_task_{MessageLoop::kTaskIdNull}; - - // The registered callbacks. - typedef std::map Callbacks; - Callbacks registered_callbacks_; - - // File descriptor for accepting signals indicated by |signal_mask_|. - int descriptor_; - - // A set of signals to be handled after the dispatcher is running. - sigset_t signal_mask_; - - // A copy of the signal mask before the dispatcher starts, which will be - // used to restore to the original state when the dispatcher stops. - sigset_t saved_signal_mask_; - - // Resets the given signal to its default behavior. Doesn't touch - // |registered_callbacks_|. - CHROMEOS_PRIVATE void ResetSignal(int signal); - - // Updates the set of signals that this handler listens to. - CHROMEOS_PRIVATE void UpdateSignals(); - - DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandler); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_H_ diff --git a/chromeos/asynchronous_signal_handler_interface.h b/chromeos/asynchronous_signal_handler_interface.h deleted file mode 100644 index 51e90c5..0000000 --- a/chromeos/asynchronous_signal_handler_interface.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_ -#define LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_ - -#include - -#include -#include - -namespace chromeos { - -// Sets up signal handlers for registered signals, and converts signal receipt -// into a write on a pipe. Watches that pipe for data and, when some appears, -// execute the associated callback. -class CHROMEOS_EXPORT AsynchronousSignalHandlerInterface { - public: - virtual ~AsynchronousSignalHandlerInterface() = default; - - // The callback called when a signal is received. - using SignalHandler = base::Callback; - - // Register a new handler for the given |signal|, replacing any previously - // registered handler. |callback| will be called on the thread the - // |AsynchronousSignalHandlerInterface| implementation is bound to when a - // signal is received. The received |signalfd_siginfo| will be passed to - // |callback|. |callback| must returns |true| if the signal handler must be - // unregistered, and |false| otherwise. Due to an implementation detail, you - // cannot set any sigaction flags you might be accustomed to using. This might - // matter if you hoped to use SA_NOCLDSTOP to avoid getting a SIGCHLD when a - // child process receives a SIGSTOP. - virtual void RegisterHandler(int signal, const SignalHandler& callback) = 0; - - // Unregister a previously registered handler for the given |signal|. - virtual void UnregisterHandler(int signal) = 0; -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_ diff --git a/chromeos/asynchronous_signal_handler_unittest.cc b/chromeos/asynchronous_signal_handler_unittest.cc deleted file mode 100644 index 949b02a..0000000 --- a/chromeos/asynchronous_signal_handler_unittest.cc +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/asynchronous_signal_handler.h" - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -class AsynchronousSignalHandlerTest : public ::testing::Test { - public: - AsynchronousSignalHandlerTest() {} - virtual ~AsynchronousSignalHandlerTest() {} - - virtual void SetUp() { - chromeos_loop_.SetAsCurrent(); - handler_.Init(); - } - - virtual void TearDown() {} - - bool RecordInfoAndQuit(bool response, const struct signalfd_siginfo& info) { - infos_.push_back(info); - chromeos_loop_.PostTask(FROM_HERE, chromeos_loop_.QuitClosure()); - return response; - } - - protected: - base::MessageLoopForIO base_loop_; - BaseMessageLoop chromeos_loop_{&base_loop_}; - std::vector infos_; - AsynchronousSignalHandler handler_; - - private: - DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandlerTest); -}; - -TEST_F(AsynchronousSignalHandlerTest, CheckTerm) { - handler_.RegisterHandler( - SIGTERM, - base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, - base::Unretained(this), - true)); - EXPECT_EQ(0, infos_.size()); - EXPECT_EQ(0, kill(getpid(), SIGTERM)); - - // Spin the message loop. - MessageLoop::current()->Run(); - - ASSERT_EQ(1, infos_.size()); - EXPECT_EQ(SIGTERM, infos_[0].ssi_signo); -} - -TEST_F(AsynchronousSignalHandlerTest, CheckSignalUnregistration) { - handler_.RegisterHandler( - SIGCHLD, - base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, - base::Unretained(this), - true)); - EXPECT_EQ(0, infos_.size()); - EXPECT_EQ(0, kill(getpid(), SIGCHLD)); - - // Spin the message loop. - MessageLoop::current()->Run(); - - ASSERT_EQ(1, infos_.size()); - EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo); - - EXPECT_EQ(0, kill(getpid(), SIGCHLD)); - - // Run the loop with a timeout, as no message are expected. - chromeos_loop_.PostDelayedTask(FROM_HERE, - base::Bind(&MessageLoop::BreakLoop, - base::Unretained(&chromeos_loop_)), - base::TimeDelta::FromMilliseconds(10)); - MessageLoop::current()->Run(); - - // The signal handle should have been unregistered. No new message are - // expected. - EXPECT_EQ(1, infos_.size()); -} - -TEST_F(AsynchronousSignalHandlerTest, CheckMultipleSignal) { - const uint8_t NB_SIGNALS = 5; - handler_.RegisterHandler( - SIGCHLD, - base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, - base::Unretained(this), - false)); - EXPECT_EQ(0, infos_.size()); - for (int i = 0; i < NB_SIGNALS; ++i) { - EXPECT_EQ(0, kill(getpid(), SIGCHLD)); - - // Spin the message loop. - MessageLoop::current()->Run(); - } - - ASSERT_EQ(NB_SIGNALS, infos_.size()); - for (int i = 0; i < NB_SIGNALS; ++i) { - EXPECT_EQ(SIGCHLD, infos_[i].ssi_signo); - } -} - -TEST_F(AsynchronousSignalHandlerTest, CheckChld) { - handler_.RegisterHandler( - SIGCHLD, - base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit, - base::Unretained(this), - false)); - pid_t child_pid = fork(); - if (child_pid == 0) { - _Exit(EXIT_SUCCESS); - } - - EXPECT_EQ(0, infos_.size()); - // Spin the message loop. - MessageLoop::current()->Run(); - - ASSERT_EQ(1, infos_.size()); - EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo); - EXPECT_EQ(child_pid, infos_[0].ssi_pid); - EXPECT_EQ(static_cast(CLD_EXITED), infos_[0].ssi_code); - EXPECT_EQ(EXIT_SUCCESS, infos_[0].ssi_status); -} - -} // namespace chromeos diff --git a/chromeos/backoff_entry.cc b/chromeos/backoff_entry.cc deleted file mode 100644 index f3cb1a8..0000000 --- a/chromeos/backoff_entry.cc +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -#include -#include -#include - -namespace chromeos { - -BackoffEntry::BackoffEntry(const BackoffEntry::Policy* const policy) - : policy_(policy) { - DCHECK(policy_); - Reset(); -} - -void BackoffEntry::InformOfRequest(bool succeeded) { - if (!succeeded) { - ++failure_count_; - exponential_backoff_release_time_ = CalculateReleaseTime(); - } else { - // We slowly decay the number of times delayed instead of - // resetting it to 0 in order to stay stable if we receive - // successes interleaved between lots of failures. Note that in - // the normal case, the calculated release time (in the next - // statement) will be in the past once the method returns. - if (failure_count_ > 0) - --failure_count_; - - // The reason why we are not just cutting the release time to - // ImplGetTimeNow() is on the one hand, it would unset a release - // time set by SetCustomReleaseTime and on the other we would like - // to push every request up to our "horizon" when dealing with - // multiple in-flight requests. Ex: If we send three requests and - // we receive 2 failures and 1 success. The success that follows - // those failures will not reset the release time, further - // requests will then need to wait the delay caused by the 2 - // failures. - base::TimeDelta delay; - if (policy_->always_use_initial_delay) - delay = base::TimeDelta::FromMilliseconds(policy_->initial_delay_ms); - exponential_backoff_release_time_ = std::max( - ImplGetTimeNow() + delay, exponential_backoff_release_time_); - } -} - -bool BackoffEntry::ShouldRejectRequest() const { - return exponential_backoff_release_time_ > ImplGetTimeNow(); -} - -base::TimeDelta BackoffEntry::GetTimeUntilRelease() const { - base::TimeTicks now = ImplGetTimeNow(); - if (exponential_backoff_release_time_ <= now) - return base::TimeDelta(); - return exponential_backoff_release_time_ - now; -} - -base::TimeTicks BackoffEntry::GetReleaseTime() const { - return exponential_backoff_release_time_; -} - -void BackoffEntry::SetCustomReleaseTime(const base::TimeTicks& release_time) { - exponential_backoff_release_time_ = release_time; -} - -bool BackoffEntry::CanDiscard() const { - if (policy_->entry_lifetime_ms == -1) - return false; - - base::TimeTicks now = ImplGetTimeNow(); - - int64 unused_since_ms = - (now - exponential_backoff_release_time_).InMilliseconds(); - - // Release time is further than now, we are managing it. - if (unused_since_ms < 0) - return false; - - if (failure_count_ > 0) { - // Need to keep track of failures until maximum back-off period - // has passed (since further failures can add to back-off). - return unused_since_ms >= std::max(policy_->maximum_backoff_ms, - policy_->entry_lifetime_ms); - } - - // Otherwise, consider the entry is outdated if it hasn't been used for the - // specified lifetime period. - return unused_since_ms >= policy_->entry_lifetime_ms; -} - -void BackoffEntry::Reset() { - failure_count_ = 0; - - // We leave exponential_backoff_release_time_ unset, meaning 0. We could - // initialize to ImplGetTimeNow() but because it's a virtual method it's - // not safe to call in the constructor (and the constructor calls Reset()). - // The effects are the same, i.e. ShouldRejectRequest() will return false - // right after Reset(). - exponential_backoff_release_time_ = base::TimeTicks(); -} - -base::TimeTicks BackoffEntry::ImplGetTimeNow() const { - return base::TimeTicks::Now(); -} - -base::TimeTicks BackoffEntry::CalculateReleaseTime() const { - int effective_failure_count = - std::max(0, failure_count_ - policy_->num_errors_to_ignore); - - // If always_use_initial_delay is true, it's equivalent to - // the effective_failure_count always being one greater than when it's false. - if (policy_->always_use_initial_delay) - ++effective_failure_count; - - if (effective_failure_count == 0) { - // Never reduce previously set release horizon, e.g. due to Retry-After - // header. - return std::max(ImplGetTimeNow(), exponential_backoff_release_time_); - } - - // The delay is calculated with this formula: - // delay = initial_backoff * multiply_factor^( - // effective_failure_count - 1) * Uniform(1 - jitter_factor, 1] - // Note: if the failure count is too high, |delay_ms| will become infinity - // after the exponential calculation, and then NaN after the jitter is - // accounted for. Both cases are handled by using CheckedNumeric to - // perform the conversion to integers. - double delay_ms = policy_->initial_delay_ms; - delay_ms *= pow(policy_->multiply_factor, effective_failure_count - 1); - delay_ms -= base::RandDouble() * policy_->jitter_factor * delay_ms; - - // Do overflow checking in microseconds, the internal unit of TimeTicks. - const int64_t kTimeTicksNowUs = - (ImplGetTimeNow() - base::TimeTicks()).InMicroseconds(); - base::internal::CheckedNumeric calculated_release_time_us = - delay_ms + 0.5; - calculated_release_time_us *= base::Time::kMicrosecondsPerMillisecond; - calculated_release_time_us += kTimeTicksNowUs; - - const int64_t kMaxTime = std::numeric_limits::max(); - base::internal::CheckedNumeric maximum_release_time_us = kMaxTime; - if (policy_->maximum_backoff_ms >= 0) { - maximum_release_time_us = policy_->maximum_backoff_ms; - maximum_release_time_us *= base::Time::kMicrosecondsPerMillisecond; - maximum_release_time_us += kTimeTicksNowUs; - } - - // Decide between maximum release time and calculated release time, accounting - // for overflow with both. - int64 release_time_us = std::min( - calculated_release_time_us.ValueOrDefault(kMaxTime), - maximum_release_time_us.ValueOrDefault(kMaxTime)); - - // Never reduce previously set release horizon, e.g. due to Retry-After - // header. - return std::max( - base::TimeTicks() + base::TimeDelta::FromMicroseconds(release_time_us), - exponential_backoff_release_time_); -} - -} // namespace chromeos diff --git a/chromeos/backoff_entry.h b/chromeos/backoff_entry.h deleted file mode 100644 index aeace00..0000000 --- a/chromeos/backoff_entry.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_BACKOFF_ENTRY_H_ -#define LIBCHROMEOS_CHROMEOS_BACKOFF_ENTRY_H_ - -#include -#include - -namespace chromeos { - -// Provides the core logic needed for randomized exponential back-off -// on requests to a given resource, given a back-off policy. -// -// This class is largely taken from net/base/backoff_entry.h from Chromium. -// TODO(avakulenko): Consider packaging portions of Chrome's //net functionality -// into the current libchrome library. -class CHROMEOS_EXPORT BackoffEntry { - public: - // The set of parameters that define a back-off policy. - struct Policy { - // Number of initial errors (in sequence) to ignore before applying - // exponential back-off rules. - int num_errors_to_ignore; - - // Initial delay. The interpretation of this value depends on - // always_use_initial_delay. It's either how long we wait between - // requests before backoff starts, or how much we delay the first request - // after backoff starts. - int initial_delay_ms; - - // Factor by which the waiting time will be multiplied. - double multiply_factor; - - // Fuzzing percentage. ex: 10% will spread requests randomly - // between 90%-100% of the calculated time. - double jitter_factor; - - // Maximum amount of time we are willing to delay our request, -1 - // for no maximum. - int64 maximum_backoff_ms; - - // Time to keep an entry from being discarded even when it - // has no significant state, -1 to never discard. - int64 entry_lifetime_ms; - - // If true, we always use a delay of initial_delay_ms, even before - // we've seen num_errors_to_ignore errors. Otherwise, initial_delay_ms - // is the first delay once we start exponential backoff. - // - // So if we're ignoring 1 error, we'll see (N, N, Nm, Nm^2, ...) if true, - // and (0, 0, N, Nm, ...) when false, where N is initial_backoff_ms and - // m is multiply_factor, assuming we've already seen one success. - bool always_use_initial_delay; - }; - - // Lifetime of policy must enclose lifetime of BackoffEntry. The - // pointer must be valid but is not dereferenced during construction. - explicit BackoffEntry(const Policy* const policy); - virtual ~BackoffEntry() = default; - - // Inform this item that a request for the network resource it is - // tracking was made, and whether it failed or succeeded. - void InformOfRequest(bool succeeded); - - // Returns true if a request for the resource this item tracks should - // be rejected at the present time due to exponential back-off policy. - bool ShouldRejectRequest() const; - - // Returns the absolute time after which this entry (given its present - // state) will no longer reject requests. - base::TimeTicks GetReleaseTime() const; - - // Returns the time until a request can be sent. - base::TimeDelta GetTimeUntilRelease() const; - - // Causes this object reject requests until the specified absolute time. - // This can be used to e.g. implement support for a Retry-After header. - void SetCustomReleaseTime(const base::TimeTicks& release_time); - - // Returns true if this object has no significant state (i.e. you could - // just as well start with a fresh BackoffEntry object), and hasn't - // had for Policy::entry_lifetime_ms. - bool CanDiscard() const; - - // Resets this entry to a fresh (as if just constructed) state. - void Reset(); - - // Returns the failure count for this entry. - int failure_count() const { return failure_count_; } - - protected: - // Equivalent to TimeTicks::Now(), virtual so unit tests can override. - virtual base::TimeTicks ImplGetTimeNow() const; - - private: - // Calculates when requests should again be allowed through. - base::TimeTicks CalculateReleaseTime() const; - - // Timestamp calculated by the exponential back-off algorithm at which we are - // allowed to start sending requests again. - base::TimeTicks exponential_backoff_release_time_; - - // Counts request errors; decremented on success. - int failure_count_; - - const Policy* const policy_; - - DISALLOW_COPY_AND_ASSIGN(BackoffEntry); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_BACKOFF_ENTRY_H_ diff --git a/chromeos/backoff_entry_unittest.cc b/chromeos/backoff_entry_unittest.cc deleted file mode 100644 index 27f181a..0000000 --- a/chromeos/backoff_entry_unittest.cc +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include - -using base::TimeDelta; -using base::TimeTicks; - -namespace chromeos { - -BackoffEntry::Policy base_policy = { 0, 1000, 2.0, 0.0, 20000, 2000, false }; - -class TestBackoffEntry : public BackoffEntry { - public: - explicit TestBackoffEntry(const Policy* const policy) - : BackoffEntry(policy), - now_(TimeTicks()) { - // Work around initialization in constructor not picking up - // fake time. - SetCustomReleaseTime(TimeTicks()); - } - - ~TestBackoffEntry() override {} - - TimeTicks ImplGetTimeNow() const override { return now_; } - - void set_now(const TimeTicks& now) { - now_ = now; - } - - private: - TimeTicks now_; - - DISALLOW_COPY_AND_ASSIGN(TestBackoffEntry); -}; - -TEST(BackoffEntryTest, BaseTest) { - TestBackoffEntry entry(&base_policy); - EXPECT_FALSE(entry.ShouldRejectRequest()); - EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease()); - - entry.InformOfRequest(false); - EXPECT_TRUE(entry.ShouldRejectRequest()); - EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); -} - -TEST(BackoffEntryTest, CanDiscardNeverExpires) { - BackoffEntry::Policy never_expires_policy = base_policy; - never_expires_policy.entry_lifetime_ms = -1; - TestBackoffEntry never_expires(&never_expires_policy); - EXPECT_FALSE(never_expires.CanDiscard()); - never_expires.set_now(TimeTicks() + TimeDelta::FromDays(100)); - EXPECT_FALSE(never_expires.CanDiscard()); -} - -TEST(BackoffEntryTest, CanDiscard) { - TestBackoffEntry entry(&base_policy); - // Because lifetime is non-zero, we shouldn't be able to discard yet. - EXPECT_FALSE(entry.CanDiscard()); - - // Test the "being used" case. - entry.InformOfRequest(false); - EXPECT_FALSE(entry.CanDiscard()); - - // Test the case where there are errors but we can time out. - entry.set_now( - entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1)); - EXPECT_FALSE(entry.CanDiscard()); - entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( - base_policy.maximum_backoff_ms + 1)); - EXPECT_TRUE(entry.CanDiscard()); - - // Test the final case (no errors, dependent only on specified lifetime). - entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( - base_policy.entry_lifetime_ms - 1)); - entry.InformOfRequest(true); - EXPECT_FALSE(entry.CanDiscard()); - entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( - base_policy.entry_lifetime_ms)); - EXPECT_TRUE(entry.CanDiscard()); -} - -TEST(BackoffEntryTest, CanDiscardAlwaysDelay) { - BackoffEntry::Policy always_delay_policy = base_policy; - always_delay_policy.always_use_initial_delay = true; - always_delay_policy.entry_lifetime_ms = 0; - - TestBackoffEntry entry(&always_delay_policy); - - // Because lifetime is non-zero, we shouldn't be able to discard yet. - entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); - EXPECT_TRUE(entry.CanDiscard()); - - // Even with no failures, we wait until the delay before we allow discard. - entry.InformOfRequest(true); - EXPECT_FALSE(entry.CanDiscard()); - - // Wait until the delay expires, and we can discard the entry again. - entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1000)); - EXPECT_TRUE(entry.CanDiscard()); -} - -TEST(BackoffEntryTest, CanDiscardNotStored) { - BackoffEntry::Policy no_store_policy = base_policy; - no_store_policy.entry_lifetime_ms = 0; - TestBackoffEntry not_stored(&no_store_policy); - EXPECT_TRUE(not_stored.CanDiscard()); -} - -TEST(BackoffEntryTest, ShouldIgnoreFirstTwo) { - BackoffEntry::Policy lenient_policy = base_policy; - lenient_policy.num_errors_to_ignore = 2; - - BackoffEntry entry(&lenient_policy); - - entry.InformOfRequest(false); - EXPECT_FALSE(entry.ShouldRejectRequest()); - - entry.InformOfRequest(false); - EXPECT_FALSE(entry.ShouldRejectRequest()); - - entry.InformOfRequest(false); - EXPECT_TRUE(entry.ShouldRejectRequest()); -} - -TEST(BackoffEntryTest, ReleaseTimeCalculation) { - TestBackoffEntry entry(&base_policy); - - // With zero errors, should return "now". - TimeTicks result = entry.GetReleaseTime(); - EXPECT_EQ(entry.ImplGetTimeNow(), result); - - // 1 error. - entry.InformOfRequest(false); - result = entry.GetReleaseTime(); - EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(1000), result); - EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); - - // 2 errors. - entry.InformOfRequest(false); - result = entry.GetReleaseTime(); - EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(2000), result); - EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); - - // 3 errors. - entry.InformOfRequest(false); - result = entry.GetReleaseTime(); - EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result); - EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease()); - - // 6 errors (to check it doesn't pass maximum). - entry.InformOfRequest(false); - entry.InformOfRequest(false); - entry.InformOfRequest(false); - result = entry.GetReleaseTime(); - EXPECT_EQ( - entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(20000), result); -} - -TEST(BackoffEntryTest, ReleaseTimeCalculationAlwaysDelay) { - BackoffEntry::Policy always_delay_policy = base_policy; - always_delay_policy.always_use_initial_delay = true; - always_delay_policy.num_errors_to_ignore = 2; - - TestBackoffEntry entry(&always_delay_policy); - - // With previous requests, should return "now". - TimeTicks result = entry.GetReleaseTime(); - EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease()); - - // 1 error. - entry.InformOfRequest(false); - EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); - - // 2 errors. - entry.InformOfRequest(false); - EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); - - // 3 errors, exponential backoff starts. - entry.InformOfRequest(false); - EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); - - // 4 errors. - entry.InformOfRequest(false); - EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease()); - - // 8 errors (to check it doesn't pass maximum). - entry.InformOfRequest(false); - entry.InformOfRequest(false); - entry.InformOfRequest(false); - entry.InformOfRequest(false); - result = entry.GetReleaseTime(); - EXPECT_EQ(TimeDelta::FromMilliseconds(20000), entry.GetTimeUntilRelease()); -} - -TEST(BackoffEntryTest, ReleaseTimeCalculationWithJitter) { - for (int i = 0; i < 10; ++i) { - BackoffEntry::Policy jittery_policy = base_policy; - jittery_policy.jitter_factor = 0.2; - - TestBackoffEntry entry(&jittery_policy); - - entry.InformOfRequest(false); - entry.InformOfRequest(false); - entry.InformOfRequest(false); - TimeTicks result = entry.GetReleaseTime(); - EXPECT_LE( - entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(3200), result); - EXPECT_GE( - entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result); - } -} - -TEST(BackoffEntryTest, FailureThenSuccess) { - TestBackoffEntry entry(&base_policy); - - // Failure count 1, establishes horizon. - entry.InformOfRequest(false); - TimeTicks release_time = entry.GetReleaseTime(); - EXPECT_EQ(TimeTicks() + TimeDelta::FromMilliseconds(1000), release_time); - - // Success, failure count 0, should not advance past - // the horizon that was already set. - entry.set_now(release_time - TimeDelta::FromMilliseconds(200)); - entry.InformOfRequest(true); - EXPECT_EQ(release_time, entry.GetReleaseTime()); - - // Failure, failure count 1. - entry.InformOfRequest(false); - EXPECT_EQ(release_time + TimeDelta::FromMilliseconds(800), - entry.GetReleaseTime()); -} - -TEST(BackoffEntryTest, FailureThenSuccessAlwaysDelay) { - BackoffEntry::Policy always_delay_policy = base_policy; - always_delay_policy.always_use_initial_delay = true; - always_delay_policy.num_errors_to_ignore = 1; - - TestBackoffEntry entry(&always_delay_policy); - - // Failure count 1. - entry.InformOfRequest(false); - EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); - - // Failure count 2. - entry.InformOfRequest(false); - EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); - entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); - - // Success. We should go back to the original delay. - entry.InformOfRequest(true); - EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); - - // Failure count reaches 2 again. We should increase the delay once more. - entry.InformOfRequest(false); - EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); - entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); -} - -TEST(BackoffEntryTest, RetainCustomHorizon) { - TestBackoffEntry custom(&base_policy); - TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3); - custom.SetCustomReleaseTime(custom_horizon); - custom.InformOfRequest(false); - custom.InformOfRequest(true); - custom.set_now(TimeTicks() + TimeDelta::FromDays(2)); - custom.InformOfRequest(false); - custom.InformOfRequest(true); - EXPECT_EQ(custom_horizon, custom.GetReleaseTime()); - - // Now check that once we are at or past the custom horizon, - // we get normal behavior. - custom.set_now(TimeTicks() + TimeDelta::FromDays(3)); - custom.InformOfRequest(false); - EXPECT_EQ( - TimeTicks() + TimeDelta::FromDays(3) + TimeDelta::FromMilliseconds(1000), - custom.GetReleaseTime()); -} - -TEST(BackoffEntryTest, RetainCustomHorizonWhenInitialErrorsIgnored) { - // Regression test for a bug discovered during code review. - BackoffEntry::Policy lenient_policy = base_policy; - lenient_policy.num_errors_to_ignore = 1; - TestBackoffEntry custom(&lenient_policy); - TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3); - custom.SetCustomReleaseTime(custom_horizon); - custom.InformOfRequest(false); // This must not reset the horizon. - EXPECT_EQ(custom_horizon, custom.GetReleaseTime()); -} - -TEST(BackoffEntryTest, OverflowProtection) { - BackoffEntry::Policy large_multiply_policy = base_policy; - large_multiply_policy.multiply_factor = 256; - TestBackoffEntry custom(&large_multiply_policy); - - // Trigger enough failures such that more than 11 bits of exponent are used - // to represent the exponential backoff intermediate values. Given a multiply - // factor of 256 (2^8), 129 iterations is enough: 2^(8*(129-1)) = 2^1024. - for (int i = 0; i < 129; ++i) { - custom.set_now(custom.ImplGetTimeNow() + custom.GetTimeUntilRelease()); - custom.InformOfRequest(false); - ASSERT_TRUE(custom.ShouldRejectRequest()); - } - - // Max delay should still be respected. - EXPECT_EQ(20000, custom.GetTimeUntilRelease().InMilliseconds()); -} - -} // namespace diff --git a/chromeos/bind_lambda.h b/chromeos/bind_lambda.h deleted file mode 100644 index 27b4d4e..0000000 --- a/chromeos/bind_lambda.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_BIND_LAMBDA_H_ -#define LIBCHROMEOS_CHROMEOS_BIND_LAMBDA_H_ - -#include - -//////////////////////////////////////////////////////////////////////////////// -// This file is an extension to base/bind_internal.h and adds a RunnableAdapter -// class specialization that wraps a functor (including lambda objects), so -// they can be used in base::Callback/base::Bind constructs. -// By including this file you will gain the ability to write expressions like: -// base::Callback callback = base::Bind([](int value) { -// return value * value; -// }); -//////////////////////////////////////////////////////////////////////////////// -namespace base { -namespace internal { - -// LambdaAdapter is a helper class that specializes on different function call -// signatures and provides the RunType and Run() method required by -// RunnableAdapter<> class. -template -class LambdaAdapter; - -// R(...) -template -class LambdaAdapter { - public: - typedef R(RunType)(Args...); - LambdaAdapter(Lambda lambda) : lambda_(lambda) {} - R Run(Args... args) { return lambda_(CallbackForward(args)...); } - - private: - Lambda lambda_; -}; - -// R(...) const -template -class LambdaAdapter { - public: - typedef R(RunType)(Args...); - LambdaAdapter(Lambda lambda) : lambda_(lambda) {} - R Run(Args... args) { return lambda_(CallbackForward(args)...); } - - private: - Lambda lambda_; -}; - -template -class RunnableAdapter - : public LambdaAdapter { - public: - explicit RunnableAdapter(Lambda lambda) - : LambdaAdapter(lambda) {} -}; - -} // namespace internal -} // namespace base - -#endif // LIBCHROMEOS_CHROMEOS_BIND_LAMBDA_H_ diff --git a/chromeos/binder_watcher.cc b/chromeos/binder_watcher.cc deleted file mode 100644 index 30e7754..0000000 --- a/chromeos/binder_watcher.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 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 - -#include -#include -#include - -using android::IPCThreadState; -using android::ProcessState; - -namespace chromeos { - -BinderWatcher::BinderWatcher() = default; - -BinderWatcher::~BinderWatcher() = default; - -bool BinderWatcher::Init() { - int binder_fd = -1; - ProcessState::self()->setThreadPoolMaxThreadCount(0); - IPCThreadState::self()->disableBackgroundScheduling(true); - IPCThreadState::self()->setupPolling(&binder_fd); - LOG(INFO) << "Got binder FD " << binder_fd; - if (binder_fd < 0) - return false; - - if (!base::MessageLoopForIO::current()->WatchFileDescriptor( - binder_fd, true /* persistent */, base::MessageLoopForIO::WATCH_READ, - &watcher_, this)) { - LOG(ERROR) << "Failed to watch binder FD"; - return false; - } - return true; -} - -void BinderWatcher::OnFileCanReadWithoutBlocking(int fd) { - IPCThreadState::self()->handlePolledCommands(); -} - -void BinderWatcher::OnFileCanWriteWithoutBlocking(int fd) { - NOTREACHED() << "Unexpected writable notification for FD " << fd; -} - -} // namespace chromeos diff --git a/chromeos/binder_watcher.h b/chromeos/binder_watcher.h deleted file mode 100644 index 198f4f8..0000000 --- a/chromeos/binder_watcher.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 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. - */ - -#ifndef LIBCHROMEOS_CHROMEOS_BINDER_WATCHER_H_ -#define LIBCHROMEOS_CHROMEOS_BINDER_WATCHER_H_ - -#include -#include - -namespace chromeos { - -// Bridge between libbinder and base::MessageLoop. Construct at startup to make -// the message loop watch for binder events and pass them to libbinder. -class BinderWatcher : public base::MessageLoopForIO::Watcher { - public: - BinderWatcher(); - ~BinderWatcher() override; - - // Initializes the object, returning true on success. - bool Init(); - - // base::MessageLoopForIO::Watcher: - void OnFileCanReadWithoutBlocking(int fd) override; - void OnFileCanWriteWithoutBlocking(int fd) override; - - private: - base::MessageLoopForIO::FileDescriptorWatcher watcher_; - - DISALLOW_COPY_AND_ASSIGN(BinderWatcher); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_BINDER_WATCHER_H_ diff --git a/chromeos/chromeos_export.h b/chromeos/chromeos_export.h deleted file mode 100644 index 8b61394..0000000 --- a/chromeos/chromeos_export.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_CHROMEOS_EXPORT_H_ -#define LIBCHROMEOS_CHROMEOS_CHROMEOS_EXPORT_H_ - -// Use CHROMEOS_EXPORT attribute to decorate your classes, methods and variables -// that need to be exported out of libchromeos. By default, any symbol not -// explicitly marked with CHROMEOS_EXPORT attribute is not exported. - -// Put CHROMEOS_EXPORT in front of methods or variables and in between the -// class and the tag name: -/* - -CHROMEOS_EXPORT void foo(); - -class CHROMEOS_EXPORT Bar { - public: - void baz(); // Exported since it is a member of an exported class. -}; - -*/ - -// Exporting a class automatically exports all of its members. However there are -// no export entries for non-static member variables since they are not accessed -// directly, but rather through "this" pointer. Class methods, type information, -// virtual table (if any), and static member variables are exported. - -// Finally, template functions and template members of a class may not be -// inlined by the compiler automatically and the out-of-line version will not -// be exported and fail to link. Marking those inline explicitly might help. -// Alternatively, exporting specific instantiation of the template could be -// used with "extern template" and combining this with CHROMEOS_EXPORT. -#define CHROMEOS_EXPORT __attribute__((__visibility__("default"))) - -// On occasion you might need to disable exporting a particular symbol if -// you don't want the clients to see it. For example, you can explicitly -// hide a member of an exported class: -/* - -class CHROMEOS_EXPORT Foo { - public: - void bar(); // Exported since it is a member of an exported class. - - private: - CHROMEOS_PRIVATE void baz(); // Explicitly removed from export table. -}; - -*/ - -// Note that even though a class may have a private member it doesn't mean -// that it must not be exported, since the compiler might still need it. -// For example, an inline public method calling a private method will not link -// if that private method is not exported. -// So be careful with hiding members if you don't want to deal with obscure -// linker errors. -#define CHROMEOS_PRIVATE __attribute__((__visibility__("hidden"))) - -#endif // LIBCHROMEOS_CHROMEOS_CHROMEOS_EXPORT_H_ diff --git a/chromeos/cryptohome.cc b/chromeos/cryptohome.cc deleted file mode 100644 index 5aa53d8..0000000 --- a/chromeos/cryptohome.cc +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/cryptohome.h" - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -using base::FilePath; - -namespace chromeos { -namespace cryptohome { -namespace home { - -const char kGuestUserName[] = "$guest"; - -static char g_user_home_prefix[PATH_MAX] = "/home/user/"; -static char g_root_home_prefix[PATH_MAX] = "/home/root/"; -static char g_system_salt_path[PATH_MAX] = "/home/.shadow/salt"; - -static std::string* salt = nullptr; - -static bool EnsureSystemSaltIsLoaded() { - if (salt && !salt->empty()) - return true; - FilePath salt_path(g_system_salt_path); - int64_t file_size; - if (!base::GetFileSize(salt_path, &file_size)) { - PLOG(ERROR) << "Could not get size of system salt: " << g_system_salt_path; - return false; - } - if (file_size > static_cast(std::numeric_limits::max())) { - LOG(ERROR) << "System salt too large: " << file_size; - return false; - } - std::vector buf; - buf.resize(file_size); - unsigned int data_read = base::ReadFile(salt_path, buf.data(), file_size); - if (data_read != file_size) { - PLOG(ERROR) << "Could not read entire file: " << data_read - << " != " << file_size; - return false; - } - - if (!salt) - salt = new std::string(); - salt->assign(buf.data(), file_size); - return true; -} - -std::string SanitizeUserName(const std::string& username) { - if (!EnsureSystemSaltIsLoaded()) - return std::string(); - - unsigned char binmd[SHA_DIGEST_LENGTH]; - std::string lowercase(username); - std::transform( - lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower); - SHA_CTX ctx; - SHA1_Init(&ctx); - SHA1_Update(&ctx, salt->data(), salt->size()); - SHA1_Update(&ctx, lowercase.data(), lowercase.size()); - SHA1_Final(binmd, &ctx); - std::string final = base::HexEncode(binmd, sizeof(binmd)); - // Stay compatible with CryptoLib::HexEncodeToBuffer() - std::transform(final.begin(), final.end(), final.begin(), ::tolower); - return final; -} - -FilePath GetUserPathPrefix() { - return FilePath(g_user_home_prefix); -} - -FilePath GetRootPathPrefix() { - return FilePath(g_root_home_prefix); -} - -FilePath GetHashedUserPath(const std::string& hashed_username) { - return FilePath( - base::StringPrintf("%s%s", g_user_home_prefix, hashed_username.c_str())); -} - -FilePath GetUserPath(const std::string& username) { - if (!EnsureSystemSaltIsLoaded()) - return FilePath(""); - return GetHashedUserPath(SanitizeUserName(username)); -} - -FilePath GetRootPath(const std::string& username) { - if (!EnsureSystemSaltIsLoaded()) - return FilePath(""); - return FilePath(base::StringPrintf( - "%s%s", g_root_home_prefix, SanitizeUserName(username).c_str())); -} - -FilePath GetDaemonPath(const std::string& username, const std::string& daemon) { - if (!EnsureSystemSaltIsLoaded()) - return FilePath(""); - return GetRootPath(username).Append(daemon); -} - -bool IsSanitizedUserName(const std::string& sanitized) { - std::vector bytes; - return (sanitized.length() == 2 * SHA_DIGEST_LENGTH) && - base::HexStringToBytes(sanitized, &bytes); -} - -void SetUserHomePrefix(const std::string& prefix) { - if (prefix.length() < sizeof(g_user_home_prefix)) { - snprintf( - g_user_home_prefix, sizeof(g_user_home_prefix), "%s", prefix.c_str()); - } -} - -void SetRootHomePrefix(const std::string& prefix) { - if (prefix.length() < sizeof(g_root_home_prefix)) { - snprintf( - g_root_home_prefix, sizeof(g_root_home_prefix), "%s", prefix.c_str()); - } -} - -std::string* GetSystemSalt() { - return salt; -} - -void SetSystemSalt(std::string* value) { - salt = value; -} - -} // namespace home -} // namespace cryptohome -} // namespace chromeos diff --git a/chromeos/cryptohome.h b/chromeos/cryptohome.h deleted file mode 100644 index 8584507..0000000 --- a/chromeos/cryptohome.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_CRYPTOHOME_H_ -#define LIBCHROMEOS_CHROMEOS_CRYPTOHOME_H_ - -#include - -#include -#include - -namespace chromeos { -namespace cryptohome { -namespace home { - -CHROMEOS_EXPORT extern const char kGuestUserName[]; - -// Returns the common prefix under which the mount points for user homes are -// created. -CHROMEOS_EXPORT base::FilePath GetUserPathPrefix(); - -// Returns the common prefix under which the mount points for root homes are -// created. -CHROMEOS_EXPORT base::FilePath GetRootPathPrefix(); - -// Returns the path at which the user home for |username| will be mounted. -// Returns "" for failures. -CHROMEOS_EXPORT base::FilePath GetUserPath(const std::string& username); - -// Returns the path at which the user home for |hashed_username| will be -// mounted. Useful when you already have the username hashed. -// Returns "" for failures. -CHROMEOS_EXPORT base::FilePath GetHashedUserPath( - const std::string& hashed_username); - -// Returns the path at which the root home for |username| will be mounted. -// Returns "" for failures. -CHROMEOS_EXPORT base::FilePath GetRootPath(const std::string& username); - -// Returns the path at which the daemon |daemon| should store per-user data. -CHROMEOS_EXPORT base::FilePath GetDaemonPath(const std::string& username, - const std::string& daemon); - -// Checks whether |sanitized| has the format of a sanitized username. -CHROMEOS_EXPORT bool IsSanitizedUserName(const std::string& sanitized); - -// Returns a sanitized form of |username|. For x != y, SanitizeUserName(x) != -// SanitizeUserName(y). -CHROMEOS_EXPORT std::string SanitizeUserName(const std::string& username); - -// Overrides the common prefix under which the mount points for user homes are -// created. This is used for testing only. -CHROMEOS_EXPORT void SetUserHomePrefix(const std::string& prefix); - -// Overrides the common prefix under which the mount points for root homes are -// created. This is used for testing only. -CHROMEOS_EXPORT void SetRootHomePrefix(const std::string& prefix); - -// Overrides the contents of the system salt. -// salt should be non-NULL and non-empty when attempting to avoid filesystem -// usage in tests. -// Note: -// (1) Never mix usage with SetSystemSaltPath(). -// (2) Ownership of the pointer stays with the caller. -CHROMEOS_EXPORT void SetSystemSalt(std::string* salt); - -// Returns the system salt. -CHROMEOS_EXPORT std::string* GetSystemSalt(); - -} // namespace home -} // namespace cryptohome -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_CRYPTOHOME_H_ diff --git a/chromeos/daemons/daemon.cc b/chromeos/daemons/daemon.cc deleted file mode 100644 index a964e95..0000000 --- a/chromeos/daemons/daemon.cc +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include - -namespace chromeos { - -Daemon::Daemon() : exit_code_{EX_OK} { - chromeos_message_loop_.SetAsCurrent(); -} - -Daemon::~Daemon() { -} - -int Daemon::Run() { - int exit_code = OnInit(); - if (exit_code != EX_OK) - return exit_code; - - chromeos_message_loop_.Run(); - - OnShutdown(&exit_code_); - - // base::RunLoop::QuitClosure() causes the message loop to quit - // immediately, even if pending tasks are still queued. - // Run a secondary loop to make sure all those are processed. - // This becomes important when working with D-Bus since dbus::Bus does - // a bunch of clean-up tasks asynchronously when shutting down. - while (chromeos_message_loop_.RunOnce(false /* may_block */)) {} - - return exit_code_; -} - -void Daemon::Quit() { QuitWithExitCode(EX_OK); } - -void Daemon::QuitWithExitCode(int exit_code) { - exit_code_ = exit_code; - message_loop_.PostTask(FROM_HERE, QuitClosure()); -} - -void Daemon::RegisterHandler( - int signal, - const AsynchronousSignalHandlerInterface::SignalHandler& callback) { - async_signal_handler_.RegisterHandler(signal, callback); -} - -void Daemon::UnregisterHandler(int signal) { - async_signal_handler_.UnregisterHandler(signal); -} - -int Daemon::OnInit() { - async_signal_handler_.Init(); - for (int signal : {SIGTERM, SIGINT}) { - async_signal_handler_.RegisterHandler( - signal, base::Bind(&Daemon::Shutdown, base::Unretained(this))); - } - async_signal_handler_.RegisterHandler( - SIGHUP, base::Bind(&Daemon::Restart, base::Unretained(this))); - return EX_OK; -} - -void Daemon::OnShutdown(int* exit_code) { - // Do nothing. -} - -bool Daemon::OnRestart() { - // Not handled. - return false; // Returning false will shut down the daemon instead. -} - -bool Daemon::Shutdown(const signalfd_siginfo& info) { - Quit(); - return true; // Unregister the signal handler. -} - -bool Daemon::Restart(const signalfd_siginfo& info) { - if (OnRestart()) - return false; // Keep listening to the signal. - Quit(); - return true; // Unregister the signal handler. -} - -} // namespace chromeos diff --git a/chromeos/daemons/daemon.h b/chromeos/daemons/daemon.h deleted file mode 100644 index c1b7d2b..0000000 --- a/chromeos/daemons/daemon.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DAEMONS_DAEMON_H_ -#define LIBCHROMEOS_CHROMEOS_DAEMONS_DAEMON_H_ - -#include - -#include -#include -#include -#include -#include -#include - -struct signalfd_siginfo; - -namespace chromeos { - -// Daemon is a simple base class for system daemons. It provides a lot -// of useful facilities such as a message loop, handling of SIGTERM, SIGINT, and -// SIGHUP system signals. -// You can use this class directly to implement your daemon or you can -// specialize it by creating your own class and deriving it from -// chromeos::Daemon. Override some of the virtual methods provide to fine-tune -// its behavior to suit your daemon's needs. -class CHROMEOS_EXPORT Daemon : public AsynchronousSignalHandlerInterface { - public: - Daemon(); - virtual ~Daemon(); - - // Performs proper initialization of the daemon and runs the message loop. - // Blocks until the daemon is finished. The return value is the error - // code that should be returned from daemon's main(). Returns EX_OK (0) on - // success. - virtual int Run(); - - // Can be used by call-backs to trigger shut-down of a running message loop. - // Calls QuiteWithExitCode(EX_OK); - // WARNING: This method (as well as QuitWithExitCode) can only be called when - // the message loop is running (that is, during Daemon::Run() call). Calling - // these methods before (e.g. during OnInit()) or after (e.g in OnShutdown()) - // will lead to abnormal process termination. - void Quit(); - - // |exit_code| is the status code to be returned when the daemon process - // quits. See the warning for Quit() above regarding the allowed scope for - // this method. - void QuitWithExitCode(int exit_code); - - // AsynchronousSignalHandlerInterface overrides. - // Register/unregister custom signal handlers for the daemon. The semantics - // are identical to AsynchronousSignalHandler::RegisterHandler and - // AsynchronousSignalHandler::UnregisterHandler, except that handlers for - // SIGTERM, SIGINT, and SIGHUP cannot be modified. - void RegisterHandler( - int signal, const - AsynchronousSignalHandlerInterface::SignalHandler& callback) override; - void UnregisterHandler(int signal) override; - - protected: - // Overload to provide your own initialization code that should happen just - // before running the message loop. Return EX_OK (0) on success or any other - // non-zero error codes. If an error is returned, the message loop execution - // is aborted and Daemon::Run() exits early. - // When overloading, make sure you call the base implementation of OnInit(). - virtual int OnInit(); - // Called when the message loops exits and before Daemon::Run() returns. - // Overload to clean up the data that was set up during OnInit(). - // |return_code| contains the current error code that will be returned from - // Run(). You can override this value with your own error code if needed. - // When overloading, make sure you call the base implementation of - // OnShutdown(). - virtual void OnShutdown(int* exit_code); - // Called when the SIGHUP signal is received. In response to this call, your - // daemon could reset/reload the configuration and re-initialize its state - // as if the process has been reloaded. - // Return true if the signal was processed successfully and the daemon - // reset its configuration. Returning false will force the daemon to - // quit (and subsequently relaunched by an upstart job, if one is configured). - // The default implementation just returns false (unhandled), which terminates - // the daemon, so do not call the base implementation of OnRestart() from - // your overload. - virtual bool OnRestart(); - - // Returns a delegate to Quit() method in the base::RunLoop instance. - base::Closure QuitClosure() const { - return chromeos_message_loop_.QuitClosure(); - } - - private: - // Called when SIGTERM/SIGINT signals are received. - bool Shutdown(const signalfd_siginfo& info); - // Called when SIGHUP signal is received. - bool Restart(const signalfd_siginfo& info); - - // |at_exit_manager_| must be first to make sure it is initialized before - // other members, especially the |message_loop_|. - base::AtExitManager at_exit_manager_; - // The main message loop for the daemon. - base::MessageLoopForIO message_loop_; - // The chromeos wrapper for the main message loop. - BaseMessageLoop chromeos_message_loop_{&message_loop_}; - // A helper to dispatch signal handlers asynchronously, so that the main - // system signal handler returns as soon as possible. - AsynchronousSignalHandler async_signal_handler_; - // Process exit code specified in QuitWithExitCode() method call. - int exit_code_; - - DISALLOW_COPY_AND_ASSIGN(Daemon); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DAEMONS_DAEMON_H_ diff --git a/chromeos/daemons/dbus_daemon.cc b/chromeos/daemons/dbus_daemon.cc deleted file mode 100644 index c5d1ca9..0000000 --- a/chromeos/daemons/dbus_daemon.cc +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include - -using chromeos::dbus_utils::AsyncEventSequencer; -using chromeos::dbus_utils::ExportedObjectManager; - -namespace chromeos { - -DBusDaemon::DBusDaemon() { -} - -DBusDaemon::~DBusDaemon() { - if (bus_) - bus_->ShutdownAndBlock(); -} - -int DBusDaemon::OnInit() { - int exit_code = Daemon::OnInit(); - if (exit_code != EX_OK) - return exit_code; - - dbus::Bus::Options options; - options.bus_type = dbus::Bus::SYSTEM; - - bus_ = new dbus::Bus(options); - CHECK(bus_->Connect()); - - return exit_code; -} - -DBusServiceDaemon::DBusServiceDaemon(const std::string& service_name) - : service_name_(service_name) { -} - -DBusServiceDaemon::DBusServiceDaemon( - const std::string& service_name, - const dbus::ObjectPath& object_manager_path) - : service_name_(service_name), object_manager_path_(object_manager_path) { -} - -DBusServiceDaemon::DBusServiceDaemon(const std::string& service_name, - base::StringPiece object_manager_path) - : DBusServiceDaemon(service_name, - dbus::ObjectPath(object_manager_path.as_string())) { -} - -int DBusServiceDaemon::OnInit() { - int exit_code = DBusDaemon::OnInit(); - if (exit_code != EX_OK) - return exit_code; - - scoped_refptr sequencer(new AsyncEventSequencer()); - if (object_manager_path_.IsValid()) { - object_manager_.reset( - new ExportedObjectManager(bus_, object_manager_path_)); - object_manager_->RegisterAsync( - sequencer->GetHandler("ObjectManager.RegisterAsync() failed.", true)); - } - RegisterDBusObjectsAsync(sequencer.get()); - sequencer->OnAllTasksCompletedCall({ - base::Bind(&DBusServiceDaemon::TakeServiceOwnership, - base::Unretained(this)) - }); - return EX_OK; -} - -void DBusServiceDaemon::RegisterDBusObjectsAsync( - dbus_utils::AsyncEventSequencer* sequencer) { - // Do nothing here. - // Overload this method to export custom D-Bus objects at daemon startup. -} - -void DBusServiceDaemon::TakeServiceOwnership(bool success) { - // Success should always be true since we've said that failures are fatal. - CHECK(success) << "Init of one or more objects has failed."; - CHECK(bus_->RequestOwnershipAndBlock(service_name_, - dbus::Bus::REQUIRE_PRIMARY)) - << "Unable to take ownership of " << service_name_; -} - -} // namespace chromeos diff --git a/chromeos/daemons/dbus_daemon.h b/chromeos/daemons/dbus_daemon.h deleted file mode 100644 index 2f08dfa..0000000 --- a/chromeos/daemons/dbus_daemon.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DAEMONS_DBUS_DAEMON_H_ -#define LIBCHROMEOS_CHROMEOS_DAEMONS_DBUS_DAEMON_H_ - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -namespace dbus_utils { -class AsyncEventSequencer; -} // namespace dbus_utils - -// DBusDaemon adds D-Bus support to Daemon. -// Derive your daemon from this class if you want D-Bus client services in your -// daemon (consuming other D-Bus objects). Currently uses a SYSTEM bus. -class CHROMEOS_EXPORT DBusDaemon : public Daemon { - public: - DBusDaemon(); - ~DBusDaemon() override; - - protected: - // Calls the base OnInit() and then instantiates dbus::Bus and establishes - // a D-Bus connection. - int OnInit() override; - - scoped_refptr bus_; - - private: - DISALLOW_COPY_AND_ASSIGN(DBusDaemon); -}; - -// DBusServiceDaemon adds D-Bus service support to DBusDaemon. -// Derive your daemon from this class if your daemon exposes D-Bus objects. -// Provides an ExportedObjectManager to announce your object/interface creation -// and destruction. -class CHROMEOS_EXPORT DBusServiceDaemon : public DBusDaemon { - public: - // Constructs the daemon. - // |service_name| is the name of D-Bus service provided by the daemon. - // |object_manager_path_| is a well-known D-Bus object path for - // ExportedObjectManager object. - // If |object_manager_path_| is not specified, then ExportedObjectManager is - // not created and is not available as part of the D-Bus service. - explicit DBusServiceDaemon(const std::string& service_name); - DBusServiceDaemon(const std::string& service_name, - const dbus::ObjectPath& object_manager_path); - DBusServiceDaemon(const std::string& service_name, - base::StringPiece object_manager_path); - - protected: - // OnInit() overload exporting D-Bus objects. Exports the contained - // ExportedObjectManager object and calls RegisterDBusObjectsAsync() to let - // you provide additional D-Bus objects. - int OnInit() override; - - // Overload this method to export your custom D-Bus objects at startup. - // Objects exported in this way will finish exporting before we claim the - // daemon's service name on DBus. - virtual void RegisterDBusObjectsAsync( - dbus_utils::AsyncEventSequencer* sequencer); - - std::string service_name_; - dbus::ObjectPath object_manager_path_; - std::unique_ptr object_manager_; - - private: - // A callback that will be called when all the D-Bus objects/interfaces are - // exported successfully and the daemon is ready to claim the D-Bus service - // ownership. - void TakeServiceOwnership(bool success); - - DISALLOW_COPY_AND_ASSIGN(DBusServiceDaemon); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DAEMONS_DBUS_DAEMON_H_ diff --git a/chromeos/data_encoding.cc b/chromeos/data_encoding.cc deleted file mode 100644 index 7d6b6c3..0000000 --- a/chromeos/data_encoding.cc +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include - -#include - -#include -#include -#include -#include - -namespace { - -inline int HexToDec(int hex) { - int dec = -1; - if (hex >= '0' && hex <= '9') { - dec = hex - '0'; - } else if (hex >= 'A' && hex <= 'F') { - dec = hex - 'A' + 10; - } else if (hex >= 'a' && hex <= 'f') { - dec = hex - 'a' + 10; - } - return dec; -} - -// Helper for Base64Encode() and Base64EncodeWrapLines(). -std::string Base64EncodeHelper(const void* data, size_t size) { - std::vector buffer; - buffer.resize(modp_b64_encode_len(size)); - size_t out_size = modp_b64_encode(buffer.data(), - static_cast(data), - size); - return std::string{buffer.begin(), buffer.begin() + out_size}; -} - -} // namespace - -///////////////////////////////////////////////////////////////////////// -namespace chromeos { -namespace data_encoding { - -std::string UrlEncode(const char* data, bool encodeSpaceAsPlus) { - std::string result; - - while (*data) { - char c = *data++; - // According to RFC3986 (http://www.faqs.org/rfcs/rfc3986.html), - // section 2.3. - Unreserved Characters - if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || - c == '~') { - result += c; - } else if (c == ' ' && encodeSpaceAsPlus) { - // For historical reasons, some URLs have spaces encoded as '+', - // this also applies to form data encoded as - // 'application/x-www-form-urlencoded' - result += '+'; - } else { - base::StringAppendF(&result, - "%%%02X", - static_cast(c)); // Encode as %NN - } - } - return result; -} - -std::string UrlDecode(const char* data) { - std::string result; - while (*data) { - char c = *data++; - int part1 = 0, part2 = 0; - // HexToDec would return -1 even for character 0 (end of string), - // so it is safe to access data[0] and data[1] without overrunning the buf. - if (c == '%' && (part1 = HexToDec(data[0])) >= 0 && - (part2 = HexToDec(data[1])) >= 0) { - c = static_cast((part1 << 4) | part2); - data += 2; - } else if (c == '+') { - c = ' '; - } - result += c; - } - return result; -} - -std::string WebParamsEncode(const WebParamList& params, - bool encodeSpaceAsPlus) { - std::vector pairs; - pairs.reserve(params.size()); - for (const auto& p : params) { - std::string key = UrlEncode(p.first.c_str(), encodeSpaceAsPlus); - std::string value = UrlEncode(p.second.c_str(), encodeSpaceAsPlus); - pairs.push_back(chromeos::string_utils::Join("=", key, value)); - } - - return chromeos::string_utils::Join("&", pairs); -} - -WebParamList WebParamsDecode(const std::string& data) { - WebParamList result; - std::vector params = chromeos::string_utils::Split(data, "&"); - for (const auto& p : params) { - auto pair = chromeos::string_utils::SplitAtFirst(p, "="); - result.emplace_back(UrlDecode(pair.first.c_str()), - UrlDecode(pair.second.c_str())); - } - return result; -} - -std::string Base64Encode(const void* data, size_t size) { - return Base64EncodeHelper(data, size); -} - -std::string Base64EncodeWrapLines(const void* data, size_t size) { - std::string unwrapped = Base64EncodeHelper(data, size); - std::string wrapped; - - for (size_t i = 0; i < unwrapped.size(); i += 64) { - wrapped.append(unwrapped, i, 64); - wrapped.append("\n"); - } - return wrapped; -} - -bool Base64Decode(const std::string& input, chromeos::Blob* output) { - std::string temp_buffer; - const std::string* data = &input; - if (input.find_first_of("\r\n") != std::string::npos) { - base::ReplaceChars(input, "\n", "", &temp_buffer); - base::ReplaceChars(temp_buffer, "\r", "", &temp_buffer); - data = &temp_buffer; - } - // base64 decoded data has 25% fewer bytes than the original (since every - // 3 source octets are encoded as 4 characters in base64). - // modp_b64_decode_len provides an upper estimate of the size of the output - // data. - output->resize(modp_b64_decode_len(data->size())); - - size_t size_read = modp_b64_decode(reinterpret_cast(output->data()), - data->data(), data->size()); - if (size_read == MODP_B64_ERROR) { - output->resize(0); - return false; - } - output->resize(size_read); - - return true; -} - -} // namespace data_encoding -} // namespace chromeos diff --git a/chromeos/data_encoding.h b/chromeos/data_encoding.h deleted file mode 100644 index 4332c62..0000000 --- a/chromeos/data_encoding.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DATA_ENCODING_H_ -#define LIBCHROMEOS_CHROMEOS_DATA_ENCODING_H_ - -#include -#include -#include - -#include -#include - -namespace chromeos { -namespace data_encoding { - -using WebParamList = std::vector>; - -// Encode/escape string to be used in the query portion of a URL. -// If |encodeSpaceAsPlus| is set to true, spaces are encoded as '+' instead -// of "%20" -CHROMEOS_EXPORT std::string UrlEncode(const char* data, bool encodeSpaceAsPlus); - -inline std::string UrlEncode(const char* data) { - return UrlEncode(data, true); -} - -// Decodes/unescapes a URL. Replaces all %XX sequences with actual characters. -// Also replaces '+' with spaces. -CHROMEOS_EXPORT std::string UrlDecode(const char* data); - -// Converts a list of key-value pairs into a string compatible with -// 'application/x-www-form-urlencoded' content encoding. -CHROMEOS_EXPORT std::string WebParamsEncode(const WebParamList& params, - bool encodeSpaceAsPlus); - -inline std::string WebParamsEncode(const WebParamList& params) { - return WebParamsEncode(params, true); -} - -// Parses a string of '&'-delimited key-value pairs (separated by '=') and -// encoded in a way compatible with 'application/x-www-form-urlencoded' -// content encoding. -CHROMEOS_EXPORT WebParamList WebParamsDecode(const std::string& data); - -// Encodes binary data using base64-encoding. -CHROMEOS_EXPORT std::string Base64Encode(const void* data, size_t size); - -// Encodes binary data using base64-encoding and wraps lines at 64 character -// boundary using LF as required by PEM (RFC 1421) specification. -CHROMEOS_EXPORT std::string Base64EncodeWrapLines(const void* data, - size_t size); - -// Decodes the input string from Base64. -CHROMEOS_EXPORT bool Base64Decode(const std::string& input, - chromeos::Blob* output); - -// Helper wrappers to use std::string and chromeos::Blob as binary data -// containers. -inline std::string Base64Encode(const chromeos::Blob& input) { - return Base64Encode(input.data(), input.size()); -} -inline std::string Base64EncodeWrapLines(const chromeos::Blob& input) { - return Base64EncodeWrapLines(input.data(), input.size()); -} -inline std::string Base64Encode(const std::string& input) { - return Base64Encode(input.data(), input.size()); -} -inline std::string Base64EncodeWrapLines(const std::string& input) { - return Base64EncodeWrapLines(input.data(), input.size()); -} -inline bool Base64Decode(const std::string& input, std::string* output) { - chromeos::Blob blob; - if (!Base64Decode(input, &blob)) - return false; - *output = std::string{blob.begin(), blob.end()}; - return true; -} - -} // namespace data_encoding -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DATA_ENCODING_H_ diff --git a/chromeos/data_encoding_unittest.cc b/chromeos/data_encoding_unittest.cc deleted file mode 100644 index 70bd748..0000000 --- a/chromeos/data_encoding_unittest.cc +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2011 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include - -namespace chromeos { -namespace data_encoding { - -TEST(data_encoding, UrlEncoding) { - std::string test = "\"http://sample/path/0014.html \""; - std::string encoded = UrlEncode(test.c_str()); - EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html+%22", encoded); - EXPECT_EQ(test, UrlDecode(encoded.c_str())); - - test = "\"http://sample/path/0014.html \""; - encoded = UrlEncode(test.c_str(), false); - EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html%20%22", encoded); - EXPECT_EQ(test, UrlDecode(encoded.c_str())); -} - -TEST(data_encoding, WebParamsEncoding) { - std::string encoded = - WebParamsEncode({{"q", "test"}, {"path", "/usr/bin"}, {"#", "%"}}); - EXPECT_EQ("q=test&path=%2Fusr%2Fbin&%23=%25", encoded); - - auto params = WebParamsDecode(encoded); - EXPECT_EQ(3, params.size()); - EXPECT_EQ("q", params[0].first); - EXPECT_EQ("test", params[0].second); - EXPECT_EQ("path", params[1].first); - EXPECT_EQ("/usr/bin", params[1].second); - EXPECT_EQ("#", params[2].first); - EXPECT_EQ("%", params[2].second); -} - -TEST(data_encoding, Base64Encode) { - const std::string text1 = "hello world"; - const std::string encoded1 = "aGVsbG8gd29ybGQ="; - - const std::string text2 = - "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque " - "molestie commodo. Viverra tincidunt integer erat ipsum, integer " - "molestie, arcu in, sit mauris ac a sed sit etiam."; - const std::string encoded2 = - "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBhbGlxdWF" - "tLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRpbmNpZHVudCBpbn" - "RlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFyY3UgaW4sIHNpdCBtYXVya" - "XMgYWMgYSBzZWQgc2l0IGV0aWFtLg=="; - - chromeos::Blob data3(256); - std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF. - const std::string encoded3 = - "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ" - "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW" - "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en" - "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU" - "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; - - EXPECT_EQ(encoded1, Base64Encode(text1)); - EXPECT_EQ(encoded2, Base64Encode(text2)); - EXPECT_EQ(encoded3, Base64Encode(data3)); -} - -TEST(data_encoding, Base64EncodeWrapLines) { - const std::string text1 = "hello world"; - const std::string encoded1 = "aGVsbG8gd29ybGQ=\n"; - - const std::string text2 = - "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque " - "molestie commodo. Viverra tincidunt integer erat ipsum, integer " - "molestie, arcu in, sit mauris ac a sed sit etiam."; - const std::string encoded2 = - "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n" - "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\n" - "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\n" - "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFtLg==\n"; - - chromeos::Blob data3(256); - std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF. - const std::string encoded3 = - "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\n" - "MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\n" - "YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n" - "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\n" - "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\n" - "8PHy8/T19vf4+fr7/P3+/w==\n"; - - EXPECT_EQ(encoded1, Base64EncodeWrapLines(text1)); - EXPECT_EQ(encoded2, Base64EncodeWrapLines(text2)); - EXPECT_EQ(encoded3, Base64EncodeWrapLines(data3)); -} - -TEST(data_encoding, Base64Decode) { - const std::string encoded1 = "dGVzdCBzdHJpbmc="; - - const std::string encoded2 = - "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n" - "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\r\n" - "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\r" - "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFt\n" - "Lg==\n\n\n"; - const std::string decoded2 = - "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque " - "molestie commodo. Viverra tincidunt integer erat ipsum, integer " - "molestie, arcu in, sit mauris ac a sed sit etiam."; - - const std::string encoded3 = - "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ" - "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW" - "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en" - "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU" - "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; - chromeos::Blob decoded3(256); - std::iota(decoded3.begin(), decoded3.end(), 0); // Fill with 0x00..0xFF. - - std::string decoded; - EXPECT_TRUE(Base64Decode(encoded1, &decoded)); - EXPECT_EQ("test string", decoded); - - EXPECT_TRUE(Base64Decode(encoded2, &decoded)); - EXPECT_EQ(decoded2, decoded); - - chromeos::Blob decoded_blob; - EXPECT_TRUE(Base64Decode(encoded3, &decoded_blob)); - EXPECT_EQ(decoded3, decoded_blob); - - EXPECT_FALSE(Base64Decode("A", &decoded_blob)); - EXPECT_TRUE(decoded_blob.empty()); - - EXPECT_TRUE(Base64Decode("/w==", &decoded_blob)); - EXPECT_EQ((chromeos::Blob{0xFF}), decoded_blob); - - EXPECT_TRUE(Base64Decode("//8=", &decoded_blob)); - EXPECT_EQ((chromeos::Blob{0xFF, 0xFF}), decoded_blob); - - EXPECT_FALSE(Base64Decode("AAECAwQFB,cI", &decoded_blob)); - EXPECT_TRUE(decoded_blob.empty()); -} - -} // namespace data_encoding -} // namespace chromeos diff --git a/chromeos/dbus/async_event_sequencer.cc b/chromeos/dbus/async_event_sequencer.cc deleted file mode 100644 index 741bd67..0000000 --- a/chromeos/dbus/async_event_sequencer.cc +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -namespace chromeos { - -namespace dbus_utils { - -AsyncEventSequencer::AsyncEventSequencer() { -} -AsyncEventSequencer::~AsyncEventSequencer() { -} - -AsyncEventSequencer::Handler AsyncEventSequencer::GetHandler( - const std::string& descriptive_message, - bool failure_is_fatal) { - CHECK(!started_) << "Cannot create handlers after OnAllTasksCompletedCall()"; - int unique_registration_id = ++registration_counter_; - outstanding_registrations_.insert(unique_registration_id); - return base::Bind(&AsyncEventSequencer::HandleFinish, - this, - unique_registration_id, - descriptive_message, - failure_is_fatal); -} - -AsyncEventSequencer::ExportHandler AsyncEventSequencer::GetExportHandler( - const std::string& interface_name, - const std::string& method_name, - const std::string& descriptive_message, - bool failure_is_fatal) { - auto finish_handler = GetHandler(descriptive_message, failure_is_fatal); - return base::Bind(&AsyncEventSequencer::HandleDBusMethodExported, - this, - finish_handler, - interface_name, - method_name); -} - -void AsyncEventSequencer::OnAllTasksCompletedCall( - std::vector actions) { - CHECK(!started_) << "OnAllTasksCompletedCall called twice!"; - started_ = true; - completion_actions_.assign(actions.begin(), actions.end()); - // All of our callbacks might have been called already. - PossiblyRunCompletionActions(); -} - -namespace { -void IgnoreSuccess(const AsyncEventSequencer::CompletionTask& task, - bool /*success*/) { - task.Run(); -} -void DoNothing(bool success) { -} -} // namespace - -AsyncEventSequencer::CompletionAction AsyncEventSequencer::WrapCompletionTask( - const CompletionTask& task) { - return base::Bind(&IgnoreSuccess, task); -} - -AsyncEventSequencer::CompletionAction -AsyncEventSequencer::GetDefaultCompletionAction() { - return base::Bind(&DoNothing); -} - -void AsyncEventSequencer::HandleFinish(int registration_number, - const std::string& error_message, - bool failure_is_fatal, - bool success) { - RetireRegistration(registration_number); - CheckForFailure(failure_is_fatal, success, error_message); - PossiblyRunCompletionActions(); -} - -void AsyncEventSequencer::HandleDBusMethodExported( - const AsyncEventSequencer::Handler& finish_handler, - const std::string& expected_interface_name, - const std::string& expected_method_name, - const std::string& actual_interface_name, - const std::string& actual_method_name, - bool success) { - CHECK_EQ(expected_method_name, actual_method_name) - << "Exported DBus method '" << actual_method_name << "' " - << "but expected '" << expected_method_name << "'"; - CHECK_EQ(expected_interface_name, actual_interface_name) - << "Exported method DBus interface '" << actual_interface_name << "' " - << "but expected '" << expected_interface_name << "'"; - finish_handler.Run(success); -} - -void AsyncEventSequencer::RetireRegistration(int registration_number) { - const size_t handlers_retired = - outstanding_registrations_.erase(registration_number); - CHECK_EQ(1U, handlers_retired) << "Tried to retire invalid handler " - << registration_number << ")"; -} - -void AsyncEventSequencer::CheckForFailure(bool failure_is_fatal, - bool success, - const std::string& error_message) { - if (failure_is_fatal) { - CHECK(success) << error_message; - } - if (!success) { - LOG(ERROR) << error_message; - had_failures_ = true; - } -} - -void AsyncEventSequencer::PossiblyRunCompletionActions() { - if (!started_ || !outstanding_registrations_.empty()) { - // Don't run completion actions if we have any outstanding - // Handlers outstanding or if any more handlers might - // be scheduled in the future. - return; - } - for (const auto& completion_action : completion_actions_) { - // Should this be put on the message loop or run directly? - completion_action.Run(!had_failures_); - } - // Discard our references to those actions. - completion_actions_.clear(); -} - -} // namespace dbus_utils - -} // namespace chromeos diff --git a/chromeos/dbus/async_event_sequencer.h b/chromeos/dbus/async_event_sequencer.h deleted file mode 100644 index f555c82..0000000 --- a/chromeos/dbus/async_event_sequencer.h +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_ASYNC_EVENT_SEQUENCER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_ASYNC_EVENT_SEQUENCER_H_ - -#include -#include -#include - -#include -#include -#include -#include - -namespace chromeos { - -namespace dbus_utils { - -// A helper class for coordinating the multiple async tasks. A consumer -// may grab any number of callbacks via Get*Handler() and schedule a list -// of completion actions to take. When all handlers obtained via Get*Handler() -// have been called, the AsyncEventSequencer will call its CompletionActions. -// -// Usage: -// -// void Init(const base::Callback cb) { -// scoped_refptr sequencer( -// new AsyncEventSequencer()); -// one_delegate_needing_init_.Init(sequencer->GetHandler( -// "my delegate failed to init", false)); -// dbus_init_delegate_.Init(sequencer->GetExportHandler( -// "org.test.Interface", "ExposedMethodName", -// "another delegate is flaky", false)); -// sequencer->OnAllTasksCompletedCall({cb}); -// } -class CHROMEOS_EXPORT AsyncEventSequencer - : public base::RefCounted { - public: - using Handler = base::Callback; - using ExportHandler = base::Callback; - using CompletionAction = base::Callback; - using CompletionTask = base::Callback; - - AsyncEventSequencer(); - - // Get a Finished handler callback. Each callback is "unique" in the sense - // that subsequent calls to GetHandler() will create new handlers - // which will need to be called before completion actions are run. - Handler GetHandler(const std::string& descriptive_message, - bool failure_is_fatal); - - // Like GetHandler except with a signature tailored to - // ExportedObject's ExportMethod callback requirements. Will also assert - // that the passed interface/method names from ExportedObject are correct. - ExportHandler GetExportHandler(const std::string& interface_name, - const std::string& method_name, - const std::string& descriptive_message, - bool failure_is_fatal); - - // Once all handlers obtained via GetHandler have run, - // we'll run each CompletionAction, then discard our references. - // No more handlers may be obtained after this call. - void OnAllTasksCompletedCall(std::vector actions); - - // Wrap a CompletionTask with a function that discards the result. - // This CompletionTask retains no references to the AsyncEventSequencer. - static CompletionAction WrapCompletionTask(const CompletionTask& task); - // Create a default CompletionAction that doesn't do anything when called. - static CompletionAction GetDefaultCompletionAction(); - - private: - // We'll partially bind this function before giving it back via - // GetHandler. Note that the returned callbacks have - // references to *this, which gives us the neat property that we'll - // destroy *this only when all our callbacks have been destroyed. - CHROMEOS_PRIVATE void HandleFinish(int registration_number, - const std::string& error_message, - bool failure_is_fatal, - bool success); - // Similar to HandleFinish. - CHROMEOS_PRIVATE void HandleDBusMethodExported( - const Handler& finish_handler, - const std::string& expected_interface_name, - const std::string& expected_method_name, - const std::string& actual_interface_name, - const std::string& actual_method_name, - bool success); - CHROMEOS_PRIVATE void RetireRegistration(int registration_number); - CHROMEOS_PRIVATE void CheckForFailure(bool failure_is_fatal, - bool success, - const std::string& error_message); - CHROMEOS_PRIVATE void PossiblyRunCompletionActions(); - - bool started_{false}; - int registration_counter_{0}; - std::set outstanding_registrations_; - std::vector completion_actions_; - bool had_failures_{false}; - // Ref counted objects have private destructors. - ~AsyncEventSequencer(); - friend class base::RefCounted; - DISALLOW_COPY_AND_ASSIGN(AsyncEventSequencer); -}; - -} // namespace dbus_utils - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_ASYNC_EVENT_SEQUENCER_H_ diff --git a/chromeos/dbus/async_event_sequencer_unittest.cc b/chromeos/dbus/async_event_sequencer_unittest.cc deleted file mode 100644 index 91e39cc..0000000 --- a/chromeos/dbus/async_event_sequencer_unittest.cc +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -namespace chromeos { - -namespace dbus_utils { - -namespace { - -const char kTestInterface[] = "org.test.if"; -const char kTestMethod1[] = "TestMethod1"; -const char kTestMethod2[] = "TestMethod2"; - -} // namespace - -class AsyncEventSequencerTest : public ::testing::Test { - public: - MOCK_METHOD1(HandleCompletion, void(bool all_succeeded)); - - void SetUp() { - aec_ = new AsyncEventSequencer(); - cb_ = base::Bind(&AsyncEventSequencerTest::HandleCompletion, - base::Unretained(this)); - } - - scoped_refptr aec_; - AsyncEventSequencer::CompletionAction cb_; -}; - -TEST_F(AsyncEventSequencerTest, WaitForCompletionActions) { - auto finished_handler = aec_->GetHandler("handler failed", false); - finished_handler.Run(true); - EXPECT_CALL(*this, HandleCompletion(true)).Times(1); - aec_->OnAllTasksCompletedCall({cb_}); -} - -TEST_F(AsyncEventSequencerTest, MultiInitActionsSucceed) { - auto finished_handler1 = aec_->GetHandler("handler failed", false); - auto finished_handler2 = aec_->GetHandler("handler failed", false); - aec_->OnAllTasksCompletedCall({cb_}); - finished_handler1.Run(true); - EXPECT_CALL(*this, HandleCompletion(true)).Times(1); - finished_handler2.Run(true); -} - -TEST_F(AsyncEventSequencerTest, SomeInitActionsFail) { - auto finished_handler1 = aec_->GetHandler("handler failed", false); - auto finished_handler2 = aec_->GetHandler("handler failed", false); - aec_->OnAllTasksCompletedCall({cb_}); - finished_handler1.Run(false); - EXPECT_CALL(*this, HandleCompletion(false)).Times(1); - finished_handler2.Run(true); -} - -TEST_F(AsyncEventSequencerTest, MultiDBusActionsSucceed) { - auto handler1 = aec_->GetExportHandler( - kTestInterface, kTestMethod1, "method export failed", false); - auto handler2 = aec_->GetExportHandler( - kTestInterface, kTestMethod2, "method export failed", false); - aec_->OnAllTasksCompletedCall({cb_}); - handler1.Run(kTestInterface, kTestMethod1, true); - EXPECT_CALL(*this, HandleCompletion(true)).Times(1); - handler2.Run(kTestInterface, kTestMethod2, true); -} - -TEST_F(AsyncEventSequencerTest, SomeDBusActionsFail) { - auto handler1 = aec_->GetExportHandler( - kTestInterface, kTestMethod1, "method export failed", false); - auto handler2 = aec_->GetExportHandler( - kTestInterface, kTestMethod2, "method export failed", false); - aec_->OnAllTasksCompletedCall({cb_}); - handler1.Run(kTestInterface, kTestMethod1, true); - EXPECT_CALL(*this, HandleCompletion(false)).Times(1); - handler2.Run(kTestInterface, kTestMethod2, false); -} - -TEST_F(AsyncEventSequencerTest, MixedActions) { - auto handler1 = aec_->GetExportHandler( - kTestInterface, kTestMethod1, "method export failed", false); - auto handler2 = aec_->GetHandler("handler failed", false); - aec_->OnAllTasksCompletedCall({cb_}); - handler1.Run(kTestInterface, kTestMethod1, true); - EXPECT_CALL(*this, HandleCompletion(true)).Times(1); - handler2.Run(true); -} - -} // namespace dbus_utils - -} // namespace chromeos diff --git a/chromeos/dbus/data_serialization.cc b/chromeos/dbus/data_serialization.cc deleted file mode 100644 index f57697b..0000000 --- a/chromeos/dbus/data_serialization.cc +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -void AppendValueToWriter(dbus::MessageWriter* writer, bool value) { - writer->AppendBool(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, uint8_t value) { - writer->AppendByte(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, int16_t value) { - writer->AppendInt16(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, uint16_t value) { - writer->AppendUint16(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, int32_t value) { - writer->AppendInt32(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, uint32_t value) { - writer->AppendUint32(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, int64_t value) { - writer->AppendInt64(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, uint64_t value) { - writer->AppendUint64(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, double value) { - writer->AppendDouble(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, - const std::string& value) { - writer->AppendString(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, const char* value) { - AppendValueToWriter(writer, std::string(value)); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, - const dbus::ObjectPath& value) { - writer->AppendObjectPath(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, - const dbus::FileDescriptor& value) { - writer->AppendFileDescriptor(value); -} - -void AppendValueToWriter(dbus::MessageWriter* writer, - const chromeos::Any& value) { - value.AppendToDBusMessageWriter(writer); -} - -/////////////////////////////////////////////////////////////////////////////// - -bool PopValueFromReader(dbus::MessageReader* reader, bool* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopBool(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, uint8_t* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopByte(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, int16_t* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopInt16(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, uint16_t* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopUint16(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, int32_t* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopInt32(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, uint32_t* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopUint32(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, int64_t* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopInt64(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, uint64_t* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopUint64(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, double* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopDouble(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, std::string* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopString(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, dbus::ObjectPath* value) { - dbus::MessageReader variant_reader(nullptr); - return details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopObjectPath(value); -} - -bool PopValueFromReader(dbus::MessageReader* reader, - dbus::FileDescriptor* value) { - dbus::MessageReader variant_reader(nullptr); - bool ok = details::DescendIntoVariantIfPresent(&reader, &variant_reader) && - reader->PopFileDescriptor(value); - if (ok) - value->CheckValidity(); - return ok; -} - -namespace { - -// Helper methods for PopValueFromReader(dbus::MessageReader*, Any*) -// implementation. Pops a value of particular type from |reader| and assigns -// it to |value| of type Any. -template -bool PopTypedValueFromReader(dbus::MessageReader* reader, - chromeos::Any* value) { - T data{}; - if (!PopValueFromReader(reader, &data)) - return false; - *value = std::move(data); - return true; -} - -// std::vector overload. -template -bool PopTypedArrayFromReader(dbus::MessageReader* reader, - chromeos::Any* value) { - return PopTypedValueFromReader>(reader, value); -} - -// std::map overload. -template -bool PopTypedMapFromReader(dbus::MessageReader* reader, chromeos::Any* value) { - return PopTypedValueFromReader>(reader, value); -} - -// Helper methods for reading common ARRAY signatures into a Variant. -// Note that only common types are supported. If an additional specific -// type signature is required, feel free to add support for it. -bool PopArrayValueFromReader(dbus::MessageReader* reader, - chromeos::Any* value) { - std::string signature = reader->GetDataSignature(); - if (signature == "ab") - return PopTypedArrayFromReader(reader, value); - else if (signature == "ay") - return PopTypedArrayFromReader(reader, value); - else if (signature == "an") - return PopTypedArrayFromReader(reader, value); - else if (signature == "aq") - return PopTypedArrayFromReader(reader, value); - else if (signature == "ai") - return PopTypedArrayFromReader(reader, value); - else if (signature == "au") - return PopTypedArrayFromReader(reader, value); - else if (signature == "ax") - return PopTypedArrayFromReader(reader, value); - else if (signature == "at") - return PopTypedArrayFromReader(reader, value); - else if (signature == "ad") - return PopTypedArrayFromReader(reader, value); - else if (signature == "as") - return PopTypedArrayFromReader(reader, value); - else if (signature == "ao") - return PopTypedArrayFromReader(reader, value); - else if (signature == "av") - return PopTypedArrayFromReader(reader, value); - else if (signature == "a{ss}") - return PopTypedMapFromReader(reader, value); - else if (signature == "a{sv}") - return PopTypedValueFromReader(reader, value); - else if (signature == "aa{sv}") - return PopTypedArrayFromReader(reader, value); - else if (signature == "a{sa{ss}}") - return PopTypedMapFromReader< - std::string, std::map>(reader, value); - else if (signature == "a{sa{sv}}") - return PopTypedMapFromReader< - std::string, chromeos::VariantDictionary>(reader, value); - else if (signature == "a{say}") - return PopTypedMapFromReader< - std::string, std::vector>(reader, value); - else if (signature == "a{uv}") - return PopTypedMapFromReader(reader, value); - else if (signature == "a(su)") - return PopTypedArrayFromReader< - std::tuple>(reader, value); - else if (signature == "a{uu}") - return PopTypedMapFromReader(reader, value); - else if (signature == "a(uu)") - return PopTypedArrayFromReader< - std::tuple>(reader, value); - - // When a use case for particular array signature is found, feel free - // to add handing for it here. - LOG(ERROR) << "Variant de-serialization of array containing data of " - << "type '" << signature << "' is not yet supported"; - return false; -} - -// Helper methods for reading common STRUCT signatures into a Variant. -// Note that only common types are supported. If an additional specific -// type signature is required, feel free to add support for it. -bool PopStructValueFromReader(dbus::MessageReader* reader, - chromeos::Any* value) { - std::string signature = reader->GetDataSignature(); - if (signature == "(ii)") - return PopTypedValueFromReader>(reader, value); - else if (signature == "(ss)") - return PopTypedValueFromReader>(reader, - value); - else if (signature == "(ub)") - return PopTypedValueFromReader>(reader, value); - else if (signature == "(uu)") - return PopTypedValueFromReader>(reader, - value); - - // When a use case for particular struct signature is found, feel free - // to add handing for it here. - LOG(ERROR) << "Variant de-serialization of structs of type '" << signature - << "' is not yet supported"; - return false; -} - -} // anonymous namespace - -bool PopValueFromReader(dbus::MessageReader* reader, chromeos::Any* value) { - dbus::MessageReader variant_reader(nullptr); - if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader)) - return false; - - switch (reader->GetDataType()) { - case dbus::Message::BYTE: - return PopTypedValueFromReader(reader, value); - case dbus::Message::BOOL: - return PopTypedValueFromReader(reader, value); - case dbus::Message::INT16: - return PopTypedValueFromReader(reader, value); - case dbus::Message::UINT16: - return PopTypedValueFromReader(reader, value); - case dbus::Message::INT32: - return PopTypedValueFromReader(reader, value); - case dbus::Message::UINT32: - return PopTypedValueFromReader(reader, value); - case dbus::Message::INT64: - return PopTypedValueFromReader(reader, value); - case dbus::Message::UINT64: - return PopTypedValueFromReader(reader, value); - case dbus::Message::DOUBLE: - return PopTypedValueFromReader(reader, value); - case dbus::Message::STRING: - return PopTypedValueFromReader(reader, value); - case dbus::Message::OBJECT_PATH: - return PopTypedValueFromReader(reader, value); - case dbus::Message::ARRAY: - return PopArrayValueFromReader(reader, value); - case dbus::Message::STRUCT: - return PopStructValueFromReader(reader, value); - case dbus::Message::DICT_ENTRY: - LOG(ERROR) << "Variant of DICT_ENTRY is invalid"; - return false; - case dbus::Message::VARIANT: - LOG(ERROR) << "Variant containing a variant is invalid"; - return false; - case dbus::Message::UNIX_FD: - CHECK(dbus::IsDBusTypeUnixFdSupported()) << "UNIX_FD data not supported"; - // dbus::FileDescriptor is not a copyable type. Cannot be returned via - // chromeos::Any. Fail here. - LOG(ERROR) << "Cannot return FileDescriptor via Any"; - return false; - default: - LOG(FATAL) << "Unknown D-Bus data type: " << variant_reader.GetDataType(); - return false; - } - return true; -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/data_serialization.h b/chromeos/dbus/data_serialization.h deleted file mode 100644 index a039ec2..0000000 --- a/chromeos/dbus/data_serialization.h +++ /dev/null @@ -1,886 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DATA_SERIALIZATION_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DATA_SERIALIZATION_H_ - -// The main functionality provided by this header file is methods to serialize -// native C++ data over D-Bus. This includes three major parts: -// - Methods to get the D-Bus signature for a given C++ type: -// std::string GetDBusSignature(); -// - Methods to write arbitrary C++ data to D-Bus MessageWriter: -// void AppendValueToWriter(dbus::MessageWriter* writer, const T& value); -// void AppendValueToWriterAsVariant(dbus::MessageWriter*, const T&); -// - Methods to read arbitrary C++ data from D-Bus MessageReader: -// bool PopValueFromReader(dbus::MessageReader* reader, T* value); -// bool PopVariantValueFromReader(dbus::MessageReader* reader, T* value); -// -// There are a number of overloads to handle C++ equivalents of basic D-Bus -// types: -// D-Bus Type | D-Bus Signature | Native C++ type -// -------------------------------------------------- -// BYTE | y | uint8_t -// BOOL | b | bool -// INT16 | n | int16_t -// UINT16 | q | uint16_t -// INT32 | i | int32_t (int) -// UINT32 | u | uint32_t (unsigned) -// INT64 | x | int64_t -// UINT64 | t | uint64_t -// DOUBLE | d | double -// STRING | s | std::string -// OBJECT_PATH | o | dbus::ObjectPath -// ARRAY | aT | std::vector -// STRUCT | (UV) | std::pair -// | (UVW...) | std::tuple -// DICT | a{KV} | std::map -// VARIANT | v | chromeos::Any -// UNIX_FD | h | dbus::FileDescriptor -// SIGNATURE | g | (unsupported) -// -// Additional overloads/specialization can be provided for custom types. -// In order to do that, provide overloads of AppendValueToWriter() and -// PopValueFromReader() functions in chromeos::dbus_utils namespace for the -// CustomType. As well as a template specialization of DBusType<> for the same -// CustomType. This specialization must provide three static functions: -// - static std::string GetSignature(); -// - static void Write(dbus::MessageWriter* writer, const CustomType& value); -// - static bool Read(dbus::MessageReader* reader, CustomType* value); -// See an example in DBusUtils.CustomStruct unit test in -// chromeos/dbus/data_serialization_unittest.cc. - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace google { -namespace protobuf { -class MessageLite; -} // namespace protobuf -} // namespace google - -namespace chromeos { - -// Forward-declare only. Can't include any.h right now because it needs -// AppendValueToWriter() declared below. -class Any; - -namespace dbus_utils { - -// Base class for DBusType for T not supported by D-Bus. This used to -// implement IsTypeSupported<> below. -struct Unsupported {}; - -// Generic definition of DBusType which will be specialized for particular -// types later. -// The second template parameter is used only in SFINAE situations to resolve -// class hierarchy chains for protobuf-derived classes. This type is defaulted -// to be 'void' in all other cases and simply ignored. -// See DBusType specialization for google::protobuf::MessageLite below for more -// detailed information. -template -struct DBusType : public Unsupported {}; - -// A helper type trait to determine if all of the types listed in Types... are -// supported by D-Bus. This is a generic forward-declaration which will be -// specialized for different type combinations. -template -struct IsTypeSupported; - -// Both T and the Types... must be supported for the complete set to be -// supported. -template -struct IsTypeSupported - : public std::integral_constant< - bool, - IsTypeSupported::value && IsTypeSupported::value> {}; - -// For a single type T, check if DBusType derives from Unsupported. -// If it does, then the type is not supported by the D-Bus. -template -struct IsTypeSupported - : public std::integral_constant< - bool, - !std::is_base_of>::value> {}; - -// Empty set is not supported. -template<> -struct IsTypeSupported<> : public std::false_type {}; - -//---------------------------------------------------------------------------- -// AppendValueToWriter(dbus::MessageWriter* writer, const T& value) -// Write the |value| of type T to D-Bus message. -// Explicitly delete the overloads for scalar types that are not supported by -// D-Bus. -void AppendValueToWriter(dbus::MessageWriter* writer, char value) = delete; -void AppendValueToWriter(dbus::MessageWriter* writer, float value) = delete; - -//---------------------------------------------------------------------------- -// PopValueFromReader(dbus::MessageWriter* writer, T* value) -// Reads the |value| of type T from D-Bus message. -// Explicitly delete the overloads for scalar types that are not supported by -// D-Bus. -void PopValueFromReader(dbus::MessageReader* reader, char* value) = delete; -void PopValueFromReader(dbus::MessageReader* reader, float* value) = delete; - -//---------------------------------------------------------------------------- -// Get D-Bus data signature from C++ data types. -// Specializations of a generic GetDBusSignature() provide signature strings -// for native C++ types. This function is available only for type supported -// by D-Bus. -template -inline typename std::enable_if::value, std::string>::type -GetDBusSignature() { - return DBusType::GetSignature(); -} - -namespace details { -// Helper method used by the many overloads of PopValueFromReader(). -// If the current value in the reader is of Variant type, the method descends -// into the Variant and updates the |*reader_ref| with the transient -// |variant_reader| MessageReader instance passed in. -// Returns false if it fails to descend into the Variant. -inline bool DescendIntoVariantIfPresent(dbus::MessageReader** reader_ref, - dbus::MessageReader* variant_reader) { - if ((*reader_ref)->GetDataType() != dbus::Message::VARIANT) - return true; - if (!(*reader_ref)->PopVariant(variant_reader)) - return false; - *reader_ref = variant_reader; - return true; -} - -// Helper method to format the type string of an array. -// Essentially it adds "a" in front of |element_signature|. -inline std::string GetArrayDBusSignature(const std::string& element_signature) { - return DBUS_TYPE_ARRAY_AS_STRING + element_signature; -} - -// Helper method to get a signature string for DICT_ENTRY. -// Returns "{KV}", where "K" and "V" are the type signatures for types -// KEY/VALUE. For example, GetDBusDictEntryType() would return -// "{si}". -template -inline std::string GetDBusDictEntryType() { - return DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + - GetDBusSignature() + GetDBusSignature() + - DBUS_DICT_ENTRY_END_CHAR_AS_STRING; -} - -} // namespace details - -//============================================================================= -// Specializations/overloads for AppendValueToWriter, PopValueFromReader and -// DBusType for various C++ types that can be serialized over D-Bus. - -// bool ----------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - bool value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - bool* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_BOOLEAN_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, bool value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, bool* value) { - return PopValueFromReader(reader, value); - } -}; - -// uint8_t -------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - uint8_t value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - uint8_t* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { return DBUS_TYPE_BYTE_AS_STRING; } - inline static void Write(dbus::MessageWriter* writer, uint8_t value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, uint8_t* value) { - return PopValueFromReader(reader, value); - } -}; - -// int16_t -------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - int16_t value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - int16_t* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { return DBUS_TYPE_INT16_AS_STRING; } - inline static void Write(dbus::MessageWriter* writer, int16_t value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, int16_t* value) { - return PopValueFromReader(reader, value); - } -}; - -// uint16_t ------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - uint16_t value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - uint16_t* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_UINT16_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, uint16_t value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, uint16_t* value) { - return PopValueFromReader(reader, value); - } -}; - -// int32_t -------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - int32_t value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - int32_t* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { return DBUS_TYPE_INT32_AS_STRING; } - inline static void Write(dbus::MessageWriter* writer, int32_t value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, int32_t* value) { - return PopValueFromReader(reader, value); - } -}; - -// uint32_t ------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - uint32_t value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - uint32_t* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_UINT32_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, uint32_t value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, uint32_t* value) { - return PopValueFromReader(reader, value); - } -}; - -// int64_t -------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - int64_t value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - int64_t* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { return DBUS_TYPE_INT64_AS_STRING; } - inline static void Write(dbus::MessageWriter* writer, int64_t value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, int64_t* value) { - return PopValueFromReader(reader, value); - } -}; - -// uint64_t ------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - uint64_t value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - uint64_t* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_UINT64_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, uint64_t value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, uint64_t* value) { - return PopValueFromReader(reader, value); - } -}; - -// double --------------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - double value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - double* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_DOUBLE_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, double value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, double* value) { - return PopValueFromReader(reader, value); - } -}; - -// std::string ---------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - const std::string& value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - std::string* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_STRING_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, - const std::string& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, std::string* value) { - return PopValueFromReader(reader, value); - } -}; - -// const char* -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - const char* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_STRING_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, const char* value) { - AppendValueToWriter(writer, value); - } -}; - -// const char[] -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_STRING_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, const char* value) { - AppendValueToWriter(writer, value); - } -}; - -// dbus::ObjectPath ----------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - const dbus::ObjectPath& value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - dbus::ObjectPath* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_OBJECT_PATH_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, - const dbus::ObjectPath& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, - dbus::ObjectPath* value) { - return PopValueFromReader(reader, value); - } -}; - -// dbus::FileDescriptor ------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - const dbus::FileDescriptor& value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - dbus::FileDescriptor* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_UNIX_FD_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, - const dbus::FileDescriptor& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, - dbus::FileDescriptor* value) { - return PopValueFromReader(reader, value); - } -}; - -// chromeos::Any -------------------------------------------------------------- -CHROMEOS_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer, - const chromeos::Any& value); -CHROMEOS_EXPORT bool PopValueFromReader(dbus::MessageReader* reader, - chromeos::Any* value); - -template<> -struct DBusType { - inline static std::string GetSignature() { - return DBUS_TYPE_VARIANT_AS_STRING; - } - inline static void Write(dbus::MessageWriter* writer, - const chromeos::Any& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, chromeos::Any* value) { - return PopValueFromReader(reader, value); - } -}; - -// std::vector = D-Bus ARRAY. ------------------------------------------------- -template -typename std::enable_if::value>::type AppendValueToWriter( - dbus::MessageWriter* writer, - const std::vector& value) { - dbus::MessageWriter array_writer(nullptr); - writer->OpenArray(GetDBusSignature(), &array_writer); - for (const auto& element : value) { - // Use DBusType::Write() instead of AppendValueToWriter() to delay - // binding to AppendValueToWriter() to the point of instantiation of this - // template. - DBusType::Write(&array_writer, element); - } - writer->CloseContainer(&array_writer); -} - -template -typename std::enable_if::value, bool>::type -PopValueFromReader(dbus::MessageReader* reader, std::vector* value) { - dbus::MessageReader variant_reader(nullptr); - dbus::MessageReader array_reader(nullptr); - if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || - !reader->PopArray(&array_reader)) - return false; - value->clear(); - while (array_reader.HasMoreData()) { - T data; - // Use DBusType::Read() instead of PopValueFromReader() to delay - // binding to PopValueFromReader() to the point of instantiation of this - // template. - if (!DBusType::Read(&array_reader, &data)) - return false; - value->push_back(std::move(data)); - } - return true; -} - -namespace details { -// DBusArrayType<> is a helper base class for DBusType> that provides -// GetSignature/Write/Read methods for T types that are supported by D-Bus -// and not having those methods for types that are not supported by D-Bus. -template -struct DBusArrayType { - // Returns "aT", where "T" is the signature string for type T. - inline static std::string GetSignature() { - return GetArrayDBusSignature(GetDBusSignature()); - } - inline static void Write(dbus::MessageWriter* writer, - const std::vector& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, - std::vector* value) { - return PopValueFromReader(reader, value); - } -}; - -// Explicit specialization for unsupported type T. -template -struct DBusArrayType : public Unsupported {}; - -} // namespace details - -template -struct DBusType> - : public details::DBusArrayType::value, T, ALLOC> {}; - -// std::pair = D-Bus STRUCT with two elements. -------------------------------- -namespace details { - -// Helper class to get a D-Bus signature of a list of types. -// For example, TupleTraits::GetSignature() will -// return "ibs". -template -struct TupleTraits; - -template -struct TupleTraits { - static std::string GetSignature() { - return GetDBusSignature() + - TupleTraits::GetSignature(); - } -}; - -template<> -struct TupleTraits<> { - static std::string GetSignature() { return std::string{}; } -}; - -} // namespace details - -template -inline std::string GetStructDBusSignature() { - // Returns "(T...)", where "T..." is the signature strings for types T... - return DBUS_STRUCT_BEGIN_CHAR_AS_STRING + - details::TupleTraits::GetSignature() + - DBUS_STRUCT_END_CHAR_AS_STRING; -} - -template -typename std::enable_if::value>::type AppendValueToWriter( - dbus::MessageWriter* writer, - const std::pair& value) { - dbus::MessageWriter struct_writer(nullptr); - writer->OpenStruct(&struct_writer); - // Use DBusType::Write() instead of AppendValueToWriter() to delay - // binding to AppendValueToWriter() to the point of instantiation of this - // template. - DBusType::Write(&struct_writer, value.first); - DBusType::Write(&struct_writer, value.second); - writer->CloseContainer(&struct_writer); -} - -template -typename std::enable_if::value, bool>::type -PopValueFromReader(dbus::MessageReader* reader, std::pair* value) { - dbus::MessageReader variant_reader(nullptr); - dbus::MessageReader struct_reader(nullptr); - if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || - !reader->PopStruct(&struct_reader)) - return false; - // Use DBusType::Read() instead of PopValueFromReader() to delay - // binding to PopValueFromReader() to the point of instantiation of this - // template. - return DBusType::Read(&struct_reader, &value->first) && - DBusType::Read(&struct_reader, &value->second); -} - -namespace details { - -// DBusArrayType<> is a helper base class for DBusType> that provides -// GetSignature/Write/Read methods for types that are supported by D-Bus -// and not having those methods for types that are not supported by D-Bus. -template -struct DBusPairType { - // Returns "(UV)", where "U" and "V" are the signature strings for types U, V. - inline static std::string GetSignature() { - return GetStructDBusSignature(); - } - inline static void Write(dbus::MessageWriter* writer, - const std::pair& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, std::pair* value) { - return PopValueFromReader(reader, value); - } -}; - -// Either U, or V, or both are not supported by D-Bus. -template -struct DBusPairType : public Unsupported {}; - -} // namespace details - -template -struct DBusType> - : public details::DBusPairType::value, U, V> {}; - -// std::tuple = D-Bus STRUCT with arbitrary number of members. ---------------- -namespace details { - -// TupleIterator is a helper class to iterate over all the elements -// of a tuple from index I to N. TupleIterator<>::Read and ::Write methods -// are called for each element of the tuple and iteration continues until I == N -// in which case the specialization for I==N below stops the recursion. -template -struct TupleIterator { - // Tuple is just a convenience alias to a tuple containing elements of type T. - using Tuple = std::tuple; - // ValueType is the type of the element at index I. - using ValueType = typename std::tuple_element::type; - - // Write the tuple element at index I to D-Bus message. - static void Write(dbus::MessageWriter* writer, const Tuple& value) { - // Use DBusType::Write() instead of AppendValueToWriter() to delay - // binding to AppendValueToWriter() to the point of instantiation of this - // template. - DBusType::Write(writer, std::get(value)); - TupleIterator::Write(writer, value); - } - - // Read the tuple element at index I from D-Bus message. - static bool Read(dbus::MessageReader* reader, Tuple* value) { - // Use DBusType::Read() instead of PopValueFromReader() to delay - // binding to PopValueFromReader() to the point of instantiation of this - // template. - return DBusType::Read(reader, &std::get(*value)) && - TupleIterator::Read(reader, value); - } -}; - -// Specialization to end the iteration when the index reaches the last element. -template -struct TupleIterator { - using Tuple = std::tuple; - static void Write(dbus::MessageWriter* writer, const Tuple& value) {} - static bool Read(dbus::MessageReader* reader, Tuple* value) { return true; } -}; - -} // namespace details - -template -typename std::enable_if::value>::type AppendValueToWriter( - dbus::MessageWriter* writer, - const std::tuple& value) { - dbus::MessageWriter struct_writer(nullptr); - writer->OpenStruct(&struct_writer); - details::TupleIterator<0, sizeof...(T), T...>::Write(&struct_writer, value); - writer->CloseContainer(&struct_writer); -} - -template -typename std::enable_if::value, bool>::type -PopValueFromReader(dbus::MessageReader* reader, std::tuple* value) { - dbus::MessageReader variant_reader(nullptr); - dbus::MessageReader struct_reader(nullptr); - if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || - !reader->PopStruct(&struct_reader)) - return false; - return details::TupleIterator<0, sizeof...(T), T...>::Read(&struct_reader, - value); -} - -namespace details { - -// DBusTupleType<> is a helper base class for DBusType> that -// provides GetSignature/Write/Read methods for types that are supported by -// D-Bus and not having those methods for types that are not supported by D-Bus. -template -struct DBusTupleType { - // Returns "(T...)", where "T..." are the signature strings for types T... - inline static std::string GetSignature() { - return GetStructDBusSignature(); - } - inline static void Write(dbus::MessageWriter* writer, - const std::tuple& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, - std::tuple* value) { - return PopValueFromReader(reader, value); - } -}; - -// Some/all of types T... are not supported by D-Bus. -template -struct DBusTupleType : public Unsupported {}; - -} // namespace details - -template -struct DBusType> - : public details::DBusTupleType::value, T...> {}; - -// std::map = D-Bus ARRAY of DICT_ENTRY. -------------------------------------- -template -typename std::enable_if::value>::type -AppendValueToWriter(dbus::MessageWriter* writer, - const std::map& value) { - dbus::MessageWriter dict_writer(nullptr); - writer->OpenArray(details::GetDBusDictEntryType(), &dict_writer); - for (const auto& pair : value) { - dbus::MessageWriter entry_writer(nullptr); - dict_writer.OpenDictEntry(&entry_writer); - // Use DBusType::Write() instead of AppendValueToWriter() to delay - // binding to AppendValueToWriter() to the point of instantiation of this - // template. - DBusType::Write(&entry_writer, pair.first); - DBusType::Write(&entry_writer, pair.second); - dict_writer.CloseContainer(&entry_writer); - } - writer->CloseContainer(&dict_writer); -} - -template -typename std::enable_if::value, bool>::type -PopValueFromReader(dbus::MessageReader* reader, - std::map* value) { - dbus::MessageReader variant_reader(nullptr); - dbus::MessageReader array_reader(nullptr); - if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || - !reader->PopArray(&array_reader)) - return false; - value->clear(); - while (array_reader.HasMoreData()) { - dbus::MessageReader dict_entry_reader(nullptr); - if (!array_reader.PopDictEntry(&dict_entry_reader)) - return false; - KEY key; - VALUE data; - // Use DBusType::Read() instead of PopValueFromReader() to delay - // binding to PopValueFromReader() to the point of instantiation of this - // template. - if (!DBusType::Read(&dict_entry_reader, &key) || - !DBusType::Read(&dict_entry_reader, &data)) - return false; - value->emplace(std::move(key), std::move(data)); - } - return true; -} - -namespace details { - -// DBusArrayType<> is a helper base class for DBusType> that provides -// GetSignature/Write/Read methods for T types that are supported by D-Bus -// and not having those methods for types that are not supported by D-Bus. -template -struct DBusMapType { - // Returns "a{KV}", where "K" and "V" are the signature strings for types - // KEY/VALUE. - inline static std::string GetSignature() { - return GetArrayDBusSignature(GetDBusDictEntryType()); - } - inline static void Write(dbus::MessageWriter* writer, - const std::map& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, - std::map* value) { - return PopValueFromReader(reader, value); - } -}; - -// Types KEY, VALUE or both are not supported by D-Bus. -template -struct DBusMapType : public Unsupported {}; - -} // namespace details - -template -struct DBusType> - : public details::DBusMapType::value, - KEY, - VALUE, - PRED, - ALLOC> {}; - -// google::protobuf::MessageLite = D-Bus ARRAY of BYTE ------------------------ -inline void AppendValueToWriter(dbus::MessageWriter* writer, - const google::protobuf::MessageLite& value) { - writer->AppendProtoAsArrayOfBytes(value); -} - -inline bool PopValueFromReader(dbus::MessageReader* reader, - google::protobuf::MessageLite* value) { - return reader->PopArrayOfBytesAsProto(value); -} - -// is_protobuf_t is a helper type trait to determine if type T derives from -// google::protobuf::MessageLite. -template -using is_protobuf = std::is_base_of; - -// Specialize DBusType for classes that derive from protobuf::MessageLite. -// Here we perform a partial specialization of DBusType only for types -// that derive from google::protobuf::MessageLite. This is done by employing -// the second template parameter in DBusType and this basically relies on C++ -// SFINAE rules. "typename std::enable_if::value>::type" will -// evaluate to "void" for classes T that descend from MessageLite and will be -// an invalid construct for other types/classes which will automatically -// remove this particular specialization from name resolution context. -template -struct DBusType::value>::type> { - inline static std::string GetSignature() { - return GetDBusSignature>(); - } - inline static void Write(dbus::MessageWriter* writer, const T& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, T* value) { - return PopValueFromReader(reader, value); - } -}; - -//---------------------------------------------------------------------------- -// AppendValueToWriterAsVariant(dbus::MessageWriter* writer, const T& value) -// Write the |value| of type T to D-Bus message as a VARIANT. -// This overload is provided only if T is supported by D-Bus. -template -typename std::enable_if::value>::type -AppendValueToWriterAsVariant(dbus::MessageWriter* writer, const T& value) { - std::string data_type = GetDBusSignature(); - dbus::MessageWriter variant_writer(nullptr); - writer->OpenVariant(data_type, &variant_writer); - // Use DBusType::Write() instead of AppendValueToWriter() to delay - // binding to AppendValueToWriter() to the point of instantiation of this - // template. - DBusType::Write(&variant_writer, value); - writer->CloseContainer(&variant_writer); -} - -// Special case: do not allow to write a Variant containing a Variant. -// Just redirect to normal AppendValueToWriter(). -inline void AppendValueToWriterAsVariant(dbus::MessageWriter* writer, - const chromeos::Any& value) { - return AppendValueToWriter(writer, value); -} - -//---------------------------------------------------------------------------- -// PopVariantValueFromReader(dbus::MessageWriter* writer, T* value) -// Reads a Variant containing the |value| of type T from D-Bus message. -// Note that the generic PopValueFromReader(...) can do this too. -// This method is provided for two reasons: -// 1. For API symmetry with AppendValueToWriter/AppendValueToWriterAsVariant. -// 2. To be used when it is important to assert that the data was sent -// specifically as a Variant. -// This overload is provided only if T is supported by D-Bus. -template -typename std::enable_if::value, bool>::type -PopVariantValueFromReader(dbus::MessageReader* reader, T* value) { - dbus::MessageReader variant_reader(nullptr); - if (!reader->PopVariant(&variant_reader)) - return false; - // Use DBusType::Read() instead of PopValueFromReader() to delay - // binding to PopValueFromReader() to the point of instantiation of this - // template. - return DBusType::Read(&variant_reader, value); -} - -// Special handling of request to read a Variant of Variant. -inline bool PopVariantValueFromReader(dbus::MessageReader* reader, Any* value) { - return PopValueFromReader(reader, value); -} - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DATA_SERIALIZATION_H_ diff --git a/chromeos/dbus/data_serialization_unittest.cc b/chromeos/dbus/data_serialization_unittest.cc deleted file mode 100644 index 953a608..0000000 --- a/chromeos/dbus/data_serialization_unittest.cc +++ /dev/null @@ -1,786 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include - -#include "unittests/test.pb.h" - -using dbus::FileDescriptor; -using dbus::Message; -using dbus::MessageReader; -using dbus::MessageWriter; -using dbus::ObjectPath; -using dbus::Response; - -namespace chromeos { -namespace dbus_utils { - -TEST(DBusUtils, Supported_BasicTypes) { - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); - EXPECT_TRUE(IsTypeSupported::value); -} - -TEST(DBusUtils, Unsupported_BasicTypes) { - EXPECT_FALSE(IsTypeSupported::value); - EXPECT_FALSE(IsTypeSupported::value); -} - -TEST(DBusUtils, Supported_ComplexTypes) { - EXPECT_TRUE(IsTypeSupported>::value); - EXPECT_TRUE(IsTypeSupported>::value); - EXPECT_TRUE((IsTypeSupported>::value)); - EXPECT_TRUE( - (IsTypeSupported>>::value)); - EXPECT_TRUE((IsTypeSupported>::value)); - EXPECT_TRUE( - IsTypeSupported>::value); -} - -TEST(DBusUtils, Unsupported_ComplexTypes) { - EXPECT_FALSE(IsTypeSupported>::value); - EXPECT_FALSE((IsTypeSupported>::value)); - EXPECT_FALSE((IsTypeSupported>::value)); - EXPECT_FALSE((IsTypeSupported>::value)); - EXPECT_FALSE((IsTypeSupported>::value)); - EXPECT_FALSE((IsTypeSupported>::value)); -} - -TEST(DBusUtils, Supported_TypeSet) { - EXPECT_TRUE((IsTypeSupported::value)); - EXPECT_TRUE((IsTypeSupported, uint8_t>::value)); -} - -TEST(DBusUtils, Unupported_TypeSet) { - EXPECT_FALSE((IsTypeSupported::value)); - EXPECT_FALSE( - (IsTypeSupported, uint8_t>>::value)); - EXPECT_FALSE((IsTypeSupported::value)); - EXPECT_FALSE((IsTypeSupported, float>::value)); -} - -TEST(DBusUtils, Signatures_BasicTypes) { - EXPECT_EQ("b", GetDBusSignature()); - EXPECT_EQ("y", GetDBusSignature()); - EXPECT_EQ("n", GetDBusSignature()); - EXPECT_EQ("q", GetDBusSignature()); - EXPECT_EQ("i", GetDBusSignature()); - EXPECT_EQ("u", GetDBusSignature()); - EXPECT_EQ("x", GetDBusSignature()); - EXPECT_EQ("t", GetDBusSignature()); - EXPECT_EQ("d", GetDBusSignature()); - EXPECT_EQ("s", GetDBusSignature()); - EXPECT_EQ("o", GetDBusSignature()); - EXPECT_EQ("h", GetDBusSignature()); - EXPECT_EQ("v", GetDBusSignature()); -} - -TEST(DBusUtils, Signatures_Arrays) { - EXPECT_EQ("ab", GetDBusSignature>()); - EXPECT_EQ("ay", GetDBusSignature>()); - EXPECT_EQ("an", GetDBusSignature>()); - EXPECT_EQ("aq", GetDBusSignature>()); - EXPECT_EQ("ai", GetDBusSignature>()); - EXPECT_EQ("au", GetDBusSignature>()); - EXPECT_EQ("ax", GetDBusSignature>()); - EXPECT_EQ("at", GetDBusSignature>()); - EXPECT_EQ("ad", GetDBusSignature>()); - EXPECT_EQ("as", GetDBusSignature>()); - EXPECT_EQ("ao", GetDBusSignature>()); - EXPECT_EQ("ah", GetDBusSignature>()); - EXPECT_EQ("av", GetDBusSignature>()); - EXPECT_EQ("a(is)", - (GetDBusSignature>>())); - EXPECT_EQ("aad", GetDBusSignature>>()); -} - -TEST(DBusUtils, Signatures_Maps) { - EXPECT_EQ("a{sb}", (GetDBusSignature>())); - EXPECT_EQ("a{ss}", (GetDBusSignature>())); - EXPECT_EQ("a{sv}", (GetDBusSignature>())); - EXPECT_EQ("a{id}", (GetDBusSignature>())); - EXPECT_EQ( - "a{ia{ss}}", - (GetDBusSignature>>())); -} - -TEST(DBusUtils, Signatures_Pairs) { - EXPECT_EQ("(sb)", (GetDBusSignature>())); - EXPECT_EQ("(sv)", (GetDBusSignature>())); - EXPECT_EQ("(id)", (GetDBusSignature>())); -} - -TEST(DBusUtils, Signatures_Tuples) { - EXPECT_EQ("(i)", (GetDBusSignature>())); - EXPECT_EQ("(sv)", (GetDBusSignature>())); - EXPECT_EQ("(id(si))", - (GetDBusSignature< - std::tuple>>())); -} - -TEST(DBusUtils, Signatures_Protobufs) { - EXPECT_EQ("ay", (GetDBusSignature())); - EXPECT_EQ("ay", (GetDBusSignature())); -} - -// Test that a byte can be properly written and read. We only have this -// test for byte, as repeating this for other basic types is too redundant. -TEST(DBusUtils, AppendAndPopByte) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, uint8_t{123}); - EXPECT_EQ("y", message->GetSignature()); - - MessageReader reader(message.get()); - EXPECT_TRUE(reader.HasMoreData()); // Should have data to read. - EXPECT_EQ(Message::BYTE, reader.GetDataType()); - - bool bool_value = false; - // Should fail as the type is not bool here. - EXPECT_FALSE(PopValueFromReader(&reader, &bool_value)); - - uint8_t byte_value = 0; - EXPECT_TRUE(PopValueFromReader(&reader, &byte_value)); - EXPECT_EQ(123, byte_value); // Should match with the input. - EXPECT_FALSE(reader.HasMoreData()); // Should not have more data to read. - - // Try to get another byte. Should fail. - EXPECT_FALSE(PopValueFromReader(&reader, &byte_value)); -} - -// Check all basic types can be properly written and read. -TEST(DBusUtils, AppendAndPopBasicDataTypes) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - - // Append 0, true, 2, 3, 4, 5, 6, 7, 8.0, "string", "/object/path". - AppendValueToWriter(&writer, uint8_t{0}); - AppendValueToWriter(&writer, bool{true}); - AppendValueToWriter(&writer, int16_t{2}); - AppendValueToWriter(&writer, uint16_t{3}); - AppendValueToWriter(&writer, int32_t{4}); - AppendValueToWriter(&writer, uint32_t{5}); - AppendValueToWriter(&writer, int64_t{6}); - AppendValueToWriter(&writer, uint64_t{7}); - AppendValueToWriter(&writer, double{8.0}); - AppendValueToWriter(&writer, std::string{"string"}); - AppendValueToWriter(&writer, ObjectPath{"/object/path"}); - - EXPECT_EQ("ybnqiuxtdso", message->GetSignature()); - - uint8_t byte_value = 0; - bool bool_value = false; - int16_t int16_value = 0; - uint16_t uint16_value = 0; - int32_t int32_value = 0; - uint32_t uint32_value = 0; - int64_t int64_value = 0; - uint64_t uint64_value = 0; - double double_value = 0; - std::string string_value; - ObjectPath object_path_value; - - MessageReader reader(message.get()); - EXPECT_TRUE(reader.HasMoreData()); - EXPECT_TRUE(PopValueFromReader(&reader, &byte_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &int16_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &uint16_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &int32_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &uint32_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &int64_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &uint64_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &double_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &object_path_value)); - EXPECT_FALSE(reader.HasMoreData()); - - // 0, true, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path" should be returned. - EXPECT_EQ(0, byte_value); - EXPECT_TRUE(bool_value); - EXPECT_EQ(2, int16_value); - EXPECT_EQ(3U, uint16_value); - EXPECT_EQ(4, int32_value); - EXPECT_EQ(5U, uint32_value); - EXPECT_EQ(6, int64_value); - EXPECT_EQ(7U, uint64_value); - EXPECT_DOUBLE_EQ(8.0, double_value); - EXPECT_EQ("string", string_value); - EXPECT_EQ(ObjectPath{"/object/path"}, object_path_value); -} - -// Check all basic types can be properly written and read. -TEST(DBusUtils, AppendAndPopFileDescriptor) { - if (!dbus::IsDBusTypeUnixFdSupported()) { - LOG(WARNING) << "FD passing is not supported"; - return; - } - - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - - // Append stdout. - FileDescriptor temp(1); - // Descriptor should not be valid until checked. - EXPECT_FALSE(temp.is_valid()); - // NB: thread IO requirements not relevant for unit tests. - temp.CheckValidity(); - EXPECT_TRUE(temp.is_valid()); - AppendValueToWriter(&writer, temp); - - EXPECT_EQ("h", message->GetSignature()); - - FileDescriptor fd_value; - - MessageReader reader(message.get()); - EXPECT_TRUE(reader.HasMoreData()); - EXPECT_TRUE(PopValueFromReader(&reader, &fd_value)); - EXPECT_FALSE(reader.HasMoreData()); - // Descriptor is automatically checked for validity as part of - // PopValueFromReader() call. - EXPECT_TRUE(fd_value.is_valid()); -} - -// Check all variant types can be properly written and read. -TEST(DBusUtils, AppendAndPopVariantDataTypes) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - - // Append 10, false, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path". - AppendValueToWriterAsVariant(&writer, uint8_t{10}); - AppendValueToWriterAsVariant(&writer, bool{false}); - AppendValueToWriterAsVariant(&writer, int16_t{12}); - AppendValueToWriterAsVariant(&writer, uint16_t{13}); - AppendValueToWriterAsVariant(&writer, int32_t{14}); - AppendValueToWriterAsVariant(&writer, uint32_t{15}); - AppendValueToWriterAsVariant(&writer, int64_t{16}); - AppendValueToWriterAsVariant(&writer, uint64_t{17}); - AppendValueToWriterAsVariant(&writer, double{18.5}); - AppendValueToWriterAsVariant(&writer, std::string{"data"}); - AppendValueToWriterAsVariant(&writer, ObjectPath{"/obj/path"}); - AppendValueToWriterAsVariant(&writer, Any{17}); - AppendValueToWriterAsVariant(&writer, - Any{std::vector>{{6, 7}}}); - - EXPECT_EQ("vvvvvvvvvvvvv", message->GetSignature()); - - uint8_t byte_value = 0; - bool bool_value = true; - int16_t int16_value = 0; - uint16_t uint16_value = 0; - int32_t int32_value = 0; - uint32_t uint32_value = 0; - int64_t int64_value = 0; - uint64_t uint64_value = 0; - double double_value = 0; - std::string string_value; - ObjectPath object_path_value; - Any any_value; - Any any_vector_vector; - - MessageReader reader(message.get()); - EXPECT_TRUE(reader.HasMoreData()); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &byte_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &bool_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &int16_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint16_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &int32_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint32_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &int64_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint64_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &double_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &string_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &object_path_value)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &any_value)); - // Not implemented. - EXPECT_FALSE(PopVariantValueFromReader(&reader, &any_vector_vector)); - EXPECT_FALSE(reader.HasMoreData()); - - EXPECT_EQ(10, byte_value); - EXPECT_FALSE(bool_value); - EXPECT_EQ(12, int16_value); - EXPECT_EQ(13U, uint16_value); - EXPECT_EQ(14, int32_value); - EXPECT_EQ(15U, uint32_value); - EXPECT_EQ(16, int64_value); - EXPECT_EQ(17U, uint64_value); - EXPECT_DOUBLE_EQ(18.5, double_value); - EXPECT_EQ("data", string_value); - EXPECT_EQ(ObjectPath{"/obj/path"}, object_path_value); - EXPECT_EQ(17, any_value.Get()); - EXPECT_TRUE(any_vector_vector.IsEmpty()); -} - -TEST(DBusUtils, AppendAndPopBasicAny) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - - // Append 10, true, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path". - AppendValueToWriter(&writer, Any(uint8_t{10})); - AppendValueToWriter(&writer, Any(bool{true})); - AppendValueToWriter(&writer, Any(int16_t{12})); - AppendValueToWriter(&writer, Any(uint16_t{13})); - AppendValueToWriter(&writer, Any(int32_t{14})); - AppendValueToWriter(&writer, Any(uint32_t{15})); - AppendValueToWriter(&writer, Any(int64_t{16})); - AppendValueToWriter(&writer, Any(uint64_t{17})); - AppendValueToWriter(&writer, Any(double{18.5})); - AppendValueToWriter(&writer, Any(std::string{"data"})); - AppendValueToWriter(&writer, Any(ObjectPath{"/obj/path"})); - EXPECT_EQ("vvvvvvvvvvv", message->GetSignature()); - - Any byte_value; - Any bool_value; - Any int16_value; - Any uint16_value; - Any int32_value; - Any uint32_value; - Any int64_value; - Any uint64_value; - Any double_value; - Any string_value; - Any object_path_value; - - MessageReader reader(message.get()); - EXPECT_TRUE(reader.HasMoreData()); - EXPECT_TRUE(PopValueFromReader(&reader, &byte_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &int16_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &uint16_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &int32_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &uint32_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &int64_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &uint64_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &double_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &object_path_value)); - EXPECT_FALSE(reader.HasMoreData()); - - // Must be: 10, true, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path". - EXPECT_EQ(10, byte_value.Get()); - EXPECT_TRUE(bool_value.Get()); - EXPECT_EQ(12, int16_value.Get()); - EXPECT_EQ(13U, uint16_value.Get()); - EXPECT_EQ(14, int32_value.Get()); - EXPECT_EQ(15U, uint32_value.Get()); - EXPECT_EQ(16, int64_value.Get()); - EXPECT_EQ(17U, uint64_value.Get()); - EXPECT_DOUBLE_EQ(18.5, double_value.Get()); - EXPECT_EQ("data", string_value.Get()); - EXPECT_EQ(ObjectPath{"/obj/path"}, object_path_value.Get()); -} - -TEST(DBusUtils, ArrayOfBytes) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector bytes{1, 2, 3}; - AppendValueToWriter(&writer, bytes); - - EXPECT_EQ("ay", message->GetSignature()); - - MessageReader reader(message.get()); - std::vector bytes_out; - EXPECT_TRUE(PopValueFromReader(&reader, &bytes_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(bytes, bytes_out); -} - -TEST(DBusUtils, ArrayOfBytes_Empty) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector bytes; - AppendValueToWriter(&writer, bytes); - - EXPECT_EQ("ay", message->GetSignature()); - - MessageReader reader(message.get()); - std::vector bytes_out; - EXPECT_TRUE(PopValueFromReader(&reader, &bytes_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(bytes, bytes_out); -} - -TEST(DBusUtils, ArrayOfStrings) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector strings{"foo", "bar", "baz"}; - AppendValueToWriter(&writer, strings); - - EXPECT_EQ("as", message->GetSignature()); - - MessageReader reader(message.get()); - std::vector strings_out; - EXPECT_TRUE(PopValueFromReader(&reader, &strings_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(strings, strings_out); -} - -TEST(DBusUtils, ArrayOfInt64) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector values{-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, - std::numeric_limits::min(), - std::numeric_limits::max()}; - AppendValueToWriter(&writer, values); - - EXPECT_EQ("ax", message->GetSignature()); - - MessageReader reader(message.get()); - std::vector values_out; - EXPECT_TRUE(PopValueFromReader(&reader, &values_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(values, values_out); -} - -TEST(DBusUtils, ArrayOfObjectPaths) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector object_paths{ - ObjectPath("/object/path/1"), - ObjectPath("/object/path/2"), - ObjectPath("/object/path/3"), - }; - AppendValueToWriter(&writer, object_paths); - - EXPECT_EQ("ao", message->GetSignature()); - - MessageReader reader(message.get()); - std::vector object_paths_out; - EXPECT_TRUE(PopValueFromReader(&reader, &object_paths_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(object_paths, object_paths_out); -} - -TEST(DBusUtils, ArraysAsVariant) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector int_array{1, 2, 3}; - std::vector str_array{"foo", "bar", "baz"}; - std::vector dbl_array_empty{}; - std::map dict_ss{{"k1", "v1"}, {"k2", "v2"}}; - VariantDictionary dict_sv{{"k1", 1}, {"k2", "v2"}}; - AppendValueToWriterAsVariant(&writer, int_array); - AppendValueToWriterAsVariant(&writer, str_array); - AppendValueToWriterAsVariant(&writer, dbl_array_empty); - AppendValueToWriterAsVariant(&writer, dict_ss); - AppendValueToWriterAsVariant(&writer, dict_sv); - - EXPECT_EQ("vvvvv", message->GetSignature()); - - Any int_array_out; - Any str_array_out; - Any dbl_array_out; - Any dict_ss_out; - Any dict_sv_out; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_array_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &str_array_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &dbl_array_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &dict_ss_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &dict_sv_out)); - EXPECT_FALSE(reader.HasMoreData()); - - EXPECT_EQ(int_array, int_array_out.Get>()); - EXPECT_EQ(str_array, str_array_out.Get>()); - EXPECT_EQ(dbl_array_empty, dbl_array_out.Get>()); - EXPECT_EQ(dict_ss, (dict_ss_out.Get>())); - EXPECT_EQ(dict_sv["k1"].Get(), - dict_sv_out.Get().at("k1").Get()); - EXPECT_EQ(dict_sv["k2"].Get(), - dict_sv_out.Get().at("k2").Get()); -} - -TEST(DBusUtils, VariantDictionary) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - VariantDictionary values{ - {"key1", uint8_t{10}}, - {"key2", bool{true}}, - {"key3", int16_t{12}}, - {"key4", uint16_t{13}}, - {"key5", int32_t{14}}, - {"key6", uint32_t{15}}, - {"key7", int64_t{16}}, - {"key8", uint64_t{17}}, - {"key9", double{18.5}}, - {"keyA", std::string{"data"}}, - {"keyB", ObjectPath{"/obj/path"}}, - }; - AppendValueToWriter(&writer, values); - - EXPECT_EQ("a{sv}", message->GetSignature()); - - MessageReader reader(message.get()); - VariantDictionary values_out; - EXPECT_TRUE(PopValueFromReader(&reader, &values_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(values.size(), values_out.size()); - EXPECT_EQ(values["key1"].Get(), values_out["key1"].Get()); - EXPECT_EQ(values["key2"].Get(), values_out["key2"].Get()); - EXPECT_EQ(values["key3"].Get(), values_out["key3"].Get()); - EXPECT_EQ(values["key4"].Get(), values_out["key4"].Get()); - EXPECT_EQ(values["key5"].Get(), values_out["key5"].Get()); - EXPECT_EQ(values["key6"].Get(), values_out["key6"].Get()); - EXPECT_EQ(values["key7"].Get(), values_out["key7"].Get()); - EXPECT_EQ(values["key8"].Get(), values_out["key8"].Get()); - EXPECT_EQ(values["key9"].Get(), values_out["key9"].Get()); - EXPECT_EQ(values["keyA"].Get(), - values_out["keyA"].Get()); - EXPECT_EQ(values["keyB"].Get(), - values_out["keyB"].Get()); -} - -TEST(DBusUtils, StringToStringMap) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::map values{ - {"key1", "value1"}, - {"key2", "value2"}, - {"key3", "value3"}, - {"key4", "value4"}, - {"key5", "value5"}, - }; - AppendValueToWriter(&writer, values); - - EXPECT_EQ("a{ss}", message->GetSignature()); - - MessageReader reader(message.get()); - std::map values_out; - EXPECT_TRUE(PopValueFromReader(&reader, &values_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(values, values_out); -} - -TEST(DBusUtils, Pair) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::pair struct1{"value2", 3}; - AppendValueToWriter(&writer, struct1); - std::pair> struct2{1, {2, 3}}; - AppendValueToWriter(&writer, struct2); - - EXPECT_EQ("(si)(i(ii))", message->GetSignature()); - - std::pair struct1_out; - std::pair> struct2_out; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &struct1_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &struct2_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(struct1, struct1_out); - EXPECT_EQ(struct2, struct2_out); -} - -TEST(DBusUtils, Tuple) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::tuple struct1{"value2", 3}; - AppendValueToWriter(&writer, struct1); - std::tuple>> struct2{ - 1, "a", {{2, 3}} - }; - AppendValueToWriter(&writer, struct2); - - EXPECT_EQ("(si)(isa(ii))", message->GetSignature()); - - std::tuple struct1_out; - std::tuple>> struct2_out; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &struct1_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &struct2_out)); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(struct1, struct1_out); - EXPECT_EQ(struct2, struct2_out); -} - -TEST(DBusUtils, ReinterpretVariant) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector str_array{"foo", "bar", "baz"}; - std::map dict_ss{{"k1", "v1"}, {"k2", "v2"}}; - VariantDictionary dict_sv{{"k1", "v1"}, {"k2", "v2"}}; - AppendValueToWriterAsVariant(&writer, 123); - AppendValueToWriterAsVariant(&writer, str_array); - AppendValueToWriterAsVariant(&writer, 1.7); - AppendValueToWriterAsVariant(&writer, dict_ss); - AppendValueToWriter(&writer, dict_sv); - - EXPECT_EQ("vvvva{sv}", message->GetSignature()); - - int int_out = 0; - std::vector str_array_out; - double dbl_out = 0.0; - std::map dict_ss_out; - std::map dict_ss_out2; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &str_array_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &dbl_out)); - EXPECT_TRUE(PopValueFromReader(&reader, &dict_ss_out)); - EXPECT_TRUE(PopValueFromReader(&reader, - &dict_ss_out2)); // Read "a{sv}" as "a{ss}". - EXPECT_FALSE(reader.HasMoreData()); - - EXPECT_EQ(123, int_out); - EXPECT_EQ(str_array, str_array_out); - EXPECT_DOUBLE_EQ(1.7, dbl_out); - EXPECT_EQ(dict_ss, dict_ss_out); - EXPECT_EQ(dict_ss, dict_ss_out2); -} - -// Test handling of custom data types. -struct Person { - std::string first_name; - std::string last_name; - int age; - // Provide == operator so we can easily compare arrays of Person. - bool operator==(const Person& rhs) const { - return first_name == rhs.first_name && last_name == rhs.last_name && - age == rhs.age; - } -}; - -// Overload AppendValueToWriter() for "Person" structure. -void AppendValueToWriter(dbus::MessageWriter* writer, const Person& value) { - dbus::MessageWriter struct_writer(nullptr); - writer->OpenStruct(&struct_writer); - AppendValueToWriter(&struct_writer, value.first_name); - AppendValueToWriter(&struct_writer, value.last_name); - AppendValueToWriter(&struct_writer, value.age); - writer->CloseContainer(&struct_writer); -} - -// Overload PopValueFromReader() for "Person" structure. -bool PopValueFromReader(dbus::MessageReader* reader, Person* value) { - dbus::MessageReader variant_reader(nullptr); - dbus::MessageReader struct_reader(nullptr); - if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) || - !reader->PopStruct(&struct_reader)) - return false; - return PopValueFromReader(&struct_reader, &value->first_name) && - PopValueFromReader(&struct_reader, &value->last_name) && - PopValueFromReader(&struct_reader, &value->age); -} - -// Specialize DBusType for "Person" structure. -template<> -struct DBusType { - inline static std::string GetSignature() { - return GetStructDBusSignature(); - } - inline static void Write(dbus::MessageWriter* writer, const Person& value) { - AppendValueToWriter(writer, value); - } - inline static bool Read(dbus::MessageReader* reader, Person* value) { - return PopValueFromReader(reader, value); - } -}; - -TEST(DBusUtils, CustomStruct) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector people{{"John", "Doe", 32}, {"Jane", "Smith", 48}}; - AppendValueToWriter(&writer, people); - AppendValueToWriterAsVariant(&writer, people); - AppendValueToWriterAsVariant(&writer, people); - - EXPECT_EQ("a(ssi)vv", message->GetSignature()); - - std::vector people_out1; - std::vector people_out2; - std::vector people_out3; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &people_out1)); - EXPECT_TRUE(PopValueFromReader(&reader, &people_out2)); - EXPECT_TRUE(PopVariantValueFromReader(&reader, &people_out3)); - EXPECT_FALSE(reader.HasMoreData()); - - EXPECT_EQ(people, people_out1); - EXPECT_EQ(people, people_out2); - EXPECT_EQ(people, people_out3); -} - -TEST(DBusUtils, CustomStructInComplexTypes) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - std::vector people{{"John", "Doe", 32}, {"Jane", "Smith", 48}}; - std::vector> data{ - { - {1, Person{"John", "Doe", 32}}, - {2, Person{"Jane", "Smith", 48}}, - } - }; - AppendValueToWriter(&writer, data); - - EXPECT_EQ("aa{i(ssi)}", message->GetSignature()); - - std::vector> data_out; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &data_out)); - EXPECT_FALSE(reader.HasMoreData()); - - EXPECT_EQ(data, data_out); -} - -TEST(DBusUtils, EmptyVariant) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - EXPECT_DEATH(AppendValueToWriter(&writer, Any{}), - "Must not be called on an empty Any"); -} - -TEST(DBusUtils, IncompatibleVariant) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - EXPECT_DEATH(AppendValueToWriter(&writer, Any{2.2f}), - "Type 'float' is not supported by D-Bus"); -} - -TEST(DBusUtils, Protobuf) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - - dbus_utils_test::TestMessage test_message; - test_message.set_foo(123); - test_message.set_bar("abcd"); - - AppendValueToWriter(&writer, test_message); - - EXPECT_EQ("ay", message->GetSignature()); - - dbus_utils_test::TestMessage test_message_out; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &test_message_out)); - EXPECT_FALSE(reader.HasMoreData()); - - EXPECT_EQ(123, test_message_out.foo()); - EXPECT_EQ("abcd", test_message_out.bar()); -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_method_invoker.cc b/chromeos/dbus/dbus_method_invoker.cc deleted file mode 100644 index 4f1f28f..0000000 --- a/chromeos/dbus/dbus_method_invoker.cc +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -namespace chromeos { -namespace dbus_utils { - -void TranslateErrorResponse(const AsyncErrorCallback& callback, - dbus::ErrorResponse* resp) { - if (!callback.is_null()) { - ErrorPtr error; - dbus::MessageReader reader(resp); - std::string error_message; - if (ExtractMessageParameters(&reader, &error, &error_message)) - AddDBusError(&error, resp->GetErrorName(), error_message); - callback.Run(error.get()); - } -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_method_invoker.h b/chromeos/dbus/dbus_method_invoker.h deleted file mode 100644 index aea5695..0000000 --- a/chromeos/dbus/dbus_method_invoker.h +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file provides a way to call D-Bus methods on objects in remote processes -// as if they were native C++ function calls. - -// CallMethodAndBlock (along with CallMethodAndBlockWithTimeout) lets you call -// a D-Bus method synchronously and pass all the required parameters as C++ -// function arguments. CallMethodAndBlock relies on automatic C++ to D-Bus data -// serialization implemented in chromeos/dbus/data_serialization.h. -// CallMethodAndBlock invokes the D-Bus method and returns the Response. - -// The method call response should be parsed with ExtractMethodCallResults(). -// The method takes an optional list of pointers to the expected return values -// of the D-Bus method. - -// CallMethod and CallMethodWithTimeout are similar to CallMethodAndBlock but -// make the calls asynchronously. They take two callbacks: one for successful -// method invocation and the second is for error conditions. - -// Here is an example of synchronous calls: -// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus: - -// using chromeos::dbus_utils::CallMethodAndBlock; -// using chromeos::dbus_utils::ExtractMethodCallResults; -// -// chromeos::ErrorPtr error; -// auto resp = CallMethodAndBlock(obj, -// "org.chromium.MyService.MyInterface", -// "MyMethod", -// &error, -// 2, 8.7); -// std::string return_value; -// if (resp && ExtractMethodCallResults(resp.get(), &error, &return_value)) { -// // Use the |return_value|. -// } else { -// // An error occurred. Use |error| to get details. -// } - -// And here is how to call D-Bus methods asynchronously: -// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus: - -// using chromeos::dbus_utils::CallMethod; -// using chromeos::dbus_utils::ExtractMethodCallResults; -// -// void OnSuccess(const std::string& return_value) { -// // Use the |return_value|. -// } -// -// void OnError(chromeos::Error* error) { -// // An error occurred. Use |error| to get details. -// } -// -// chromeos::dbus_utils::CallMethod(obj, -// "org.chromium.MyService.MyInterface", -// "MyMethod", -// base::Bind(OnSuccess), -// base::Bind(OnError), -// 2, 8.7); - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_INVOKER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_INVOKER_H_ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// A helper method to dispatch a blocking D-Bus method call. Can specify -// zero or more method call arguments in |args| which will be sent over D-Bus. -// This method sends a D-Bus message and blocks for a time period specified -// in |timeout_ms| while waiting for a reply. The time out is in milliseconds or -// -1 (DBUS_TIMEOUT_USE_DEFAULT) for default, or DBUS_TIMEOUT_INFINITE for no -// timeout. If a timeout occurs, the response object contains an error object -// with DBUS_ERROR_NO_REPLY error code (those constants come from libdbus -// [dbus/dbus.h]). -// Returns a dbus::Response object on success. On failure, returns nullptr and -// fills in additional error details into the |error| object. -template -inline std::unique_ptr CallMethodAndBlockWithTimeout( - int timeout_ms, - dbus::ObjectProxy* object, - const std::string& interface_name, - const std::string& method_name, - ErrorPtr* error, - const Args&... args) { - dbus::MethodCall method_call(interface_name, method_name); - // Add method arguments to the message buffer. - dbus::MessageWriter writer(&method_call); - DBusParamWriter::Append(&writer, args...); - dbus::ScopedDBusError dbus_error; - auto response = object->CallMethodAndBlockWithErrorDetails( - &method_call, timeout_ms, &dbus_error); - if (!response) { - if (dbus_error.is_set()) { - Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - dbus_error.name(), - dbus_error.message()); - } else { - Error::AddToPrintf(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_FAILED, - "Failed to call D-Bus method: %s.%s", - interface_name.c_str(), - method_name.c_str()); - } - } - return std::unique_ptr(response.release()); -} - -// Same as CallMethodAndBlockWithTimeout() but uses a default timeout value. -template -inline std::unique_ptr CallMethodAndBlock( - dbus::ObjectProxy* object, - const std::string& interface_name, - const std::string& method_name, - ErrorPtr* error, - const Args&... args) { - return CallMethodAndBlockWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - object, - interface_name, - method_name, - error, - args...); -} - -namespace internal { -// In order to support non-copyable dbus::FileDescriptor, we have this -// internal::HackMove() helper function that does really nothing for normal -// types but uses Pass() for file descriptors so we can move them out from -// the temporaries created inside DBusParamReader<...>::Invoke(). -// If only libchrome supported real rvalues so we can just do std::move() and -// be done with it. -template -inline const T& HackMove(const T& val) { - return val; -} - -// Even though |val| here is passed as const&, the actual value is created -// inside DBusParamReader<...>::Invoke() and is temporary in nature, so it is -// safe to move the file descriptor out of |val|. That's why we are doing -// const_cast here. It is a bit hacky, but there is no negative side effects. -inline dbus::FileDescriptor HackMove(const dbus::FileDescriptor& val) { - return const_cast(val).Pass(); -} -} // namespace internal - -// Extracts the parameters of |ResultTypes...| types from the message reader -// and puts the values in the resulting |tuple|. Returns false on error and -// provides additional error details in |error| object. -template -inline bool ExtractMessageParametersAsTuple( - dbus::MessageReader* reader, - ErrorPtr* error, - std::tuple* val_tuple) { - auto callback = [val_tuple](const ResultTypes&... params) { - *val_tuple = std::forward_as_tuple(internal::HackMove(params)...); - }; - return DBusParamReader::Invoke( - callback, reader, error); -} -// Overload of ExtractMessageParametersAsTuple to handle reference types in -// tuples created with std::tie(). -template -inline bool ExtractMessageParametersAsTuple( - dbus::MessageReader* reader, - ErrorPtr* error, - std::tuple* ref_tuple) { - auto callback = [ref_tuple](const ResultTypes&... params) { - *ref_tuple = std::forward_as_tuple(internal::HackMove(params)...); - }; - return DBusParamReader::Invoke( - callback, reader, error); -} - -// A helper method to extract a list of values from a message buffer. -// The function will return false and provide detailed error information on -// failure. It fails if the D-Bus message buffer (represented by the |reader|) -// contains too many, too few parameters or the parameters are of wrong types -// (signatures). -// The usage pattern is as follows: -// -// int32_t data1; -// std::string data2; -// ErrorPtr error; -// if (ExtractMessageParameters(reader, &error, &data1, &data2)) { ... } -// -// The above example extracts an Int32 and a String from D-Bus message buffer. -template -inline bool ExtractMessageParameters(dbus::MessageReader* reader, - ErrorPtr* error, - ResultTypes*... results) { - auto ref_tuple = std::tie(*results...); - return ExtractMessageParametersAsTuple( - reader, error, &ref_tuple); -} - -// Convenient helper method to extract return value(s) of a D-Bus method call. -// |results| must be zero or more pointers to data expected to be returned -// from the method called. If an error occurs, returns false and provides -// additional details in |error| object. -// -// It is OK to call this method even if the D-Bus method doesn't expect -// any return values. Just do not specify any output |results|. In this case, -// ExtractMethodCallResults() will verify that the method didn't return any -// data in the |message|. -template -inline bool ExtractMethodCallResults(dbus::Message* message, - ErrorPtr* error, - ResultTypes*... results) { - CHECK(message) << "Unable to extract parameters from a NULL message."; - - dbus::MessageReader reader(message); - if (message->GetMessageType() == dbus::Message::MESSAGE_ERROR) { - std::string error_message; - if (ExtractMessageParameters(&reader, error, &error_message)) - AddDBusError(error, message->GetErrorName(), error_message); - return false; - } - return ExtractMessageParameters(&reader, error, results...); -} - -////////////////////////////////////////////////////////////////////////////// -// Asynchronous method invocation support - -using AsyncErrorCallback = base::Callback; - -// A helper function that translates dbus::ErrorResponse response -// from D-Bus into chromeos::Error* and invokes the |callback|. -void CHROMEOS_EXPORT TranslateErrorResponse(const AsyncErrorCallback& callback, - dbus::ErrorResponse* resp); - -// A helper function that translates dbus::Response from D-Bus into -// a list of C++ values passed as parameters to |success_callback|. If the -// response message doesn't have the correct number of parameters, or they -// are of wrong types, an error is sent to |error_callback|. -template -void TranslateSuccessResponse( - const base::Callback& success_callback, - const AsyncErrorCallback& error_callback, - dbus::Response* resp) { - auto callback = [&success_callback](const OutArgs&... params) { - if (!success_callback.is_null()) { - success_callback.Run(params...); - } - }; - ErrorPtr error; - dbus::MessageReader reader(resp); - if (!DBusParamReader::Invoke(callback, &reader, &error) && - !error_callback.is_null()) { - error_callback.Run(error.get()); - } -} - -// A helper method to dispatch a non-blocking D-Bus method call. Can specify -// zero or more method call arguments in |params| which will be sent over D-Bus. -// This method sends a D-Bus message and returns immediately. -// When the remote method returns successfully, the success callback is -// invoked with the return value(s), if any. -// On error, the error callback is called. Note, the error callback can be -// called synchronously (before CallMethodWithTimeout returns) if there was -// a problem invoking a method (e.g. object or method doesn't exist). -// If the response is not received within |timeout_ms|, an error callback is -// called with DBUS_ERROR_NO_REPLY error code. -template -inline void CallMethodWithTimeout( - int timeout_ms, - dbus::ObjectProxy* object, - const std::string& interface_name, - const std::string& method_name, - const base::Callback& success_callback, - const AsyncErrorCallback& error_callback, - const InArgs&... params) { - dbus::MethodCall method_call(interface_name, method_name); - dbus::MessageWriter writer(&method_call); - DBusParamWriter::Append(&writer, params...); - - dbus::ObjectProxy::ErrorCallback dbus_error_callback = - base::Bind(&TranslateErrorResponse, error_callback); - dbus::ObjectProxy::ResponseCallback dbus_success_callback = base::Bind( - &TranslateSuccessResponse, success_callback, error_callback); - - object->CallMethodWithErrorCallback( - &method_call, timeout_ms, dbus_success_callback, dbus_error_callback); -} - -// Same as CallMethodWithTimeout() but uses a default timeout value. -template -inline void CallMethod(dbus::ObjectProxy* object, - const std::string& interface_name, - const std::string& method_name, - const base::Callback& success_callback, - const AsyncErrorCallback& error_callback, - const InArgs&... params) { - return CallMethodWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, - object, - interface_name, - method_name, - success_callback, - error_callback, - params...); -} - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_INVOKER_H_ diff --git a/chromeos/dbus/dbus_method_invoker_unittest.cc b/chromeos/dbus/dbus_method_invoker_unittest.cc deleted file mode 100644 index 6159bc8..0000000 --- a/chromeos/dbus/dbus_method_invoker_unittest.cc +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include "unittests/test.pb.h" - -using testing::AnyNumber; -using testing::InSequence; -using testing::Invoke; -using testing::Return; -using testing::_; - -using dbus::MessageReader; -using dbus::MessageWriter; -using dbus::Response; - -namespace chromeos { -namespace dbus_utils { - -const char kTestPath[] = "/test/path"; -const char kTestServiceName[] = "org.test.Object"; -const char kTestInterface[] = "org.test.Object.TestInterface"; -const char kTestMethod1[] = "TestMethod1"; -const char kTestMethod2[] = "TestMethod2"; -const char kTestMethod3[] = "TestMethod3"; -const char kTestMethod4[] = "TestMethod4"; - -class DBusMethodInvokerTest : public testing::Test { - public: - void SetUp() override { - dbus::Bus::Options options; - options.bus_type = dbus::Bus::SYSTEM; - bus_ = new dbus::MockBus(options); - // By default, don't worry about threading assertions. - EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); - EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); - // Use a mock exported object. - mock_object_proxy_ = new dbus::MockObjectProxy( - bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); - EXPECT_CALL(*bus_, - GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) - .WillRepeatedly(Return(mock_object_proxy_.get())); - int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT; - EXPECT_CALL(*mock_object_proxy_, - MockCallMethodAndBlockWithErrorDetails(_, def_timeout_ms, _)) - .WillRepeatedly(Invoke(this, &DBusMethodInvokerTest::CreateResponse)); - } - - void TearDown() override { bus_ = nullptr; } - - Response* CreateResponse(dbus::MethodCall* method_call, - int timeout_ms, - dbus::ScopedDBusError* dbus_error) { - if (method_call->GetInterface() == kTestInterface) { - if (method_call->GetMember() == kTestMethod1) { - MessageReader reader(method_call); - int v1, v2; - // Input: two ints. - // Output: sum of the ints converted to string. - if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) { - auto response = Response::CreateEmpty(); - MessageWriter writer(response.get()); - writer.AppendString(std::to_string(v1 + v2)); - return response.release(); - } - } else if (method_call->GetMember() == kTestMethod2) { - method_call->SetSerial(123); - dbus_set_error(dbus_error->get(), "org.MyError", "My error message"); - return nullptr; - } else if (method_call->GetMember() == kTestMethod3) { - MessageReader reader(method_call); - dbus_utils_test::TestMessage msg; - if (PopValueFromReader(&reader, &msg)) { - auto response = Response::CreateEmpty(); - MessageWriter writer(response.get()); - AppendValueToWriter(&writer, msg); - return response.release(); - } - } else if (method_call->GetMember() == kTestMethod4) { - method_call->SetSerial(123); - MessageReader reader(method_call); - dbus::FileDescriptor fd; - if (reader.PopFileDescriptor(&fd)) { - auto response = Response::CreateEmpty(); - MessageWriter writer(response.get()); - fd.CheckValidity(); - writer.AppendFileDescriptor(fd); - return response.release(); - } - } - } - - LOG(ERROR) << "Unexpected method call: " << method_call->ToString(); - return nullptr; - } - - std::string CallTestMethod(int v1, int v2) { - std::unique_ptr response = - chromeos::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), - kTestInterface, - kTestMethod1, - nullptr, - v1, v2); - EXPECT_NE(nullptr, response.get()); - std::string result; - using chromeos::dbus_utils::ExtractMethodCallResults; - EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result)); - return result; - } - - dbus_utils_test::TestMessage CallProtobufTestMethod( - const dbus_utils_test::TestMessage& message) { - std::unique_ptr response = - chromeos::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), - kTestInterface, - kTestMethod3, - nullptr, - message); - EXPECT_NE(nullptr, response.get()); - dbus_utils_test::TestMessage result; - using chromeos::dbus_utils::ExtractMethodCallResults; - EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result)); - return result; - } - - // Sends a file descriptor received over D-Bus back to the caller. - dbus::FileDescriptor EchoFD(const dbus::FileDescriptor& fd_in) { - std::unique_ptr response = - chromeos::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), - kTestInterface, kTestMethod4, - nullptr, fd_in); - EXPECT_NE(nullptr, response.get()); - dbus::FileDescriptor fd_out; - using chromeos::dbus_utils::ExtractMethodCallResults; - EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &fd_out)); - return fd_out.Pass(); - } - - scoped_refptr bus_; - scoped_refptr mock_object_proxy_; -}; - -TEST_F(DBusMethodInvokerTest, TestSuccess) { - EXPECT_EQ("4", CallTestMethod(2, 2)); - EXPECT_EQ("10", CallTestMethod(3, 7)); - EXPECT_EQ("-4", CallTestMethod(13, -17)); -} - -TEST_F(DBusMethodInvokerTest, TestFailure) { - chromeos::ErrorPtr error; - std::unique_ptr response = - chromeos::dbus_utils::CallMethodAndBlock( - mock_object_proxy_.get(), kTestInterface, kTestMethod2, &error); - EXPECT_EQ(nullptr, response.get()); - EXPECT_EQ(chromeos::errors::dbus::kDomain, error->GetDomain()); - EXPECT_EQ("org.MyError", error->GetCode()); - EXPECT_EQ("My error message", error->GetMessage()); -} - -TEST_F(DBusMethodInvokerTest, TestProtobuf) { - dbus_utils_test::TestMessage test_message; - test_message.set_foo(123); - test_message.set_bar("bar"); - - dbus_utils_test::TestMessage resp = CallProtobufTestMethod(test_message); - - EXPECT_EQ(123, resp.foo()); - EXPECT_EQ("bar", resp.bar()); -} - -TEST_F(DBusMethodInvokerTest, TestFileDescriptors) { - // Passing a file descriptor over D-Bus would effectively duplicate the fd. - // So the resulting file descriptor value would be different but it still - // should be valid. - dbus::FileDescriptor fd_stdin(0); - fd_stdin.CheckValidity(); - EXPECT_NE(fd_stdin.value(), EchoFD(fd_stdin).value()); - dbus::FileDescriptor fd_stdout(1); - fd_stdout.CheckValidity(); - EXPECT_NE(fd_stdout.value(), EchoFD(fd_stdout).value()); - dbus::FileDescriptor fd_stderr(2); - fd_stderr.CheckValidity(); - EXPECT_NE(fd_stderr.value(), EchoFD(fd_stderr).value()); -} - -////////////////////////////////////////////////////////////////////////////// -// Asynchronous method invocation support - -class AsyncDBusMethodInvokerTest : public testing::Test { - public: - void SetUp() override { - dbus::Bus::Options options; - options.bus_type = dbus::Bus::SYSTEM; - bus_ = new dbus::MockBus(options); - // By default, don't worry about threading assertions. - EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); - EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); - // Use a mock exported object. - mock_object_proxy_ = new dbus::MockObjectProxy( - bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); - EXPECT_CALL(*bus_, - GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) - .WillRepeatedly(Return(mock_object_proxy_.get())); - int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT; - EXPECT_CALL(*mock_object_proxy_, - CallMethodWithErrorCallback(_, def_timeout_ms, _, _)) - .WillRepeatedly(Invoke(this, &AsyncDBusMethodInvokerTest::HandleCall)); - } - - void TearDown() override { bus_ = nullptr; } - - void HandleCall(dbus::MethodCall* method_call, - int timeout_ms, - dbus::ObjectProxy::ResponseCallback success_callback, - dbus::ObjectProxy::ErrorCallback error_callback) { - if (method_call->GetInterface() == kTestInterface) { - if (method_call->GetMember() == kTestMethod1) { - MessageReader reader(method_call); - int v1, v2; - // Input: two ints. - // Output: sum of the ints converted to string. - if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) { - auto response = Response::CreateEmpty(); - MessageWriter writer(response.get()); - writer.AppendString(std::to_string(v1 + v2)); - success_callback.Run(response.get()); - } - return; - } else if (method_call->GetMember() == kTestMethod2) { - method_call->SetSerial(123); - auto error_response = dbus::ErrorResponse::FromMethodCall( - method_call, "org.MyError", "My error message"); - error_callback.Run(error_response.get()); - return; - } - } - - LOG(FATAL) << "Unexpected method call: " << method_call->ToString(); - } - - struct SuccessCallback { - SuccessCallback(const std::string& in_result, int* in_counter) - : result(in_result), counter(in_counter) {} - - explicit SuccessCallback(int* in_counter) : counter(in_counter) {} - - void operator()(const std::string& actual_result) { - (*counter)++; - EXPECT_EQ(result, actual_result); - } - std::string result; - int* counter; - }; - - struct ErrorCallback { - ErrorCallback(const std::string& in_domain, - const std::string& in_code, - const std::string& in_message, - int* in_counter) - : domain(in_domain), - code(in_code), - message(in_message), - counter(in_counter) {} - - explicit ErrorCallback(int* in_counter) : counter(in_counter) {} - - void operator()(chromeos::Error* error) { - (*counter)++; - EXPECT_NE(nullptr, error); - EXPECT_EQ(domain, error->GetDomain()); - EXPECT_EQ(code, error->GetCode()); - EXPECT_EQ(message, error->GetMessage()); - } - - std::string domain; - std::string code; - std::string message; - int* counter; - }; - - scoped_refptr bus_; - scoped_refptr mock_object_proxy_; -}; - -TEST_F(AsyncDBusMethodInvokerTest, TestSuccess) { - int error_count = 0; - int success_count = 0; - chromeos::dbus_utils::CallMethod( - mock_object_proxy_.get(), - kTestInterface, - kTestMethod1, - base::Bind(SuccessCallback{"4", &success_count}), - base::Bind(ErrorCallback{&error_count}), - 2, 2); - chromeos::dbus_utils::CallMethod( - mock_object_proxy_.get(), - kTestInterface, - kTestMethod1, - base::Bind(SuccessCallback{"10", &success_count}), - base::Bind(ErrorCallback{&error_count}), - 3, 7); - chromeos::dbus_utils::CallMethod( - mock_object_proxy_.get(), - kTestInterface, - kTestMethod1, - base::Bind(SuccessCallback{"-4", &success_count}), - base::Bind(ErrorCallback{&error_count}), - 13, -17); - EXPECT_EQ(0, error_count); - EXPECT_EQ(3, success_count); -} - -TEST_F(AsyncDBusMethodInvokerTest, TestFailure) { - int error_count = 0; - int success_count = 0; - chromeos::dbus_utils::CallMethod( - mock_object_proxy_.get(), - kTestInterface, - kTestMethod2, - base::Bind(SuccessCallback{&success_count}), - base::Bind(ErrorCallback{chromeos::errors::dbus::kDomain, - "org.MyError", - "My error message", - &error_count}), - 2, 2); - EXPECT_EQ(1, error_count); - EXPECT_EQ(0, success_count); -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_method_response.cc b/chromeos/dbus/dbus_method_response.cc deleted file mode 100644 index 25d0b27..0000000 --- a/chromeos/dbus/dbus_method_response.cc +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { -namespace dbus_utils { - -DBusMethodResponseBase::DBusMethodResponseBase(dbus::MethodCall* method_call, - ResponseSender sender) - : sender_(sender), method_call_(method_call) { -} - -DBusMethodResponseBase::~DBusMethodResponseBase() { - if (method_call_) { - // Response hasn't been sent by the handler. Abort the call. - Abort(); - } -} - -void DBusMethodResponseBase::ReplyWithError(const chromeos::Error* error) { - CheckCanSendResponse(); - auto response = GetDBusError(method_call_, error); - SendRawResponse(std::move(response)); -} - -void DBusMethodResponseBase::ReplyWithError( - const tracked_objects::Location& location, - const std::string& error_domain, - const std::string& error_code, - const std::string& error_message) { - ErrorPtr error; - Error::AddTo(&error, location, error_domain, error_code, error_message); - ReplyWithError(error.get()); -} - -void DBusMethodResponseBase::Abort() { - SendRawResponse(std::unique_ptr()); -} - -void DBusMethodResponseBase::SendRawResponse( - std::unique_ptr response) { - CheckCanSendResponse(); - method_call_ = nullptr; // Mark response as sent. - sender_.Run(scoped_ptr{response.release()}); -} - -std::unique_ptr -DBusMethodResponseBase::CreateCustomResponse() const { - return std::unique_ptr{ - dbus::Response::FromMethodCall(method_call_).release()}; -} - -bool DBusMethodResponseBase::IsResponseSent() const { - return (method_call_ == nullptr); -} - -void DBusMethodResponseBase::CheckCanSendResponse() const { - CHECK(method_call_) << "Response already sent"; -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_method_response.h b/chromeos/dbus/dbus_method_response.h deleted file mode 100644 index b0c69b6..0000000 --- a/chromeos/dbus/dbus_method_response.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_RESPONSE_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_RESPONSE_H_ - -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -class Error; - -namespace dbus_utils { - -using ResponseSender = dbus::ExportedObject::ResponseSender; - -// DBusMethodResponseBase is a helper class used with asynchronous D-Bus method -// handlers to encapsulate the information needed to send the method call -// response when it is available. -class CHROMEOS_EXPORT DBusMethodResponseBase { - public: - DBusMethodResponseBase(dbus::MethodCall* method_call, ResponseSender sender); - virtual ~DBusMethodResponseBase(); - - // Sends an error response. Marshals the |error| object over D-Bus. - // If |error| is from the "dbus" error domain, takes the |error_code| from - // |error| and uses it as the DBus error name. - // For error is from other domains, the full error information (domain, error - // code, error message) is encoded into the D-Bus error message and returned - // to the caller as "org.freedesktop.DBus.Failed". - void ReplyWithError(const chromeos::Error* error); - - // Constructs chromeos::Error object from the parameters specified and send - // the error information over D-Bus using the method above. - void ReplyWithError(const tracked_objects::Location& location, - const std::string& error_domain, - const std::string& error_code, - const std::string& error_message); - - // Sends a raw D-Bus response message. - void SendRawResponse(std::unique_ptr response); - - // Creates a custom response object for the current method call. - std::unique_ptr CreateCustomResponse() const; - - // Checks if the response has been sent already. - bool IsResponseSent() const; - - protected: - void CheckCanSendResponse() const; - - // Aborts the method execution. Does not send any response message. - void Abort(); - - private: - // A callback to be called to send the method call response message. - ResponseSender sender_; - // |method_call_| is actually owned by |sender_| (it is embedded as unique_ptr - // in the bound parameter list in the Callback). We set it to nullptr after - // the method call response has been sent to ensure we can't possibly try - // to send a response again somehow. - dbus::MethodCall* method_call_; - - DISALLOW_COPY_AND_ASSIGN(DBusMethodResponseBase); -}; - -// DBusMethodResponse is an explicitly-typed version of DBusMethodResponse. -// Using DBusMethodResponse indicates the types a D-Bus method -// is expected to return. -template -class DBusMethodResponse : public DBusMethodResponseBase { - public: - // Make the base class's custom constructor available to DBusMethodResponse. - using DBusMethodResponseBase::DBusMethodResponseBase; - - // Sends the a successful response. |return_values| can contain a list - // of return values to be sent to the caller. - inline void Return(const Types&... return_values) { - CheckCanSendResponse(); - auto response = CreateCustomResponse(); - dbus::MessageWriter writer(response.get()); - DBusParamWriter::Append(&writer, return_values...); - SendRawResponse(std::move(response)); - } -}; - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_RESPONSE_H_ diff --git a/chromeos/dbus/dbus_object.cc b/chromeos/dbus/dbus_object.cc deleted file mode 100644 index 16539e2..0000000 --- a/chromeos/dbus/dbus_object.cc +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -////////////////////////////////////////////////////////////////////////////// - -DBusInterface::DBusInterface(DBusObject* dbus_object, - const std::string& interface_name) - : dbus_object_(dbus_object), interface_name_(interface_name) { -} - -void DBusInterface::AddProperty(const std::string& property_name, - ExportedPropertyBase* prop_base) { - dbus_object_->property_set_.RegisterProperty( - interface_name_, property_name, prop_base); -} - -void DBusInterface::ExportAsync( - ExportedObjectManager* object_manager, - dbus::Bus* bus, - dbus::ExportedObject* exported_object, - const dbus::ObjectPath& object_path, - const AsyncEventSequencer::CompletionAction& completion_callback) { - VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '" - << object_path.value() << "'"; - scoped_refptr sequencer(new AsyncEventSequencer()); - for (const auto& pair : handlers_) { - std::string method_name = pair.first; - VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name; - std::string export_error = "Failed exporting " + method_name + " method"; - auto export_handler = sequencer->GetExportHandler( - interface_name_, method_name, export_error, true); - auto method_handler = - base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this)); - exported_object->ExportMethod( - interface_name_, method_name, method_handler, export_handler); - } - - std::vector actions; - if (object_manager) { - auto property_writer_callback = - dbus_object_->property_set_.GetPropertyWriter(interface_name_); - actions.push_back( - base::Bind(&DBusInterface::ClaimInterface, - weak_factory_.GetWeakPtr(), - object_manager->AsWeakPtr(), - object_path, - property_writer_callback)); - } - actions.push_back(completion_callback); - sequencer->OnAllTasksCompletedCall(actions); -} - -void DBusInterface::ExportAndBlock( - ExportedObjectManager* object_manager, - dbus::Bus* bus, - dbus::ExportedObject* exported_object, - const dbus::ObjectPath& object_path) { - VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '" - << object_path.value() << "'"; - for (const auto& pair : handlers_) { - std::string method_name = pair.first; - VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name; - auto method_handler = - base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this)); - if (!exported_object->ExportMethodAndBlock( - interface_name_, method_name, method_handler)) { - LOG(FATAL) << "Failed exporting " << method_name << " method"; - } - } - - if (object_manager) { - auto property_writer_callback = - dbus_object_->property_set_.GetPropertyWriter(interface_name_); - ClaimInterface(object_manager->AsWeakPtr(), - object_path, - property_writer_callback, - true); - } -} - -void DBusInterface::ClaimInterface( - base::WeakPtr object_manager, - const dbus::ObjectPath& object_path, - const ExportedPropertySet::PropertyWriter& writer, - bool all_succeeded) { - if (!all_succeeded || !object_manager) { - LOG(ERROR) << "Skipping claiming interface: " << interface_name_; - return; - } - object_manager->ClaimInterface(object_path, interface_name_, writer); - release_interface_cb_.Reset( - base::Bind(&ExportedObjectManager::ReleaseInterface, - object_manager, object_path, interface_name_)); -} - -void DBusInterface::HandleMethodCall(dbus::MethodCall* method_call, - ResponseSender sender) { - std::string method_name = method_call->GetMember(); - // Make a local copy of |interface_name_| because calling HandleMethod() - // can potentially kill this interface object... - std::string interface_name = interface_name_; - VLOG(1) << "Received method call request: " << interface_name << "." - << method_name << "(" << method_call->GetSignature() << ")"; - auto pair = handlers_.find(method_name); - if (pair == handlers_.end()) { - auto response = - dbus::ErrorResponse::FromMethodCall(method_call, - DBUS_ERROR_UNKNOWN_METHOD, - "Unknown method: " + method_name); - sender.Run(response.Pass()); - return; - } - VLOG(1) << "Dispatching DBus method call: " << method_name; - pair->second->HandleMethod(method_call, sender); -} - -void DBusInterface::AddHandlerImpl( - const std::string& method_name, - std::unique_ptr handler) { - VLOG(1) << "Declaring method handler: " << interface_name_ << "." - << method_name; - auto res = handlers_.insert(std::make_pair(method_name, std::move(handler))); - CHECK(res.second) << "Method '" << method_name << "' already exists"; -} - -void DBusInterface::AddSignalImpl( - const std::string& signal_name, - const std::shared_ptr& signal) { - VLOG(1) << "Declaring a signal sink: " << interface_name_ << "." - << signal_name; - CHECK(signals_.insert(std::make_pair(signal_name, signal)).second) - << "The signal '" << signal_name << "' is already registered"; -} - -/////////////////////////////////////////////////////////////////////////////// - -DBusObject::DBusObject(ExportedObjectManager* object_manager, - const scoped_refptr& bus, - const dbus::ObjectPath& object_path) - : property_set_(bus.get()), bus_(bus), object_path_(object_path) { - if (object_manager) - object_manager_ = object_manager->AsWeakPtr(); -} - -DBusObject::~DBusObject() { - if (exported_object_) - exported_object_->Unregister(); -} - -DBusInterface* DBusObject::AddOrGetInterface( - const std::string& interface_name) { - auto iter = interfaces_.find(interface_name); - if (iter == interfaces_.end()) { - VLOG(1) << "Adding an interface '" << interface_name << "' to object '" - << object_path_.value() << "'."; - // Interface doesn't exist yet. Create one... - std::unique_ptr new_itf( - new DBusInterface(this, interface_name)); - iter = interfaces_.insert(std::make_pair(interface_name, - std::move(new_itf))).first; - } - return iter->second.get(); -} - -DBusInterface* DBusObject::FindInterface( - const std::string& interface_name) const { - auto itf_iter = interfaces_.find(interface_name); - return (itf_iter == interfaces_.end()) ? nullptr : itf_iter->second.get(); -} - -void DBusObject::RegisterAsync( - const AsyncEventSequencer::CompletionAction& completion_callback) { - VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'."; - CHECK(exported_object_ == nullptr) << "Object already registered."; - scoped_refptr sequencer(new AsyncEventSequencer()); - exported_object_ = bus_->GetExportedObject(object_path_); - - // Add the org.freedesktop.DBus.Properties interface to the object. - DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface); - prop_interface->AddSimpleMethodHandler( - dbus::kPropertiesGetAll, - base::Unretained(&property_set_), - &ExportedPropertySet::HandleGetAll); - prop_interface->AddSimpleMethodHandlerWithError( - dbus::kPropertiesGet, - base::Unretained(&property_set_), - &ExportedPropertySet::HandleGet); - prop_interface->AddSimpleMethodHandlerWithError( - dbus::kPropertiesSet, - base::Unretained(&property_set_), - &ExportedPropertySet::HandleSet); - property_set_.OnPropertiesInterfaceExported(prop_interface); - - // Export interface methods - for (const auto& pair : interfaces_) { - pair.second->ExportAsync( - object_manager_.get(), - bus_.get(), - exported_object_, - object_path_, - sequencer->GetHandler("Failed to export interface " + pair.first, - false)); - } - - sequencer->OnAllTasksCompletedCall({completion_callback}); -} - -void DBusObject::RegisterAndBlock() { - VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'."; - CHECK(exported_object_ == nullptr) << "Object already registered."; - exported_object_ = bus_->GetExportedObject(object_path_); - - // Add the org.freedesktop.DBus.Properties interface to the object. - DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface); - prop_interface->AddSimpleMethodHandler( - dbus::kPropertiesGetAll, - base::Unretained(&property_set_), - &ExportedPropertySet::HandleGetAll); - prop_interface->AddSimpleMethodHandlerWithError( - dbus::kPropertiesGet, - base::Unretained(&property_set_), - &ExportedPropertySet::HandleGet); - prop_interface->AddSimpleMethodHandlerWithError( - dbus::kPropertiesSet, - base::Unretained(&property_set_), - &ExportedPropertySet::HandleSet); - property_set_.OnPropertiesInterfaceExported(prop_interface); - - // Export interface methods - for (const auto& pair : interfaces_) { - pair.second->ExportAndBlock( - object_manager_.get(), - bus_.get(), - exported_object_, - object_path_); - } -} - -void DBusObject::UnregisterAsync() { - VLOG(1) << "Unregistering D-Bus object '" << object_path_.value() << "'."; - CHECK(exported_object_ != nullptr) << "Object not registered."; - - // This will unregister the object path from the bus. - exported_object_->Unregister(); - // This will remove |exported_object_| from bus's object table. This function - // will also post a task to unregister |exported_object_| (same as the call - // above), which will be a no-op since it is already done by then. - // By doing both in here, the object path is guarantee to be reusable upon - // return from this function. - bus_->UnregisterExportedObject(object_path_); - exported_object_ = nullptr; -} - -bool DBusObject::SendSignal(dbus::Signal* signal) { - if (exported_object_) { - exported_object_->SendSignal(signal); - return true; - } - LOG(ERROR) << "Trying to send a signal from an object that is not exported"; - return false; -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_object.h b/chromeos/dbus/dbus_object.h deleted file mode 100644 index 10894e6..0000000 --- a/chromeos/dbus/dbus_object.h +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// DBusObject is a special helper class that simplifies the implementation of -// D-Bus objects in C++. It provides an easy way to define interfaces with -// methods and properties and offloads a lot of work to register the object and -// all of its interfaces, to marshal method calls (by converting D-Bus method -// parameters to native C++ types and invoking native method handlers), etc. - -// The basic usage pattern of this class is as follows: -/* -class MyDbusObject { - public: - MyDbusObject(ExportedObjectManager* object_manager, - const scoped_refptr& bus) - : dbus_object_(object_manager, bus, - dbus::ObjectPath("/org/chromium/my_obj")) {} - - void Init(const AsyncEventSequencer::CompletionAction& callback) { - DBusInterface* my_interface = - dbus_object_.AddOrGetInterface("org.chromium.MyInterface"); - my_interface->AddSimpleMethodHandler("Method1", this, - &MyDbusObject::Method1); - my_interface->AddSimpleMethodHandlerWithError("Method2", this, - &MyDbusObject::Method2); - my_interface->AddMethodHandler("Method3", this, &MyDbusObject::Method3); - my_interface->AddProperty("Property1", &prop1_); - my_interface->AddProperty("Property2", &prop2_); - prop1_.SetValue("prop1_value"); - prop2_.SetValue(50); - // Register the object by exporting its methods and properties and - // exposing them to D-Bus clients. - dbus_object_.RegisterAsync(callback); - } - - private: - DBusObject dbus_object_; - - // Make sure the properties outlive the DBusObject they are registered with. - chromeos::dbus_utils::ExportedProperty prop1_; - chromeos::dbus_utils::ExportedProperty prop2_; - int Method1() { return 5; } - bool Method2(chromeos::ErrorPtr* error, const std::string& message); - void Method3(std::unique_ptr> response, - const std::string& message) { - if (message.empty()) { - response->ReplyWithError(chromeos::errors::dbus::kDomain, - DBUS_ERROR_INVALID_ARGS, - "Message string cannot be empty"); - return; - } - int32_t message_len = message.length(); - response->Return(message_len); - } - - DISALLOW_COPY_AND_ASSIGN(MyDbusObject); -}; -*/ - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_H_ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -class ExportedObjectManager; -class ExportedPropertyBase; -class DBusObject; - -// This is an implementation proxy class for a D-Bus interface of an object. -// The important functionality for the users is the ability to add D-Bus method -// handlers and define D-Bus object properties. This is achieved by using one -// of the overload of AddSimpleMethodHandler()/AddMethodHandler() and -// AddProperty() respectively. -// There are three overloads for DBusInterface::AddSimpleMethodHandler() and -// AddMethodHandler() each: -// 1. That takes a handler as base::Callback -// 2. That takes a static function -// 3. That takes a class instance pointer and a class member function -// The signature of the handler for AddSimpleMethodHandler must be one of: -// R(Args... args) [IN only] -// void(Args... args) [IN/OUT] -// The signature of the handler for AddSimpleMethodHandlerWithError must be: -// bool(ErrorPtr* error, Args... args) [IN/OUT] -// The signature of the handler for AddSimpleMethodHandlerWithErrorAndMessage: -// bool(ErrorPtr* error, dbus::Message* msg, Args... args) [IN/OUT] -// The signature of the handler for AddMethodHandler must be: -// void(std::unique_ptr> response, -// Args... args) [IN] -// The signature of the handler for AddMethodHandlerWithMessage must be: -// void(std::unique_ptr> response, -// dbus::Message* msg, Args... args) [IN] -// There is also an AddRawMethodHandler() call that lets provide a custom -// handler that can parse its own input parameter and construct a custom -// response. -// The signature of the handler for AddRawMethodHandler must be: -// void(dbus::MethodCall* method_call, ResponseSender sender) -class CHROMEOS_EXPORT DBusInterface final { - public: - DBusInterface(DBusObject* dbus_object, const std::string& interface_name); - - // Register sync DBus method handler for |method_name| as base::Callback. - template - inline void AddSimpleMethodHandler( - const std::string& method_name, - const base::Callback& handler) { - Handler>::Add( - this, method_name, handler); - } - - // Register sync D-Bus method handler for |method_name| as a static - // function. - template - inline void AddSimpleMethodHandler(const std::string& method_name, - R(*handler)(Args...)) { - Handler>::Add( - this, method_name, base::Bind(handler)); - } - - // Register sync D-Bus method handler for |method_name| as a class member - // function. - template - inline void AddSimpleMethodHandler(const std::string& method_name, - Instance instance, - R(Class::*handler)(Args...)) { - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Same as above but for const-method of a class. - template - inline void AddSimpleMethodHandler(const std::string& method_name, - Instance instance, - R(Class::*handler)(Args...) const) { - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Register sync DBus method handler for |method_name| as base::Callback. - template - inline void AddSimpleMethodHandlerWithError( - const std::string& method_name, - const base::Callback& handler) { - Handler>::Add( - this, method_name, handler); - } - - // Register sync D-Bus method handler for |method_name| as a static - // function. - template - inline void AddSimpleMethodHandlerWithError( - const std::string& method_name, - bool(*handler)(ErrorPtr*, Args...)) { - Handler>::Add( - this, method_name, base::Bind(handler)); - } - - // Register sync D-Bus method handler for |method_name| as a class member - // function. - template - inline void AddSimpleMethodHandlerWithError( - const std::string& method_name, - Instance instance, - bool(Class::*handler)(ErrorPtr*, Args...)) { - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Same as above but for const-method of a class. - template - inline void AddSimpleMethodHandlerWithError( - const std::string& method_name, - Instance instance, - bool(Class::*handler)(ErrorPtr*, Args...) const) { - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Register sync DBus method handler for |method_name| as base::Callback. - // Passing the method sender as a first parameter to the callback. - template - inline void AddSimpleMethodHandlerWithErrorAndMessage( - const std::string& method_name, - const base::Callback& - handler) { - Handler>::Add( - this, method_name, handler); - } - - // Register sync D-Bus method handler for |method_name| as a static - // function. Passing the method D-Bus message as the second parameter to the - // callback. - template - inline void AddSimpleMethodHandlerWithErrorAndMessage( - const std::string& method_name, - bool(*handler)(ErrorPtr*, dbus::Message*, Args...)) { - Handler>::Add( - this, method_name, base::Bind(handler)); - } - - // Register sync D-Bus method handler for |method_name| as a class member - // function. Passing the method D-Bus message as the second parameter to the - // callback. - template - inline void AddSimpleMethodHandlerWithErrorAndMessage( - const std::string& method_name, - Instance instance, - bool(Class::*handler)(ErrorPtr*, dbus::Message*, Args...)) { - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Same as above but for const-method of a class. - template - inline void AddSimpleMethodHandlerWithErrorAndMessage( - const std::string& method_name, - Instance instance, - bool(Class::*handler)(ErrorPtr*, dbus::Message*, Args...) const) { - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Register an async DBus method handler for |method_name| as base::Callback. - template - inline void AddMethodHandler( - const std::string& method_name, - const base::Callback, Args...)>& handler) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, handler); - } - - // Register an async D-Bus method handler for |method_name| as a static - // function. - template - inline void AddMethodHandler( - const std::string& method_name, - void (*handler)(std::unique_ptr, Args...)) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, base::Bind(handler)); - } - - // Register an async D-Bus method handler for |method_name| as a class member - // function. - template - inline void AddMethodHandler( - const std::string& method_name, - Instance instance, - void(Class::*handler)(std::unique_ptr, Args...)) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Same as above but for const-method of a class. - template - inline void AddMethodHandler( - const std::string& method_name, - Instance instance, - void(Class::*handler)(std::unique_ptr, Args...) const) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Register an async DBus method handler for |method_name| as base::Callback. - template - inline void AddMethodHandlerWithMessage( - const std::string& method_name, - const base::Callback, dbus::Message*, - Args...)>& handler) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, handler); - } - - // Register an async D-Bus method handler for |method_name| as a static - // function. - template - inline void AddMethodHandlerWithMessage( - const std::string& method_name, - void (*handler)(std::unique_ptr, dbus::Message*, Args...)) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, base::Bind(handler)); - } - - // Register an async D-Bus method handler for |method_name| as a class member - // function. - template - inline void AddMethodHandlerWithMessage( - const std::string& method_name, - Instance instance, - void(Class::*handler)(std::unique_ptr, - dbus::Message*, Args...)) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Same as above but for const-method of a class. - template - inline void AddMethodHandlerWithMessage( - const std::string& method_name, - Instance instance, - void(Class::*handler)(std::unique_ptr, dbus::Message*, - Args...) const) { - static_assert(std::is_base_of::value, - "Response must be DBusMethodResponse"); - Handler>::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Register a raw D-Bus method handler for |method_name| as base::Callback. - inline void AddRawMethodHandler( - const std::string& method_name, - const base::Callback& handler) { - Handler::Add(this, method_name, handler); - } - - // Register a raw D-Bus method handler for |method_name| as a class member - // function. - template - inline void AddRawMethodHandler( - const std::string& method_name, - Instance instance, - void(Class::*handler)(dbus::MethodCall*, ResponseSender)) { - Handler::Add( - this, method_name, base::Bind(handler, instance)); - } - - // Register a D-Bus property. - void AddProperty(const std::string& property_name, - ExportedPropertyBase* prop_base); - - // Registers a D-Bus signal that has a specified number and types (|Args|) of - // arguments. Returns a weak pointer to the DBusSignal object which can be - // used to send the signal on this interface when needed: - /* - DBusInterface* itf = dbus_object->AddOrGetInterface("Interface"); - auto signal = itf->RegisterSignal("MySignal"); - ... - // Send the Interface.MySig(12, true) signal. - if (signal.lock()->Send(12, true)) { ... } - */ - // Or if the signal signature is long or complex, you can alias the - // DBusSignal signal type and use RegisterSignalOfType method - // instead: - /* - DBusInterface* itf = dbus_object->AddOrGetInterface("Interface"); - using MySignal = DBusSignal; - auto signal = itf->RegisterSignalOfType("MySignal"); - ... - // Send the Interface.MySig(12, true) signal. - if (signal.lock()->Send(12, true)) { ... } - */ - // If the signal with the given name was already registered, the existing - // copy of the signal proxy object is returned as long as the method signature - // of the original signal matches the current call. If it doesn't, the method - // aborts. - - // RegisterSignalOfType can be used to create a signal if the type of the - // complete DBusSignal class which is pre-defined/aliased earlier. - template - inline std::weak_ptr RegisterSignalOfType( - const std::string& signal_name) { - auto signal = std::make_shared( - dbus_object_, interface_name_, signal_name); - AddSignalImpl(signal_name, signal); - return signal; - } - - // For simple signal arguments, you can specify their types directly in - // RegisterSignal(): - // auto signal = itf->RegisterSignal("SignalName"); - // This will create a callback signal object that expects one int argument. - template - inline std::weak_ptr> RegisterSignal( - const std::string& signal_name) { - return RegisterSignalOfType>(signal_name); - } - - private: - // Helper to create an instance of DBusInterfaceMethodHandlerInterface-derived - // handler and add it to the method handler map of the interface. - // This makes the actual AddXXXMethodHandler() methods very light-weight and - // easier to provide different overloads for various method handler kinds. - // Using struct here to allow partial specialization on HandlerType while - // letting the compiler to deduce the type of the callback without explicitly - // specifying it. - template - struct Handler { - template - inline static void Add(DBusInterface* self, - const std::string& method_name, - const CallbackType& callback) { - std::unique_ptr sync_method_handler( - new HandlerType(callback)); - self->AddHandlerImpl(method_name, std::move(sync_method_handler)); - } - }; - // A generic D-Bus method handler for the interface. It extracts the method - // name from |method_call|, looks up a registered handler from |handlers_| - // map and dispatched the call to that handler. - void HandleMethodCall(dbus::MethodCall* method_call, ResponseSender sender); - // Helper to add a handler for method |method_name| to the |handlers_| map. - // Not marked CHROMEOS_PRIVATE because it needs to be called by the inline - // template functions AddMethodHandler(...) - void AddHandlerImpl( - const std::string& method_name, - std::unique_ptr handler); - // Helper to add a signal object to the |signals_| map. - // Not marked CHROMEOS_PRIVATE because it needs to be called by the inline - // template function RegisterSignalOfType(...) - void AddSignalImpl(const std::string& signal_name, - const std::shared_ptr& signal); - // Exports all the methods and properties of this interface and claims the - // D-Bus interface. - // object_manager - ExportedObjectManager instance that notifies D-Bus - // listeners of a new interface being claimed. - // exported_object - instance of D-Bus object the interface is being added to. - // object_path - D-Bus object path for the object instance. - // interface_name - name of interface being registered. - // completion_callback - a callback to be called when the asynchronous - // registration operation is completed. - CHROMEOS_PRIVATE void ExportAsync( - ExportedObjectManager* object_manager, - dbus::Bus* bus, - dbus::ExportedObject* exported_object, - const dbus::ObjectPath& object_path, - const AsyncEventSequencer::CompletionAction& completion_callback); - // Exports all the methods and properties of this interface and claims the - // D-Bus interface synchronously. - // object_manager - ExportedObjectManager instance that notifies D-Bus - // listeners of a new interface being claimed. - // exported_object - instance of D-Bus object the interface is being added to. - // object_path - D-Bus object path for the object instance. - // interface_name - name of interface being registered. - CHROMEOS_PRIVATE void ExportAndBlock( - ExportedObjectManager* object_manager, - dbus::Bus* bus, - dbus::ExportedObject* exported_object, - const dbus::ObjectPath& object_path); - - CHROMEOS_PRIVATE void ClaimInterface( - base::WeakPtr object_manager, - const dbus::ObjectPath& object_path, - const ExportedPropertySet::PropertyWriter& writer, - bool all_succeeded); - - // Method registration map. - std::map> - handlers_; - // Signal registration map. - std::map> signals_; - - friend class DBusObject; - friend class DBusInterfaceTestHelper; - DBusObject* dbus_object_; - std::string interface_name_; - base::ScopedClosureRunner release_interface_cb_; - - base::WeakPtrFactory weak_factory_{this}; - DISALLOW_COPY_AND_ASSIGN(DBusInterface); -}; - -// A D-Bus object implementation class. Manages the interfaces implemented -// by this object. -class CHROMEOS_EXPORT DBusObject { - public: - // object_manager - ExportedObjectManager instance that notifies D-Bus - // listeners of a new interface being claimed and property - // changes on those interfaces. - // object_path - D-Bus object path for the object instance. - DBusObject(ExportedObjectManager* object_manager, - const scoped_refptr& bus, - const dbus::ObjectPath& object_path); - virtual ~DBusObject(); - - // Returns an proxy handler for the interface |interface_name|. If the - // interface proxy does not exist yet, it will be automatically created. - DBusInterface* AddOrGetInterface(const std::string& interface_name); - - // Finds an interface with the given name. Returns nullptr if there is no - // interface registered by this name. - DBusInterface* FindInterface(const std::string& interface_name) const; - - // Registers the object instance with D-Bus. This is an asynchronous call - // that will call |completion_callback| when the object and all of its - // interfaces are registered. - virtual void RegisterAsync( - const AsyncEventSequencer::CompletionAction& completion_callback); - - // Registers the object instance with D-Bus. This is call is synchronous and - // will block until the object and all of its interfaces are registered. - virtual void RegisterAndBlock(); - - // Unregister the object instance with D-Bus. This will unregister the - // |exported_object_| and its path from the bus. The destruction of - // |exported_object_| will be deferred in an async task posted by the bus. - // It is guarantee that upon return from this call a new DBusObject with the - // same object path can be created/registered. - virtual void UnregisterAsync(); - - // Returns the ExportedObjectManager proxy, if any. If DBusObject has been - // constructed without an object manager, this method returns an empty - // smart pointer (containing nullptr). - const base::WeakPtr& GetObjectManager() const { - return object_manager_; - } - - // Sends a signal from the exported D-Bus object. - bool SendSignal(dbus::Signal* signal); - - // Returns the reference to dbus::Bus this object is associated with. - scoped_refptr GetBus() { return bus_; } - - private: - // A map of all the interfaces added to this object. - std::map> interfaces_; - // Exported property set for properties registered with the interfaces - // implemented by this D-Bus object. - ExportedPropertySet property_set_; - // Delegate object implementing org.freedesktop.DBus.ObjectManager interface. - base::WeakPtr object_manager_; - // D-Bus bus object. - scoped_refptr bus_; - // D-Bus object path for this object. - dbus::ObjectPath object_path_; - // D-Bus object instance once this object is successfully exported. - dbus::ExportedObject* exported_object_ = nullptr; // weak; owned by |bus_|. - - friend class DBusInterface; - DISALLOW_COPY_AND_ASSIGN(DBusObject); -}; - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_H_ diff --git a/chromeos/dbus/dbus_object_internal_impl.h b/chromeos/dbus/dbus_object_internal_impl.h deleted file mode 100644 index 544dadb..0000000 --- a/chromeos/dbus/dbus_object_internal_impl.h +++ /dev/null @@ -1,363 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file provides internal implementation details of dispatching D-Bus -// method calls to a D-Bus object methods by reading the expected parameter -// values from D-Bus message buffer then invoking a native C++ callback with -// those parameters passed in. If the callback returns a value, that value is -// sent back to the caller of D-Bus method via the response message. - -// This is achieved by redirecting the parsing of parameter values from D-Bus -// message buffer to DBusParamReader helper class. -// DBusParamReader de-serializes the parameter values from the D-Bus message -// and calls the provided native C++ callback with those arguments. -// However it expects the callback with a simple signature like this: -// void callback(Args...); -// The method handlers for DBusObject, on the other hand, have one of the -// following signatures: -// void handler(Args...); -// ReturnType handler(Args...); -// bool handler(ErrorPtr* error, Args...); -// void handler(std::unique_ptr>, Args...); -// -// To make this all work, we craft a simple callback suitable for -// DBusParamReader using a lambda in DBusInvoker::Invoke() and redirect the call -// to the appropriate method handler using additional data captured by the -// lambda object. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// This is an abstract base class to allow dispatching a native C++ callback -// method when a corresponding D-Bus method is called. -class DBusInterfaceMethodHandlerInterface { - public: - virtual ~DBusInterfaceMethodHandlerInterface() = default; - - // Returns true if the method has been handled synchronously (whether or not - // a success or error response message had been sent). - virtual void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) = 0; -}; - -// This is a special implementation of DBusInterfaceMethodHandlerInterface for -// extremely simple synchronous method handlers that cannot possibly fail -// (that is, they do not send an error response). -// The handler is expected to take an arbitrary number of arguments of type -// |Args...| which can contain both inputs (passed in by value or constant -// reference) and outputs (passed in as pointers)... -// It may also return a single value of type R (or could be a void function if -// no return value is to be sent to the caller). If the handler has a return -// value, then it cannot have any output parameters in its parameter list. -// The signature of the callback handler is expected to be: -// R(Args...) -template -class SimpleDBusInterfaceMethodHandler - : public DBusInterfaceMethodHandlerInterface { - public: - // A constructor that takes a |handler| to be called when HandleMethod() - // virtual function is invoked. - explicit SimpleDBusInterfaceMethodHandler( - const base::Callback& handler) : handler_(handler) {} - - void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) override { - DBusMethodResponse method_response(method_call, sender); - auto invoke_callback = [this, &method_response](const Args&... args) { - method_response.Return(handler_.Run(args...)); - }; - - ErrorPtr param_reader_error; - dbus::MessageReader reader(method_call); - // The handler is expected a return value, don't allow output parameters. - if (!DBusParamReader::Invoke( - invoke_callback, &reader, ¶m_reader_error)) { - // Error parsing method arguments. - method_response.ReplyWithError(param_reader_error.get()); - } - } - - private: - // C++ callback to be called when a DBus method is dispatched. - base::Callback handler_; - DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandler); -}; - -// Specialization of SimpleDBusInterfaceMethodHandlerInterface for -// R=void (methods with no return values). -template -class SimpleDBusInterfaceMethodHandler - : public DBusInterfaceMethodHandlerInterface { - public: - // A constructor that takes a |handler| to be called when HandleMethod() - // virtual function is invoked. - explicit SimpleDBusInterfaceMethodHandler( - const base::Callback& handler) : handler_(handler) {} - - void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) override { - DBusMethodResponseBase method_response(method_call, sender); - auto invoke_callback = [this, &method_response](const Args&... args) { - handler_.Run(args...); - auto response = method_response.CreateCustomResponse(); - dbus::MessageWriter writer(response.get()); - DBusParamWriter::AppendDBusOutParams(&writer, args...); - method_response.SendRawResponse(std::move(response)); - }; - - ErrorPtr param_reader_error; - dbus::MessageReader reader(method_call); - if (!DBusParamReader::Invoke( - invoke_callback, &reader, ¶m_reader_error)) { - // Error parsing method arguments. - method_response.ReplyWithError(param_reader_error.get()); - } - } - - private: - // C++ callback to be called when a DBus method is dispatched. - base::Callback handler_; - DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandler); -}; - -// An implementation of DBusInterfaceMethodHandlerInterface for simple -// synchronous method handlers that may fail and return an error response -// message. -// The handler is expected to take an arbitrary number of arguments of type -// |Args...| which can contain both inputs (passed in by value or constant -// reference) and outputs (passed in as pointers)... -// In case of an error, the handler must return false and set the error details -// into the |error| object provided. -// The signature of the callback handler is expected to be: -// bool(ErrorPtr*, Args...) -template -class SimpleDBusInterfaceMethodHandlerWithError - : public DBusInterfaceMethodHandlerInterface { - public: - // A constructor that takes a |handler| to be called when HandleMethod() - // virtual function is invoked. - explicit SimpleDBusInterfaceMethodHandlerWithError( - const base::Callback& handler) - : handler_(handler) {} - - void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) override { - DBusMethodResponseBase method_response(method_call, sender); - auto invoke_callback = [this, &method_response](const Args&... args) { - ErrorPtr error; - if (!handler_.Run(&error, args...)) { - method_response.ReplyWithError(error.get()); - } else { - auto response = method_response.CreateCustomResponse(); - dbus::MessageWriter writer(response.get()); - DBusParamWriter::AppendDBusOutParams(&writer, args...); - method_response.SendRawResponse(std::move(response)); - } - }; - - ErrorPtr param_reader_error; - dbus::MessageReader reader(method_call); - if (!DBusParamReader::Invoke( - invoke_callback, &reader, ¶m_reader_error)) { - // Error parsing method arguments. - method_response.ReplyWithError(param_reader_error.get()); - } - } - - private: - // C++ callback to be called when a DBus method is dispatched. - base::Callback handler_; - DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandlerWithError); -}; - -// An implementation of SimpleDBusInterfaceMethodHandlerWithErrorAndMessage -// which is almost identical to SimpleDBusInterfaceMethodHandlerWithError with -// the exception that the callback takes an additional parameter - raw D-Bus -// message used to invoke the method handler. -// The handler is expected to take an arbitrary number of arguments of type -// |Args...| which can contain both inputs (passed in by value or constant -// reference) and outputs (passed in as pointers)... -// In case of an error, the handler must return false and set the error details -// into the |error| object provided. -// The signature of the callback handler is expected to be: -// bool(ErrorPtr*, dbus::Message*, Args...) -template -class SimpleDBusInterfaceMethodHandlerWithErrorAndMessage - : public DBusInterfaceMethodHandlerInterface { - public: - // A constructor that takes a |handler| to be called when HandleMethod() - // virtual function is invoked. - explicit SimpleDBusInterfaceMethodHandlerWithErrorAndMessage( - const base::Callback& handler) - : handler_(handler) {} - - void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) override { - DBusMethodResponseBase method_response(method_call, sender); - auto invoke_callback = - [this, method_call, &method_response](const Args&... args) { - ErrorPtr error; - if (!handler_.Run(&error, method_call, args...)) { - method_response.ReplyWithError(error.get()); - } else { - auto response = method_response.CreateCustomResponse(); - dbus::MessageWriter writer(response.get()); - DBusParamWriter::AppendDBusOutParams(&writer, args...); - method_response.SendRawResponse(std::move(response)); - } - }; - - ErrorPtr param_reader_error; - dbus::MessageReader reader(method_call); - if (!DBusParamReader::Invoke( - invoke_callback, &reader, ¶m_reader_error)) { - // Error parsing method arguments. - method_response.ReplyWithError(param_reader_error.get()); - } - } - - private: - // C++ callback to be called when a DBus method is dispatched. - base::Callback handler_; - DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandlerWithErrorAndMessage); -}; - -// An implementation of DBusInterfaceMethodHandlerInterface for more generic -// (and possibly asynchronous) method handlers. The handler is expected -// to take an arbitrary number of input arguments of type |Args...| and send -// the method call response (including a possible error response) using -// the provided DBusMethodResponse object. -// The signature of the callback handler is expected to be: -// void(std::unique_ptr, Args...) -template -class DBusInterfaceMethodHandler : public DBusInterfaceMethodHandlerInterface { - public: - // A constructor that takes a |handler| to be called when HandleMethod() - // virtual function is invoked. - explicit DBusInterfaceMethodHandler( - const base::Callback, Args...)>& handler) - : handler_(handler) {} - - // This method forwards the call to |handler_| after extracting the required - // arguments from the DBus message buffer specified in |method_call|. - // The output parameters of |handler_| (if any) are sent back to the called. - void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) override { - auto invoke_callback = [this, method_call, &sender](const Args&... args) { - std::unique_ptr response(new Response(method_call, sender)); - handler_.Run(std::move(response), args...); - }; - - ErrorPtr param_reader_error; - dbus::MessageReader reader(method_call); - if (!DBusParamReader::Invoke( - invoke_callback, &reader, ¶m_reader_error)) { - // Error parsing method arguments. - DBusMethodResponseBase method_response(method_call, sender); - method_response.ReplyWithError(param_reader_error.get()); - } - } - - private: - // C++ callback to be called when a D-Bus method is dispatched. - base::Callback, Args...)> handler_; - - DISALLOW_COPY_AND_ASSIGN(DBusInterfaceMethodHandler); -}; - -// An implementation of DBusInterfaceMethodHandlerWithMessage which is almost -// identical to AddSimpleMethodHandlerWithError with the exception that the -// callback takes an additional parameter - raw D-Bus message. -// The handler is expected to take an arbitrary number of input arguments of -// type |Args...| and send the method call response (including a possible error -// response) using the provided DBusMethodResponse object. -// The signature of the callback handler is expected to be: -// void(std::unique_ptr, dbus::Message*, -// Args...); -template -class DBusInterfaceMethodHandlerWithMessage - : public DBusInterfaceMethodHandlerInterface { - public: - // A constructor that takes a |handler| to be called when HandleMethod() - // virtual function is invoked. - explicit DBusInterfaceMethodHandlerWithMessage( - const base::Callback, dbus::Message*, - Args...)>& handler) - : handler_(handler) {} - - // This method forwards the call to |handler_| after extracting the required - // arguments from the DBus message buffer specified in |method_call|. - // The output parameters of |handler_| (if any) are sent back to the called. - void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) override { - auto invoke_callback = [this, method_call, &sender](const Args&... args) { - std::unique_ptr response(new Response(method_call, sender)); - handler_.Run(std::move(response), method_call, args...); - }; - - ErrorPtr param_reader_error; - dbus::MessageReader reader(method_call); - if (!DBusParamReader::Invoke( - invoke_callback, &reader, ¶m_reader_error)) { - // Error parsing method arguments. - DBusMethodResponseBase method_response(method_call, sender); - method_response.ReplyWithError(param_reader_error.get()); - } - } - - private: - // C++ callback to be called when a D-Bus method is dispatched. - base::Callback, - dbus::Message*, Args...)> handler_; - - DISALLOW_COPY_AND_ASSIGN(DBusInterfaceMethodHandlerWithMessage); -}; - -// An implementation of DBusInterfaceMethodHandlerInterface that has custom -// processing of both input and output parameters. This class is used by -// DBusObject::AddRawMethodHandler and expects the callback to be of the -// following signature: -// void(dbus::MethodCall*, ResponseSender) -// It will be up to the callback to parse the input parameters from the -// message buffer and construct the D-Bus Response object. -class RawDBusInterfaceMethodHandler - : public DBusInterfaceMethodHandlerInterface { - public: - // A constructor that takes a |handler| to be called when HandleMethod() - // virtual function is invoked. - RawDBusInterfaceMethodHandler( - const base::Callback& handler) - : handler_(handler) {} - - void HandleMethod(dbus::MethodCall* method_call, - ResponseSender sender) override { - handler_.Run(method_call, sender); - } - - private: - // C++ callback to be called when a D-Bus method is dispatched. - base::Callback handler_; - - DISALLOW_COPY_AND_ASSIGN(RawDBusInterfaceMethodHandler); -}; - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_ diff --git a/chromeos/dbus/dbus_object_test_helpers.h b/chromeos/dbus/dbus_object_test_helpers.h deleted file mode 100644 index db24c9f..0000000 --- a/chromeos/dbus/dbus_object_test_helpers.h +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Helper utilities to simplify testing of D-Bus object implementations. -// Since the method handlers could now be asynchronous, they use callbacks to -// provide method return values. This makes it really difficult to invoke -// such handlers in unit tests (even if they are actually synchronous but -// still use DBusMethodResponse to send back the method results). -// This file provide testing-only helpers to make calling D-Bus method handlers -// easier. -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ - -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// Helper friend class to call DBusInterface::HandleMethodCall() since it is -// a private method of the class and we don't want to make it public. -class DBusInterfaceTestHelper final { - public: - static void HandleMethodCall(DBusInterface* itf, - dbus::MethodCall* method_call, - ResponseSender sender) { - itf->HandleMethodCall(method_call, sender); - } -}; - -namespace testing { - -// This is a simple class that has weak pointer semantics and holds an -// instance of D-Bus method call response message. We use this in tests -// to get the response in case the handler processes a method call request -// synchronously. Otherwise the ResponseHolder object will be destroyed and -// ResponseHolder::ReceiveResponse() will not be called since we bind the -// callback to the object instance via a weak pointer. -struct ResponseHolder final : public base::SupportsWeakPtr { - void ReceiveResponse(scoped_ptr response) { - response_.reset(response.release()); - } - - std::unique_ptr response_; -}; - -// Dispatches a D-Bus method call to the corresponding handler. -// Used mostly for testing purposes. This method is inlined so that it is -// not included in the shipping code of libchromeos, and included at the -// call sites. Returns a response from the method handler or nullptr if the -// method hasn't provided the response message immediately -// (i.e. it is asynchronous). -inline std::unique_ptr CallMethod( - const DBusObject& object, dbus::MethodCall* method_call) { - DBusInterface* itf = object.FindInterface(method_call->GetInterface()); - std::unique_ptr response; - if (!itf) { - response = CreateDBusErrorResponse( - method_call, - DBUS_ERROR_UNKNOWN_INTERFACE, - "Interface you invoked a method on isn't known by the object."); - } else { - ResponseHolder response_holder; - DBusInterfaceTestHelper::HandleMethodCall( - itf, method_call, base::Bind(&ResponseHolder::ReceiveResponse, - response_holder.AsWeakPtr())); - response = std::move(response_holder.response_); - } - return response; -} - -// MethodHandlerInvoker is similar to CallMethod() function above, except -// it allows the callers to invoke the method handlers directly bypassing -// the DBusObject/DBusInterface infrastructure. -// This works only on synchronous methods though. The handler must reply -// before the handler exits. -template -struct MethodHandlerInvoker { - // MethodHandlerInvoker::Call() calls a member |method| of a class - // |instance| and passes the |args| to it. The method's return value provided - // via handler's DBusMethodResponse is then extracted and returned. - // If the method handler returns an error, the error information is passed - // to the caller via the |error| object (and the method returns a default - // value of type RetType as a placeholder). - // If the method handler asynchronous and did not provide a reply (success or - // error) before the handler exits, this method aborts with a CHECK(). - template - static RetType Call( - ErrorPtr* error, - Class* instance, - void(Class::*method)(std::unique_ptr>, - Params...), - Args... args) { - ResponseHolder response_holder; - dbus::MethodCall method_call("test.interface", "TestMethod"); - method_call.SetSerial(123); - std::unique_ptr> method_response{ - new DBusMethodResponse( - &method_call, base::Bind(&ResponseHolder::ReceiveResponse, - response_holder.AsWeakPtr())) - }; - (instance->*method)(std::move(method_response), args...); - CHECK(response_holder.response_.get()) - << "No response received. Asynchronous methods are not supported."; - RetType ret_val; - ExtractMethodCallResults(response_holder.response_.get(), error, &ret_val); - return ret_val; - } -}; - -// Specialization of MethodHandlerInvoker for methods that do not return -// values (void methods). -template<> -struct MethodHandlerInvoker { - template - static void Call( - ErrorPtr* error, - Class* instance, - void(Class::*method)(std::unique_ptr>, Params...), - Args... args) { - ResponseHolder response_holder; - dbus::MethodCall method_call("test.interface", "TestMethod"); - method_call.SetSerial(123); - std::unique_ptr> method_response{ - new DBusMethodResponse<>(&method_call, - base::Bind(&ResponseHolder::ReceiveResponse, - response_holder.AsWeakPtr())) - }; - (instance->*method)(std::move(method_response), args...); - CHECK(response_holder.response_.get()) - << "No response received. Asynchronous methods are not supported."; - ExtractMethodCallResults(response_holder.response_.get(), error); - } -}; - -} // namespace testing -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ diff --git a/chromeos/dbus/dbus_object_unittest.cc b/chromeos/dbus/dbus_object_unittest.cc deleted file mode 100644 index 87a79e8..0000000 --- a/chromeos/dbus/dbus_object_unittest.cc +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using ::testing::AnyNumber; -using ::testing::Return; -using ::testing::Invoke; -using ::testing::Mock; -using ::testing::_; - -namespace chromeos { -namespace dbus_utils { - -namespace { - -const char kMethodsExportedOn[] = "/export"; - -const char kTestInterface1[] = "org.chromium.Test.MathInterface"; -const char kTestMethod_Add[] = "Add"; -const char kTestMethod_Negate[] = "Negate"; -const char kTestMethod_Positive[] = "Positive"; -const char kTestMethod_AddSubtract[] = "AddSubtract"; - -const char kTestInterface2[] = "org.chromium.Test.StringInterface"; -const char kTestMethod_StrLen[] = "StrLen"; -const char kTestMethod_CheckNonEmpty[] = "CheckNonEmpty"; - -const char kTestInterface3[] = "org.chromium.Test.NoOpInterface"; -const char kTestMethod_NoOp[] = "NoOp"; -const char kTestMethod_WithMessage[] = "TestWithMessage"; -const char kTestMethod_WithMessageAsync[] = "TestWithMessageAsync"; - -struct Calc { - int Add(int x, int y) { return x + y; } - int Negate(int x) { return -x; } - void Positive(std::unique_ptr> response, - double x) { - if (x >= 0.0) { - response->Return(x); - return; - } - ErrorPtr error; - Error::AddTo(&error, FROM_HERE, "test", "not_positive", - "Negative value passed in"); - response->ReplyWithError(error.get()); - } - void AddSubtract(int x, int y, int* sum, int* diff) { - *sum = x + y; - *diff = x - y; - } -}; - -int StrLen(const std::string& str) { - return str.size(); -} - -bool CheckNonEmpty(ErrorPtr* error, const std::string& str) { - if (!str.empty()) - return true; - Error::AddTo(error, FROM_HERE, "test", "string_empty", "String is empty"); - return false; -} - -void NoOp() {} - -bool TestWithMessage(ErrorPtr* error, - dbus::Message* message, - std::string* str) { - *str = message->GetSender(); - return true; -} - -void TestWithMessageAsync( - std::unique_ptr> response, - dbus::Message* message) { - response->Return(message->GetSender()); -} - -} // namespace - -class DBusObjectTest : public ::testing::Test { - public: - virtual void SetUp() { - dbus::Bus::Options options; - options.bus_type = dbus::Bus::SYSTEM; - bus_ = new dbus::MockBus(options); - // By default, don't worry about threading assertions. - EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); - EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); - // Use a mock exported object. - const dbus::ObjectPath kMethodsExportedOnPath{ - std::string{kMethodsExportedOn}}; - mock_exported_object_ = - new dbus::MockExportedObject(bus_.get(), kMethodsExportedOnPath); - EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath)) - .Times(AnyNumber()) - .WillRepeatedly(Return(mock_exported_object_.get())); - EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _)) - .Times(AnyNumber()); - EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1); - - dbus_object_ = std::unique_ptr( - new DBusObject(nullptr, bus_, kMethodsExportedOnPath)); - - DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1); - itf1->AddSimpleMethodHandler( - kTestMethod_Add, base::Unretained(&calc_), &Calc::Add); - itf1->AddSimpleMethodHandler( - kTestMethod_Negate, base::Unretained(&calc_), &Calc::Negate); - itf1->AddMethodHandler( - kTestMethod_Positive, base::Unretained(&calc_), &Calc::Positive); - itf1->AddSimpleMethodHandler( - kTestMethod_AddSubtract, base::Unretained(&calc_), &Calc::AddSubtract); - DBusInterface* itf2 = dbus_object_->AddOrGetInterface(kTestInterface2); - itf2->AddSimpleMethodHandler(kTestMethod_StrLen, StrLen); - itf2->AddSimpleMethodHandlerWithError(kTestMethod_CheckNonEmpty, - CheckNonEmpty); - DBusInterface* itf3 = dbus_object_->AddOrGetInterface(kTestInterface3); - base::Callback noop_callback = base::Bind(NoOp); - itf3->AddSimpleMethodHandler(kTestMethod_NoOp, noop_callback); - itf3->AddSimpleMethodHandlerWithErrorAndMessage( - kTestMethod_WithMessage, base::Bind(&TestWithMessage)); - itf3->AddMethodHandlerWithMessage(kTestMethod_WithMessageAsync, - base::Bind(&TestWithMessageAsync)); - - dbus_object_->RegisterAsync( - AsyncEventSequencer::GetDefaultCompletionAction()); - } - - void ExpectError(dbus::Response* response, const std::string& expected_code) { - EXPECT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - EXPECT_EQ(expected_code, response->GetErrorName()); - } - - scoped_refptr bus_; - scoped_refptr mock_exported_object_; - std::unique_ptr dbus_object_; - Calc calc_; -}; - -TEST_F(DBusObjectTest, Add) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendInt32(2); - writer.AppendInt32(3); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - int result; - ASSERT_TRUE(reader.PopInt32(&result)); - ASSERT_FALSE(reader.HasMoreData()); - ASSERT_EQ(5, result); -} - -TEST_F(DBusObjectTest, Negate) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Negate); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendInt32(98765); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - int result; - ASSERT_TRUE(reader.PopInt32(&result)); - ASSERT_FALSE(reader.HasMoreData()); - ASSERT_EQ(-98765, result); -} - -TEST_F(DBusObjectTest, PositiveSuccess) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendDouble(17.5); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - double result; - ASSERT_TRUE(reader.PopDouble(&result)); - ASSERT_FALSE(reader.HasMoreData()); - ASSERT_DOUBLE_EQ(17.5, result); -} - -TEST_F(DBusObjectTest, PositiveFailure) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendDouble(-23.2); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ExpectError(response.get(), DBUS_ERROR_FAILED); -} - -TEST_F(DBusObjectTest, AddSubtract) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_AddSubtract); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendInt32(2); - writer.AppendInt32(3); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - int sum = 0, diff = 0; - ASSERT_TRUE(reader.PopInt32(&sum)); - ASSERT_TRUE(reader.PopInt32(&diff)); - ASSERT_FALSE(reader.HasMoreData()); - EXPECT_EQ(5, sum); - EXPECT_EQ(-1, diff); -} - -TEST_F(DBusObjectTest, StrLen0) { - dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString(""); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - int result; - ASSERT_TRUE(reader.PopInt32(&result)); - ASSERT_FALSE(reader.HasMoreData()); - ASSERT_EQ(0, result); -} - -TEST_F(DBusObjectTest, StrLen4) { - dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString("test"); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - int result; - ASSERT_TRUE(reader.PopInt32(&result)); - ASSERT_FALSE(reader.HasMoreData()); - ASSERT_EQ(4, result); -} - -TEST_F(DBusObjectTest, CheckNonEmpty_Success) { - dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString("test"); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType()); - dbus::MessageReader reader(response.get()); - EXPECT_FALSE(reader.HasMoreData()); -} - -TEST_F(DBusObjectTest, CheckNonEmpty_Failure) { - dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString(""); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ErrorPtr error; - ExtractMethodCallResults(response.get(), &error); - ASSERT_NE(nullptr, error.get()); - EXPECT_EQ("test", error->GetDomain()); - EXPECT_EQ("string_empty", error->GetCode()); - EXPECT_EQ("String is empty", error->GetMessage()); -} - -TEST_F(DBusObjectTest, CheckNonEmpty_MissingParams) { - dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty); - method_call.SetSerial(123); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - dbus::MessageReader reader(response.get()); - std::string message; - ASSERT_TRUE(reader.PopString(&message)); - EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName()); - EXPECT_EQ("Too few parameters in a method call", message); - EXPECT_FALSE(reader.HasMoreData()); -} - -TEST_F(DBusObjectTest, NoOp) { - dbus::MethodCall method_call(kTestInterface3, kTestMethod_NoOp); - method_call.SetSerial(123); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(DBusObjectTest, TestWithMessage) { - const std::string sender{":1.2345"}; - dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessage); - method_call.SetSerial(123); - method_call.SetSender(sender); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - std::string message; - ASSERT_TRUE(reader.PopString(&message)); - ASSERT_FALSE(reader.HasMoreData()); - EXPECT_EQ(sender, message); -} - -TEST_F(DBusObjectTest, TestWithMessageAsync) { - const std::string sender{":6.7890"}; - dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessageAsync); - method_call.SetSerial(123); - method_call.SetSender(sender); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - std::string message; - ASSERT_TRUE(reader.PopString(&message)); - ASSERT_FALSE(reader.HasMoreData()); - EXPECT_EQ(sender, message); -} - -TEST_F(DBusObjectTest, TooFewParams) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendInt32(2); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS); -} - -TEST_F(DBusObjectTest, TooManyParams) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendInt32(1); - writer.AppendInt32(2); - writer.AppendInt32(3); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS); -} - -TEST_F(DBusObjectTest, ParamTypeMismatch) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendInt32(1); - writer.AppendBool(false); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS); -} - -TEST_F(DBusObjectTest, ParamAsVariant) { - dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendVariantOfInt32(10); - writer.AppendVariantOfInt32(3); - auto response = testing::CallMethod(*dbus_object_, &method_call); - dbus::MessageReader reader(response.get()); - int result; - ASSERT_TRUE(reader.PopInt32(&result)); - ASSERT_FALSE(reader.HasMoreData()); - ASSERT_EQ(13, result); -} - -TEST_F(DBusObjectTest, UnknownMethod) { - dbus::MethodCall method_call(kTestInterface2, kTestMethod_Add); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendInt32(1); - writer.AppendBool(false); - auto response = testing::CallMethod(*dbus_object_, &method_call); - ExpectError(response.get(), DBUS_ERROR_UNKNOWN_METHOD); -} - -TEST_F(DBusObjectTest, ShouldReleaseOnlyClaimedInterfaces) { - const dbus::ObjectPath kObjectManagerPath{std::string{"/"}}; - const dbus::ObjectPath kMethodsExportedOnPath{ - std::string{kMethodsExportedOn}}; - MockExportedObjectManager mock_object_manager{bus_, kObjectManagerPath}; - dbus_object_ = std::unique_ptr( - new DBusObject(&mock_object_manager, bus_, kMethodsExportedOnPath)); - EXPECT_CALL(mock_object_manager, ClaimInterface(_, _, _)).Times(0); - EXPECT_CALL(mock_object_manager, ReleaseInterface(_, _)).Times(0); - DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1); - itf1->AddSimpleMethodHandler( - kTestMethod_Add, base::Unretained(&calc_), &Calc::Add); - // When we tear down our DBusObject, it should release only interfaces it has - // previously claimed. This prevents a check failing inside the - // ExportedObjectManager. Since no interfaces have finished exporting - // handlers, nothing should be released. - dbus_object_.reset(); -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_param_reader.h b/chromeos/dbus/dbus_param_reader.h deleted file mode 100644 index 0ac1c2a..0000000 --- a/chromeos/dbus/dbus_param_reader.h +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file provides generic method to parse function call arguments from -// D-Bus message buffer and subsequently invokes a provided native C++ callback -// with the parameter values passed as the callback arguments. - -// This functionality is achieved by parsing method arguments one by one, -// left to right from the C++ callback's type signature, and moving the parsed -// arguments to the back to the next call to DBusInvoke::Invoke's arguments as -// const refs. Each iteration has one fewer template specialization arguments, -// until there is only the return type remaining and we fall through to either -// the void or the non-void final specialization. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PARAM_READER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PARAM_READER_H_ - -#include - -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// A generic DBusParamReader stub class which allows us to specialize on -// a variable list of expected function parameters later on. -// This struct in itself is not used. But its concrete template specializations -// defined below are. -// |allow_out_params| controls whether DBusParamReader allows the parameter -// list to contain OUT parameters (pointers). -template -struct DBusParamReader; - -// A generic specialization of DBusParamReader to handle variable function -// parameters. This specialization pops one parameter off the D-Bus message -// buffer and calls other specializations of DBusParamReader with fewer -// parameters to pop the remaining parameters. -// CurrentParam - the type of the current method parameter we are processing. -// RestOfParams - the types of remaining parameters to be processed. -template -struct DBusParamReader { - // DBusParamReader::Invoke() is a member function that actually extracts the - // current parameter from the message buffer. - // handler - the C++ callback functor to be called when all the - // parameters are processed. - // method_call - D-Bus method call object we are processing. - // reader - D-Bus message reader to pop the current argument value from. - // args... - the callback parameters processed so far. - template - static bool Invoke(const CallbackType& handler, - dbus::MessageReader* reader, - ErrorPtr* error, - const Args&... args) { - return InvokeHelper( - handler, reader, error, static_cast(args)...); - } - - // - // There are two specializations of this function: - // 1. For the case where ParamType is a value type (D-Bus IN parameter). - // 2. For the case where ParamType is a pointer (D-Bus OUT parameter). - // In the second case, the parameter is not popped off the message reader, - // since we do not expect the client to provide any data for it. - // However after the final handler is called, the values for the OUT - // parameters should be sent back in the method call response message. - - // Overload 1: ParamType is not a pointer. - template - static typename std::enable_if::value, bool>::type - InvokeHelper(const CallbackType& handler, - dbus::MessageReader* reader, - ErrorPtr* error, - const Args&... args) { - if (!reader->HasMoreData()) { - Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, - DBUS_ERROR_INVALID_ARGS, - "Too few parameters in a method call"); - return false; - } - // ParamType could be a reference type (e.g. 'const std::string&'). - // Here we need a value type so we can create an object of this type and - // pop the value off the message buffer into. Using std::decay<> to get - // the value type. If ParamType is already a value type, ParamValueType will - // be the same as ParamType. - using ParamValueType = typename std::decay::type; - // The variable to hold the value of the current parameter we reading from - // the message buffer. - ParamValueType current_param; - if (!DBusType::Read(reader, ¤t_param)) { - Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, - DBUS_ERROR_INVALID_ARGS, - "Method parameter type mismatch"); - return false; - } - // Call DBusParamReader::Invoke() to process the rest of parameters. - // Note that this is not a recursive call because it is calling a different - // method of a different class. We exclude the current parameter type - // (ParamType) from DBusParamReader<> template parameter list and forward - // all the parameters to the arguments of Invoke() and append the current - // parameter to the end of the parameter list. We pass it as a const - // reference to allow to use move-only types such as std::unique_ptr<> and - // to eliminate unnecessarily copying data. - return DBusParamReader::Invoke( - handler, reader, error, - static_cast(args)..., - static_cast(current_param)); - } - - // Overload 2: ParamType is a pointer. - template - static typename std::enable_if::value, bool>::type - InvokeHelper(const CallbackType& handler, - dbus::MessageReader* reader, - ErrorPtr* error, - const Args&... args) { - // ParamType is a pointer. This is expected to be an output parameter. - // Create storage for it and the handler will provide a value for it. - using ParamValueType = typename std::remove_pointer::type; - // The variable to hold the value of the current parameter we are passing - // to the handler. - ParamValueType current_param{}; // Default-initialize the value. - // Call DBusParamReader::Invoke() to process the rest of parameters. - // Note that this is not a recursive call because it is calling a different - // method of a different class. We exclude the current parameter type - // (ParamType) from DBusParamReader<> template parameter list and forward - // all the parameters to the arguments of Invoke() and append the current - // parameter to the end of the parameter list. - return DBusParamReader::Invoke( - handler, reader, error, - static_cast(args)..., - ¤t_param); - } -}; // struct DBusParamReader - -// The final specialization of DBusParamReader<> used when no more parameters -// are expected in the message buffer. Actually dispatches the call to the -// handler with all the accumulated arguments. -template -struct DBusParamReader { - template - static bool Invoke(const CallbackType& handler, - dbus::MessageReader* reader, - ErrorPtr* error, - const Args&... args) { - if (reader->HasMoreData()) { - Error::AddTo(error, FROM_HERE, errors::dbus::kDomain, - DBUS_ERROR_INVALID_ARGS, - "Too many parameters in a method call"); - return false; - } - handler(args...); - return true; - } -}; // struct DBusParamReader<> - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PARAM_READER_H_ diff --git a/chromeos/dbus/dbus_param_reader_unittest.cc b/chromeos/dbus/dbus_param_reader_unittest.cc deleted file mode 100644 index d498516..0000000 --- a/chromeos/dbus/dbus_param_reader_unittest.cc +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include - -using dbus::MessageReader; -using dbus::MessageWriter; -using dbus::Response; - -namespace chromeos { -namespace dbus_utils { - -TEST(DBusParamReader, NoArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called]() { called = true; }; - EXPECT_TRUE(DBusParamReader::Invoke(callback, &reader, nullptr)); - EXPECT_TRUE(called); -} - -TEST(DBusParamReader, OneArg) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, 123); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](int param1) { - EXPECT_EQ(123, param1); - called = true; - }; - EXPECT_TRUE( - (DBusParamReader::Invoke(callback, &reader, nullptr))); - EXPECT_TRUE(called); -} - -TEST(DBusParamReader, ManyArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, true); - AppendValueToWriter(&writer, 1972); - AppendValueToWriter(&writer, - VariantDictionary{{"key", std::string{"value"}}}); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](bool p1, int p2, const VariantDictionary& p3) { - EXPECT_TRUE(p1); - EXPECT_EQ(1972, p2); - EXPECT_EQ(1, p3.size()); - EXPECT_EQ("value", p3.find("key")->second.Get()); - called = true; - }; - EXPECT_TRUE((DBusParamReader::Invoke( - callback, &reader, nullptr))); - EXPECT_TRUE(called); -} - -TEST(DBusParamReader, TooManyArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, true); - AppendValueToWriter(&writer, 1972); - AppendValueToWriter(&writer, - VariantDictionary{{"key", std::string{"value"}}}); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](bool param1, int param2) { - EXPECT_TRUE(param1); - EXPECT_EQ(1972, param2); - called = true; - }; - ErrorPtr error; - EXPECT_FALSE( - (DBusParamReader::Invoke(callback, &reader, &error))); - EXPECT_FALSE(called); - EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); - EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); - EXPECT_EQ("Too many parameters in a method call", error->GetMessage()); -} - -TEST(DBusParamReader, TooFewArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, true); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](bool param1, int param2) { - EXPECT_TRUE(param1); - EXPECT_EQ(1972, param2); - called = true; - }; - ErrorPtr error; - EXPECT_FALSE( - (DBusParamReader::Invoke(callback, &reader, &error))); - EXPECT_FALSE(called); - EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); - EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); - EXPECT_EQ("Too few parameters in a method call", error->GetMessage()); -} - -TEST(DBusParamReader, TypeMismatch) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, true); - AppendValueToWriter(&writer, 1972); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](bool param1, double param2) { - EXPECT_TRUE(param1); - EXPECT_DOUBLE_EQ(1972.0, param2); - called = true; - }; - ErrorPtr error; - EXPECT_FALSE(( - DBusParamReader::Invoke(callback, &reader, &error))); - EXPECT_FALSE(called); - EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); - EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); - EXPECT_EQ("Method parameter type mismatch", error->GetMessage()); -} - -TEST(DBusParamReader, NoArgs_With_OUT) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](int* param1) { - EXPECT_EQ(0, *param1); - called = true; - }; - EXPECT_TRUE( - (DBusParamReader::Invoke(callback, &reader, nullptr))); - EXPECT_TRUE(called); -} - -TEST(DBusParamReader, OneArg_Before_OUT) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, 123); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](int param1, double* param2) { - EXPECT_EQ(123, param1); - EXPECT_DOUBLE_EQ(0.0, *param2); - called = true; - }; - EXPECT_TRUE(( - DBusParamReader::Invoke(callback, &reader, nullptr))); - EXPECT_TRUE(called); -} - -TEST(DBusParamReader, OneArg_After_OUT) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, 123); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](double* param1, int param2) { - EXPECT_DOUBLE_EQ(0.0, *param1); - EXPECT_EQ(123, param2); - called = true; - }; - EXPECT_TRUE(( - DBusParamReader::Invoke(callback, &reader, nullptr))); - EXPECT_TRUE(called); -} - -TEST(DBusParamReader, ManyArgs_With_OUT) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, true); - AppendValueToWriter(&writer, 1972); - AppendValueToWriter(&writer, - VariantDictionary{{"key", std::string{"value"}}}); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](bool p1, - std::string* p2, - int p3, - int* p4, - const VariantDictionary& p5, - bool* p6) { - EXPECT_TRUE(p1); - EXPECT_EQ("", *p2); - EXPECT_EQ(1972, p3); - EXPECT_EQ(0, *p4); - EXPECT_EQ(1, p5.size()); - EXPECT_EQ("value", p5.find("key")->second.Get()); - EXPECT_FALSE(*p6); - called = true; - }; - EXPECT_TRUE((DBusParamReader::Invoke(callback, &reader, nullptr))); - EXPECT_TRUE(called); -} - -TEST(DBusParamReader, TooManyArgs_With_OUT) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, true); - AppendValueToWriter(&writer, 1972); - AppendValueToWriter(&writer, - VariantDictionary{{"key", std::string{"value"}}}); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](bool param1, int param2, int* param3) { - EXPECT_TRUE(param1); - EXPECT_EQ(1972, param2); - EXPECT_EQ(0, *param3); - called = true; - }; - ErrorPtr error; - EXPECT_FALSE((DBusParamReader::Invoke( - callback, &reader, &error))); - EXPECT_FALSE(called); - EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); - EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); - EXPECT_EQ("Too many parameters in a method call", error->GetMessage()); -} - -TEST(DBusParamReader, TooFewArgs_With_OUT) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - AppendValueToWriter(&writer, true); - MessageReader reader(message.get()); - bool called = false; - auto callback = [&called](bool param1, int param2, int* param3) { - EXPECT_TRUE(param1); - EXPECT_EQ(1972, param2); - EXPECT_EQ(0, *param3); - called = true; - }; - ErrorPtr error; - EXPECT_FALSE((DBusParamReader::Invoke( - callback, &reader, &error))); - EXPECT_FALSE(called); - EXPECT_EQ(errors::dbus::kDomain, error->GetDomain()); - EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode()); - EXPECT_EQ("Too few parameters in a method call", error->GetMessage()); -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_param_writer.h b/chromeos/dbus/dbus_param_writer.h deleted file mode 100644 index 16a5ce2..0000000 --- a/chromeos/dbus/dbus_param_writer.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// DBusParamWriter::Append(writer, ...) provides functionality opposite -// to that of DBusParamReader. It writes each of the arguments to D-Bus message -// writer and return true if successful. - -// DBusParamWriter::AppendDBusOutParams(writer, ...) is similar to Append() -// but is used to send out the D-Bus OUT (pointer type) parameters in a D-Bus -// method response message. This method skips any non-pointer parameters and -// only appends the data for arguments that are pointers. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PARAM_WRITER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PARAM_WRITER_H_ - -#include -#include - -namespace chromeos { -namespace dbus_utils { - -class DBusParamWriter final { - public: - // Generic writer method that takes 1 or more arguments. It recursively calls - // itself (each time with one fewer arguments) until no more is left. - template - static void Append(dbus::MessageWriter* writer, - const ParamType& param, - const RestOfParams&... rest) { - // Append the current |param| to D-Bus, then call Append() with one - // fewer arguments, until none is left and stand-alone version of - // Append(dbus::MessageWriter*) is called to end the iteration. - DBusType::Write(writer, param); - Append(writer, rest...); - } - - // The final overload of DBusParamWriter::Append() used when no more - // parameters are remaining to be written. - // Does nothing and finishes meta-recursion. - static void Append(dbus::MessageWriter* /*writer*/) {} - - // Generic writer method that takes 1 or more arguments. It recursively calls - // itself (each time with one fewer arguments) until no more is left. - // Handles non-pointer parameter by just skipping over it. - template - static void AppendDBusOutParams(dbus::MessageWriter* writer, - const ParamType& param, - const RestOfParams&... rest) { - // Skip the current |param| and call Append() with one fewer arguments, - // until none is left and stand-alone version of - // AppendDBusOutParams(dbus::MessageWriter*) is called to end the iteration. - AppendDBusOutParams(writer, rest...); - } - - // Generic writer method that takes 1 or more arguments. It recursively calls - // itself (each time with one fewer arguments) until no more is left. - // Handles only a parameter of pointer type and writes the data pointed to - // to the output message buffer. - template - static void AppendDBusOutParams(dbus::MessageWriter* writer, - ParamType* param, - const RestOfParams&... rest) { - // Append the current |param| to D-Bus, then call Append() with one - // fewer arguments, until none is left and stand-alone version of - // Append(dbus::MessageWriter*) is called to end the iteration. - DBusType::Write(writer, *param); - AppendDBusOutParams(writer, rest...); - } - - // The final overload of DBusParamWriter::AppendDBusOutParams() used when no - // more parameters are remaining to be written. - // Does nothing and finishes meta-recursion. - static void AppendDBusOutParams(dbus::MessageWriter* /*writer*/) {} -}; - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PARAM_WRITER_H_ diff --git a/chromeos/dbus/dbus_param_writer_unittest.cc b/chromeos/dbus/dbus_param_writer_unittest.cc deleted file mode 100644 index 37c3f6f..0000000 --- a/chromeos/dbus/dbus_param_writer_unittest.cc +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include - -using dbus::MessageReader; -using dbus::MessageWriter; -using dbus::ObjectPath; -using dbus::Response; - -namespace chromeos { -namespace dbus_utils { - -TEST(DBusParamWriter, Append_NoArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - DBusParamWriter::Append(&writer); - EXPECT_EQ("", message->GetSignature()); -} - -TEST(DBusParamWriter, Append_OneArg) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - DBusParamWriter::Append(&writer, int32_t{2}); - EXPECT_EQ("i", message->GetSignature()); - DBusParamWriter::Append(&writer, std::string{"foo"}); - EXPECT_EQ("is", message->GetSignature()); - DBusParamWriter::Append(&writer, ObjectPath{"/o"}); - EXPECT_EQ("iso", message->GetSignature()); - - int32_t int_value = 0; - std::string string_value; - ObjectPath path_value; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &path_value)); - - EXPECT_EQ(2, int_value); - EXPECT_EQ("foo", string_value); - EXPECT_EQ(ObjectPath{"/o"}, path_value); -} - -TEST(DBusParamWriter, Append_ManyArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - DBusParamWriter::Append(&writer, int32_t{9}, Any{7.5}, true); - EXPECT_EQ("ivb", message->GetSignature()); - - int32_t int_value = 0; - Any variant_value; - bool bool_value = false; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &variant_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); - - EXPECT_EQ(9, int_value); - EXPECT_DOUBLE_EQ(7.5, variant_value.Get()); - EXPECT_TRUE(bool_value); -} - -TEST(DBusParamWriter, AppendDBusOutParams_NoArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - DBusParamWriter::AppendDBusOutParams(&writer); - EXPECT_EQ("", message->GetSignature()); -} - -TEST(DBusParamWriter, AppendDBusOutParams_OneArg) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - int32_t int_value_in{5}; - std::string string_value_in{"bar"}; - ObjectPath path_value_in{"/obj/path"}; - - DBusParamWriter::AppendDBusOutParams(&writer, &int_value_in); - EXPECT_EQ("i", message->GetSignature()); - DBusParamWriter::AppendDBusOutParams(&writer, &string_value_in); - EXPECT_EQ("is", message->GetSignature()); - DBusParamWriter::AppendDBusOutParams(&writer, &path_value_in); - EXPECT_EQ("iso", message->GetSignature()); - - int32_t int_value = 0; - std::string string_value; - ObjectPath path_value; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &path_value)); - - EXPECT_EQ(5, int_value); - EXPECT_EQ("bar", string_value); - EXPECT_EQ(ObjectPath{"/obj/path"}, path_value); -} - -TEST(DBusParamWriter, AppendDBusOutParams_ManyArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - int32_t int_value_in{8}; - Any variant_value_in{8.5}; - bool bool_value_in{true}; - DBusParamWriter::AppendDBusOutParams( - &writer, &int_value_in, &variant_value_in, &bool_value_in); - EXPECT_EQ("ivb", message->GetSignature()); - - int32_t int_value = 0; - Any variant_value; - bool bool_value = false; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &variant_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); - - EXPECT_EQ(8, int_value); - EXPECT_DOUBLE_EQ(8.5, variant_value.Get()); - EXPECT_TRUE(bool_value); -} - -TEST(DBusParamWriter, AppendDBusOutParams_Mixed_NoArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - DBusParamWriter::AppendDBusOutParams(&writer, 3, 5); - EXPECT_EQ("", message->GetSignature()); -} - -TEST(DBusParamWriter, AppendDBusOutParams_Mixed_OneArg) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - int32_t int_value_in{5}; - std::string str_value_in{"bar"}; - ObjectPath path_value_in{"/obj"}; - - DBusParamWriter::AppendDBusOutParams(&writer, 2, &int_value_in); - EXPECT_EQ("i", message->GetSignature()); - DBusParamWriter::AppendDBusOutParams(&writer, &str_value_in, 0); - EXPECT_EQ("is", message->GetSignature()); - DBusParamWriter::AppendDBusOutParams(&writer, 1, &path_value_in, 2); - EXPECT_EQ("iso", message->GetSignature()); - - int32_t int_value = 0; - std::string string_value; - ObjectPath path_value; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &string_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &path_value)); - - EXPECT_EQ(5, int_value); - EXPECT_EQ("bar", string_value); - EXPECT_EQ(ObjectPath{"/obj"}, path_value); -} - -TEST(DBusParamWriter, AppendDBusOutParams_Mixed_ManyArgs) { - std::unique_ptr message(Response::CreateEmpty().release()); - MessageWriter writer(message.get()); - int32_t int_value_in{8}; - Any variant_value_in{7.5}; - bool bool_value_in{true}; - DBusParamWriter::AppendDBusOutParams( - &writer, 0, &int_value_in, 1, &variant_value_in, 2, &bool_value_in, 3); - EXPECT_EQ("ivb", message->GetSignature()); - - int32_t int_value = 0; - Any variant_value; - bool bool_value = false; - - MessageReader reader(message.get()); - EXPECT_TRUE(PopValueFromReader(&reader, &int_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &variant_value)); - EXPECT_TRUE(PopValueFromReader(&reader, &bool_value)); - - EXPECT_EQ(8, int_value); - EXPECT_DOUBLE_EQ(7.5, variant_value.Get()); - EXPECT_TRUE(bool_value); -} -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_property.h b/chromeos/dbus/dbus_property.h deleted file mode 100644 index 0f76a13..0000000 --- a/chromeos/dbus/dbus_property.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PROPERTY_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PROPERTY_H_ - -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// Re-implementation of dbus::Property that can handle any type supported by -// D-Bus data serialization layer, such as vectors, maps, tuples, etc. -// This class is pretty much a copy of dbus::Property from dbus/property.h -// except that it provides the implementations for PopValueFromReader and -// AppendSetValueToWriter. -template -class Property : public dbus::PropertyBase { - public: - Property() = default; - - // Retrieves the cached value. - const T& value() const { return value_; } - - // Requests an updated value from the remote object incurring a - // round-trip. |callback| will be called when the new value is available. - // This may not be implemented by some interfaces. - void Get(dbus::PropertySet::GetCallback callback) { - property_set()->Get(this, callback); - } - - // Synchronous vesion of Get(). - bool GetAndBlock() { - return property_set()->GetAndBlock(this); - } - - // Requests that the remote object change the property value to |value|, - // |callback| will be called to indicate the success or failure of the - // request, however the new value may not be available depending on the - // remote object. - void Set(const T& value, dbus::PropertySet::SetCallback callback) { - set_value_ = value; - property_set()->Set(this, callback); - } - - // Synchronous version of Set(). - bool SetAndBlock(const T& value) { - set_value_ = value; - return property_set()->SetAndBlock(this); - } - - // Method used by PropertySet to retrieve the value from a MessageReader, - // no knowledge of the contained type is required, this method returns - // true if its expected type was found, false if not. - bool PopValueFromReader(dbus::MessageReader* reader) override { - return PopVariantValueFromReader(reader, &value_); - } - - // Method used by PropertySet to append the set value to a MessageWriter, - // no knowledge of the contained type is required. - // Implementation provided by specialization. - void AppendSetValueToWriter(dbus::MessageWriter* writer) override { - AppendValueToWriterAsVariant(writer, set_value_); - } - - // Method used by test and stub implementations of dbus::PropertySet::Set - // to replace the property value with the set value without using a - // dbus::MessageReader. - void ReplaceValueWithSetValue() override { - value_ = set_value_; - property_set()->NotifyPropertyChanged(name()); - } - - // Method used by test and stub implementations to directly set the - // value of a property. - void ReplaceValue(const T& value) { - value_ = value; - property_set()->NotifyPropertyChanged(name()); - } - - private: - // Current cached value of the property. - T value_; - - // Replacement value of the property. - T set_value_; -}; - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_PROPERTY_H_ diff --git a/chromeos/dbus/dbus_service_watcher.cc b/chromeos/dbus/dbus_service_watcher.cc deleted file mode 100644 index 6b6319e..0000000 --- a/chromeos/dbus/dbus_service_watcher.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { -namespace dbus_utils { - -DBusServiceWatcher::DBusServiceWatcher( - scoped_refptr bus, - const std::string& connection_name, - const base::Closure& on_connection_vanish) - : bus_{bus}, - connection_name_{connection_name}, - on_connection_vanish_{on_connection_vanish} { - monitoring_callback_ = base::Bind( - &DBusServiceWatcher::OnServiceOwnerChange, weak_factory_.GetWeakPtr()); - // Register to listen, and then request the current owner; - bus_->ListenForServiceOwnerChange(connection_name_, monitoring_callback_); - bus_->GetServiceOwner(connection_name_, monitoring_callback_); -} - -DBusServiceWatcher::~DBusServiceWatcher() { - bus_->UnlistenForServiceOwnerChange( - connection_name_, monitoring_callback_); -} - -void DBusServiceWatcher::OnServiceOwnerChange( - const std::string& service_owner) { - if (service_owner.empty()) { - on_connection_vanish_.Run(); - } -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_service_watcher.h b/chromeos/dbus/dbus_service_watcher.h deleted file mode 100644 index 175ac96..0000000 --- a/chromeos/dbus/dbus_service_watcher.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SERVICE_WATCHER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SERVICE_WATCHER_H_ - -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// DBusServiceWatcher just asks the bus to notify us when the owner of a remote -// DBus connection transitions to the empty string. After registering a -// callback to be notified of name owner transitions, for the given -// |connection_name|, DBusServiceWatcher asks for the current owner. If at any -// point an empty string is found for the connection name owner, -// DBusServiceWatcher will call back to notify of the connection vanishing. -// -// The chief value of this class is that it manages the lifetime of the -// registered callback in the Bus, because failure to remove callbacks will -// cause the Bus to crash the process on destruction. -class CHROMEOS_EXPORT DBusServiceWatcher { - public: - DBusServiceWatcher(scoped_refptr bus, - const std::string& connection_name, - const base::Closure& on_connection_vanish); - virtual ~DBusServiceWatcher(); - virtual std::string connection_name() const { return connection_name_; } - - private: - void OnServiceOwnerChange(const std::string& service_owner); - - scoped_refptr bus_; - const std::string connection_name_; - dbus::Bus::GetServiceOwnerCallback monitoring_callback_; - base::Closure on_connection_vanish_; - - base::WeakPtrFactory weak_factory_{this}; - DISALLOW_COPY_AND_ASSIGN(DBusServiceWatcher); -}; - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SERVICE_WATCHER_H_ diff --git a/chromeos/dbus/dbus_signal.cc b/chromeos/dbus/dbus_signal.cc deleted file mode 100644 index b4e12d3..0000000 --- a/chromeos/dbus/dbus_signal.cc +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { -namespace dbus_utils { - -DBusSignalBase::DBusSignalBase(DBusObject* dbus_object, - const std::string& interface_name, - const std::string& signal_name) - : interface_name_(interface_name), - signal_name_(signal_name), - dbus_object_(dbus_object) { -} - -bool DBusSignalBase::SendSignal(dbus::Signal* signal) const { - // This sends the signal asynchronously. However, the raw message inside - // the signal object is ref-counted, so we're fine to pass a stack-allocated - // Signal object here. - return dbus_object_->SendSignal(signal); -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/dbus_signal.h b/chromeos/dbus/dbus_signal.h deleted file mode 100644 index ed7e129..0000000 --- a/chromeos/dbus/dbus_signal.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SIGNAL_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SIGNAL_H_ - -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -class DBusObject; - -// Base class for D-Bus signal proxy classes. -// Used mostly to store the polymorphic DBusSignal<...> in a single map -// container inside DBusInterface object. -class CHROMEOS_EXPORT DBusSignalBase { - public: - DBusSignalBase(DBusObject* dbus_object, - const std::string& interface_name, - const std::string& signal_name); - virtual ~DBusSignalBase() = default; - - protected: - bool SendSignal(dbus::Signal* signal) const; - - std::string interface_name_; - std::string signal_name_; - - private: - DBusObject* dbus_object_; - - DISALLOW_COPY_AND_ASSIGN(DBusSignalBase); -}; - -// DBusSignal<...> is a concrete signal proxy class that knows about the -// exact number of signal arguments and their types. -template -class DBusSignal : public DBusSignalBase { - public: - // Expose the custom constructor from DBusSignalBase. - using DBusSignalBase::DBusSignalBase; - ~DBusSignal() override = default; - - // DBusSignal<...>::Send(...) dispatches the signal with the given arguments. - bool Send(const Args&... args) const { - dbus::Signal signal(interface_name_, signal_name_); - dbus::MessageWriter signal_writer(&signal); - DBusParamWriter::Append(&signal_writer, args...); - return SendSignal(&signal); - } - - private: - DISALLOW_COPY_AND_ASSIGN(DBusSignal); -}; - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SIGNAL_H_ diff --git a/chromeos/dbus/dbus_signal_handler.h b/chromeos/dbus/dbus_signal_handler.h deleted file mode 100644 index 13a2b82..0000000 --- a/chromeos/dbus/dbus_signal_handler.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SIGNAL_HANDLER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SIGNAL_HANDLER_H_ - -#include - -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// chromeos::dbus_utils::ConnectToSignal() is a helper function similar to -// dbus::ObjectProxy::ConnectToSignal() but the |signal_callback| is an actual -// C++ signal handler with expected signal parameters as native method args. -// -// chromeos::dbus_utils::ConnectToSignal() actually registers a stub signal -// handler with D-Bus which has a standard signature that matches -// dbus::ObjectProxy::SignalCallback. -// -// When a D-Bus signal is emitted, the stub handler is invoked, which unpacks -// the expected parameters from dbus::Signal message and then calls -// |signal_callback| with unpacked arguments. -// -// If the signal message doesn't contain correct number or types of arguments, -// an error message is logged to the system log and the signal is ignored -// (|signal_callback| is not invoked). -template -void ConnectToSignal( - dbus::ObjectProxy* object_proxy, - const std::string& interface_name, - const std::string& signal_name, - base::Callback signal_callback, - dbus::ObjectProxy::OnConnectedCallback on_connected_callback) { - // DBusParamReader::Invoke() needs a functor object, not a base::Callback. - // Wrap the callback with lambda so we can redirect the call. - auto signal_callback_wrapper = [signal_callback](const Args&... args) { - if (!signal_callback.is_null()) { - signal_callback.Run(args...); - } - }; - - // Raw signal handler stub method. When called, unpacks the signal arguments - // from |signal| message buffer and redirects the call to - // |signal_callback_wrapper| which, in turn, would call the user-provided - // |signal_callback|. - auto dbus_signal_callback = [signal_callback_wrapper](dbus::Signal* signal) { - dbus::MessageReader reader(signal); - DBusParamReader::Invoke( - signal_callback_wrapper, &reader, nullptr); - }; - - // Register our stub handler with D-Bus ObjectProxy. - object_proxy->ConnectToSignal(interface_name, - signal_name, - base::Bind(dbus_signal_callback), - on_connected_callback); -} - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_SIGNAL_HANDLER_H_ diff --git a/chromeos/dbus/dbus_signal_handler_unittest.cc b/chromeos/dbus/dbus_signal_handler_unittest.cc deleted file mode 100644 index 88d5559..0000000 --- a/chromeos/dbus/dbus_signal_handler_unittest.cc +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include - -using testing::AnyNumber; -using testing::Return; -using testing::SaveArg; -using testing::_; - -namespace chromeos { -namespace dbus_utils { - -const char kTestPath[] = "/test/path"; -const char kTestServiceName[] = "org.test.Object"; -const char kInterface[] = "org.test.Object.TestInterface"; -const char kSignal[] = "TestSignal"; - -class DBusSignalHandlerTest : public testing::Test { - public: - void SetUp() override { - dbus::Bus::Options options; - options.bus_type = dbus::Bus::SYSTEM; - bus_ = new dbus::MockBus(options); - // By default, don't worry about threading assertions. - EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); - EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); - // Use a mock object proxy. - mock_object_proxy_ = new dbus::MockObjectProxy( - bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); - EXPECT_CALL(*bus_, - GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) - .WillRepeatedly(Return(mock_object_proxy_.get())); - } - - void TearDown() override { bus_ = nullptr; } - - protected: - template - void CallSignal(SignalHandlerSink* sink, Args... args) { - dbus::ObjectProxy::SignalCallback signal_callback; - EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _)) - .WillOnce(SaveArg<2>(&signal_callback)); - - chromeos::dbus_utils::ConnectToSignal( - mock_object_proxy_.get(), - kInterface, - kSignal, - base::Bind(&SignalHandlerSink::Handler, base::Unretained(sink)), - {}); - - dbus::Signal signal(kInterface, kSignal); - dbus::MessageWriter writer(&signal); - DBusParamWriter::Append(&writer, args...); - signal_callback.Run(&signal); - } - - scoped_refptr bus_; - scoped_refptr mock_object_proxy_; -}; - -TEST_F(DBusSignalHandlerTest, ConnectToSignal) { - EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _)) - .Times(1); - - chromeos::dbus_utils::ConnectToSignal( - mock_object_proxy_.get(), kInterface, kSignal, base::Closure{}, {}); -} - -TEST_F(DBusSignalHandlerTest, CallSignal_3Args) { - class SignalHandlerSink { - public: - MOCK_METHOD3(Handler, void(int, int, double)); - } sink; - - EXPECT_CALL(sink, Handler(10, 20, 30.5)).Times(1); - CallSignal(&sink, 10, 20, 30.5); -} - -TEST_F(DBusSignalHandlerTest, CallSignal_2Args) { - class SignalHandlerSink { - public: - // Take string both by reference and by value to make sure this works too. - MOCK_METHOD2(Handler, void(const std::string&, std::string)); - } sink; - - EXPECT_CALL(sink, Handler(std::string{"foo"}, std::string{"bar"})).Times(1); - CallSignal(&sink, std::string{"foo"}, std::string{"bar"}); -} - -TEST_F(DBusSignalHandlerTest, CallSignal_NoArgs) { - class SignalHandlerSink { - public: - MOCK_METHOD0(Handler, void()); - } sink; - - EXPECT_CALL(sink, Handler()).Times(1); - CallSignal(&sink); -} - -TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooManyArgs) { - class SignalHandlerSink { - public: - MOCK_METHOD0(Handler, void()); - } sink; - - // Handler() expects no args, but we send an int. - EXPECT_CALL(sink, Handler()).Times(0); - CallSignal(&sink, 5); -} - -TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooFewArgs) { - class SignalHandlerSink { - public: - MOCK_METHOD2(Handler, void(std::string, bool)); - } sink; - - // Handler() expects 2 args while we send it just one. - EXPECT_CALL(sink, Handler(_, _)).Times(0); - CallSignal(&sink, std::string{"bar"}); -} - -TEST_F(DBusSignalHandlerTest, CallSignal_Error_TypeMismatchArgs) { - class SignalHandlerSink { - public: - MOCK_METHOD2(Handler, void(std::string, bool)); - } sink; - - // Handler() expects "sb" while we send it "ii". - EXPECT_CALL(sink, Handler(_, _)).Times(0); - CallSignal(&sink, 1, 2); -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/exported_object_manager.cc b/chromeos/dbus/exported_object_manager.cc deleted file mode 100644 index f952e57..0000000 --- a/chromeos/dbus/exported_object_manager.cc +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include - -using chromeos::dbus_utils::AsyncEventSequencer; - -namespace chromeos { - -namespace dbus_utils { - -ExportedObjectManager::ExportedObjectManager(scoped_refptr bus, - const dbus::ObjectPath& path) - : bus_(bus), dbus_object_(nullptr, bus, path) { -} - -void ExportedObjectManager::RegisterAsync( - const AsyncEventSequencer::CompletionAction& completion_callback) { - VLOG(1) << "Registering object manager"; - bus_->AssertOnOriginThread(); - DBusInterface* itf = - dbus_object_.AddOrGetInterface(dbus::kObjectManagerInterface); - itf->AddSimpleMethodHandler(dbus::kObjectManagerGetManagedObjects, - base::Unretained(this), - &ExportedObjectManager::HandleGetManagedObjects); - - signal_itf_added_ = itf->RegisterSignalOfType( - dbus::kObjectManagerInterfacesAdded); - signal_itf_removed_ = itf->RegisterSignalOfType( - dbus::kObjectManagerInterfacesRemoved); - dbus_object_.RegisterAsync(completion_callback); -} - -void ExportedObjectManager::ClaimInterface( - const dbus::ObjectPath& path, - const std::string& interface_name, - const ExportedPropertySet::PropertyWriter& property_writer) { - bus_->AssertOnOriginThread(); - // We're sending signals that look like: - // org.freedesktop.DBus.ObjectManager.InterfacesAdded ( - // OBJPATH object_path, - // DICT> interfaces_and_properties); - VariantDictionary property_dict; - property_writer.Run(&property_dict); - std::map interfaces_and_properties{ - {interface_name, property_dict} - }; - signal_itf_added_.lock()->Send(path, interfaces_and_properties); - registered_objects_[path][interface_name] = property_writer; -} - -void ExportedObjectManager::ReleaseInterface( - const dbus::ObjectPath& path, - const std::string& interface_name) { - bus_->AssertOnOriginThread(); - auto interfaces_for_path_itr = registered_objects_.find(path); - CHECK(interfaces_for_path_itr != registered_objects_.end()) - << "Attempting to signal interface removal for path " << path.value() - << " which was never registered."; - auto& interfaces_for_path = interfaces_for_path_itr->second; - auto property_for_interface_itr = interfaces_for_path.find(interface_name); - CHECK(property_for_interface_itr != interfaces_for_path.end()) - << "Attempted to remove interface " << interface_name << " from " - << path.value() << ", but this interface was never registered."; - interfaces_for_path.erase(interface_name); - if (interfaces_for_path.empty()) - registered_objects_.erase(path); - - // We're sending signals that look like: - // org.freedesktop.DBus.ObjectManager.InterfacesRemoved ( - // OBJPATH object_path, ARRAY interfaces); - signal_itf_removed_.lock()->Send(path, - std::vector{interface_name}); -} - -ExportedObjectManager::ObjectMap -ExportedObjectManager::HandleGetManagedObjects() { - // Implements the GetManagedObjects method: - // - // org.freedesktop.DBus.ObjectManager.GetManagedObjects ( - // out DICT>> ) - bus_->AssertOnOriginThread(); - ExportedObjectManager::ObjectMap objects; - for (const auto path_pair : registered_objects_) { - std::map& interfaces = - objects[path_pair.first]; - const InterfaceProperties& interface2properties = path_pair.second; - for (const auto interface : interface2properties) { - interface.second.Run(&interfaces[interface.first]); - } - } - return objects; -} - -} // namespace dbus_utils - -} // namespace chromeos diff --git a/chromeos/dbus/exported_object_manager.h b/chromeos/dbus/exported_object_manager.h deleted file mode 100644 index 183e676..0000000 --- a/chromeos/dbus/exported_object_manager.h +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_EXPORTED_OBJECT_MANAGER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_EXPORTED_OBJECT_MANAGER_H_ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -namespace dbus_utils { - -// ExportedObjectManager is a delegate that implements the -// org.freedesktop.DBus.ObjectManager interface on behalf of another -// object. It handles sending signals when new interfaces are added. -// -// This class is very similar to the ExportedPropertySet class, except that -// it allows objects to expose an object manager interface rather than the -// properties interface. -// -// Example usage: -// -// class ExampleObjectManager { -// public: -// ExampleObjectManager(dbus::Bus* bus) -// : object_manager_(bus, "/my/objects/path") { } -// -// void RegisterAsync(const CompletionAction& cb) { -// object_manager_.RegisterAsync(cb); -// } -// void ClaimInterface(const dbus::ObjectPath& path, -// const std::string& interface_name, -// const ExportedPropertySet::PropertyWriter& writer) { -// object_manager_->ClaimInterface(...); -// } -// void ReleaseInterface(const dbus::ObjectPath& path, -// const std::string& interface_name) { -// object_manager_->ReleaseInterface(...); -// } -// -// private: -// ExportedObjectManager object_manager_; -// }; -// -// class MyObjectClaimingAnInterface { -// public: -// MyObjectClaimingAnInterface(ExampleObjectManager* object_manager) -// : object_manager_(object_manager) {} -// -// void OnInitFinish(bool success) { -// if (!success) { /* handle that */ } -// object_manager_->ClaimInterface( -// my_path_, my_interface_, my_properties_.GetWriter()); -// } -// -// private: -// struct Properties : public ExportedPropertySet { -// public: -// /* Lots of interesting properties. */ -// }; -// -// Properties my_properties_; -// ExampleObjectManager* object_manager_; -// }; -class CHROMEOS_EXPORT ExportedObjectManager - : public base::SupportsWeakPtr { - public: - using ObjectMap = - std::map>; - using InterfaceProperties = - std::map; - - ExportedObjectManager(scoped_refptr bus, - const dbus::ObjectPath& path); - virtual ~ExportedObjectManager() = default; - - // Registers methods implementing the ObjectManager interface on the object - // exported on the path given in the constructor. Must be called on the - // origin thread. - virtual void RegisterAsync( - const chromeos::dbus_utils::AsyncEventSequencer::CompletionAction& - completion_callback); - - // Trigger a signal that |path| has added an interface |interface_name| - // with properties as given by |writer|. - virtual void ClaimInterface( - const dbus::ObjectPath& path, - const std::string& interface_name, - const ExportedPropertySet::PropertyWriter& writer); - - // Trigger a signal that |path| has removed an interface |interface_name|. - virtual void ReleaseInterface(const dbus::ObjectPath& path, - const std::string& interface_name); - - const scoped_refptr& GetBus() const { return bus_; } - - private: - CHROMEOS_PRIVATE ObjectMap HandleGetManagedObjects(); - - scoped_refptr bus_; - chromeos::dbus_utils::DBusObject dbus_object_; - // Tracks all objects currently known to the ExportedObjectManager. - std::map registered_objects_; - - using SignalInterfacesAdded = - DBusSignal>; - using SignalInterfacesRemoved = - DBusSignal>; - - std::weak_ptr signal_itf_added_; - std::weak_ptr signal_itf_removed_; - - friend class ExportedObjectManagerTest; - DISALLOW_COPY_AND_ASSIGN(ExportedObjectManager); -}; - -} // namespace dbus_utils - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_EXPORTED_OBJECT_MANAGER_H_ diff --git a/chromeos/dbus/exported_object_manager_unittest.cc b/chromeos/dbus/exported_object_manager_unittest.cc deleted file mode 100644 index 3df1466..0000000 --- a/chromeos/dbus/exported_object_manager_unittest.cc +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using ::testing::AnyNumber; -using ::testing::InSequence; -using ::testing::Invoke; -using ::testing::Return; -using ::testing::_; - -namespace chromeos { - -namespace dbus_utils { - -namespace { - -const dbus::ObjectPath kTestPath(std::string("/test/om_path")); -const dbus::ObjectPath kClaimedTestPath(std::string("/test/claimed_path")); -const std::string kClaimedInterface("claimed.interface"); -const std::string kTestPropertyName("PropertyName"); -const std::string kTestPropertyValue("PropertyValue"); - -void WriteTestPropertyDict(VariantDictionary* dict) { - dict->insert(std::make_pair(kTestPropertyName, Any(kTestPropertyValue))); -} - -void ReadTestPropertyDict(dbus::MessageReader* reader) { - dbus::MessageReader all_properties(nullptr); - dbus::MessageReader each_property(nullptr); - ASSERT_TRUE(reader->PopArray(&all_properties)); - ASSERT_TRUE(all_properties.PopDictEntry(&each_property)); - std::string property_name; - std::string property_value; - ASSERT_TRUE(each_property.PopString(&property_name)); - ASSERT_TRUE(each_property.PopVariantOfString(&property_value)); - EXPECT_FALSE(each_property.HasMoreData()); - EXPECT_FALSE(all_properties.HasMoreData()); - EXPECT_EQ(property_name, kTestPropertyName); - EXPECT_EQ(property_value, kTestPropertyValue); -} - -void VerifyInterfaceClaimSignal(dbus::Signal* signal) { - EXPECT_EQ(signal->GetInterface(), std::string(dbus::kObjectManagerInterface)); - EXPECT_EQ(signal->GetMember(), - std::string(dbus::kObjectManagerInterfacesAdded)); - // org.freedesktop.DBus.ObjectManager.InterfacesAdded ( - // OBJPATH object_path, - // DICT> interfaces_and_properties); - dbus::MessageReader reader(signal); - dbus::MessageReader all_interfaces(nullptr); - dbus::MessageReader each_interface(nullptr); - dbus::ObjectPath path; - ASSERT_TRUE(reader.PopObjectPath(&path)); - ASSERT_TRUE(reader.PopArray(&all_interfaces)); - ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface)); - std::string interface_name; - ASSERT_TRUE(each_interface.PopString(&interface_name)); - ReadTestPropertyDict(&each_interface); - EXPECT_FALSE(each_interface.HasMoreData()); - EXPECT_FALSE(all_interfaces.HasMoreData()); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(interface_name, kClaimedInterface); - EXPECT_EQ(path, kClaimedTestPath); -} - -void VerifyInterfaceDropSignal(dbus::Signal* signal) { - EXPECT_EQ(signal->GetInterface(), std::string(dbus::kObjectManagerInterface)); - EXPECT_EQ(signal->GetMember(), - std::string(dbus::kObjectManagerInterfacesRemoved)); - // org.freedesktop.DBus.ObjectManager.InterfacesRemoved ( - // OBJPATH object_path, ARRAY interfaces); - dbus::MessageReader reader(signal); - dbus::MessageReader each_interface(nullptr); - dbus::ObjectPath path; - ASSERT_TRUE(reader.PopObjectPath(&path)); - ASSERT_TRUE(reader.PopArray(&each_interface)); - std::string interface_name; - ASSERT_TRUE(each_interface.PopString(&interface_name)); - EXPECT_FALSE(each_interface.HasMoreData()); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(interface_name, kClaimedInterface); - EXPECT_EQ(path, kClaimedTestPath); -} - -} // namespace - -class ExportedObjectManagerTest : public ::testing::Test { - public: - void SetUp() override { - dbus::Bus::Options options; - options.bus_type = dbus::Bus::SYSTEM; - bus_ = new dbus::MockBus(options); - // By default, don't worry about threading assertions. - EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); - EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); - // Use a mock exported object. - mock_exported_object_ = new dbus::MockExportedObject(bus_.get(), kTestPath); - EXPECT_CALL(*bus_, GetExportedObject(kTestPath)).Times(1).WillOnce( - Return(mock_exported_object_.get())); - EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _)) - .Times(AnyNumber()); - om_.reset(new ExportedObjectManager(bus_.get(), kTestPath)); - property_writer_ = base::Bind(&WriteTestPropertyDict); - om_->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction()); - } - - void TearDown() override { - EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1); - om_.reset(); - bus_ = nullptr; - } - - std::unique_ptr CallHandleGetManagedObjects() { - dbus::MethodCall method_call(dbus::kObjectManagerInterface, - dbus::kObjectManagerGetManagedObjects); - method_call.SetSerial(1234); - return chromeos::dbus_utils::testing::CallMethod(om_->dbus_object_, - &method_call); - } - - scoped_refptr bus_; - scoped_refptr mock_exported_object_; - std::unique_ptr om_; - ExportedPropertySet::PropertyWriter property_writer_; -}; - -TEST_F(ExportedObjectManagerTest, ClaimInterfaceSendsSignals) { - EXPECT_CALL(*mock_exported_object_, SendSignal(_)) - .Times(1).WillOnce(Invoke(&VerifyInterfaceClaimSignal)); - om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); -} - -TEST_F(ExportedObjectManagerTest, ReleaseInterfaceSendsSignals) { - InSequence dummy; - EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); - EXPECT_CALL(*mock_exported_object_, SendSignal(_)) - .Times(1).WillOnce(Invoke(&VerifyInterfaceDropSignal)); - om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); - om_->ReleaseInterface(kClaimedTestPath, kClaimedInterface); -} - -TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseEmptyCorrectness) { - auto response = CallHandleGetManagedObjects(); - dbus::MessageReader reader(response.get()); - dbus::MessageReader all_paths(nullptr); - ASSERT_TRUE(reader.PopArray(&all_paths)); - EXPECT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseCorrectness) { - // org.freedesktop.DBus.ObjectManager.GetManagedObjects ( - // out DICT>> ) - EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); - om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); - auto response = CallHandleGetManagedObjects(); - dbus::MessageReader reader(response.get()); - dbus::MessageReader all_paths(nullptr); - dbus::MessageReader each_path(nullptr); - dbus::MessageReader all_interfaces(nullptr); - dbus::MessageReader each_interface(nullptr); - ASSERT_TRUE(reader.PopArray(&all_paths)); - ASSERT_TRUE(all_paths.PopDictEntry(&each_path)); - dbus::ObjectPath path; - ASSERT_TRUE(each_path.PopObjectPath(&path)); - ASSERT_TRUE(each_path.PopArray(&all_interfaces)); - ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface)); - std::string interface_name; - ASSERT_TRUE(each_interface.PopString(&interface_name)); - ReadTestPropertyDict(&each_interface); - EXPECT_FALSE(each_interface.HasMoreData()); - EXPECT_FALSE(all_interfaces.HasMoreData()); - EXPECT_FALSE(each_path.HasMoreData()); - EXPECT_FALSE(all_paths.HasMoreData()); - EXPECT_FALSE(reader.HasMoreData()); - EXPECT_EQ(path, kClaimedTestPath); - EXPECT_EQ(interface_name, kClaimedInterface); -} - -} // namespace dbus_utils - -} // namespace chromeos diff --git a/chromeos/dbus/exported_property_set.cc b/chromeos/dbus/exported_property_set.cc deleted file mode 100644 index 0f7d7cf..0000000 --- a/chromeos/dbus/exported_property_set.cc +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include // For kPropertyInterface - -#include -#include -#include - -using chromeos::dbus_utils::AsyncEventSequencer; - -namespace chromeos { - -namespace dbus_utils { - -ExportedPropertySet::ExportedPropertySet(dbus::Bus* bus) - : bus_(bus), weak_ptr_factory_(this) { -} - -void ExportedPropertySet::OnPropertiesInterfaceExported( - DBusInterface* prop_interface) { - signal_properties_changed_ = - prop_interface->RegisterSignalOfType( - dbus::kPropertiesChanged); -} - -ExportedPropertySet::PropertyWriter ExportedPropertySet::GetPropertyWriter( - const std::string& interface_name) { - return base::Bind(&ExportedPropertySet::WritePropertiesToDict, - weak_ptr_factory_.GetWeakPtr(), - interface_name); -} - -void ExportedPropertySet::RegisterProperty( - const std::string& interface_name, - const std::string& property_name, - ExportedPropertyBase* exported_property) { - bus_->AssertOnOriginThread(); - auto& prop_map = properties_[interface_name]; - auto res = prop_map.insert(std::make_pair(property_name, exported_property)); - CHECK(res.second) << "Property '" << property_name << "' already exists"; - // Technically, the property set exists longer than the properties themselves, - // so we could use Unretained here rather than a weak pointer. - ExportedPropertyBase::OnUpdateCallback cb = - base::Bind(&ExportedPropertySet::HandlePropertyUpdated, - weak_ptr_factory_.GetWeakPtr(), - interface_name, - property_name); - exported_property->SetUpdateCallback(cb); -} - -VariantDictionary ExportedPropertySet::HandleGetAll( - const std::string& interface_name) { - bus_->AssertOnOriginThread(); - return GetInterfaceProperties(interface_name); -} - -VariantDictionary ExportedPropertySet::GetInterfaceProperties( - const std::string& interface_name) const { - VariantDictionary properties; - auto property_map_itr = properties_.find(interface_name); - if (property_map_itr != properties_.end()) { - for (const auto& kv : property_map_itr->second) - properties.insert(std::make_pair(kv.first, kv.second->GetValue())); - } - return properties; -} - -void ExportedPropertySet::WritePropertiesToDict( - const std::string& interface_name, - VariantDictionary* dict) { - *dict = GetInterfaceProperties(interface_name); -} - -bool ExportedPropertySet::HandleGet(chromeos::ErrorPtr* error, - const std::string& interface_name, - const std::string& property_name, - chromeos::Any* result) { - bus_->AssertOnOriginThread(); - auto property_map_itr = properties_.find(interface_name); - if (property_map_itr == properties_.end()) { - chromeos::Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_UNKNOWN_INTERFACE, - "No such interface on object."); - return false; - } - LOG(INFO) << "Looking for " << property_name << " on " << interface_name; - auto property_itr = property_map_itr->second.find(property_name); - if (property_itr == property_map_itr->second.end()) { - chromeos::Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_UNKNOWN_PROPERTY, - "No such property on interface."); - return false; - } - *result = property_itr->second->GetValue(); - return true; -} - -bool ExportedPropertySet::HandleSet(chromeos::ErrorPtr* error, - const std::string& interface_name, - const std::string& property_name, - const chromeos::Any& value) { - bus_->AssertOnOriginThread(); - auto property_map_itr = properties_.find(interface_name); - if (property_map_itr == properties_.end()) { - chromeos::Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_UNKNOWN_INTERFACE, - "No such interface on object."); - return false; - } - LOG(INFO) << "Looking for " << property_name << " on " << interface_name; - auto property_itr = property_map_itr->second.find(property_name); - if (property_itr == property_map_itr->second.end()) { - chromeos::Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_UNKNOWN_PROPERTY, - "No such property on interface."); - return false; - } - - return property_itr->second->SetValue(error, value); -} - -void ExportedPropertySet::HandlePropertyUpdated( - const std::string& interface_name, - const std::string& property_name, - const ExportedPropertyBase* exported_property) { - bus_->AssertOnOriginThread(); - // Send signal only if the object has been exported successfully. - // This could happen when a property value is changed (which triggers - // the notification) before D-Bus interface is completely exported/claimed. - auto signal = signal_properties_changed_.lock(); - if (!signal) - return; - VariantDictionary changed_properties{ - {property_name, exported_property->GetValue()}}; - // The interface specification tells us to include this list of properties - // which have changed, but for whom no value is conveyed. Currently, we - // don't do anything interesting here. - std::vector invalidated_properties; // empty. - signal->Send(interface_name, changed_properties, invalidated_properties); -} - -void ExportedPropertyBase::NotifyPropertyChanged() { - // These is a brief period after the construction of an ExportedProperty - // when this callback is not initialized because the property has not - // been registered with the parent ExportedPropertySet. During this period - // users should be initializing values via SetValue, and no notifications - // should be triggered by the ExportedPropertySet. - if (!on_update_callback_.is_null()) { - on_update_callback_.Run(this); - } -} - -void ExportedPropertyBase::SetUpdateCallback(const OnUpdateCallback& cb) { - on_update_callback_ = cb; -} - -void ExportedPropertyBase::SetAccessMode( - ExportedPropertyBase::Access access_mode) { - access_mode_ = access_mode; -} - -ExportedPropertyBase::Access ExportedPropertyBase::GetAccessMode() const { - return access_mode_; -} - -} // namespace dbus_utils - -} // namespace chromeos diff --git a/chromeos/dbus/exported_property_set.h b/chromeos/dbus/exported_property_set.h deleted file mode 100644 index b7069ba..0000000 --- a/chromeos/dbus/exported_property_set.h +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_EXPORTED_PROPERTY_SET_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_EXPORTED_PROPERTY_SET_H_ - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -namespace dbus_utils { - -// This class may be used to implement the org.freedesktop.DBus.Properties -// interface. It sends the update signal on property updates: -// -// org.freedesktop.DBus.Properties.PropertiesChanged ( -// STRING interface_name, -// DICT changed_properties, -// ARRAY invalidated_properties); -// -// -// and implements the required methods of the interface: -// -// org.freedesktop.DBus.Properties.Get(in STRING interface_name, -// in STRING property_name, -// out VARIANT value); -// org.freedesktop.DBus.Properties.Set(in STRING interface_name, -// in STRING property_name, -// in VARIANT value); -// org.freedesktop.DBus.Properties.GetAll(in STRING interface_name, -// out DICT props); -// -// This class is very similar to the PropertySet class in Chrome, except that -// it allows objects to expose properties rather than to consume them. -// It is used as part of DBusObject to implement D-Bus object properties on -// registered interfaces. See description of DBusObject class for more details. - -class DBusInterface; -class DBusObject; - -class CHROMEOS_EXPORT ExportedPropertyBase { - public: - enum class Access { - kReadOnly, - kWriteOnly, - kReadWrite, - }; - - ExportedPropertyBase() = default; - virtual ~ExportedPropertyBase() = default; - - using OnUpdateCallback = base::Callback; - - // Called by ExportedPropertySet to register a callback. This callback - // triggers ExportedPropertySet to send a signal from the properties - // interface of the exported object. - virtual void SetUpdateCallback(const OnUpdateCallback& cb); - - // Returns the contained value as Any. - virtual chromeos::Any GetValue() const = 0; - - virtual bool SetValue(chromeos::ErrorPtr* error, - const chromeos::Any& value) = 0; - - void SetAccessMode(Access access_mode); - Access GetAccessMode() const; - - protected: - // Notify the listeners of OnUpdateCallback that the property has changed. - void NotifyPropertyChanged(); - - private: - OnUpdateCallback on_update_callback_; - // Default to read-only. - Access access_mode_{Access::kReadOnly}; -}; - -class CHROMEOS_EXPORT ExportedPropertySet { - public: - using PropertyWriter = base::Callback; - - explicit ExportedPropertySet(dbus::Bus* bus); - virtual ~ExportedPropertySet() = default; - - // Called to notify ExportedPropertySet that the Properties interface of the - // D-Bus object has been exported successfully and property notification - // signals can be sent out. - void OnPropertiesInterfaceExported(DBusInterface* prop_interface); - - // Return a callback that knows how to write this property set's properties - // to a message. This writer retains a weak pointer to this, and must - // only be invoked on the same thread as the rest of ExportedPropertySet. - PropertyWriter GetPropertyWriter(const std::string& interface_name); - - void RegisterProperty(const std::string& interface_name, - const std::string& property_name, - ExportedPropertyBase* exported_property); - - // D-Bus methods for org.freedesktop.DBus.Properties interface. - VariantDictionary HandleGetAll(const std::string& interface_name); - bool HandleGet(chromeos::ErrorPtr* error, - const std::string& interface_name, - const std::string& property_name, - chromeos::Any* result); - // While Properties.Set has a handler to complete the interface, we don't - // support writable properties. This is almost a feature, since bindings for - // many languages don't support errors coming back from invalid writes. - // Instead, use setters in exposed interfaces. - bool HandleSet(chromeos::ErrorPtr* error, - const std::string& interface_name, - const std::string& property_name, - const chromeos::Any& value); - // Returns a string-to-variant map of all the properties for the given - // interface and their values. - VariantDictionary GetInterfaceProperties( - const std::string& interface_name) const; - - private: - // Used to write the dictionary of string->variant to a message. - // This dictionary represents the property name/value pairs for the - // given interface. - CHROMEOS_PRIVATE void WritePropertiesToDict(const std::string& interface_name, - VariantDictionary* dict); - CHROMEOS_PRIVATE void HandlePropertyUpdated( - const std::string& interface_name, - const std::string& property_name, - const ExportedPropertyBase* exported_property); - - dbus::Bus* bus_; // weak; owned by outer DBusObject containing this object. - // This is a map from interface name -> property name -> pointer to property. - std::map> - properties_; - - // D-Bus callbacks may last longer the property set exporting those methods. - base::WeakPtrFactory weak_ptr_factory_; - - using SignalPropertiesChanged = - DBusSignal>; - - std::weak_ptr signal_properties_changed_; - - friend class DBusObject; - friend class ExportedPropertySetTest; - DISALLOW_COPY_AND_ASSIGN(ExportedPropertySet); -}; - -template -class ExportedProperty : public ExportedPropertyBase { - public: - ExportedProperty() = default; - ~ExportedProperty() override = default; - - // Retrieves the current value. - const T& value() const { return value_; } - - // Set the value exposed to remote applications. This triggers notifications - // of changes over the Properties interface. - void SetValue(const T& new_value) { - if (value_ != new_value) { - value_ = new_value; - this->NotifyPropertyChanged(); - } - } - - // Set the validator for value checking when setting the property by remote - // application. - void SetValidator( - const base::Callback& validator) { - validator_ = validator; - } - - // Implementation provided by specialization. - chromeos::Any GetValue() const override { return value_; } - - bool SetValue(chromeos::ErrorPtr* error, - const chromeos::Any& value) override { - if (GetAccessMode() == ExportedPropertyBase::Access::kReadOnly) { - chromeos::Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_PROPERTY_READ_ONLY, - "Property is read-only."); - return false; - } - if (!value.IsTypeCompatible()) { - chromeos::Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_INVALID_ARGS, - "Argument type mismatched."); - return false; - } - if (value_ == value.Get()) { - // No change to the property value, nothing to be done. - return true; - } - if (!validator_.is_null() && !validator_.Run(error, value.Get())) { - return false; - } - value_ = value.Get(); - return true; - } - - private: - T value_{}; - base::Callback validator_; - - DISALLOW_COPY_AND_ASSIGN(ExportedProperty); -}; - -} // namespace dbus_utils - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_EXPORTED_PROPERTY_SET_H_ diff --git a/chromeos/dbus/exported_property_set_unittest.cc b/chromeos/dbus/exported_property_set_unittest.cc deleted file mode 100644 index ff38c37..0000000 --- a/chromeos/dbus/exported_property_set_unittest.cc +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using ::testing::AnyNumber; -using ::testing::Return; -using ::testing::Invoke; -using ::testing::_; -using ::testing::Unused; - -namespace chromeos { - -namespace dbus_utils { - -namespace { - -const char kBoolPropName[] = "BoolProp"; -const char kUint8PropName[] = "Uint8Prop"; -const char kInt16PropName[] = "Int16Prop"; -const char kUint16PropName[] = "Uint16Prop"; -const char kInt32PropName[] = "Int32Prop"; -const char kUint32PropName[] = "Uint32Prop"; -const char kInt64PropName[] = "Int64Prop"; -const char kUint64PropName[] = "Uint64Prop"; -const char kDoublePropName[] = "DoubleProp"; -const char kStringPropName[] = "StringProp"; -const char kPathPropName[] = "PathProp"; -const char kStringListPropName[] = "StringListProp"; -const char kPathListPropName[] = "PathListProp"; -const char kUint8ListPropName[] = "Uint8ListProp"; - -const char kTestInterface1[] = "org.chromium.TestInterface1"; -const char kTestInterface2[] = "org.chromium.TestInterface2"; -const char kTestInterface3[] = "org.chromium.TestInterface3"; - -const std::string kTestString("lies"); -const dbus::ObjectPath kMethodsExportedOnPath(std::string("/export")); -const dbus::ObjectPath kTestObjectPathInit(std::string("/path_init")); -const dbus::ObjectPath kTestObjectPathUpdate(std::string("/path_update")); - -} // namespace - -class ExportedPropertySetTest : public ::testing::Test { - public: - struct Properties { - public: - ExportedProperty bool_prop_; - ExportedProperty uint8_prop_; - ExportedProperty int16_prop_; - ExportedProperty uint16_prop_; - ExportedProperty int32_prop_; - ExportedProperty uint32_prop_; - ExportedProperty int64_prop_; - ExportedProperty uint64_prop_; - ExportedProperty double_prop_; - ExportedProperty string_prop_; - ExportedProperty path_prop_; - ExportedProperty> stringlist_prop_; - ExportedProperty> pathlist_prop_; - ExportedProperty> uint8list_prop_; - - Properties(scoped_refptr bus, const dbus::ObjectPath& path) - : dbus_object_(nullptr, bus, path) { - // The empty string is not a valid value for an ObjectPath. - path_prop_.SetValue(kTestObjectPathInit); - DBusInterface* itf1 = dbus_object_.AddOrGetInterface(kTestInterface1); - itf1->AddProperty(kBoolPropName, &bool_prop_); - itf1->AddProperty(kUint8PropName, &uint8_prop_); - itf1->AddProperty(kInt16PropName, &int16_prop_); - // I chose this weird grouping because N=2 is about all the permutations - // of GetAll that I want to anticipate. - DBusInterface* itf2 = dbus_object_.AddOrGetInterface(kTestInterface2); - itf2->AddProperty(kUint16PropName, &uint16_prop_); - itf2->AddProperty(kInt32PropName, &int32_prop_); - DBusInterface* itf3 = dbus_object_.AddOrGetInterface(kTestInterface3); - itf3->AddProperty(kUint32PropName, &uint32_prop_); - itf3->AddProperty(kInt64PropName, &int64_prop_); - itf3->AddProperty(kUint64PropName, &uint64_prop_); - itf3->AddProperty(kDoublePropName, &double_prop_); - itf3->AddProperty(kStringPropName, &string_prop_); - itf3->AddProperty(kPathPropName, &path_prop_); - itf3->AddProperty(kStringListPropName, &stringlist_prop_); - itf3->AddProperty(kPathListPropName, &pathlist_prop_); - itf3->AddProperty(kUint8ListPropName, &uint8list_prop_); - dbus_object_.RegisterAsync( - AsyncEventSequencer::GetDefaultCompletionAction()); - } - virtual ~Properties() {} - - DBusObject dbus_object_; - }; - - void SetUp() override { - dbus::Bus::Options options; - options.bus_type = dbus::Bus::SYSTEM; - bus_ = new dbus::MockBus(options); - // By default, don't worry about threading assertions. - EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); - EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); - // Use a mock exported object. - mock_exported_object_ = - new dbus::MockExportedObject(bus_.get(), kMethodsExportedOnPath); - EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath)) - .Times(1).WillOnce(Return(mock_exported_object_.get())); - - EXPECT_CALL(*mock_exported_object_, - ExportMethod(dbus::kPropertiesInterface, _, _, _)).Times(3); - p_.reset(new Properties(bus_, kMethodsExportedOnPath)); - } - - void TearDown() override { - EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1); - } - - void AssertMethodReturnsError(dbus::MethodCall* method_call) { - method_call->SetSerial(123); - auto response = testing::CallMethod(p_->dbus_object_, method_call); - ASSERT_NE(dynamic_cast(response.get()), nullptr); - } - - std::unique_ptr GetPropertyOnInterface( - const std::string& interface_name, - const std::string& property_name) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGet); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString(interface_name); - writer.AppendString(property_name); - return testing::CallMethod(p_->dbus_object_, &method_call); - } - - std::unique_ptr SetPropertyOnInterface( - const std::string& interface_name, - const std::string& property_name, - const chromeos::Any& value) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesSet); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString(interface_name); - writer.AppendString(property_name); - dbus_utils::AppendValueToWriter(&writer, value); - return testing::CallMethod(p_->dbus_object_, &method_call); - } - - std::unique_ptr last_response_; - scoped_refptr bus_; - scoped_refptr mock_exported_object_; - std::unique_ptr p_; -}; - -template -class PropertyValidatorObserver { - public: - PropertyValidatorObserver() - : validate_property_callback_( - base::Bind(&PropertyValidatorObserver::ValidateProperty, - base::Unretained(this))) {} - virtual ~PropertyValidatorObserver() {} - - MOCK_METHOD2_T(ValidateProperty, - bool(chromeos::ErrorPtr* error, const T& value)); - - const base::Callback& - validate_property_callback() const { - return validate_property_callback_; - } - - private: - base::Callback - validate_property_callback_; - - DISALLOW_COPY_AND_ASSIGN(PropertyValidatorObserver); -}; - -TEST_F(ExportedPropertySetTest, UpdateNotifications) { - EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(14); - p_->bool_prop_.SetValue(true); - p_->uint8_prop_.SetValue(1); - p_->int16_prop_.SetValue(1); - p_->uint16_prop_.SetValue(1); - p_->int32_prop_.SetValue(1); - p_->uint32_prop_.SetValue(1); - p_->int64_prop_.SetValue(1); - p_->uint64_prop_.SetValue(1); - p_->double_prop_.SetValue(1.0); - p_->string_prop_.SetValue(kTestString); - p_->path_prop_.SetValue(kTestObjectPathUpdate); - p_->stringlist_prop_.SetValue({kTestString}); - p_->pathlist_prop_.SetValue({kTestObjectPathUpdate}); - p_->uint8list_prop_.SetValue({1}); -} - -TEST_F(ExportedPropertySetTest, UpdateToSameValue) { - EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); - p_->bool_prop_.SetValue(true); - p_->bool_prop_.SetValue(true); -} - -TEST_F(ExportedPropertySetTest, GetAllNoArgs) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGetAll); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetAllInvalidInterface) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGetAll); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString("org.chromium.BadInterface"); - auto response = testing::CallMethod(p_->dbus_object_, &method_call); - dbus::MessageReader response_reader(response.get()); - dbus::MessageReader dict_reader(nullptr); - ASSERT_TRUE(response_reader.PopArray(&dict_reader)); - // The response should just be a an empty array, since there are no properties - // on this interface. The spec doesn't say much about error conditions here, - // so I'm going to assume this is a valid implementation. - ASSERT_FALSE(dict_reader.HasMoreData()); - ASSERT_FALSE(response_reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetAllExtraArgs) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGetAll); - dbus::MessageWriter writer(&method_call); - writer.AppendString(kTestInterface1); - writer.AppendString(kTestInterface1); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetAllCorrectness) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGetAll); - method_call.SetSerial(123); - dbus::MessageWriter writer(&method_call); - writer.AppendString(kTestInterface2); - auto response = testing::CallMethod(p_->dbus_object_, &method_call); - dbus::MessageReader response_reader(response.get()); - dbus::MessageReader dict_reader(nullptr); - dbus::MessageReader entry_reader(nullptr); - ASSERT_TRUE(response_reader.PopArray(&dict_reader)); - ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader)); - std::string property_name; - ASSERT_TRUE(entry_reader.PopString(&property_name)); - uint16_t value16; - int32_t value32; - if (property_name.compare(kUint16PropName) == 0) { - ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16)); - ASSERT_FALSE(entry_reader.HasMoreData()); - ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader)); - ASSERT_TRUE(entry_reader.PopString(&property_name)); - ASSERT_EQ(property_name.compare(kInt32PropName), 0); - ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32)); - } else { - ASSERT_EQ(property_name.compare(kInt32PropName), 0); - ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32)); - ASSERT_FALSE(entry_reader.HasMoreData()); - ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader)); - ASSERT_TRUE(entry_reader.PopString(&property_name)); - ASSERT_EQ(property_name.compare(kUint16PropName), 0); - ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16)); - } - ASSERT_FALSE(entry_reader.HasMoreData()); - ASSERT_FALSE(dict_reader.HasMoreData()); - ASSERT_FALSE(response_reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetNoArgs) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGet); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetInvalidInterface) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGet); - dbus::MessageWriter writer(&method_call); - writer.AppendString("org.chromium.BadInterface"); - writer.AppendString(kInt16PropName); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetBadPropertyName) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGet); - dbus::MessageWriter writer(&method_call); - writer.AppendString(kTestInterface1); - writer.AppendString("IAmNotAProperty"); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetPropIfMismatch) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGet); - dbus::MessageWriter writer(&method_call); - writer.AppendString(kTestInterface1); - writer.AppendString(kStringPropName); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetNoPropertyName) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGet); - dbus::MessageWriter writer(&method_call); - writer.AppendString(kTestInterface1); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetExtraArgs) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGet); - dbus::MessageWriter writer(&method_call); - writer.AppendString(kTestInterface1); - writer.AppendString(kBoolPropName); - writer.AppendString("Extra param"); - AssertMethodReturnsError(&method_call); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithBool) { - auto response = GetPropertyOnInterface(kTestInterface1, kBoolPropName); - dbus::MessageReader reader(response.get()); - bool value; - ASSERT_TRUE(reader.PopVariantOfBool(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithUint8) { - auto response = GetPropertyOnInterface(kTestInterface1, kUint8PropName); - dbus::MessageReader reader(response.get()); - uint8_t value; - ASSERT_TRUE(reader.PopVariantOfByte(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithInt16) { - auto response = GetPropertyOnInterface(kTestInterface1, kInt16PropName); - dbus::MessageReader reader(response.get()); - int16_t value; - ASSERT_TRUE(reader.PopVariantOfInt16(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithUint16) { - auto response = GetPropertyOnInterface(kTestInterface2, kUint16PropName); - dbus::MessageReader reader(response.get()); - uint16_t value; - ASSERT_TRUE(reader.PopVariantOfUint16(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithInt32) { - auto response = GetPropertyOnInterface(kTestInterface2, kInt32PropName); - dbus::MessageReader reader(response.get()); - int32_t value; - ASSERT_TRUE(reader.PopVariantOfInt32(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithUint32) { - auto response = GetPropertyOnInterface(kTestInterface3, kUint32PropName); - dbus::MessageReader reader(response.get()); - uint32_t value; - ASSERT_TRUE(reader.PopVariantOfUint32(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithInt64) { - auto response = GetPropertyOnInterface(kTestInterface3, kInt64PropName); - dbus::MessageReader reader(response.get()); - int64_t value; - ASSERT_TRUE(reader.PopVariantOfInt64(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithUint64) { - auto response = GetPropertyOnInterface(kTestInterface3, kUint64PropName); - dbus::MessageReader reader(response.get()); - uint64_t value; - ASSERT_TRUE(reader.PopVariantOfUint64(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithDouble) { - auto response = GetPropertyOnInterface(kTestInterface3, kDoublePropName); - dbus::MessageReader reader(response.get()); - double value; - ASSERT_TRUE(reader.PopVariantOfDouble(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithString) { - auto response = GetPropertyOnInterface(kTestInterface3, kStringPropName); - dbus::MessageReader reader(response.get()); - std::string value; - ASSERT_TRUE(reader.PopVariantOfString(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithPath) { - auto response = GetPropertyOnInterface(kTestInterface3, kPathPropName); - dbus::MessageReader reader(response.get()); - dbus::ObjectPath value; - ASSERT_TRUE(reader.PopVariantOfObjectPath(&value)); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithStringList) { - auto response = GetPropertyOnInterface(kTestInterface3, kStringListPropName); - dbus::MessageReader reader(response.get()); - dbus::MessageReader variant_reader(nullptr); - std::vector value; - ASSERT_TRUE(reader.PopVariant(&variant_reader)); - ASSERT_TRUE(variant_reader.PopArrayOfStrings(&value)); - ASSERT_FALSE(variant_reader.HasMoreData()); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithPathList) { - auto response = GetPropertyOnInterface(kTestInterface3, kPathListPropName); - dbus::MessageReader reader(response.get()); - dbus::MessageReader variant_reader(nullptr); - std::vector value; - ASSERT_TRUE(reader.PopVariant(&variant_reader)); - ASSERT_TRUE(variant_reader.PopArrayOfObjectPaths(&value)); - ASSERT_FALSE(variant_reader.HasMoreData()); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, GetWorksWithUint8List) { - auto response = GetPropertyOnInterface(kTestInterface3, kPathListPropName); - dbus::MessageReader reader(response.get()); - dbus::MessageReader variant_reader(nullptr); - const uint8_t* buffer; - size_t buffer_len; - ASSERT_TRUE(reader.PopVariant(&variant_reader)); - // |buffer| remains under the control of the MessageReader. - ASSERT_TRUE(variant_reader.PopArrayOfBytes(&buffer, &buffer_len)); - ASSERT_FALSE(variant_reader.HasMoreData()); - ASSERT_FALSE(reader.HasMoreData()); -} - -TEST_F(ExportedPropertySetTest, SetInvalidInterface) { - auto response = SetPropertyOnInterface( - "BadInterfaceName", kStringPropName, chromeos::Any(kTestString)); - ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(DBUS_ERROR_UNKNOWN_INTERFACE, response->GetErrorName()); -} - -TEST_F(ExportedPropertySetTest, SetBadPropertyName) { - auto response = SetPropertyOnInterface( - kTestInterface3, "IAmNotAProperty", chromeos::Any(kTestString)); - ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(DBUS_ERROR_UNKNOWN_PROPERTY, response->GetErrorName()); -} - -TEST_F(ExportedPropertySetTest, SetFailsWithReadOnlyProperty) { - auto response = SetPropertyOnInterface( - kTestInterface3, kStringPropName, chromeos::Any(kTestString)); - ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(DBUS_ERROR_PROPERTY_READ_ONLY, response->GetErrorName()); -} - -TEST_F(ExportedPropertySetTest, SetFailsWithMismatchedValueType) { - p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); - auto response = SetPropertyOnInterface( - kTestInterface3, kStringPropName, chromeos::Any(true)); - ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName()); -} - -namespace { - -bool SetInvalidProperty(chromeos::ErrorPtr* error, Unused) { - chromeos::Error::AddTo(error, - FROM_HERE, - errors::dbus::kDomain, - DBUS_ERROR_INVALID_ARGS, - "Invalid value"); - return false; -} - -} // namespace - -TEST_F(ExportedPropertySetTest, SetFailsWithValidator) { - PropertyValidatorObserver property_validator; - p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); - p_->string_prop_.SetValidator( - property_validator.validate_property_callback()); - - chromeos::ErrorPtr error = chromeos::Error::Create( - FROM_HERE, errors::dbus::kDomain, DBUS_ERROR_INVALID_ARGS, ""); - EXPECT_CALL(property_validator, ValidateProperty(_, kTestString)) - .WillOnce(Invoke(SetInvalidProperty)); - auto response = SetPropertyOnInterface( - kTestInterface3, kStringPropName, chromeos::Any(kTestString)); - ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName()); -} - -TEST_F(ExportedPropertySetTest, SetWorksWithValidator) { - PropertyValidatorObserver property_validator; - p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); - p_->string_prop_.SetValidator( - property_validator.validate_property_callback()); - - EXPECT_CALL(property_validator, ValidateProperty(_, kTestString)) - .WillOnce(Return(true)); - auto response = SetPropertyOnInterface( - kTestInterface3, kStringPropName, chromeos::Any(kTestString)); - ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(kTestString, p_->string_prop_.value()); -} - -TEST_F(ExportedPropertySetTest, SetWorksWithSameValue) { - PropertyValidatorObserver property_validator; - p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); - p_->string_prop_.SetValidator( - property_validator.validate_property_callback()); - EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); - p_->string_prop_.SetValue(kTestString); - - // No need to validate the value if it is the same as the current one. - EXPECT_CALL(property_validator, ValidateProperty(_, _)).Times(0); - auto response = SetPropertyOnInterface( - kTestInterface3, kStringPropName, chromeos::Any(kTestString)); - ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(kTestString, p_->string_prop_.value()); -} - -TEST_F(ExportedPropertySetTest, SetWorksWithoutValidator) { - p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite); - auto response = SetPropertyOnInterface( - kTestInterface3, kStringPropName, chromeos::Any(kTestString)); - ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType()); - ASSERT_EQ(kTestString, p_->string_prop_.value()); -} - -namespace { - -void VerifySignal(dbus::Signal* signal) { - ASSERT_NE(signal, nullptr); - std::string interface_name; - std::string property_name; - uint8_t value; - dbus::MessageReader reader(signal); - dbus::MessageReader array_reader(signal); - dbus::MessageReader dict_reader(signal); - ASSERT_TRUE(reader.PopString(&interface_name)); - ASSERT_TRUE(reader.PopArray(&array_reader)); - ASSERT_TRUE(array_reader.PopDictEntry(&dict_reader)); - ASSERT_TRUE(dict_reader.PopString(&property_name)); - ASSERT_TRUE(dict_reader.PopVariantOfByte(&value)); - ASSERT_FALSE(dict_reader.HasMoreData()); - ASSERT_FALSE(array_reader.HasMoreData()); - ASSERT_TRUE(reader.HasMoreData()); - // Read the (empty) list of invalidated property names. - ASSERT_TRUE(reader.PopArray(&array_reader)); - ASSERT_FALSE(array_reader.HasMoreData()); - ASSERT_FALSE(reader.HasMoreData()); - ASSERT_EQ(value, 57); - ASSERT_EQ(property_name, std::string(kUint8PropName)); - ASSERT_EQ(interface_name, std::string(kTestInterface1)); -} - -} // namespace - -TEST_F(ExportedPropertySetTest, SignalsAreParsable) { - EXPECT_CALL(*mock_exported_object_, SendSignal(_)) - .Times(1).WillOnce(Invoke(&VerifySignal)); - p_->uint8_prop_.SetValue(57); -} - -} // namespace dbus_utils - -} // namespace chromeos diff --git a/chromeos/dbus/mock_dbus_object.h b/chromeos/dbus/mock_dbus_object.h deleted file mode 100644 index 2c30970..0000000 --- a/chromeos/dbus/mock_dbus_object.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_MOCK_DBUS_OBJECT_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_MOCK_DBUS_OBJECT_H_ - -#include - -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -class MockDBusObject : public DBusObject { - public: - MockDBusObject(ExportedObjectManager* object_manager, - const scoped_refptr& bus, - const dbus::ObjectPath& object_path) - : DBusObject(object_manager, bus, object_path) {} - ~MockDBusObject() override = default; - - MOCK_METHOD1(RegisterAsync, - void(const AsyncEventSequencer::CompletionAction&)); -}; // class MockDBusObject - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_MOCK_DBUS_OBJECT_H_ diff --git a/chromeos/dbus/mock_exported_object_manager.h b/chromeos/dbus/mock_exported_object_manager.h deleted file mode 100644 index 2b49dbd..0000000 --- a/chromeos/dbus/mock_exported_object_manager.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_ - -#include - -#include -#include -#include -#include - -namespace chromeos { - -namespace dbus_utils { - -class MockExportedObjectManager : public ExportedObjectManager { - public: - using CompletionAction = - chromeos::dbus_utils::AsyncEventSequencer::CompletionAction; - - using ExportedObjectManager::ExportedObjectManager; - ~MockExportedObjectManager() override = default; - - MOCK_METHOD1(RegisterAsync, - void(const CompletionAction& completion_callback)); - MOCK_METHOD3(ClaimInterface, - void(const dbus::ObjectPath& path, - const std::string& interface_name, - const ExportedPropertySet::PropertyWriter& writer)); - MOCK_METHOD2(ReleaseInterface, - void(const dbus::ObjectPath& path, - const std::string& interface_name)); -}; - -} // namespace dbus_utils - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_ diff --git a/chromeos/dbus/test.proto b/chromeos/dbus/test.proto deleted file mode 100644 index 84607a3..0000000 --- a/chromeos/dbus/test.proto +++ /dev/null @@ -1,8 +0,0 @@ -option optimize_for = LITE_RUNTIME; - -package dbus_utils_test; - -message TestMessage { - optional int32 foo = 1; - optional string bar = 2; -} diff --git a/chromeos/dbus/utils.cc b/chromeos/dbus/utils.cc deleted file mode 100644 index 3f58dd8..0000000 --- a/chromeos/dbus/utils.cc +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -std::unique_ptr CreateDBusErrorResponse( - dbus::MethodCall* method_call, - const std::string& error_name, - const std::string& error_message) { - auto resp = dbus::ErrorResponse::FromMethodCall( - method_call, error_name, error_message); - return std::unique_ptr(resp.release()); -} - -std::unique_ptr GetDBusError(dbus::MethodCall* method_call, - const chromeos::Error* error) { - CHECK(error) << "Error object must be specified"; - std::string error_name = DBUS_ERROR_FAILED; // Default error code. - std::string error_message; - - // Special case for "dbus" error domain. - // Pop the error code and message from the error chain and use them as the - // actual D-Bus error message. - if (error->GetDomain() == errors::dbus::kDomain) { - error_name = error->GetCode(); - error_message = error->GetMessage(); - error = error->GetInnerError(); - } - - // Append any inner errors to the error message. - while (error) { - // Format error string as "domain/code:message". - if (!error_message.empty()) - error_message += ';'; - error_message += - error->GetDomain() + '/' + error->GetCode() + ':' + error->GetMessage(); - error = error->GetInnerError(); - } - return CreateDBusErrorResponse(method_call, error_name, error_message); -} - -void AddDBusError(chromeos::ErrorPtr* error, - const std::string& dbus_error_name, - const std::string& dbus_error_message) { - std::vector parts = string_utils::Split(dbus_error_message, ";"); - std::vector> errors; - for (const std::string& part : parts) { - // Each part should be in format of "domain/code:message" - size_t slash_pos = part.find('/'); - size_t colon_pos = part.find(':'); - if (slash_pos != std::string::npos && colon_pos != std::string::npos && - slash_pos < colon_pos) { - // If we have both '/' and ':' and in proper order, then we have a - // correctly encoded error object. - std::string domain = part.substr(0, slash_pos); - std::string code = part.substr(slash_pos + 1, colon_pos - slash_pos - 1); - std::string message = part.substr(colon_pos + 1); - errors.emplace_back(domain, code, message); - } else if (slash_pos == std::string::npos && - colon_pos == std::string::npos && errors.empty()) { - // If we don't have both '/' and ':' and this is the first error object, - // then we had a D-Bus error at the top of the error chain. - errors.emplace_back(errors::dbus::kDomain, dbus_error_name, part); - } else { - // We have a malformed part. The whole D-Bus error was most likely - // not generated by GetDBusError(). To be safe, stop parsing it - // and return the error as received from D-Bus. - errors.clear(); // Remove any errors accumulated so far. - errors.emplace_back( - errors::dbus::kDomain, dbus_error_name, dbus_error_message); - break; - } - } - - // Go backwards and add the parsed errors to the error chain. - for (auto it = errors.crbegin(); it != errors.crend(); ++it) { - Error::AddTo( - error, FROM_HERE, std::get<0>(*it), std::get<1>(*it), std::get<2>(*it)); - } -} - -} // namespace dbus_utils -} // namespace chromeos diff --git a/chromeos/dbus/utils.h b/chromeos/dbus/utils.h deleted file mode 100644 index 52c33f7..0000000 --- a/chromeos/dbus/utils.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_DBUS_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_DBUS_UTILS_H_ - -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { -namespace dbus_utils { - -// A helper function to create a D-Bus error response object as unique_ptr<>. -CHROMEOS_EXPORT std::unique_ptr CreateDBusErrorResponse( - dbus::MethodCall* method_call, - const std::string& error_name, - const std::string& error_message); - -// Create a D-Bus error response object from chromeos::Error. If the last -// error in the error chain belongs to "dbus" error domain, its error code -// and message are directly translated to D-Bus error code and message. -// Any inner errors are formatted as "domain/code:message" string and appended -// to the D-Bus error message, delimited by semi-colons. -CHROMEOS_EXPORT std::unique_ptr GetDBusError( - dbus::MethodCall* method_call, - const chromeos::Error* error); - -// AddDBusError() is the opposite of GetDBusError(). It de-serializes the Error -// object received over D-Bus. -CHROMEOS_EXPORT void AddDBusError(chromeos::ErrorPtr* error, - const std::string& dbus_error_name, - const std::string& dbus_error_message); - -} // namespace dbus_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_DBUS_UTILS_H_ diff --git a/chromeos/errors/error.cc b/chromeos/errors/error.cc deleted file mode 100644 index b3fa711..0000000 --- a/chromeos/errors/error.cc +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -using chromeos::Error; -using chromeos::ErrorPtr; - -namespace { -inline void LogError(const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message) { - // Use logging::LogMessage() directly instead of LOG(ERROR) to substitute - // the current error location with the location passed in to the Error object. - // This way the log will contain the actual location of the error, and not - // as if it always comes from chromeos/errors/error.cc(22). - logging::LogMessage( - location.file_name(), location.line_number(), logging::LOG_ERROR).stream() - << location.function_name() << "(...): " - << "Domain=" << domain << ", Code=" << code << ", Message=" << message; -} -} // anonymous namespace - -ErrorPtr Error::Create(const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message) { - return Create(location, domain, code, message, ErrorPtr()); -} - -ErrorPtr Error::Create(const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message, - ErrorPtr inner_error) { - LogError(location, domain, code, message); - return ErrorPtr( - new Error(location, domain, code, message, std::move(inner_error))); -} - -void Error::AddTo(ErrorPtr* error, - const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message) { - if (error) { - *error = Create(location, domain, code, message, std::move(*error)); - } else { - // Create already logs the error, but if |error| is nullptr, - // we still want to log the error... - LogError(location, domain, code, message); - } -} - -void Error::AddToPrintf(ErrorPtr* error, - const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const char* format, - ...) { - va_list ap; - va_start(ap, format); - std::string message = base::StringPrintV(format, ap); - va_end(ap); - AddTo(error, location, domain, code, message); -} - -ErrorPtr Error::Clone() const { - ErrorPtr inner_error = inner_error_ ? inner_error_->Clone() : nullptr; - return ErrorPtr( - new Error(location_, domain_, code_, message_, std::move(inner_error))); -} - -bool Error::HasDomain(const std::string& domain) const { - return FindErrorOfDomain(this, domain) != nullptr; -} - -bool Error::HasError(const std::string& domain, const std::string& code) const { - return FindError(this, domain, code) != nullptr; -} - -const Error* Error::GetFirstError() const { - const Error* err = this; - while (err->GetInnerError()) - err = err->GetInnerError(); - return err; -} - -Error::Error(const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message, - ErrorPtr inner_error) - : Error{tracked_objects::LocationSnapshot{location}, - domain, - code, - message, - std::move(inner_error)} { -} - -Error::Error(const tracked_objects::LocationSnapshot& location, - const std::string& domain, - const std::string& code, - const std::string& message, - ErrorPtr inner_error) - : domain_(domain), - code_(code), - message_(message), - location_(location), - inner_error_(std::move(inner_error)) { -} - -const Error* Error::FindErrorOfDomain(const Error* error_chain_start, - const std::string& domain) { - while (error_chain_start) { - if (error_chain_start->GetDomain() == domain) - break; - error_chain_start = error_chain_start->GetInnerError(); - } - return error_chain_start; -} - -const Error* Error::FindError(const Error* error_chain_start, - const std::string& domain, - const std::string& code) { - while (error_chain_start) { - if (error_chain_start->GetDomain() == domain && - error_chain_start->GetCode() == code) - break; - error_chain_start = error_chain_start->GetInnerError(); - } - return error_chain_start; -} diff --git a/chromeos/errors/error.h b/chromeos/errors/error.h deleted file mode 100644 index eb8406c..0000000 --- a/chromeos/errors/error.h +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_ERRORS_ERROR_H_ -#define LIBCHROMEOS_CHROMEOS_ERRORS_ERROR_H_ - -#include -#include - -#include -#include -#include - -namespace chromeos { - -class Error; // Forward declaration. - -using ErrorPtr = std::unique_ptr; - -class CHROMEOS_EXPORT Error { - public: - virtual ~Error() = default; - - // Creates an instance of Error class. - static ErrorPtr Create(const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message); - static ErrorPtr Create(const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message, - ErrorPtr inner_error); - // If |error| is not nullptr, creates another instance of Error class, - // initializes it with specified arguments and adds it to the head of - // the error chain pointed to by |error|. - static void AddTo(ErrorPtr* error, - const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message); - // Same as the Error::AddTo above, but allows to pass in a printf-like - // format string and optional parameters to format the error message. - static void AddToPrintf(ErrorPtr* error, - const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const char* format, - ...) PRINTF_FORMAT(5, 6); - - // Clones error with all inner errors. - ErrorPtr Clone() const; - - // Returns the error domain, code and message - const std::string& GetDomain() const { return domain_; } - const std::string& GetCode() const { return code_; } - const std::string& GetMessage() const { return message_; } - - // Returns the location of the error in the source code. - const tracked_objects::LocationSnapshot& GetLocation() const { - return location_; - } - - // Checks if this or any of the inner errors in the chain has the specified - // error domain. - bool HasDomain(const std::string& domain) const; - - // Checks if this or any of the inner errors in the chain matches the - // specified error domain and code. - bool HasError(const std::string& domain, const std::string& code) const; - - // Gets a pointer to the inner error, if present. Returns nullptr otherwise. - const Error* GetInnerError() const { return inner_error_.get(); } - - // Gets a pointer to the first error occurred. - // Returns itself if no inner error are available. - const Error* GetFirstError() const; - - // Finds an error object of particular domain in the error chain stating at - // |error_chain_start|. Returns the a pointer to the first matching error - // object found. - // Returns nullptr if no match is found. - // This method is safe to call on a nullptr |error_chain_start| in which case - // the result will also be nullptr. - static const Error* FindErrorOfDomain(const Error* error_chain_start, - const std::string& domain); - // Finds an error of particular domain with the given code in the error chain - // stating at |error_chain_start|. Returns the pointer to the first matching - // error object. - // Returns nullptr if no match is found or if |error_chain_start| is nullptr. - static const Error* FindError(const Error* error_chain_start, - const std::string& domain, - const std::string& code); - - protected: - // Constructor is protected since this object is supposed to be - // created via the Create factory methods. - Error(const tracked_objects::Location& location, - const std::string& domain, - const std::string& code, - const std::string& message, - ErrorPtr inner_error); - - Error(const tracked_objects::LocationSnapshot& location, - const std::string& domain, - const std::string& code, - const std::string& message, - ErrorPtr inner_error); - - // Error domain. The domain defines the scopes for error codes. - // Two errors with the same code but different domains are different errors. - std::string domain_; - // Error code. A unique error code identifier within the given domain. - std::string code_; - // Human-readable error message. - std::string message_; - // Error origin in the source code. - tracked_objects::LocationSnapshot location_; - // Pointer to inner error, if any. This forms a chain of errors. - ErrorPtr inner_error_; - - private: - DISALLOW_COPY_AND_ASSIGN(Error); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_ERRORS_ERROR_H_ diff --git a/chromeos/errors/error_codes.cc b/chromeos/errors/error_codes.cc deleted file mode 100644 index 14af14b..0000000 --- a/chromeos/errors/error_codes.cc +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { -namespace errors { - -namespace dbus { -const char kDomain[] = "dbus"; -} // namespace dbus - -namespace json { -const char kDomain[] = "json_parser"; -const char kParseError[] = "json_parse_error"; -const char kObjectExpected[] = "json_object_expected"; -} // namespace json - -namespace http { -const char kDomain[] = "http"; -} // namespace http - -namespace system { -const char kDomain[] = "system"; - -namespace { -const struct ErrorMapEntry { - const char* error_code; - int errnum; -} error_map[] = { -#define ERROR_ENTRY(err) { #err, err } - ERROR_ENTRY(EPERM), // Operation not permitted - ERROR_ENTRY(ENOENT), // No such file or directory - ERROR_ENTRY(ESRCH), // No such process - ERROR_ENTRY(EINTR), // Interrupted system call - ERROR_ENTRY(EIO), // I/O error - ERROR_ENTRY(ENXIO), // No such device or address - ERROR_ENTRY(E2BIG), // Argument list too long - ERROR_ENTRY(ENOEXEC), // Exec format error - ERROR_ENTRY(EBADF), // Bad file number - ERROR_ENTRY(ECHILD), // No child processes - ERROR_ENTRY(EAGAIN), // Try again - ERROR_ENTRY(ENOMEM), // Out of memory - ERROR_ENTRY(EACCES), // Permission denied - ERROR_ENTRY(EFAULT), // Bad address - ERROR_ENTRY(ENOTBLK), // Block device required - ERROR_ENTRY(EBUSY), // Device or resource busy - ERROR_ENTRY(EEXIST), // File exists - ERROR_ENTRY(EXDEV), // Cross-device link - ERROR_ENTRY(ENODEV), // No such device - ERROR_ENTRY(ENOTDIR), // Not a directory - ERROR_ENTRY(EISDIR), // Is a directory - ERROR_ENTRY(EINVAL), // Invalid argument - ERROR_ENTRY(ENFILE), // File table overflow - ERROR_ENTRY(EMFILE), // Too many open files - ERROR_ENTRY(ENOTTY), // Not a typewriter - ERROR_ENTRY(ETXTBSY), // Text file busy - ERROR_ENTRY(EFBIG), // File too large - ERROR_ENTRY(ENOSPC), // No space left on device - ERROR_ENTRY(ESPIPE), // Illegal seek - ERROR_ENTRY(EROFS), // Read-only file system - ERROR_ENTRY(EMLINK), // Too many links - ERROR_ENTRY(EPIPE), // Broken pipe - ERROR_ENTRY(EDOM), // Math argument out of domain of func - ERROR_ENTRY(ERANGE), // Math result not representable - ERROR_ENTRY(EDEADLK), // Resource deadlock would occur - ERROR_ENTRY(ENAMETOOLONG), // File name too long - ERROR_ENTRY(ENOLCK), // No record locks available - ERROR_ENTRY(ENOSYS), // Function not implemented - ERROR_ENTRY(ENOTEMPTY), // Directory not empty - ERROR_ENTRY(ELOOP), // Too many symbolic links encountered - ERROR_ENTRY(ENOMSG), // No message of desired type - ERROR_ENTRY(EIDRM), // Identifier removed -#ifdef __linux__ - ERROR_ENTRY(ECHRNG), // Channel number out of range - ERROR_ENTRY(EL2NSYNC), // Level 2 not synchronized - ERROR_ENTRY(EL3HLT), // Level 3 halted - ERROR_ENTRY(EL3RST), // Level 3 reset - ERROR_ENTRY(ELNRNG), // Link number out of range - ERROR_ENTRY(EUNATCH), // Protocol driver not attached - ERROR_ENTRY(ENOCSI), // No CSI structure available - ERROR_ENTRY(EL2HLT), // Level 2 halted - ERROR_ENTRY(EBADE), // Invalid exchange - ERROR_ENTRY(EBADR), // Invalid request descriptor - ERROR_ENTRY(EXFULL), // Exchange full - ERROR_ENTRY(ENOANO), // No anode - ERROR_ENTRY(EBADRQC), // Invalid request code - ERROR_ENTRY(EBADSLT), // Invalid slot - ERROR_ENTRY(EBFONT), // Bad font file format -#endif // __linux__ - ERROR_ENTRY(ENOSTR), // Device not a stream - ERROR_ENTRY(ENODATA), // No data available - ERROR_ENTRY(ETIME), // Timer expired - ERROR_ENTRY(ENOSR), // Out of streams resources -#ifdef __linux__ - ERROR_ENTRY(ENONET), // Machine is not on the network - ERROR_ENTRY(ENOPKG), // Package not installed -#endif // __linux__ - ERROR_ENTRY(EREMOTE), // Object is remote - ERROR_ENTRY(ENOLINK), // Link has been severed -#ifdef __linux__ - ERROR_ENTRY(EADV), // Advertise error - ERROR_ENTRY(ESRMNT), // Srmount error - ERROR_ENTRY(ECOMM), // Communication error on send -#endif // __linux__ - ERROR_ENTRY(EPROTO), // Protocol error - ERROR_ENTRY(EMULTIHOP), // Multihop attempted -#ifdef __linux__ - ERROR_ENTRY(EDOTDOT), // RFS specific error -#endif // __linux__ - ERROR_ENTRY(EBADMSG), // Not a data message - ERROR_ENTRY(EOVERFLOW), // Value too large for defined data type -#ifdef __linux__ - ERROR_ENTRY(ENOTUNIQ), // Name not unique on network - ERROR_ENTRY(EBADFD), // File descriptor in bad state - ERROR_ENTRY(EREMCHG), // Remote address changed - ERROR_ENTRY(ELIBACC), // Can not access a needed shared library - ERROR_ENTRY(ELIBBAD), // Accessing a corrupted shared library - ERROR_ENTRY(ELIBSCN), // .lib section in a.out corrupted - ERROR_ENTRY(ELIBMAX), // Attempting to link in too many shared libs. - ERROR_ENTRY(ELIBEXEC), // Cannot exec a shared library directly -#endif // __linux__ - ERROR_ENTRY(EILSEQ), // Illegal byte sequence -#ifdef __linux__ - ERROR_ENTRY(ERESTART), // Interrupted system call should be restarted - ERROR_ENTRY(ESTRPIPE), // Streams pipe error -#endif // __linux__ - ERROR_ENTRY(EUSERS), // Too many users - ERROR_ENTRY(ENOTSOCK), // Socket operation on non-socket - ERROR_ENTRY(EDESTADDRREQ), // Destination address required - ERROR_ENTRY(EMSGSIZE), // Message too long - ERROR_ENTRY(EPROTOTYPE), // Protocol wrong type for socket - ERROR_ENTRY(ENOPROTOOPT), // Protocol not available - ERROR_ENTRY(EPROTONOSUPPORT), // Protocol not supported - ERROR_ENTRY(ESOCKTNOSUPPORT), // Socket type not supported - ERROR_ENTRY(EOPNOTSUPP), // Operation not supported o/transport endpoint - ERROR_ENTRY(EPFNOSUPPORT), // Protocol family not supported - ERROR_ENTRY(EAFNOSUPPORT), // Address family not supported by protocol - ERROR_ENTRY(EADDRINUSE), // Address already in use - ERROR_ENTRY(EADDRNOTAVAIL), // Cannot assign requested address - ERROR_ENTRY(ENETDOWN), // Network is down - ERROR_ENTRY(ENETUNREACH), // Network is unreachable - ERROR_ENTRY(ENETRESET), // Network dropped connection because of reset - ERROR_ENTRY(ECONNABORTED), // Software caused connection abort - ERROR_ENTRY(ECONNRESET), // Connection reset by peer - ERROR_ENTRY(ENOBUFS), // No buffer space available - ERROR_ENTRY(EISCONN), // Transport endpoint is already connected - ERROR_ENTRY(ENOTCONN), // Transport endpoint is not connected - ERROR_ENTRY(ESHUTDOWN), // Cannot send after transp. endpoint shutdown - ERROR_ENTRY(ETOOMANYREFS), // Too many references: cannot splice - ERROR_ENTRY(ETIMEDOUT), // Connection timed out - ERROR_ENTRY(ECONNREFUSED), // Connection refused - ERROR_ENTRY(EHOSTDOWN), // Host is down - ERROR_ENTRY(EHOSTUNREACH), // No route to host - ERROR_ENTRY(EALREADY), // Operation already in progress - ERROR_ENTRY(EINPROGRESS), // Operation now in progress - ERROR_ENTRY(ESTALE), // Stale file handle -#ifdef __linux__ - ERROR_ENTRY(EUCLEAN), // Structure needs cleaning - ERROR_ENTRY(ENOTNAM), // Not a XENIX named type file - ERROR_ENTRY(ENAVAIL), // No XENIX semaphores available - ERROR_ENTRY(EISNAM), // Is a named type file - ERROR_ENTRY(EREMOTEIO), // Remote I/O error -#endif // __linux__ - ERROR_ENTRY(EDQUOT), // Quota exceeded -#ifdef __linux__ - ERROR_ENTRY(ENOMEDIUM), // No medium found - ERROR_ENTRY(EMEDIUMTYPE), // Wrong medium type -#endif // __linux__ - ERROR_ENTRY(ECANCELED), // Operation Canceled -#ifdef __linux__ - ERROR_ENTRY(ENOKEY), // Required key not available - ERROR_ENTRY(EKEYEXPIRED), // Key has expired - ERROR_ENTRY(EKEYREVOKED), // Key has been revoked - ERROR_ENTRY(EKEYREJECTED), // Key was rejected by service -#endif // __linux__ - ERROR_ENTRY(EOWNERDEAD), // Owner died - ERROR_ENTRY(ENOTRECOVERABLE), // State not recoverable -#ifdef __linux__ - ERROR_ENTRY(ERFKILL), // Operation not possible due to RF-kill - ERROR_ENTRY(EHWPOISON), // Memory page has hardware error -#endif // __linux__ -#undef ERROR_ENTRY - // This list comes from system header. The elements are ordered - // by increasing errnum values which is the same order used in the header - // file. So, when new error codes are added to glibc, it should be relatively - // easy to identify them and add them to this list. -}; - -// Gets the error code string from system error code. If unknown system error -// number is provided, returns an empty string. -std::string ErrorCodeFromSystemError(int errnum) { - std::string error_code; - for (const ErrorMapEntry& entry : error_map) { - if (entry.errnum == errnum) { - error_code = entry.error_code; - break; - } - } - return error_code; -} - -} // anonymous namespace - -void AddSystemError(ErrorPtr* error, - const tracked_objects::Location& location, - int errnum) { - std::string message = base::safe_strerror(errnum); - std::string code = ErrorCodeFromSystemError(errnum); - if (message.empty()) - message = "Unknown error " + std::to_string(errnum); - - if (code.empty()) - code = "error_" + std::to_string(errnum); - - Error::AddTo(error, location, kDomain, code, message); -} - -} // namespace system - -} // namespace errors -} // namespace chromeos diff --git a/chromeos/errors/error_codes.h b/chromeos/errors/error_codes.h deleted file mode 100644 index d612640..0000000 --- a/chromeos/errors/error_codes.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_ERRORS_ERROR_CODES_H_ -#define LIBCHROMEOS_CHROMEOS_ERRORS_ERROR_CODES_H_ - -#include - -#include -#include - -namespace chromeos { -namespace errors { - -namespace dbus { -CHROMEOS_EXPORT extern const char kDomain[]; -} // namespace dbus - -namespace json { -CHROMEOS_EXPORT extern const char kDomain[]; -CHROMEOS_EXPORT extern const char kParseError[]; -CHROMEOS_EXPORT extern const char kObjectExpected[]; -} // namespace json - -namespace http { -CHROMEOS_EXPORT extern const char kDomain[]; -} // namespace http - -namespace system { -CHROMEOS_EXPORT extern const char kDomain[]; - -// Adds an Error object to the error chain identified by |error|, using -// the system error code (see "errno"). -CHROMEOS_EXPORT void AddSystemError(ErrorPtr* error, - const tracked_objects::Location& location, - int errnum); -} // namespace system - -} // namespace errors -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_ERRORS_ERROR_CODES_H_ diff --git a/chromeos/errors/error_codes_unittest.cc b/chromeos/errors/error_codes_unittest.cc deleted file mode 100644 index 49633da..0000000 --- a/chromeos/errors/error_codes_unittest.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -using chromeos::errors::system::AddSystemError; - -TEST(SystemErrorCodes, AddTo) { - chromeos::ErrorPtr error; - - AddSystemError(&error, FROM_HERE, ENOENT); - EXPECT_EQ(chromeos::errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("ENOENT", error->GetCode()); - EXPECT_EQ("No such file or directory", error->GetMessage()); - error.reset(); - - AddSystemError(&error, FROM_HERE, EPROTO); - EXPECT_EQ(chromeos::errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EPROTO", error->GetCode()); - EXPECT_EQ("Protocol error", error->GetMessage()); - error.reset(); -} - -TEST(SystemErrorCodes, AddTo_UnknownError) { - chromeos::ErrorPtr error; - AddSystemError(&error, FROM_HERE, 10000); - EXPECT_EQ(chromeos::errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("error_10000", error->GetCode()); - EXPECT_EQ("Unknown error 10000", error->GetMessage()); -} diff --git a/chromeos/errors/error_unittest.cc b/chromeos/errors/error_unittest.cc deleted file mode 100644 index 9e6a2e9..0000000 --- a/chromeos/errors/error_unittest.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -using chromeos::Error; - -namespace { - -chromeos::ErrorPtr GenerateNetworkError() { - tracked_objects::Location loc("GenerateNetworkError", - "error_unittest.cc", - 15, - ::tracked_objects::GetProgramCounter()); - return Error::Create(loc, "network", "not_found", "Resource not found"); -} - -chromeos::ErrorPtr GenerateHttpError() { - chromeos::ErrorPtr inner = GenerateNetworkError(); - return Error::Create(FROM_HERE, "HTTP", "404", "Not found", std::move(inner)); -} - -} // namespace - -TEST(Error, Single) { - chromeos::ErrorPtr err = GenerateNetworkError(); - EXPECT_EQ("network", err->GetDomain()); - EXPECT_EQ("not_found", err->GetCode()); - EXPECT_EQ("Resource not found", err->GetMessage()); - EXPECT_EQ("GenerateNetworkError", err->GetLocation().function_name); - EXPECT_EQ("error_unittest.cc", err->GetLocation().file_name); - EXPECT_EQ(15, err->GetLocation().line_number); - EXPECT_EQ(nullptr, err->GetInnerError()); - EXPECT_TRUE(err->HasDomain("network")); - EXPECT_FALSE(err->HasDomain("HTTP")); - EXPECT_FALSE(err->HasDomain("foo")); - EXPECT_TRUE(err->HasError("network", "not_found")); - EXPECT_FALSE(err->HasError("network", "404")); - EXPECT_FALSE(err->HasError("HTTP", "404")); - EXPECT_FALSE(err->HasError("HTTP", "not_found")); - EXPECT_FALSE(err->HasError("foo", "bar")); -} - -TEST(Error, Nested) { - chromeos::ErrorPtr err = GenerateHttpError(); - EXPECT_EQ("HTTP", err->GetDomain()); - EXPECT_EQ("404", err->GetCode()); - EXPECT_EQ("Not found", err->GetMessage()); - EXPECT_NE(nullptr, err->GetInnerError()); - EXPECT_EQ("network", err->GetInnerError()->GetDomain()); - EXPECT_TRUE(err->HasDomain("network")); - EXPECT_TRUE(err->HasDomain("HTTP")); - EXPECT_FALSE(err->HasDomain("foo")); - EXPECT_TRUE(err->HasError("network", "not_found")); - EXPECT_FALSE(err->HasError("network", "404")); - EXPECT_TRUE(err->HasError("HTTP", "404")); - EXPECT_FALSE(err->HasError("HTTP", "not_found")); - EXPECT_FALSE(err->HasError("foo", "bar")); -} - -TEST(Error, Clone) { - chromeos::ErrorPtr err = GenerateHttpError(); - chromeos::ErrorPtr clone = err->Clone(); - const chromeos::Error* error1 = err.get(); - const chromeos::Error* error2 = clone.get(); - while (error1 && error2) { - EXPECT_NE(error1, error2); - EXPECT_EQ(error1->GetDomain(), error2->GetDomain()); - EXPECT_EQ(error1->GetCode(), error2->GetCode()); - EXPECT_EQ(error1->GetMessage(), error2->GetMessage()); - EXPECT_EQ(error1->GetLocation().function_name, - error2->GetLocation().function_name); - EXPECT_EQ(error1->GetLocation().file_name, error2->GetLocation().file_name); - EXPECT_EQ(error1->GetLocation().line_number, - error2->GetLocation().line_number); - error1 = error1->GetInnerError(); - error2 = error2->GetInnerError(); - } - EXPECT_EQ(error1, error2); -} diff --git a/chromeos/file_utils.cc b/chromeos/file_utils.cc deleted file mode 100644 index ba08acc..0000000 --- a/chromeos/file_utils.cc +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/file_utils.h" - -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { - -namespace { - -enum { - kPermissions600 = S_IRUSR | S_IWUSR, - kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO -}; - -// Verify that base file permission enums are compatible with S_Ixxx. If these -// asserts ever fail, we'll need to ensure that users of these functions switch -// away from using base permission enums and add a note to the function comments -// indicating that base enums can not be used. -static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH, - "base file permissions don't match unistd.h permissions"); -static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH, - "base file permissions don't match unistd.h permissions"); - -enum RegularFileOrDeleteResult { - kFailure = 0, // Failed to delete whatever was at the path. - kRegularFile = 1, // Regular file existed and was unchanged. - kEmpty = 2 // Anything that was at the path has been deleted. -}; - -// Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise -// deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult -// enum indicating what is at |path| after the function finishes. -RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path, - uid_t uid, - gid_t gid) { - // Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets - // us use the safer fstat() instead of having to use lstat(). - base::ScopedFD scoped_fd(HANDLE_EINTR(openat( - AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW))); - bool path_not_empty = (errno == ELOOP || scoped_fd != -1); - - // If there is a file/directory at |path|, see if it matches our criteria. - if (scoped_fd != -1) { - struct stat file_stat; - if (fstat(scoped_fd.get(), &file_stat) != -1 && - S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid && - file_stat.st_gid == gid) { - return kRegularFile; - } - } - - // If we get here and anything was at |path|, try to delete it so we can put - // our file there. - if (path_not_empty) { - if (!base::DeleteFile(path, true)) { - PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"'; - return kFailure; - } - } - - return kEmpty; -} - -// Handles common touch functionality but also provides an optional |fd_out| -// so that any further modifications to the file (e.g. permissions) can safely -// use the fd rather than the path. |fd_out| will only be set if a new file -// is created, otherwise it will be unchanged. -// If |fd_out| is null, this function will close the file, otherwise it's -// expected that |fd_out| will close the file when it goes out of scope. -bool TouchFileInternal(const base::FilePath& path, - uid_t uid, - gid_t gid, - base::ScopedFD* fd_out) { - RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid); - switch (result) { - case kFailure: - return false; - case kRegularFile: - return true; - case kEmpty: - break; - } - - // base::CreateDirectory() returns true if the directory already existed. - if (!base::CreateDirectory(path.DirName())) { - PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"'; - return false; - } - - // Create the file as owner-only initially. - base::ScopedFD scoped_fd( - HANDLE_EINTR(openat(AT_FDCWD, - path.value().c_str(), - O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC, - kPermissions600))); - if (scoped_fd == -1) { - PLOG(WARNING) << "Failed to create file \"" << path.value() << '"'; - return false; - } - - if (fd_out) { - fd_out->swap(scoped_fd); - } - return true; -} - -} // namespace - -bool TouchFile(const base::FilePath& path, - int new_file_permissions, - uid_t uid, - gid_t gid) { - // Make sure |permissions| doesn't have any out-of-range bits. - if (new_file_permissions & ~kPermissions777) { - LOG(WARNING) << "Illegal permissions: " << new_file_permissions; - return false; - } - - base::ScopedFD scoped_fd; - if (!TouchFileInternal(path, uid, gid, &scoped_fd)) { - return false; - } - - // scoped_fd is valid only if a new file was created. - if (scoped_fd != -1 && - HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) { - PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"'; - base::DeleteFile(path, false); - return false; - } - - return true; -} - -bool TouchFile(const base::FilePath& path) { - // Use TouchFile() instead of TouchFileInternal() to explicitly set - // permissions to 600 in case umask is set strangely. - return TouchFile(path, kPermissions600, geteuid(), getegid()); -} - -} // namespace chromeos diff --git a/chromeos/file_utils.h b/chromeos/file_utils.h deleted file mode 100644 index 4cb1f46..0000000 --- a/chromeos/file_utils.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_FILE_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_FILE_UTILS_H_ - -#include - -#include -#include - -namespace chromeos { - -// Ensures a regular file owned by user |uid| and group |gid| exists at |path|. -// Any other entity at |path| will be deleted and replaced with an empty -// regular file. If a new file is needed, any missing parent directories will -// be created, and the file will be assigned |new_file_permissions|. -// Should be safe to use in all directories, including tmpdirs with the sticky -// bit set. -// Returns true if the file existed or was able to be created. -CHROMEOS_EXPORT bool TouchFile(const base::FilePath& path, - int new_file_permissions, - uid_t uid, - gid_t gid); - -// Convenience version of TouchFile() defaulting to 600 permissions and the -// current euid/egid. -// Should be safe to use in all directories, including tmpdirs with the sticky -// bit set. -CHROMEOS_EXPORT bool TouchFile(const base::FilePath& path); - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_FILE_UTILS_H_ diff --git a/chromeos/file_utils_unittest.cc b/chromeos/file_utils_unittest.cc deleted file mode 100644 index 0c23986..0000000 --- a/chromeos/file_utils_unittest.cc +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/file_utils.h" - -#include -#include - -#include - -#include -#include -#include - -namespace chromeos { - -class FileUtilsTest : public testing::Test { - public: - FileUtilsTest() { - CHECK(temp_dir_.CreateUniqueTempDir()); - file_path_ = temp_dir_.path().Append("test.temp"); - } - - protected: - base::FilePath file_path_; - base::ScopedTempDir temp_dir_; - - // Writes |contents| to |file_path_|. Pulled into a separate function just - // to improve readability of tests. - void WriteFile(const std::string& contents) { - EXPECT_EQ(contents.length(), - base::WriteFile(file_path_, contents.c_str(), contents.length())); - } - - // Verifies that the file at |file_path_| exists and contains |contents|. - void ExpectFileContains(const std::string& contents) { - EXPECT_TRUE(base::PathExists(file_path_)); - std::string new_contents; - EXPECT_TRUE(base::ReadFileToString(file_path_, &new_contents)); - EXPECT_EQ(contents, new_contents); - } - - // Verifies that the file at |file_path_| has |permissions|. - void ExpectFilePermissions(int permissions) { - int actual_permissions; - EXPECT_TRUE(base::GetPosixFilePermissions(file_path_, &actual_permissions)); - EXPECT_EQ(permissions, actual_permissions); - } -}; - -namespace { - -enum { - kPermissions600 = - base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER, - kPermissions700 = base::FILE_PERMISSION_USER_MASK, - kPermissions777 = base::FILE_PERMISSION_MASK -}; - -} // namespace - -TEST_F(FileUtilsTest, TouchFileCreate) { - EXPECT_TRUE(TouchFile(file_path_)); - ExpectFileContains(""); - ExpectFilePermissions(kPermissions600); -} - -TEST_F(FileUtilsTest, TouchFileCreateThroughUmask) { - mode_t old_umask = umask(kPermissions777); - EXPECT_TRUE(TouchFile(file_path_)); - umask(old_umask); - ExpectFileContains(""); - ExpectFilePermissions(kPermissions600); -} - -TEST_F(FileUtilsTest, TouchFileCreateDirectoryStructure) { - file_path_ = temp_dir_.path().Append("foo/bar/baz/test.temp"); - EXPECT_TRUE(TouchFile(file_path_)); - ExpectFileContains(""); -} - -TEST_F(FileUtilsTest, TouchFileExisting) { - WriteFile("abcd"); - EXPECT_TRUE(TouchFile(file_path_)); - ExpectFileContains("abcd"); -} - -TEST_F(FileUtilsTest, TouchFileReplaceDirectory) { - EXPECT_TRUE(base::CreateDirectory(file_path_)); - EXPECT_TRUE(TouchFile(file_path_)); - EXPECT_FALSE(base::DirectoryExists(file_path_)); - ExpectFileContains(""); -} - -TEST_F(FileUtilsTest, TouchFileReplaceSymlink) { - base::FilePath symlink_target = temp_dir_.path().Append("target.temp"); - EXPECT_TRUE(base::CreateSymbolicLink(symlink_target, file_path_)); - EXPECT_TRUE(TouchFile(file_path_)); - EXPECT_FALSE(base::IsLink(file_path_)); - ExpectFileContains(""); -} - -TEST_F(FileUtilsTest, TouchFileReplaceOtherUser) { - WriteFile("abcd"); - EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid() + 1, getegid())); - ExpectFileContains(""); -} - -TEST_F(FileUtilsTest, TouchFileReplaceOtherGroup) { - WriteFile("abcd"); - EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid() + 1)); - ExpectFileContains(""); -} - -TEST_F(FileUtilsTest, TouchFileCreateWithAllPermissions) { - EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid())); - ExpectFileContains(""); - ExpectFilePermissions(kPermissions777); -} - -TEST_F(FileUtilsTest, TouchFileCreateWithOwnerPermissions) { - EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); - ExpectFileContains(""); - ExpectFilePermissions(kPermissions700); -} - -TEST_F(FileUtilsTest, TouchFileExistingPermissionsUnchanged) { - EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid())); - EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid())); - ExpectFileContains(""); - ExpectFilePermissions(kPermissions777); -} - -} // namespace chromeos diff --git a/chromeos/flag_helper.cc b/chromeos/flag_helper.cc deleted file mode 100644 index c1a65d9..0000000 --- a/chromeos/flag_helper.cc +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/flag_helper.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { - -Flag::Flag(const char* name, - const char* default_value, - const char* help, - bool visible) - : name_(name), - default_value_(default_value), - help_(help), - visible_(visible) { -} - -class HelpFlag : public chromeos::Flag { - public: - HelpFlag() : Flag("help", "false", "Show this help message", true) {} - - bool SetValue(const std::string& value) override { return true; }; - const char* GetType() const override { return "bool"; } -}; - -BoolFlag::BoolFlag(const char* name, - bool* value, - bool* no_value, - const char* default_value, - const char* help, - bool visible) - : Flag(name, default_value, help, visible), - value_(value), - no_value_(no_value) { -} - -bool BoolFlag::SetValue(const std::string& value) { - if (value.empty()) { - *value_ = true; - } else { - if (!value.compare("true")) - *value_ = true; - else if (!value.compare("false")) - *value_ = false; - else - return false; - } - - *no_value_ = !*value_; - - return true; -} - -const char* BoolFlag::GetType() const { - return "bool"; -} - -Int32Flag::Int32Flag(const char* name, - int* value, - const char* default_value, - const char* help, - bool visible) - : Flag(name, default_value, help, visible), value_(value) { -} - -bool Int32Flag::SetValue(const std::string& value) { - return base::StringToInt(value, value_); -} - -const char* Int32Flag::GetType() const { - return "int"; -} - -Int64Flag::Int64Flag(const char* name, - int64_t* value, - const char* default_value, - const char* help, - bool visible) - : Flag(name, default_value, help, visible), value_(value) { -} - -bool Int64Flag::SetValue(const std::string& value) { - return base::StringToInt64(value, value_); -} - -const char* Int64Flag::GetType() const { - return "int64"; -} - -UInt64Flag::UInt64Flag(const char* name, - uint64_t* value, - const char* default_value, - const char* help, - bool visible) - : Flag(name, default_value, help, visible), value_(value) { -} - -bool UInt64Flag::SetValue(const std::string& value) { - return base::StringToUint64(value, value_); -} - -const char* UInt64Flag::GetType() const { - return "uint64"; -} - -DoubleFlag::DoubleFlag(const char* name, - double* value, - const char* default_value, - const char* help, - bool visible) - : Flag(name, default_value, help, visible), value_(value) { -} - -bool DoubleFlag::SetValue(const std::string& value) { - return base::StringToDouble(value, value_); -} - -const char* DoubleFlag::GetType() const { - return "double"; -} - -StringFlag::StringFlag(const char* name, - std::string* value, - const char* default_value, - const char* help, - bool visible) - : Flag(name, default_value, help, visible), value_(value) { -} - -bool StringFlag::SetValue(const std::string& value) { - value_->assign(value); - - return true; -} - -const char* StringFlag::GetType() const { - return "string"; -} - -namespace { -chromeos::FlagHelper* instance_ = nullptr; -} // namespace - -FlagHelper::FlagHelper() : command_line_(nullptr) { - AddFlag(std::unique_ptr(new HelpFlag())); -} - -FlagHelper::~FlagHelper() { -} - -chromeos::FlagHelper* FlagHelper::GetInstance() { - if (!instance_) - instance_ = new FlagHelper(); - - return instance_; -} - -void FlagHelper::ResetForTesting() { - delete instance_; - instance_ = nullptr; -} - -void FlagHelper::Init(int argc, - const char* const* argv, - std::string help_usage) { - chromeos::FlagHelper* helper = GetInstance(); - if (!helper->command_line_) { - if (!base::CommandLine::InitializedForCurrentProcess()) - base::CommandLine::Init(argc, argv); - helper->command_line_ = base::CommandLine::ForCurrentProcess(); - } - - GetInstance()->SetUsageMessage(help_usage); - - GetInstance()->UpdateFlagValues(); -} - -void FlagHelper::UpdateFlagValues() { - std::string error_msg; - int error_code = EX_OK; - - // Check that base::CommandLine has been initialized. - CHECK(base::CommandLine::InitializedForCurrentProcess()); - - // If the --help flag exists, print out help message and exit. - if (command_line_->HasSwitch("help")) { - puts(GetHelpMessage().c_str()); - exit(EX_OK); - } - - // Iterate over the base::CommandLine switches. Update the value - // of the corresponding Flag if it exists, or output an error message - // if the flag wasn't defined. - const base::CommandLine::SwitchMap& switch_map = command_line_->GetSwitches(); - - for (const auto& pair : switch_map) { - const std::string& key = pair.first; - // Make sure we allow the standard logging switches (--v and --vmodule). - if (key == switches::kV || key == switches::kVModule) - continue; - - const std::string& value = pair.second; - - auto df_it = defined_flags_.find(key); - if (df_it != defined_flags_.end()) { - Flag* flag = df_it->second.get(); - if (!flag->SetValue(value)) { - base::StringAppendF( - &error_msg, - "ERROR: illegal value '%s' specified for %s flag '%s'\n", - value.c_str(), - flag->GetType(), - flag->name_); - error_code = EX_DATAERR; - } - } else { - base::StringAppendF( - &error_msg, "ERROR: unknown command line flag '%s'\n", key.c_str()); - error_code = EX_USAGE; - } - } - - if (error_code != EX_OK) { - puts(error_msg.c_str()); - exit(error_code); - } -} - -void FlagHelper::AddFlag(std::unique_ptr flag) { - defined_flags_.emplace(flag->name_, std::move(flag)); -} - -void FlagHelper::SetUsageMessage(std::string help_usage) { - help_usage_.assign(std::move(help_usage)); -} - -std::string FlagHelper::GetHelpMessage() const { - std::string help = help_usage_; - help.append("\n\n"); - for (const auto& pair : defined_flags_) { - const Flag* flag = pair.second.get(); - if (flag->visible_) { - base::StringAppendF(&help, - " --%s (%s) type: %s default: %s\n", - flag->name_, - flag->help_, - flag->GetType(), - flag->default_value_); - } - } - return help; -} - -} // namespace chromeos diff --git a/chromeos/flag_helper.h b/chromeos/flag_helper.h deleted file mode 100644 index b20ac7e..0000000 --- a/chromeos/flag_helper.h +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This is a helper class for dealing with command line flags. It uses -// base/command_line.h to parse flags from argv, but provides an API similar -// to gflags. Command line arguments with either '-' or '--' prefixes are -// treated as flags. Flags can optionally have a value set using an '=' -// delimeter, e.g. "--flag=value". An argument of "--" will terminate flag -// parsing, so that any subsequent arguments will be treated as non-flag -// arguments, regardless of prefix. Non-flag arguments are outside the scope -// of this class, and can instead be accessed through the GetArgs() function -// of the base::CommandLine singleton after FlagHelper initialization. -// -// The FlagHelper class will automatically take care of the --help flag, as -// well as aborting the program when unknown flags are passed to the -// application and when passed in parameters cannot be correctly parsed to -// their respective types. Developers define flags at compile time using the -// following macros from within main(): -// -// DEFINE_bool(name, default_value, help) -// DEFINE_int32(name, default_value, help) -// DEFINE_int64(name, default_value, help) -// DEFINE_uint64(name, default_value, help) -// DEFINE_double(name, default_value, help) -// DEFINE_string(name, default_value, help) -// -// Using the macro will create a scoped variable of the appropriate type -// with the name FLAGS_, that can be used to access the flag's -// value within the program. Here is an example of how the FlagHelper -// class is to be used: -// -// -- -// -// #include -// #include -// -// int main(int argc, char** argv) { -// DEFINE_int32(example, 0, "Example int flag"); -// chromeos::FlagHelper::Init(argc, argv, "Test application."); -// -// printf("You passed in %d to --example command line flag\n", -// FLAGS_example); -// return 0; -// } -// -// -- -// -// In order to update the FLAGS_xxxx values from their defaults to the -// values passed in to the command line, Init(...) must be called after -// all the DEFINE_xxxx macros have instantiated the variables. - -#ifndef LIBCHROMEOS_CHROMEOS_FLAG_HELPER_H_ -#define LIBCHROMEOS_CHROMEOS_FLAG_HELPER_H_ - -#include -#include -#include - -#include -#include -#include - -namespace chromeos { - -// The corresponding class representation of a command line flag, used -// to keep track of pointers to the FLAGS_xxxx variables so that they -// can be updated. -class Flag { - public: - Flag(const char* name, - const char* default_value, - const char* help, - bool visible); - virtual ~Flag() = default; - - // Sets the associated FLAGS_xxxx value, taking into account the flag type - virtual bool SetValue(const std::string& value) = 0; - - // Returns the type of the flag as a char array, for use in the help message - virtual const char* GetType() const = 0; - - const char* name_; - const char* default_value_; - const char* help_; - bool visible_; -}; - -class CHROMEOS_EXPORT BoolFlag final : public Flag { - public: - BoolFlag(const char* name, - bool* value, - bool* no_value, - const char* default_value, - const char* help, - bool visible); - bool SetValue(const std::string& value) override; - - const char* GetType() const override; - - private: - bool* value_; - bool* no_value_; -}; - -class CHROMEOS_EXPORT Int32Flag final : public Flag { - public: - Int32Flag(const char* name, - int* value, - const char* default_value, - const char* help, - bool visible); - bool SetValue(const std::string& value) override; - - const char* GetType() const override; - - private: - int* value_; -}; - -class CHROMEOS_EXPORT Int64Flag final : public Flag { - public: - Int64Flag(const char* name, - int64_t* value, - const char* default_value, - const char* help, - bool visible); - bool SetValue(const std::string& value) override; - - const char* GetType() const override; - - private: - int64_t* value_; -}; - -class CHROMEOS_EXPORT UInt64Flag final : public Flag { - public: - UInt64Flag(const char* name, - uint64_t* value, - const char* default_value, - const char* help, - bool visible); - bool SetValue(const std::string& value) override; - - const char* GetType() const override; - - private: - uint64_t* value_; -}; - -class CHROMEOS_EXPORT DoubleFlag final : public Flag { - public: - DoubleFlag(const char* name, - double* value, - const char* default_value, - const char* help, - bool visible); - bool SetValue(const std::string& value) override; - - const char* GetType() const override; - - private: - double* value_; -}; - -class CHROMEOS_EXPORT StringFlag final : public Flag { - public: - StringFlag(const char* name, - std::string* value, - const char* default_value, - const char* help, - bool visible); - bool SetValue(const std::string& value) override; - - const char* GetType() const override; - - private: - std::string* value_; -}; - -// The following macros are to be used from within main() to create -// scoped FLAGS_xxxx variables for easier access to command line flag -// values. FLAGS_noxxxx variables are also created, which are used to -// set bool flags to false. Creating the FLAGS_noxxxx variables here -// will also ensure a compiler error will be thrown if another flag -// is created with a conflicting name. -#define DEFINE_type(type, classtype, name, value, help) \ - type FLAGS_##name = value; \ - chromeos::FlagHelper::GetInstance()->AddFlag( \ - std::unique_ptr( \ - new chromeos::classtype(#name, &FLAGS_##name, #value, help, true))); - -#define DEFINE_int32(name, value, help) \ - DEFINE_type(int, Int32Flag, name, value, help) -#define DEFINE_int64(name, value, help) \ - DEFINE_type(int64_t, Int64Flag, name, value, help) -#define DEFINE_uint64(name, value, help) \ - DEFINE_type(uint64_t, UInt64Flag, name, value, help) -#define DEFINE_double(name, value, help) \ - DEFINE_type(double, DoubleFlag, name, value, help) -#define DEFINE_string(name, value, help) \ - DEFINE_type(std::string, StringFlag, name, value, help) - -// Due to the FLAGS_no##name variables, can't re-use the same DEFINE_type macro -// for defining bool flags -#define DEFINE_bool(name, value, help) \ - bool FLAGS_##name = value; \ - bool FLAGS_no##name = !value; \ - chromeos::FlagHelper::GetInstance()->AddFlag( \ - std::unique_ptr(new chromeos::BoolFlag( \ - #name, &FLAGS_##name, &FLAGS_no##name, #value, help, true))); \ - chromeos::FlagHelper::GetInstance()->AddFlag( \ - std::unique_ptr(new chromeos::BoolFlag( \ - "no" #name, &FLAGS_no##name, &FLAGS_##name, #value, help, false))); - -// The FlagHelper class is a singleton class used for registering command -// line flags and pointers to their associated scoped variables, so that -// the variables can be updated once the command line arguments have been -// parsed by base::CommandLine. -class CHROMEOS_EXPORT FlagHelper final { - public: - // The singleton accessor function. - static FlagHelper* GetInstance(); - - // Resets the singleton object. Developers shouldn't ever need to use this, - // however it is required to be run at the end of every unit test to prevent - // Flag definitions from carrying over from previous tests. - static void ResetForTesting(); - - // Initializes the base::CommandLine class, then calls UpdateFlagValues(). - static void Init(int argc, const char* const* argv, std::string help_usage); - - // Only to be used for running unit tests. - void set_command_line_for_testing(base::CommandLine* command_line) { - command_line_ = command_line; - } - - // Checks all the parsed command line flags. This iterates over the switch - // map from base::CommandLine, and finds the corresponding Flag in order to - // update the FLAGS_xxxx values to the parsed value. If the --help flag is - // passed in, it outputs a help message and exits the program. If an unknown - // flag is passed in, it outputs an error message and exits the program with - // exit code EX_USAGE. - void UpdateFlagValues(); - - // Adds a flag to be tracked and updated once the command line is actually - // parsed. This function is an implementation detail, and is not meant - // to be used directly by developers. Developers should instead use the - // DEFINE_xxxx macros to register a command line flag. - void AddFlag(std::unique_ptr flag); - - // Sets the usage message, which is prepended to the --help message. - void SetUsageMessage(std::string help_usage); - - private: - FlagHelper(); - ~FlagHelper(); - - // Generates a help message from the Usage Message and registered flags. - std::string GetHelpMessage() const; - - std::string help_usage_; - std::map> defined_flags_; - - // base::CommandLine object for parsing the command line switches. This - // object isn't owned by this class, so don't need to delete it in the - // destructor. - base::CommandLine* command_line_; - - DISALLOW_COPY_AND_ASSIGN(FlagHelper); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_FLAG_HELPER_H_ diff --git a/chromeos/flag_helper_unittest.cc b/chromeos/flag_helper_unittest.cc deleted file mode 100644 index 3178aaa..0000000 --- a/chromeos/flag_helper_unittest.cc +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include - -#include -#include -#include - -#include - -namespace chromeos { - -class FlagHelperTest : public ::testing::Test { - public: - FlagHelperTest() {} - ~FlagHelperTest() override { chromeos::FlagHelper::ResetForTesting(); } - static void SetUpTestCase() { - base::CommandLine::Init(0, nullptr); - } -}; - -// Test that the DEFINE_xxxx macros can create the respective variables -// correctly with the default value. -TEST_F(FlagHelperTest, Defaults) { - DEFINE_bool(bool1, true, "Test bool flag"); - DEFINE_bool(bool2, false, "Test bool flag"); - DEFINE_int32(int32_1, INT32_MIN, "Test int32 flag"); - DEFINE_int32(int32_2, 0, "Test int32 flag"); - DEFINE_int32(int32_3, INT32_MAX, "Test int32 flag"); - DEFINE_int64(int64_1, INT64_MIN, "Test int64 flag"); - DEFINE_int64(int64_2, 0, "Test int64 flag"); - DEFINE_int64(int64_3, INT64_MAX, "Test int64 flag"); - DEFINE_uint64(uint64_1, 0, "Test uint64 flag"); - DEFINE_uint64(uint64_2, UINT_LEAST64_MAX, "Test uint64 flag"); - DEFINE_double(double_1, -100.5, "Test double flag"); - DEFINE_double(double_2, 0, "Test double flag"); - DEFINE_double(double_3, 100.5, "Test double flag"); - DEFINE_string(string_1, "", "Test string flag"); - DEFINE_string(string_2, "value", "Test string flag"); - - const char* argv[] = {"test_program"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue"); - - EXPECT_TRUE(FLAGS_bool1); - EXPECT_FALSE(FLAGS_bool2); - EXPECT_EQ(FLAGS_int32_1, INT32_MIN); - EXPECT_EQ(FLAGS_int32_2, 0); - EXPECT_EQ(FLAGS_int32_3, INT32_MAX); - EXPECT_EQ(FLAGS_int64_1, INT64_MIN); - EXPECT_EQ(FLAGS_int64_2, 0); - EXPECT_EQ(FLAGS_int64_3, INT64_MAX); - EXPECT_EQ(FLAGS_uint64_1, 0); - EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX); - EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5); - EXPECT_DOUBLE_EQ(FLAGS_double_2, 0); - EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5); - EXPECT_STREQ(FLAGS_string_1.c_str(), ""); - EXPECT_STREQ(FLAGS_string_2.c_str(), "value"); -} - -// Test that command line flag values are parsed and update the flag -// variable values correctly when using double '--' flags -TEST_F(FlagHelperTest, SetValueDoubleDash) { - DEFINE_bool(bool1, false, "Test bool flag"); - DEFINE_bool(bool2, true, "Test bool flag"); - DEFINE_bool(bool3, false, "Test bool flag"); - DEFINE_bool(bool4, true, "Test bool flag"); - DEFINE_int32(int32_1, 1, "Test int32 flag"); - DEFINE_int32(int32_2, 1, "Test int32 flag"); - DEFINE_int32(int32_3, 1, "Test int32 flag"); - DEFINE_int64(int64_1, 1, "Test int64 flag"); - DEFINE_int64(int64_2, 1, "Test int64 flag"); - DEFINE_int64(int64_3, 1, "Test int64 flag"); - DEFINE_uint64(uint64_1, 1, "Test uint64 flag"); - DEFINE_uint64(uint64_2, 1, "Test uint64 flag"); - DEFINE_double(double_1, 1, "Test double flag"); - DEFINE_double(double_2, 1, "Test double flag"); - DEFINE_double(double_3, 1, "Test double flag"); - DEFINE_string(string_1, "default", "Test string flag"); - DEFINE_string(string_2, "default", "Test string flag"); - - const char* argv[] = {"test_program", - "--bool1", - "--nobool2", - "--bool3=true", - "--bool4=false", - "--int32_1=-2147483648", - "--int32_2=0", - "--int32_3=2147483647", - "--int64_1=-9223372036854775808", - "--int64_2=0", - "--int64_3=9223372036854775807", - "--uint64_1=0", - "--uint64_2=18446744073709551615", - "--double_1=-100.5", - "--double_2=0", - "--double_3=100.5", - "--string_1=", - "--string_2=value"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue"); - - EXPECT_TRUE(FLAGS_bool1); - EXPECT_FALSE(FLAGS_bool2); - EXPECT_TRUE(FLAGS_bool3); - EXPECT_FALSE(FLAGS_bool4); - EXPECT_EQ(FLAGS_int32_1, INT32_MIN); - EXPECT_EQ(FLAGS_int32_2, 0); - EXPECT_EQ(FLAGS_int32_3, INT32_MAX); - EXPECT_EQ(FLAGS_int64_1, INT64_MIN); - EXPECT_EQ(FLAGS_int64_2, 0); - EXPECT_EQ(FLAGS_int64_3, INT64_MAX); - EXPECT_EQ(FLAGS_uint64_1, 0); - EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX); - EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5); - EXPECT_DOUBLE_EQ(FLAGS_double_2, 0); - EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5); - EXPECT_STREQ(FLAGS_string_1.c_str(), ""); - EXPECT_STREQ(FLAGS_string_2.c_str(), "value"); -} - -// Test that command line flag values are parsed and update the flag -// variable values correctly when using single '-' flags -TEST_F(FlagHelperTest, SetValueSingleDash) { - DEFINE_bool(bool1, false, "Test bool flag"); - DEFINE_bool(bool2, true, "Test bool flag"); - DEFINE_int32(int32_1, 1, "Test int32 flag"); - DEFINE_int32(int32_2, 1, "Test int32 flag"); - DEFINE_int32(int32_3, 1, "Test int32 flag"); - DEFINE_int64(int64_1, 1, "Test int64 flag"); - DEFINE_int64(int64_2, 1, "Test int64 flag"); - DEFINE_int64(int64_3, 1, "Test int64 flag"); - DEFINE_uint64(uint64_1, 1, "Test uint64 flag"); - DEFINE_uint64(uint64_2, 1, "Test uint64 flag"); - DEFINE_double(double_1, 1, "Test double flag"); - DEFINE_double(double_2, 1, "Test double flag"); - DEFINE_double(double_3, 1, "Test double flag"); - DEFINE_string(string_1, "default", "Test string flag"); - DEFINE_string(string_2, "default", "Test string flag"); - - const char* argv[] = {"test_program", - "-bool1", - "-nobool2", - "-int32_1=-2147483648", - "-int32_2=0", - "-int32_3=2147483647", - "-int64_1=-9223372036854775808", - "-int64_2=0", - "-int64_3=9223372036854775807", - "-uint64_1=0", - "-uint64_2=18446744073709551615", - "-double_1=-100.5", - "-double_2=0", - "-double_3=100.5", - "-string_1=", - "-string_2=value"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue"); - - EXPECT_TRUE(FLAGS_bool1); - EXPECT_FALSE(FLAGS_bool2); - EXPECT_EQ(FLAGS_int32_1, INT32_MIN); - EXPECT_EQ(FLAGS_int32_2, 0); - EXPECT_EQ(FLAGS_int32_3, INT32_MAX); - EXPECT_EQ(FLAGS_int64_1, INT64_MIN); - EXPECT_EQ(FLAGS_int64_2, 0); - EXPECT_EQ(FLAGS_int64_3, INT64_MAX); - EXPECT_EQ(FLAGS_uint64_1, 0); - EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX); - EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5); - EXPECT_DOUBLE_EQ(FLAGS_double_2, 0); - EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5); - EXPECT_STREQ(FLAGS_string_1.c_str(), ""); - EXPECT_STREQ(FLAGS_string_2.c_str(), "value"); -} - -// Test that a duplicated flag on the command line picks up the last -// value set. -TEST_F(FlagHelperTest, DuplicateSetValue) { - DEFINE_int32(int32_1, 0, "Test in32 flag"); - - const char* argv[] = {"test_program", "--int32_1=5", "--int32_1=10"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestDuplicateSetvalue"); - - EXPECT_EQ(FLAGS_int32_1, 10); -} - -// Test that flags set after the -- marker are not parsed as command line flags -TEST_F(FlagHelperTest, FlagTerminator) { - DEFINE_int32(int32_1, 0, "Test int32 flag"); - - const char* argv[] = {"test_program", "--int32_1=5", "--", "--int32_1=10"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestFlagTerminator"); - - EXPECT_EQ(FLAGS_int32_1, 5); -} - -// Test that help messages are generated correctly when the --help flag -// is passed to the program. -TEST_F(FlagHelperTest, HelpMessage) { - DEFINE_bool(bool_1, true, "Test bool flag"); - DEFINE_int32(int_1, 0, "Test int flag"); - DEFINE_int64(int64_1, 0, "Test int64 flag"); - DEFINE_uint64(uint64_1, 0, "Test uint64 flag"); - DEFINE_double(double_1, 0, "Test double flag"); - DEFINE_string(string_1, "", "Test string flag"); - - const char* argv[] = {"test_program", "--int_1=value", "--help"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - - FILE* orig = stdout; - stdout = stderr; - - ASSERT_EXIT( - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestHelpMessage"), - ::testing::ExitedWithCode(EX_OK), - "TestHelpMessage\n\n" - " --bool_1 \\(Test bool flag\\) type: bool default: true\n" - " --double_1 \\(Test double flag\\) type: double default: 0\n" - " --help \\(Show this help message\\) type: bool default: false\n" - " --int64_1 \\(Test int64 flag\\) type: int64 default: 0\n" - " --int_1 \\(Test int flag\\) type: int default: 0\n" - " --string_1 \\(Test string flag\\) type: string default: \"\"\n" - " --uint64_1 \\(Test uint64 flag\\) type: uint64 default: 0\n"); - - stdout = orig; -} - -// Test that passing in unknown command line flags causes the program -// to exit with EX_USAGE error code and corresponding error message. -TEST_F(FlagHelperTest, UnknownFlag) { - const char* argv[] = {"test_program", "--flag=value"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - - FILE* orig = stdout; - stdout = stderr; - - ASSERT_EXIT(chromeos::FlagHelper::Init(arraysize(argv), argv, "TestIntExit"), - ::testing::ExitedWithCode(EX_USAGE), - "ERROR: unknown command line flag 'flag'"); - - stdout = orig; -} - -// Test that when passing an incorrect/unparsable type to a command line flag, -// the program exits with code EX_DATAERR and outputs a corresponding message. -TEST_F(FlagHelperTest, BoolParseError) { - DEFINE_bool(bool_1, 0, "Test bool flag"); - - const char* argv[] = {"test_program", "--bool_1=value"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - - FILE* orig = stdout; - stdout = stderr; - - ASSERT_EXIT( - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestBoolParseError"), - ::testing::ExitedWithCode(EX_DATAERR), - "ERROR: illegal value 'value' specified for bool flag 'bool_1'"); - - stdout = orig; -} - -// Test that when passing an incorrect/unparsable type to a command line flag, -// the program exits with code EX_DATAERR and outputs a corresponding message. -TEST_F(FlagHelperTest, Int32ParseError) { - DEFINE_int32(int_1, 0, "Test int flag"); - - const char* argv[] = {"test_program", "--int_1=value"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - - FILE* orig = stdout; - stdout = stderr; - - ASSERT_EXIT(chromeos::FlagHelper::Init(arraysize(argv), - argv, - "TestInt32ParseError"), - ::testing::ExitedWithCode(EX_DATAERR), - "ERROR: illegal value 'value' specified for int flag 'int_1'"); - - stdout = orig; -} - -// Test that when passing an incorrect/unparsable type to a command line flag, -// the program exits with code EX_DATAERR and outputs a corresponding message. -TEST_F(FlagHelperTest, Int64ParseError) { - DEFINE_int64(int64_1, 0, "Test int64 flag"); - - const char* argv[] = {"test_program", "--int64_1=value" }; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - - FILE* orig = stdout; - stdout = stderr; - - ASSERT_EXIT( - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestInt64ParseError"), - ::testing::ExitedWithCode(EX_DATAERR), - "ERROR: illegal value 'value' specified for int64 flag " - "'int64_1'"); - - stdout = orig; -} - -// Test that when passing an incorrect/unparsable type to a command line flag, -// the program exits with code EX_DATAERR and outputs a corresponding message. -TEST_F(FlagHelperTest, UInt64ParseError) { - DEFINE_uint64(uint64_1, 0, "Test uint64 flag"); - - const char* argv[] = {"test_program", "--uint64_1=value"}; - base::CommandLine command_line(arraysize(argv), argv); - - chromeos::FlagHelper::GetInstance()->set_command_line_for_testing( - &command_line); - - FILE* orig = stdout; - stdout = stderr; - - ASSERT_EXIT( - chromeos::FlagHelper::Init(arraysize(argv), argv, "TestUInt64ParseError"), - ::testing::ExitedWithCode(EX_DATAERR), - "ERROR: illegal value 'value' specified for uint64 flag " - "'uint64_1'"); - - stdout = orig; -} - -} // namespace chromeos diff --git a/chromeos/glib/abstract_dbus_service.cc b/chromeos/glib/abstract_dbus_service.cc deleted file mode 100644 index d759427..0000000 --- a/chromeos/glib/abstract_dbus_service.cc +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include "chromeos/glib/abstract_dbus_service.h" - -namespace chromeos { -namespace dbus { - -bool AbstractDbusService::Register(const chromeos::dbus::BusConnection& conn) { - return RegisterExclusiveService(conn, - service_interface(), - service_name(), - service_path(), - service_object()); -} - -bool AbstractDbusService::Run() { - if (!main_loop()) { - LOG(ERROR) << "No run loop. Call Initialize before use."; - return false; - } - ::g_main_loop_run(main_loop()); - DLOG(INFO) << "Run() completed"; - return true; -} - -bool AbstractDbusService::Shutdown() { - ::g_main_loop_quit(main_loop()); - return true; -} - -} // namespace dbus -} // namespace chromeos diff --git a/chromeos/glib/abstract_dbus_service.h b/chromeos/glib/abstract_dbus_service.h deleted file mode 100644 index c351d2c..0000000 --- a/chromeos/glib/abstract_dbus_service.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_GLIB_ABSTRACT_DBUS_SERVICE_H_ -#define LIBCHROMEOS_CHROMEOS_GLIB_ABSTRACT_DBUS_SERVICE_H_ - -#include -#include - -namespace chromeos { - -// \precondition No functions in the dbus namespace can be called before -// ::g_type_init(); - -namespace dbus { -class CHROMEOS_EXPORT AbstractDbusService { - public: - virtual ~AbstractDbusService() {} - - // Setup the wrapped GObject and the GMainLoop - virtual bool Initialize() = 0; - virtual bool Reset() = 0; - - // Registers the GObject as a service with the system DBus - // TODO(wad) make this testable by making BusConn and Proxy - // subclassing friendly. - virtual bool Register(const chromeos::dbus::BusConnection& conn); - - // Starts the run loop - virtual bool Run(); - - // Stops the run loop - virtual bool Shutdown(); - - // Used internally during registration to set the - // proper service information. - virtual const char* service_name() const = 0; - virtual const char* service_path() const = 0; - virtual const char* service_interface() const = 0; - virtual GObject* service_object() const = 0; - - protected: - virtual GMainLoop* main_loop() = 0; -}; - -} // namespace dbus -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_GLIB_ABSTRACT_DBUS_SERVICE_H_ diff --git a/chromeos/glib/dbus.cc b/chromeos/glib/dbus.cc deleted file mode 100644 index b825434..0000000 --- a/chromeos/glib/dbus.cc +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/glib/dbus.h" - -#include -#include -#include - -#include -#include - -namespace chromeos { -namespace dbus { - -bool CallPtrArray(const Proxy& proxy, - const char* method, - glib::ScopedPtrArray* result) { - glib::ScopedError error; - - ::GType g_type_array = ::dbus_g_type_get_collection("GPtrArray", - DBUS_TYPE_G_OBJECT_PATH); - - - if (!::dbus_g_proxy_call(proxy.gproxy(), method, &Resetter(&error).lvalue(), - G_TYPE_INVALID, g_type_array, - &Resetter(result).lvalue(), G_TYPE_INVALID)) { - LOG(WARNING) << "CallPtrArray failed: " - << (error->message ? error->message : "Unknown Error."); - return false; - } - - return true; -} - -BusConnection GetSystemBusConnection() { - glib::ScopedError error; - ::DBusGConnection* result = ::dbus_g_bus_get(DBUS_BUS_SYSTEM, - &Resetter(&error).lvalue()); - if (!result) { - LOG(ERROR) << "dbus_g_bus_get(DBUS_BUS_SYSTEM) failed: " - << ((error.get() && error->message) ? - error->message : "Unknown Error"); - return BusConnection(nullptr); - } - // Set to not exit when system bus is disconnected. - // This fixes the problem where when the dbus daemon is stopped, exit is - // called which kills Chrome. - ::dbus_connection_set_exit_on_disconnect( - ::dbus_g_connection_get_connection(result), FALSE); - return BusConnection(result); -} - -BusConnection GetPrivateBusConnection(const char* address) { - // Since dbus-glib does not have an API like dbus_g_connection_open_private(), - // we have to implement our own. - - // We have to call _dbus_g_value_types_init() to register standard marshalers - // just like as dbus_g_bus_get() and dbus_g_connection_open() do, but the - // function is not exported. So we call GetPrivateBusConnection() which calls - // dbus_g_bus_get() here instead. Note that if we don't call - // _dbus_g_value_types_init(), we might get "WARNING **: No demarshaller - // registered for type xxxxx" error and might not be able to handle incoming - // signals nor method calls. - { - BusConnection system_bus_connection = GetSystemBusConnection(); - if (!system_bus_connection.HasConnection()) { - return system_bus_connection; // returns NULL connection. - } - } - - ::DBusError error; - ::dbus_error_init(&error); - - ::DBusGConnection* result = nullptr; - ::DBusConnection* raw_connection - = ::dbus_connection_open_private(address, &error); - if (!raw_connection) { - LOG(WARNING) << "dbus_connection_open_private failed: " << address; - return BusConnection(nullptr); - } - - if (!::dbus_bus_register(raw_connection, &error)) { - LOG(ERROR) << "dbus_bus_register failed: " - << (error.message ? error.message : "Unknown Error."); - ::dbus_error_free(&error); - // TODO(yusukes): We don't call dbus_connection_close() nor g_object_unref() - // here for now since these calls might interfere with IBusBus connections - // in libcros and Chrome. See the comment in ~InputMethodStatusConnection() - // function in platform/cros/chromeos_input_method.cc for details. - return BusConnection(nullptr); - } - - ::dbus_connection_setup_with_g_main( - raw_connection, nullptr /* default context */); - - // A reference count of |raw_connection| is transferred to |result|. You don't - // have to (and should not) unref the |raw_connection|. - result = ::dbus_connection_get_g_connection(raw_connection); - CHECK(result); - - ::dbus_connection_set_exit_on_disconnect( - ::dbus_g_connection_get_connection(result), FALSE); - - return BusConnection(result); -} - -bool RetrieveProperties(const Proxy& proxy, - const char* interface, - glib::ScopedHashTable* result) { - glib::ScopedError error; - - if (!::dbus_g_proxy_call(proxy.gproxy(), "GetAll", &Resetter(&error).lvalue(), - G_TYPE_STRING, interface, G_TYPE_INVALID, - ::dbus_g_type_get_map("GHashTable", G_TYPE_STRING, - G_TYPE_VALUE), - &Resetter(result).lvalue(), G_TYPE_INVALID)) { - LOG(WARNING) << "RetrieveProperties failed: " - << (error->message ? error->message : "Unknown Error."); - return false; - } - return true; -} - -Proxy::Proxy() - : object_(nullptr) { -} - -// Set |connect_to_name_owner| true if you'd like to use -// dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name(). -Proxy::Proxy(const BusConnection& connection, - const char* name, - const char* path, - const char* interface, - bool connect_to_name_owner) - : object_(GetGProxy( - connection, name, path, interface, connect_to_name_owner)) { -} - -// Equivalent to Proxy(connection, name, path, interface, false). -Proxy::Proxy(const BusConnection& connection, - const char* name, - const char* path, - const char* interface) - : object_(GetGProxy(connection, name, path, interface, false)) { -} - -// Creates a peer proxy using dbus_g_proxy_new_for_peer. -Proxy::Proxy(const BusConnection& connection, - const char* path, - const char* interface) - : object_(GetGPeerProxy(connection, path, interface)) { -} - -Proxy::Proxy(const Proxy& x) - : object_(x.object_) { - if (object_) - ::g_object_ref(object_); -} - -Proxy::~Proxy() { - if (object_) - ::g_object_unref(object_); -} - -/* static */ -Proxy::value_type Proxy::GetGProxy(const BusConnection& connection, - const char* name, - const char* path, - const char* interface, - bool connect_to_name_owner) { - value_type result = nullptr; - if (connect_to_name_owner) { - glib::ScopedError error; - result = ::dbus_g_proxy_new_for_name_owner(connection.object_, - name, - path, - interface, - &Resetter(&error).lvalue()); - if (!result) { - DLOG(ERROR) << "Failed to construct proxy: " - << (error->message ? error->message : "Unknown Error") - << ": " << path; - } - } else { - result = ::dbus_g_proxy_new_for_name(connection.object_, - name, - path, - interface); - if (!result) { - LOG(ERROR) << "Failed to construct proxy: " << path; - } - } - return result; -} - -/* static */ -Proxy::value_type Proxy::GetGPeerProxy(const BusConnection& connection, - const char* path, - const char* interface) { - value_type result = ::dbus_g_proxy_new_for_peer(connection.object_, - path, - interface); - if (!result) - LOG(ERROR) << "Failed to construct peer proxy: " << path; - - return result; -} - -bool RegisterExclusiveService(const BusConnection& connection, - const char* interface_name, - const char* service_name, - const char* service_path, - GObject* object) { - CHECK(object); - CHECK(interface_name); - CHECK(service_name); - // Create a proxy to DBus itself so that we can request to become a - // service name owner and then register an object at the related service path. - Proxy proxy = chromeos::dbus::Proxy(connection, - DBUS_SERVICE_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS); - // Exclusivity is determined by replacing any existing - // service, not queuing, and ensuring we are the primary - // owner after the name is ours. - glib::ScopedError err; - guint result = 0; - // TODO(wad) determine if we are moving away from using generated functions - if (!org_freedesktop_DBus_request_name(proxy.gproxy(), - service_name, - 0, - &result, - &Resetter(&err).lvalue())) { - LOG(ERROR) << "Unable to request service name: " - << (err->message ? err->message : "Unknown Error."); - return false; - } - - // Handle the error codes, releasing the name if exclusivity conditions - // are not met. - bool needs_release = false; - if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { - LOG(ERROR) << "Failed to become the primary owner. Releasing . . ."; - needs_release = true; - } - if (result == DBUS_REQUEST_NAME_REPLY_EXISTS) { - LOG(ERROR) << "Service name exists: " << service_name; - return false; - } else if (result == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { - LOG(ERROR) << "Service name request enqueued despite our flags. Releasing"; - needs_release = true; - } - LOG_IF(WARNING, result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) - << "Service name already owned by this process"; - if (needs_release) { - if (!org_freedesktop_DBus_release_name( - proxy.gproxy(), - service_name, - &result, - &Resetter(&err).lvalue())) { - LOG(ERROR) << "Unabled to release service name: " - << (err->message ? err->message : "Unknown Error."); - } - DLOG(INFO) << "ReleaseName returned code " << result; - return false; - } - - // Determine a path from the service name and register the object. - dbus_g_connection_register_g_object(connection.g_connection(), - service_path, - object); - return true; -} - -void CallMethodWithNoArguments(const char* service_name, - const char* path, - const char* interface_name, - const char* method_name) { - Proxy proxy(dbus::GetSystemBusConnection(), - service_name, - path, - interface_name); - ::dbus_g_proxy_call_no_reply(proxy.gproxy(), method_name, G_TYPE_INVALID); -} - -void SignalWatcher::StartMonitoring(const std::string& interface, - const std::string& signal) { - DCHECK(interface_.empty()) << "StartMonitoring() must be called only once"; - interface_ = interface; - signal_ = signal; - - // Snoop on D-Bus messages so we can get notified about signals. - DBusConnection* dbus_conn = dbus_g_connection_get_connection( - GetSystemBusConnection().g_connection()); - DCHECK(dbus_conn); - - DBusError error; - dbus_error_init(&error); - dbus_bus_add_match(dbus_conn, GetDBusMatchString().c_str(), &error); - if (dbus_error_is_set(&error)) { - LOG(DFATAL) << "Got error while adding D-Bus match rule: " << error.name - << " (" << error.message << ")"; - } - - if (!dbus_connection_add_filter(dbus_conn, - &SignalWatcher::FilterDBusMessage, - this, // user_data - nullptr)) { // free_data_function - LOG(DFATAL) << "Unable to add D-Bus filter"; - } -} - -SignalWatcher::~SignalWatcher() { - if (interface_.empty()) - return; - - DBusConnection* dbus_conn = dbus_g_connection_get_connection( - dbus::GetSystemBusConnection().g_connection()); - DCHECK(dbus_conn); - - dbus_connection_remove_filter(dbus_conn, - &SignalWatcher::FilterDBusMessage, - this); - - DBusError error; - dbus_error_init(&error); - dbus_bus_remove_match(dbus_conn, GetDBusMatchString().c_str(), &error); - if (dbus_error_is_set(&error)) { - LOG(DFATAL) << "Got error while removing D-Bus match rule: " << error.name - << " (" << error.message << ")"; - } -} - -std::string SignalWatcher::GetDBusMatchString() const { - return base::StringPrintf("type='signal', interface='%s', member='%s'", - interface_.c_str(), signal_.c_str()); -} - -/* static */ -DBusHandlerResult SignalWatcher::FilterDBusMessage(DBusConnection* dbus_conn, - DBusMessage* message, - void* data) { - SignalWatcher* self = static_cast(data); - if (dbus_message_is_signal( - message, self->interface_.c_str(), self->signal_.c_str())) { - self->OnSignal(message); - return DBUS_HANDLER_RESULT_HANDLED; - } else { - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } -} - -} // namespace dbus -} // namespace chromeos diff --git a/chromeos/glib/dbus.h b/chromeos/glib/dbus.h deleted file mode 100644 index 9ef012e..0000000 --- a/chromeos/glib/dbus.h +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_GLIB_DBUS_H_ -#define LIBCHROMEOS_CHROMEOS_GLIB_DBUS_H_ - -#include -#include - -#include -#include - -#include "base/logging.h" -#include -#include - -struct DBusMessage; -struct DBusConnection; - -namespace chromeos { - -// \precondition No functions in the dbus namespace can be called before -// ::g_type_init(); - -namespace dbus { - -// \brief BusConnection manages the ref-count for a ::DBusGConnection*. -// -// A BusConnection has reference semantics bound to a particular communication -// bus. -// -// \models Copyable, Assignable -// \related GetSystemBusConnection() - -class CHROMEOS_EXPORT BusConnection { - public: - typedef ::DBusGConnection* value_type; - - BusConnection(const BusConnection& x) : object_(x.object_) { - if (object_) - ::dbus_g_connection_ref(object_); - } - - ~BusConnection() { - if (object_) - ::dbus_g_connection_unref(object_); - } - - BusConnection& operator=(BusConnection x) { - swap(*this, x); - return *this; - } - - const value_type& g_connection() const { - DCHECK(object_) << "referencing an empty connection"; - return object_; - } - - operator bool() const { return object_; } - - bool HasConnection() const { return object_; } - - private: - friend void swap(BusConnection& x, BusConnection& y); - - friend class Proxy; - friend BusConnection GetSystemBusConnection(); - friend BusConnection GetPrivateBusConnection(const char* address); - - // Constructor takes ownership - CHROMEOS_PRIVATE explicit BusConnection(::DBusGConnection* x) : object_(x) {} - - value_type object_; -}; - -inline void swap(BusConnection& x, BusConnection& y) { - std::swap(x.object_, y.object_); -} - -// \brief Proxy manages the ref-count for a ::DBusGProxy*. -// -// Proxy has reference semantics and represents a connection to on object on -// the bus. A proxy object is constructed with a connection to a bus, a name -// to an entity on the bus, a path to an object owned by the entity, and an -// interface protocol name used to communicate with the object. - -class CHROMEOS_EXPORT Proxy { - public: - typedef ::DBusGProxy* value_type; - - Proxy(); - - // Set |connect_to_name_owner| true if you'd like to use - // dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name(). - Proxy(const BusConnection& connection, - const char* name, - const char* path, - const char* interface, - bool connect_to_name_owner); - - // Equivalent to Proxy(connection, name, path, interface, false). - Proxy(const BusConnection& connection, - const char* name, - const char* path, - const char* interface); - - // Creates a peer proxy using dbus_g_proxy_new_for_peer. - Proxy(const BusConnection& connection, - const char* path, - const char* interface); - - Proxy(const Proxy& x); - - ~Proxy(); - - Proxy& operator=(Proxy x) { - swap(*this, x); - return *this; - } - - const char* path() const { - DCHECK(object_) << "referencing an empty proxy"; - return ::dbus_g_proxy_get_path(object_); - } - - // gproxy() returns a reference to the underlying ::DBusGProxy*. As this - // library evolves, the gproxy() will be moved to be private. - - const value_type& gproxy() const { - DCHECK(object_) << "referencing an empty proxy"; - return object_; - } - - operator bool() const { return object_; } - - private: - CHROMEOS_PRIVATE static value_type GetGProxy(const BusConnection& connection, - const char* name, - const char* path, - const char* interface, - bool connect_to_name_owner); - - CHROMEOS_PRIVATE static value_type GetGPeerProxy( - const BusConnection& connection, - const char* path, - const char* interface); - - CHROMEOS_PRIVATE operator int() const; // for safe bool cast - friend void swap(Proxy& x, Proxy& y); - - value_type object_; -}; - -inline void swap(Proxy& x, Proxy& y) { - std::swap(x.object_, y.object_); -} - -// \brief RegisterExclusiveService configures a GObject to run as a service on -// a supplied ::BusConnection. -// -// RegisterExclusiveService encapsulates the process of configuring the -// supplied \param object at \param service_path on the \param connection. -// Exclusivity is ensured by replacing any existing services at that named -// location and confirming that the connection is the primary owner. -// -// Type information for the \param object must be installed with -// dbus_g_object_type_install_info prior to use. - -CHROMEOS_EXPORT bool RegisterExclusiveService(const BusConnection& connection, - const char* interface_name, - const char* service_name, - const char* service_path, - GObject* object); - -template // F is a function signature -class MonitorConnection; - -template -class MonitorConnection { - public: - MonitorConnection(const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1), - void* object) - : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} - - static void Run(::DBusGProxy*, A1 x, MonitorConnection* self) { - self->monitor_(self->object_, x); - } - const Proxy& proxy() const { return proxy_; } - const std::string& name() const { return name_; } - - private: - Proxy proxy_; - std::string name_; - void (*monitor_)(void*, A1); - void* object_; -}; - -template -class MonitorConnection { - public: - MonitorConnection(const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1, A2), - void* object) - : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} - - static void Run(::DBusGProxy*, A1 x, A2 y, MonitorConnection* self) { - self->monitor_(self->object_, x, y); - } - const Proxy& proxy() const { return proxy_; } - const std::string& name() const { return name_; } - - private: - Proxy proxy_; - std::string name_; - void (*monitor_)(void*, A1, A2); - void* object_; -}; - -template -class MonitorConnection { - public: - MonitorConnection(const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1, A2, A3), - void* object) - : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} - - static void Run(::DBusGProxy*, A1 x, A2 y, A3 z, MonitorConnection* self) { - self->monitor_(self->object_, x, y, z); - } - const Proxy& proxy() const { return proxy_; } - const std::string& name() const { return name_; } - - private: - Proxy proxy_; - std::string name_; - void (*monitor_)(void*, A1, A2, A3); - void* object_; -}; - -template -class MonitorConnection { - public: - MonitorConnection(const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1, A2, A3, A4), - void* object) - : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {} - - static void Run(::DBusGProxy*, - A1 x, - A2 y, - A3 z, - A4 w, - MonitorConnection* self) { - self->monitor_(self->object_, x, y, z, w); - } - const Proxy& proxy() const { return proxy_; } - const std::string& name() const { return name_; } - - private: - Proxy proxy_; - std::string name_; - void (*monitor_)(void*, A1, A2, A3, A4); - void* object_; -}; - -template -MonitorConnection* Monitor(const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1), - void* object) { - typedef MonitorConnection ConnectionType; - - ConnectionType* result = new ConnectionType(proxy, name, monitor, object); - - ::dbus_g_proxy_add_signal( - proxy.gproxy(), name, glib::type_to_gtypeid(), G_TYPE_INVALID); - ::dbus_g_proxy_connect_signal( - proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); - return result; -} - -template -MonitorConnection* Monitor(const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1, A2), - void* object) { - typedef MonitorConnection ConnectionType; - - ConnectionType* result = new ConnectionType(proxy, name, monitor, object); - - ::dbus_g_proxy_add_signal(proxy.gproxy(), - name, - glib::type_to_gtypeid(), - glib::type_to_gtypeid(), - G_TYPE_INVALID); - ::dbus_g_proxy_connect_signal( - proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); - return result; -} - -template -MonitorConnection* Monitor(const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1, A2, A3), - void* object) { - typedef MonitorConnection ConnectionType; - - ConnectionType* result = new ConnectionType(proxy, name, monitor, object); - - ::dbus_g_proxy_add_signal(proxy.gproxy(), - name, - glib::type_to_gtypeid(), - glib::type_to_gtypeid(), - glib::type_to_gtypeid(), - G_TYPE_INVALID); - ::dbus_g_proxy_connect_signal( - proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); - return result; -} - -template -MonitorConnection* Monitor( - const Proxy& proxy, - const char* name, - void (*monitor)(void*, A1, A2, A3, A4), - void* object) { - typedef MonitorConnection ConnectionType; - - ConnectionType* result = new ConnectionType(proxy, name, monitor, object); - - ::dbus_g_proxy_add_signal(proxy.gproxy(), - name, - glib::type_to_gtypeid(), - glib::type_to_gtypeid(), - glib::type_to_gtypeid(), - glib::type_to_gtypeid(), - G_TYPE_INVALID); - ::dbus_g_proxy_connect_signal( - proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr); - return result; -} - -template -void Disconnect(MonitorConnection* connection) { - typedef MonitorConnection ConnectionType; - - ::dbus_g_proxy_disconnect_signal(connection->proxy().gproxy(), - connection->name().c_str(), - G_CALLBACK(&ConnectionType::Run), - connection); - delete connection; -} - -// \brief call_PtrArray() invokes a method on a proxy returning a -// glib::PtrArray. -// -// CallPtrArray is the first instance of what is likely to be a general -// way to make method calls to a proxy. It will likely be replaced with -// something like Call(proxy, method, arg1, arg2, ..., ResultType*) in the -// future. However, I don't yet have enough cases to generalize from. - -CHROMEOS_EXPORT bool CallPtrArray(const Proxy& proxy, - const char* method, - glib::ScopedPtrArray* result); - -// \brief RetrieveProperty() retrieves a property of an object associated with a -// proxy. -// -// Given a proxy to an object supporting the org.freedesktop.DBus.Properties -// interface, the RetrieveProperty() call will retrieve a property of the -// specified interface on the object storing it in \param result and returning -// \true. If the dbus call fails or the object returned is not of type \param T, -// then \false is returned and \param result is unchanged. -// -// \example -// Proxy proxy(GetSystemBusConnection(), -// "org.freedesktop.DeviceKit.Power", // A named entity on the bus -// battery_name, // Path to a battery on the bus -// "org.freedesktop.DBus.Properties") // Properties interface -// -// double x; -// if (RetrieveProperty(proxy, -// "org.freedesktop.DeviceKit.Power.Device", -// "percentage") -// std::cout << "Battery charge is " << x << "% of capacity."; -// \end_example - -template -inline bool RetrieveProperty(const Proxy& proxy, - const char* interface, - const char* property, - T* result) { - glib::ScopedError error; - glib::Value value; - - if (!::dbus_g_proxy_call(proxy.gproxy(), "Get", &Resetter(&error).lvalue(), - G_TYPE_STRING, interface, - G_TYPE_STRING, property, - G_TYPE_INVALID, - G_TYPE_VALUE, &value, - G_TYPE_INVALID)) { - LOG(ERROR) << "Getting property failed: " - << (error->message ? error->message : "Unknown Error."); - return false; - } - return glib::Retrieve(value, result); -} - -// \brief RetrieveProperties returns a HashTable of all properties for the -// specified interface. - -CHROMEOS_EXPORT bool RetrieveProperties(const Proxy& proxy, - const char* interface, - glib::ScopedHashTable* result); - -// \brief Returns a connection to the system bus. - -CHROMEOS_EXPORT BusConnection GetSystemBusConnection(); - -// \brief Returns a private connection to a bus at |address|. - -CHROMEOS_EXPORT BusConnection GetPrivateBusConnection(const char* address); - -// \brief Calls a method |method_name| with no arguments per the given |path| -// and |interface_name|. Ignores return value. - -CHROMEOS_EXPORT void CallMethodWithNoArguments(const char* service_name, - const char* path, - const char* interface_name, - const char* method_name); - -// \brief Low-level signal monitor base class. -// -// Used when there is no definite named signal sender (that Proxy -// could be used for). - -class CHROMEOS_EXPORT SignalWatcher { - public: - SignalWatcher() {} - ~SignalWatcher(); - void StartMonitoring(const std::string& interface, const std::string& signal); - - private: - // Callback invoked on the given signal arrival. - virtual void OnSignal(DBusMessage* message) = 0; - - // Returns a string matching the D-Bus messages that we want to listen for. - CHROMEOS_PRIVATE std::string GetDBusMatchString() const; - - // A D-Bus message filter to receive signals. - CHROMEOS_PRIVATE static DBusHandlerResult FilterDBusMessage( - DBusConnection* dbus_conn, - DBusMessage* message, - void* data); - std::string interface_; - std::string signal_; -}; - -} // namespace dbus -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_GLIB_DBUS_H_ diff --git a/chromeos/glib/object.h b/chromeos/glib/object.h deleted file mode 100644 index 9bbdb33..0000000 --- a/chromeos/glib/object.h +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_GLIB_OBJECT_H_ -#define LIBCHROMEOS_CHROMEOS_GLIB_OBJECT_H_ - -#include -#include - -#include -#include -#include - -#include -#include -#include - -namespace chromeos { - -namespace details { // NOLINT - -// \brief ResetHelper is a private class for use with Resetter(). -// -// ResetHelper passes ownership of a pointer to a scoped pointer type with reset -// on destruction. - -template // T models ScopedPtr -class ResetHelper { - public: - typedef typename T::element_type element_type; - - explicit ResetHelper(T* x) - : ptr_(nullptr), - scoped_(x) { - } - ~ResetHelper() { - scoped_->reset(ptr_); - } - element_type*& lvalue() { - return ptr_; - } - - private: - element_type* ptr_; - T* scoped_; -}; - -} // namespace details - -// \brief Resetter() is a utility function for passing pointers to -// scoped pointers. -// -// The Resetter() function return a temporary object containing an lvalue of -// \code T::element_type which can be assigned to. When the temporary object -// destructs, the associated scoped pointer is reset with the lvalue. It is of -// general use when a pointer is returned as an out-argument. -// -// \example -// void function(int** x) { -// *x = new int(10); -// } -// ... -// scoped_ptr x; -// function(Resetter(x).lvalue()); -// -// \end_example - -template // T models ScopedPtr -details::ResetHelper Resetter(T* x) { - return details::ResetHelper(x); -} - -// \precondition No functions in the glib namespace can be called before -// ::g_type_init(); - -namespace glib { - -// \brief type_to_gtypeid is a type function mapping from a canonical type to -// the GType typeid for the associated GType (see type_to_gtype). - -template ::GType type_to_gtypeid(); - -template < > -inline ::GType type_to_gtypeid() { - return G_TYPE_STRING; -} -template < > -inline ::GType type_to_gtypeid() { - return G_TYPE_STRING; -} -template < > -inline ::GType type_to_gtypeid< ::uint8_t>() { - return G_TYPE_UCHAR; -} -template < > -inline ::GType type_to_gtypeid() { - return G_TYPE_DOUBLE; -} -template < > -inline ::GType type_to_gtypeid() { - return G_TYPE_BOOLEAN; -} -class Value; -template < > -inline ::GType type_to_gtypeid() { - return G_TYPE_VALUE; -} - -template < > -inline ::GType type_to_gtypeid< ::uint32_t>() { - // REVISIT (seanparent) : There currently isn't any G_TYPE_UINT32, this code - // assumes sizeof(guint) == sizeof(guint32). Need a static_assert to assert - // that. - return G_TYPE_UINT; -} - -template < > -inline ::GType type_to_gtypeid< ::int64_t>() { - return G_TYPE_INT64; -} - -template < > -inline ::GType type_to_gtypeid< ::int32_t>() { - return G_TYPE_INT; -} - -// \brief Value (and Retrieve) support using std::string as well as const char* -// by promoting from const char* to the string. promote_from provides a mapping -// for this promotion (and possibly others in the future). - -template struct promotes_from { - typedef T type; -}; -template < > struct promotes_from { - typedef const char* type; -}; - -// \brief RawCast converts from a GValue to a value of a canonical type. -// -// RawCast is a low level function. Generally, use Cast() instead. -// -// \precondition \param x contains a value of type \param T. - -template -inline T RawCast(const ::GValue& x) { - // Use static_assert() to issue a meaningful compile-time error. - // To prevent this from happening for all references to RawCast, use sizeof(T) - // to make static_assert depend on type T and therefore prevent binding it - // unconditionally until the actual RawCast instantiation happens. - static_assert(sizeof(T) == 0, "Using RawCast on unsupported type"); - return T(); -} - -template < > -inline const char* RawCast(const ::GValue& x) { - return static_cast(::g_value_get_string(&x)); -} -template < > -inline double RawCast(const ::GValue& x) { - return static_cast(::g_value_get_double(&x)); -} -template < > -inline bool RawCast(const ::GValue& x) { - return static_cast(::g_value_get_boolean(&x)); -} -template < > -inline ::uint32_t RawCast< ::uint32_t>(const ::GValue& x) { - return static_cast< ::uint32_t>(::g_value_get_uint(&x)); -} -template < > -inline ::uint8_t RawCast< ::uint8_t>(const ::GValue& x) { - return static_cast< ::uint8_t>(::g_value_get_uchar(&x)); -} -template < > -inline ::int64_t RawCast< ::int64_t>(const ::GValue& x) { - return static_cast< ::int64_t>(::g_value_get_int64(&x)); -} -template < > -inline ::int32_t RawCast< ::int32_t>(const ::GValue& x) { - return static_cast< ::int32_t>(::g_value_get_int(&x)); -} - -inline void RawSet(GValue* x, const std::string& v) { - ::g_value_set_string(x, v.c_str()); -} -inline void RawSet(GValue* x, const char* v) { - ::g_value_set_string(x, v); -} -inline void RawSet(GValue* x, double v) { - ::g_value_set_double(x, v); -} -inline void RawSet(GValue* x, bool v) { - ::g_value_set_boolean(x, v); -} -inline void RawSet(GValue* x, ::uint32_t v) { - ::g_value_set_uint(x, v); -} -inline void RawSet(GValue* x, ::uint8_t v) { - ::g_value_set_uchar(x, v); -} -inline void RawSet(GValue* x, ::int64_t v) { - ::g_value_set_int64(x, v); -} -inline void RawSet(GValue* x, ::int32_t v) { - ::g_value_set_int(x, v); -} - -// \brief Value is a data type for managing GValues. -// -// A Value is a polymorphic container holding at most a single value. -// -// The Value wrapper ensures proper initialization, copies, and assignment of -// GValues. -// -// \note GValues are equationally incomplete and so can't support proper -// equality. The semantics of copy are verified with equality of retrieved -// values. - -class Value : public ::GValue { - public: - Value() - : GValue() { - } - explicit Value(const ::GValue& x) - : GValue() { - *this = *static_cast(&x); - } - template - explicit Value(T x) - : GValue() { - ::g_value_init(this, - type_to_gtypeid::type>()); - RawSet(this, x); - } - Value(const Value& x) - : GValue() { - if (x.empty()) - return; - ::g_value_init(this, G_VALUE_TYPE(&x)); - ::g_value_copy(&x, this); - } - ~Value() { - clear(); - } - Value& operator=(const Value& x) { - if (this == &x) - return *this; - clear(); - if (x.empty()) - return *this; - ::g_value_init(this, G_VALUE_TYPE(&x)); - ::g_value_copy(&x, this); - return *this; - } - template - Value& operator=(const T& x) { - clear(); - ::g_value_init(this, - type_to_gtypeid::type>()); - RawSet(this, x); - return *this; - } - - // Lower-case names to follow STL container conventions. - - void clear() { - if (!empty()) - ::g_value_unset(this); - } - - bool empty() const { - return G_VALUE_TYPE(this) == G_TYPE_INVALID; - } -}; - -template < > -inline const Value* RawCast(const ::GValue& x) { - return static_cast(&x); -} - -// \brief Retrieve gets a value from a GValue. -// -// \postcondition If \param x contains a value of type \param T, then the -// value is copied to \param result and \true is returned. Otherwise, \param -// result is unchanged and \false is returned. -// -// \precondition \param result is not \nullptr. - -template -bool Retrieve(const ::GValue& x, T* result) { - if (!G_VALUE_HOLDS(&x, type_to_gtypeid::type>())) { - LOG(WARNING) << "GValue retrieve failed. Expected: " - << g_type_name(type_to_gtypeid::type>()) - << ", Found: " << g_type_name(G_VALUE_TYPE(&x)); - return false; - } - - *result = RawCast::type>(x); - return true; -} - -inline bool Retrieve(const ::GValue& x, Value* result) { - *result = Value(x); - return true; -} - -// \brief ScopedError holds a ::GError* and deletes it on destruction. - -struct FreeError { - void operator()(::GError* x) const { - if (x) - ::g_error_free(x); - } -}; - -typedef ::scoped_ptr< ::GError, FreeError> ScopedError; - -// \brief ScopedArray holds a ::GArray* and deletes both the container and the -// segment containing the elements on destruction. - -struct FreeArray { - void operator()(::GArray* x) const { - if (x) - ::g_array_free(x, TRUE); - } -}; - -typedef ::scoped_ptr< ::GArray, FreeArray> ScopedArray; - -// \brief ScopedPtrArray adapts ::GPtrArray* to conform to the standard -// container requirements. -// -// \note ScopedPtrArray is only partially implemented and is being fleshed out -// as needed. -// -// \models Random Access Container, Back Insertion Sequence, ScopedPtrArray is -// not copyable and equationally incomplete. - -template // T models pointer -class ScopedPtrArray { - public: - typedef ::GPtrArray element_type; - - typedef T value_type; - typedef const value_type& const_reference; - typedef value_type* iterator; - typedef const value_type* const_iterator; - - ScopedPtrArray() - : object_(0) { - } - - explicit ScopedPtrArray(::GPtrArray* x) - : object_(x) { - } - - ~ScopedPtrArray() { - clear(); - } - - iterator begin() { - return iterator(object_ ? object_->pdata : nullptr); - } - iterator end() { - return begin() + size(); - } - const_iterator begin() const { - return const_iterator(object_ ? object_->pdata : nullptr); - } - const_iterator end() const { - return begin() + size(); - } - - // \precondition x is a pointer to an object allocated with g_new(). - - void push_back(T x) { - if (!object_) - object_ = ::g_ptr_array_sized_new(1); - ::g_ptr_array_add(object_, ::gpointer(x)); - } - - T& operator[](std::size_t n) { - DCHECK(!(size() < n)) << "ScopedPtrArray index out-of-bound."; - return *(begin() + n); - } - - std::size_t size() const { - return object_ ? object_->len : 0; - } - - void clear() { - if (object_) { - std::for_each(begin(), end(), FreeHelper()); - ::g_ptr_array_free(object_, true); - object_ = nullptr; - } - } - - void reset(::GPtrArray* p = nullptr) { - if (p != object_) { - clear(); - object_ = p; - } - } - - private: - struct FreeHelper { - void operator()(T x) const { - ::g_free(::gpointer(x)); - } - }; - - template - friend void swap(ScopedPtrArray& x, ScopedPtrArray& y); - - ::GPtrArray* object_; - - DISALLOW_COPY_AND_ASSIGN(ScopedPtrArray); -}; - -template -inline void swap(ScopedPtrArray& x, ScopedPtrArray& y) { - std::swap(x.object_, y.object_); -} - -// \brief ScopedHashTable manages the lifetime of a ::GHashTable* with an -// interface compatibitle with a scoped ptr. -// -// The ScopedHashTable is also the start of an adaptor to model a standard -// Container. The standard for an associative container would have an iterator -// returning a key value pair. However, that isn't possible with -// ::GHashTable because there is no interface returning a reference to the -// key value pair, only to retrieve the keys and values and individual elements. -// -// So the standard interface of find() wouldn't work. I considered implementing -// operator[] and count() - operator []. So retrieving a value would look like: -// -// if (table.count(key)) -// success = Retrieve(table[key], &value); -// -// But that requires hashing the key twice. -// For now I implemented a Retrieve member function to follow the pattern -// developed elsewhere in the code. -// -// bool success = Retrieve(key, &x); -// -// This is also a template to retrieve the corect type from the stored GValue -// type. -// -// I may revisit this and use scoped_ptr_malloc and a non-member function -// Retrieve() in the future. The Retrieve pattern is becoming common enough -// that I want to give some thought as to how to generalize it further. - -class ScopedHashTable { - public: - typedef ::GHashTable element_type; - - ScopedHashTable() - : object_(nullptr) { - } - - explicit ScopedHashTable(::GHashTable* p) - : object_(p) { - } - - ~ScopedHashTable() { - clear(); - } - - template - bool Retrieve(const char* key, T* result) const { - DCHECK(object_) << "Retrieve on empty ScopedHashTable."; - if (!object_) - return false; - - ::gpointer ptr = ::g_hash_table_lookup(object_, key); - if (!ptr) - return false; - return glib::Retrieve(*static_cast< ::GValue*>(ptr), result); - } - - void clear() { - if (object_) { - ::g_hash_table_unref(object_); - object_ = nullptr; - } - } - - GHashTable* get() { - return object_; - } - - void reset(::GHashTable* p = nullptr) { - if (p != object_) { - clear(); - object_ = p; - } - } - - private: - ::GHashTable* object_; -}; - -} // namespace glib -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_GLIB_OBJECT_H_ diff --git a/chromeos/glib/object_unittest.cc b/chromeos/glib/object_unittest.cc deleted file mode 100644 index 1e53296..0000000 --- a/chromeos/glib/object_unittest.cc +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2009 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/glib/object.h" - -#include - -#include -#include -#include -#include - -using chromeos::glib::ScopedPtrArray; -using chromeos::glib::ScopedError; -using chromeos::glib::Retrieve; -using chromeos::glib::Value; -using chromeos::Resetter; - -namespace { // NOLINT - -template -void SetRetrieveTest(const T& x) { - Value tmp(x); - T result; - EXPECT_TRUE(Retrieve(tmp, &result)); - EXPECT_EQ(result, x); -} - -void ModifyValue(Value* x) { - *x = 1.0 / 1231415926.0; // An unlikely value -} - -template -void MutableRegularTestValue(const T& x, O modify) { - Value tmp(x); - Value y = tmp; // copy-construction - T result; - EXPECT_TRUE(Retrieve(y, &result)); - EXPECT_EQ(result, x); - modify(&y); - LOG(INFO) << "Warning Expected."; - EXPECT_TRUE(!(Retrieve(y, &result) && result == x)); - y = tmp; // assignment - EXPECT_TRUE(Retrieve(y, &result)); - EXPECT_EQ(result, x); - modify(&y); - LOG(INFO) << "Warning Expected."; - EXPECT_TRUE(!(Retrieve(y, &result) && result == x)); -} - -void OutArgument(int** x) { - *x = new int(10); // NOLINT -} - -} // namespace - -TEST(ResetterTest, All) { - scoped_ptr x; - OutArgument(&Resetter(&x).lvalue()); - EXPECT_EQ(*x, 10); -} - -TEST(RetrieveTest, Types) { - SetRetrieveTest(std::string("Hello!")); - SetRetrieveTest(static_cast(10)); - SetRetrieveTest(10.5); - SetRetrieveTest(true); -} - -TEST(ValueTest, All) { - Value x; // default construction - Value y = x; // copy with default value - x = y; // assignment with default value - Value z(1.5); - x = z; // assignment to default value - MutableRegularTestValue(std::string("Hello!"), &ModifyValue); -} - -TEST(ScopedErrorTest, All) { - ScopedError a; // default construction - ScopedError b(::g_error_new(::g_quark_from_static_string("error"), -1, - "")); // constructor - ::GError* c = ::g_error_new(::g_quark_from_static_string("error"), -1, - ""); - ::GError* d = ::g_error_new(::g_quark_from_static_string("error"), -1, - ""); - a.reset(c); // reset form 1 - (void)d; -} - -TEST(ScopedPtrArrayTest, Construction) { - const char item[] = "a string"; - char* a = static_cast(::g_malloc(sizeof(item))); - std::strcpy(a, &item[0]); // NOLINT - - ::GPtrArray* array = ::g_ptr_array_new(); - ::g_ptr_array_add(array, ::gpointer(a)); - - ScopedPtrArray x(array); - EXPECT_EQ(x.size(), 1); - EXPECT_EQ(x[0], a); // indexing -} - -TEST(ScopedPtrArrayTest, Reset) { - const char item[] = "a string"; - char* a = static_cast(::g_malloc(sizeof(item))); - std::strcpy(a, &item[0]); // NOLINT - - ScopedPtrArray x; // default construction - x.push_back(a); - EXPECT_EQ(x.size(), 1); - x.reset(); - EXPECT_EQ(x.size(), 0); - - char* b = static_cast(::g_malloc(sizeof(item))); - std::strcpy(b, &item[0]); // NOLINT - - ::GPtrArray* array = ::g_ptr_array_new(); - ::g_ptr_array_add(array, ::gpointer(b)); - - x.reset(array); - EXPECT_EQ(x.size(), 1); -} - -TEST(ScopedPtrArrayTest, Iteration) { - char* a[] = { static_cast(::g_malloc(1)), - static_cast(::g_malloc(1)), static_cast(::g_malloc(1)) }; - - ScopedPtrArray x; - std::copy(&a[0], &a[3], std::back_inserter(x)); - EXPECT_TRUE(std::equal(x.begin(), x.end(), &a[0])); -} - diff --git a/chromeos/http/curl_api.cc b/chromeos/http/curl_api.cc deleted file mode 100644 index 215c943..0000000 --- a/chromeos/http/curl_api.cc +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { -namespace http { - -namespace { - -static_assert(CURLOPTTYPE_LONG == 0 && - CURLOPTTYPE_OBJECTPOINT == 10000 && - CURLOPTTYPE_FUNCTIONPOINT == 20000 && - CURLOPTTYPE_OFF_T == 30000, - "CURL option types are expected to be multiples of 10000"); - -inline bool VerifyOptionType(CURLoption option, int expected_type) { - int option_type = (static_cast(option) / 10000) * 10000; - return (option_type == expected_type); -} - -} // anonymous namespace - -CurlApi::CurlApi() { - curl_global_init(CURL_GLOBAL_ALL); -} - -CurlApi::~CurlApi() { - curl_global_cleanup(); -} - -CURL* CurlApi::EasyInit() { - return curl_easy_init(); -} - -void CurlApi::EasyCleanup(CURL* curl) { - curl_easy_cleanup(curl); -} - -CURLcode CurlApi::EasySetOptInt(CURL* curl, CURLoption option, int value) { - CHECK(VerifyOptionType(option, CURLOPTTYPE_LONG)) - << "Only options that expect a LONG data type must be specified here"; - // CURL actually uses "long" type, so have to make sure we feed it what it - // expects. - // NOLINTNEXTLINE(runtime/int) - return curl_easy_setopt(curl, option, static_cast(value)); -} - -CURLcode CurlApi::EasySetOptStr(CURL* curl, - CURLoption option, - const std::string& value) { - CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT)) - << "Only options that expect a STRING data type must be specified here"; - return curl_easy_setopt(curl, option, value.c_str()); -} - -CURLcode CurlApi::EasySetOptPtr(CURL* curl, CURLoption option, void* value) { - CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT)) - << "Only options that expect a pointer data type must be specified here"; - return curl_easy_setopt(curl, option, value); -} - -CURLcode CurlApi::EasySetOptCallback(CURL* curl, - CURLoption option, - intptr_t address) { - CHECK(VerifyOptionType(option, CURLOPTTYPE_FUNCTIONPOINT)) - << "Only options that expect a function pointers must be specified here"; - return curl_easy_setopt(curl, option, address); -} - -CURLcode CurlApi::EasySetOptOffT(CURL* curl, - CURLoption option, - curl_off_t value) { - CHECK(VerifyOptionType(option, CURLOPTTYPE_OFF_T)) - << "Only options that expect a large data size must be specified here"; - return curl_easy_setopt(curl, option, value); -} - -CURLcode CurlApi::EasyPerform(CURL* curl) { - return curl_easy_perform(curl); -} - -CURLcode CurlApi::EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const { - CHECK_EQ(CURLINFO_LONG, info & CURLINFO_TYPEMASK) << "Wrong option type"; - long data = 0; // NOLINT(runtime/int) - curl expects a long here. - CURLcode code = curl_easy_getinfo(curl, info, &data); - if (code == CURLE_OK) - *value = static_cast(data); - return code; -} - -CURLcode CurlApi::EasyGetInfoDbl(CURL* curl, - CURLINFO info, - double* value) const { - CHECK_EQ(CURLINFO_DOUBLE, info & CURLINFO_TYPEMASK) << "Wrong option type"; - return curl_easy_getinfo(curl, info, value); -} - -CURLcode CurlApi::EasyGetInfoStr(CURL* curl, - CURLINFO info, - std::string* value) const { - CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type"; - char* data = nullptr; - CURLcode code = curl_easy_getinfo(curl, info, &data); - if (code == CURLE_OK) - *value = data; - return code; -} - -CURLcode CurlApi::EasyGetInfoPtr(CURL* curl, - CURLINFO info, - void** value) const { - // CURL uses "string" type for generic pointer info. Go figure. - CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type"; - return curl_easy_getinfo(curl, info, value); -} - -std::string CurlApi::EasyStrError(CURLcode code) const { - return curl_easy_strerror(code); -} - -CURLM* CurlApi::MultiInit() { - return curl_multi_init(); -} - -CURLMcode CurlApi::MultiCleanup(CURLM* multi_handle) { - return curl_multi_cleanup(multi_handle); -} - -CURLMsg* CurlApi::MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) { - return curl_multi_info_read(multi_handle, msgs_in_queue); -} - -CURLMcode CurlApi::MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) { - return curl_multi_add_handle(multi_handle, curl_handle); -} - -CURLMcode CurlApi::MultiRemoveHandle(CURLM* multi_handle, CURL* curl_handle) { - return curl_multi_remove_handle(multi_handle, curl_handle); -} - -CURLMcode CurlApi::MultiSetSocketCallback(CURLM* multi_handle, - curl_socket_callback socket_callback, - void* userp) { - CURLMcode code = - curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback); - if (code != CURLM_OK) - return code; - return curl_multi_setopt(multi_handle, CURLMOPT_SOCKETDATA, userp); -} - -CURLMcode CurlApi::MultiSetTimerCallback( - CURLM* multi_handle, - curl_multi_timer_callback timer_callback, - void* userp) { - CURLMcode code = - curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback); - if (code != CURLM_OK) - return code; - return curl_multi_setopt(multi_handle, CURLMOPT_TIMERDATA, userp); -} - -CURLMcode CurlApi::MultiAssign(CURLM* multi_handle, - curl_socket_t sockfd, - void* sockp) { - return curl_multi_assign(multi_handle, sockfd, sockp); -} - -CURLMcode CurlApi::MultiSocketAction(CURLM* multi_handle, - curl_socket_t s, - int ev_bitmask, - int* running_handles) { - return curl_multi_socket_action(multi_handle, s, ev_bitmask, running_handles); -} - -std::string CurlApi::MultiStrError(CURLMcode code) const { - return curl_multi_strerror(code); -} - -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/curl_api.h b/chromeos/http/curl_api.h deleted file mode 100644 index b3c1171..0000000 --- a/chromeos/http/curl_api.h +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_CURL_API_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_CURL_API_H_ - -#include - -#include - -#include -#include - -namespace chromeos { -namespace http { - -// Abstract wrapper around libcurl C API that allows us to mock it out in tests. -class CurlInterface { - public: - CurlInterface() = default; - virtual ~CurlInterface() = default; - - // Wrapper around curl_easy_init(). - virtual CURL* EasyInit() = 0; - - // Wrapper around curl_easy_cleanup(). - virtual void EasyCleanup(CURL* curl) = 0; - - // Wrappers around curl_easy_setopt(). - virtual CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) = 0; - virtual CURLcode EasySetOptStr(CURL* curl, - CURLoption option, - const std::string& value) = 0; - virtual CURLcode EasySetOptPtr(CURL* curl, - CURLoption option, - void* value) = 0; - virtual CURLcode EasySetOptCallback(CURL* curl, - CURLoption option, - intptr_t address) = 0; - virtual CURLcode EasySetOptOffT(CURL* curl, - CURLoption option, - curl_off_t value) = 0; - - // A type-safe wrapper around function callback options. - template - inline CURLcode EasySetOptCallback(CURL* curl, - CURLoption option, - R(*callback)(Args...)) { - return EasySetOptCallback( - curl, option, reinterpret_cast(callback)); - } - - // Wrapper around curl_easy_perform(). - virtual CURLcode EasyPerform(CURL* curl) = 0; - - // Wrappers around curl_easy_getinfo(). - virtual CURLcode EasyGetInfoInt(CURL* curl, - CURLINFO info, - int* value) const = 0; - virtual CURLcode EasyGetInfoDbl(CURL* curl, - CURLINFO info, - double* value) const = 0; - virtual CURLcode EasyGetInfoStr(CURL* curl, - CURLINFO info, - std::string* value) const = 0; - virtual CURLcode EasyGetInfoPtr(CURL* curl, - CURLINFO info, - void** value) const = 0; - - // Wrapper around curl_easy_strerror(). - virtual std::string EasyStrError(CURLcode code) const = 0; - - // Wrapper around curl_multi_init(). - virtual CURLM* MultiInit() = 0; - - // Wrapper around curl_multi_cleanup(). - virtual CURLMcode MultiCleanup(CURLM* multi_handle) = 0; - - // Wrapper around curl_multi_info_read(). - virtual CURLMsg* MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) = 0; - - // Wrapper around curl_multi_add_handle(). - virtual CURLMcode MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) = 0; - - // Wrapper around curl_multi_remove_handle(). - virtual CURLMcode MultiRemoveHandle(CURLM* multi_handle, - CURL* curl_handle) = 0; - - // Wrapper around curl_multi_setopt(CURLMOPT_SOCKETFUNCTION/SOCKETDATA). - virtual CURLMcode MultiSetSocketCallback( - CURLM* multi_handle, - curl_socket_callback socket_callback, - void* userp) = 0; - - // Wrapper around curl_multi_setopt(CURLMOPT_TIMERFUNCTION/TIMERDATA). - virtual CURLMcode MultiSetTimerCallback( - CURLM* multi_handle, - curl_multi_timer_callback timer_callback, - void* userp) = 0; - - // Wrapper around curl_multi_assign(). - virtual CURLMcode MultiAssign(CURLM* multi_handle, - curl_socket_t sockfd, - void* sockp) = 0; - - // Wrapper around curl_multi_socket_action(). - virtual CURLMcode MultiSocketAction(CURLM* multi_handle, - curl_socket_t s, - int ev_bitmask, - int* running_handles) = 0; - - // Wrapper around curl_multi_strerror(). - virtual std::string MultiStrError(CURLMcode code) const = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(CurlInterface); -}; - -class CHROMEOS_EXPORT CurlApi : public CurlInterface { - public: - CurlApi(); - ~CurlApi() override; - - // Wrapper around curl_easy_init(). - CURL* EasyInit() override; - - // Wrapper around curl_easy_cleanup(). - void EasyCleanup(CURL* curl) override; - - // Wrappers around curl_easy_setopt(). - CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) override; - CURLcode EasySetOptStr(CURL* curl, - CURLoption option, - const std::string& value) override; - CURLcode EasySetOptPtr(CURL* curl, CURLoption option, void* value) override; - CURLcode EasySetOptCallback(CURL* curl, - CURLoption option, - intptr_t address) override; - CURLcode EasySetOptOffT(CURL* curl, - CURLoption option, - curl_off_t value) override; - - // Wrapper around curl_easy_perform(). - CURLcode EasyPerform(CURL* curl) override; - - // Wrappers around curl_easy_getinfo(). - CURLcode EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const override; - CURLcode EasyGetInfoDbl(CURL* curl, - CURLINFO info, - double* value) const override; - CURLcode EasyGetInfoStr(CURL* curl, - CURLINFO info, - std::string* value) const override; - CURLcode EasyGetInfoPtr(CURL* curl, - CURLINFO info, - void** value) const override; - - // Wrapper around curl_easy_strerror(). - std::string EasyStrError(CURLcode code) const override; - - // Wrapper around curl_multi_init(). - CURLM* MultiInit() override; - - // Wrapper around curl_multi_cleanup(). - CURLMcode MultiCleanup(CURLM* multi_handle) override; - - // Wrapper around curl_multi_info_read(). - CURLMsg* MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) override; - - // Wrapper around curl_multi_add_handle(). - CURLMcode MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) override; - - // Wrapper around curl_multi_remove_handle(). - CURLMcode MultiRemoveHandle(CURLM* multi_handle, CURL* curl_handle) override; - - // Wrapper around curl_multi_setopt(CURLMOPT_SOCKETFUNCTION/SOCKETDATA). - CURLMcode MultiSetSocketCallback( - CURLM* multi_handle, - curl_socket_callback socket_callback, - void* userp) override; - - // Wrapper around curl_multi_setopt(CURLMOPT_TIMERFUNCTION/TIMERDATA). - CURLMcode MultiSetTimerCallback( - CURLM* multi_handle, - curl_multi_timer_callback timer_callback, - void* userp) override; - - // Wrapper around curl_multi_assign(). - CURLMcode MultiAssign(CURLM* multi_handle, - curl_socket_t sockfd, - void* sockp) override; - - // Wrapper around curl_multi_socket_action(). - CURLMcode MultiSocketAction(CURLM* multi_handle, - curl_socket_t s, - int ev_bitmask, - int* running_handles) override; - - // Wrapper around curl_multi_strerror(). - std::string MultiStrError(CURLMcode code) const override; - - private: - DISALLOW_COPY_AND_ASSIGN(CurlApi); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_CURL_API_H_ diff --git a/chromeos/http/http_connection.h b/chromeos/http/http_connection.h deleted file mode 100644 index 014fde0..0000000 --- a/chromeos/http/http_connection.h +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_H_ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { - -class Response; - -/////////////////////////////////////////////////////////////////////////////// -// Connection class is the base class for HTTP communication session. -// It abstracts the implementation of underlying transport library (ex libcurl). -// When the Connection-derived class is constructed, it is pre-set up with -// basic initialization information necessary to initiate the server request -// connection (such as the URL, request method, etc - see -// Transport::CreateConnection() for more details). But most implementations -// would not probably initiate the physical connection until SendHeaders -// is called. -// You normally shouldn't worry about using this class directly. -// http::Request and http::Response classes use it for communication. -// Effectively this class is the interface for the request/response objects to -// the transport-specific instance of the communication channel with the -// destination server. It is created by Transport as part of initiating -// the connection to the destination URI and is shared between the request and -// response object until all the data is sent to the server and the response -// is received. The class does NOT represent a persistent TCP connection though -// (e.g. in keep-alive scenarios). -/////////////////////////////////////////////////////////////////////////////// -class CHROMEOS_EXPORT Connection - : public std::enable_shared_from_this { - public: - explicit Connection(const std::shared_ptr& transport) - : transport_(transport) {} - virtual ~Connection() = default; - - // The following methods are used by http::Request object to initiate the - // communication with the server, send the request data and receive the - // response. - - // Called by http::Request to initiate the connection with the server. - // This normally opens the socket and sends the request headers. - virtual bool SendHeaders(const HeaderList& headers, - chromeos::ErrorPtr* error) = 0; - // If needed, this function can be called to send the request body data. - virtual bool SetRequestData(StreamPtr stream, - chromeos::ErrorPtr* error) = 0; - // If needed, this function can be called to customize where the response - // data is streamed to. - virtual void SetResponseData(StreamPtr stream) = 0; - // This function is called when all the data is sent off and it's time - // to receive the response data. The method will block until the whole - // response message is received, or if an error occur in which case the - // function will return false and fill the error details in |error| object. - virtual bool FinishRequest(chromeos::ErrorPtr* error) = 0; - // Send the request asynchronously and invoke the callback with the response - // received. Returns the ID of the pending async request. - virtual RequestID FinishRequestAsync(const SuccessCallback& success_callback, - const ErrorCallback& error_callback) = 0; - - // The following methods are used by http::Response object to obtain the - // response data. They are used only after the response data has been received - // since the http::Response object is not constructed until - // Request::GetResponse()/Request::GetResponseAndBlock() methods are called. - - // Returns the HTTP status code (e.g. 200 for success). - virtual int GetResponseStatusCode() const = 0; - // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED"). - virtual std::string GetResponseStatusText() const = 0; - // Returns the HTTP protocol version (e.g. "HTTP/1.1"). - virtual std::string GetProtocolVersion() const = 0; - // Returns the value of particular response header, or empty string if the - // headers wasn't received. - virtual std::string GetResponseHeader( - const std::string& header_name) const = 0; - // Returns the response data stream. This function can be called only once - // as it transfers ownership of the data stream to the caller. Subsequent - // calls will fail with "Stream closed" error. - // Returns empty stream on failure and fills in the error information in - // |error| object. - virtual StreamPtr ExtractDataStream(chromeos::ErrorPtr* error) = 0; - - protected: - // |transport_| is mainly used to keep the object alive as long as the - // connection exists. But some implementations of Connection could use - // the Transport-derived class for their own needs as well. - std::shared_ptr transport_; - - private: - DISALLOW_COPY_AND_ASSIGN(Connection); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_H_ diff --git a/chromeos/http/http_connection_curl.cc b/chromeos/http/http_connection_curl.cc deleted file mode 100644 index c36a606..0000000 --- a/chromeos/http/http_connection_curl.cc +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { -namespace curl { - -static int curl_trace(CURL* handle, - curl_infotype type, - char* data, - size_t size, - void* userp) { - std::string msg(data, size); - - switch (type) { - case CURLINFO_TEXT: - VLOG(3) << "== Info: " << msg; - break; - case CURLINFO_HEADER_OUT: - VLOG(3) << "=> Send headers:\n" << msg; - break; - case CURLINFO_DATA_OUT: - VLOG(3) << "=> Send data:\n" << msg; - break; - case CURLINFO_SSL_DATA_OUT: - VLOG(3) << "=> Send SSL data" << msg; - break; - case CURLINFO_HEADER_IN: - VLOG(3) << "<= Recv header: " << msg; - break; - case CURLINFO_DATA_IN: - VLOG(3) << "<= Recv data:\n" << msg; - break; - case CURLINFO_SSL_DATA_IN: - VLOG(3) << "<= Recv SSL data" << msg; - break; - default: - break; - } - return 0; -} - -Connection::Connection(CURL* curl_handle, - const std::string& method, - const std::shared_ptr& curl_interface, - const std::shared_ptr& transport) - : http::Connection(transport), - method_(method), - curl_handle_(curl_handle), - curl_interface_(curl_interface) { - // Store the connection pointer inside the CURL handle so we can easily - // retrieve it when doing asynchronous I/O. - curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_PRIVATE, this); - VLOG(2) << "curl::Connection created: " << method_; -} - -Connection::~Connection() { - if (header_list_) - curl_slist_free_all(header_list_); - curl_interface_->EasyCleanup(curl_handle_); - VLOG(2) << "curl::Connection destroyed"; -} - -bool Connection::SendHeaders(const HeaderList& headers, - chromeos::ErrorPtr* error) { - headers_.insert(headers.begin(), headers.end()); - return true; -} - -bool Connection::SetRequestData(StreamPtr stream, chromeos::ErrorPtr* error) { - request_data_stream_ = std::move(stream); - return true; -} - -void Connection::SetResponseData(StreamPtr stream) { - response_data_stream_ = std::move(stream); -} - -void Connection::PrepareRequest() { - if (VLOG_IS_ON(3)) { - curl_interface_->EasySetOptCallback( - curl_handle_, CURLOPT_DEBUGFUNCTION, &curl_trace); - curl_interface_->EasySetOptInt(curl_handle_, CURLOPT_VERBOSE, 1); - } - - if (method_ != request_type::kGet) { - // Set up HTTP request data. - uint64_t data_size = 0; - if (request_data_stream_ && request_data_stream_->CanGetSize()) - data_size = request_data_stream_->GetRemainingSize(); - - if (!request_data_stream_ || request_data_stream_->CanGetSize()) { - // Data size is known (either no data, or data size is available). - if (method_ == request_type::kPut) { - curl_interface_->EasySetOptOffT( - curl_handle_, CURLOPT_INFILESIZE_LARGE, data_size); - } else { - curl_interface_->EasySetOptOffT( - curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, data_size); - } - } else { - // Data size is unknown, so use chunked upload. - headers_.emplace(http::request_header::kTransferEncoding, "chunked"); - } - - if (request_data_stream_) { - curl_interface_->EasySetOptCallback( - curl_handle_, CURLOPT_READFUNCTION, &Connection::read_callback); - curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_READDATA, this); - } - } - - if (!headers_.empty()) { - CHECK(header_list_ == nullptr); - for (auto pair : headers_) { - std::string header = - chromeos::string_utils::Join(": ", pair.first, pair.second); - VLOG(2) << "Request header: " << header; - header_list_ = curl_slist_append(header_list_, header.c_str()); - } - curl_interface_->EasySetOptPtr( - curl_handle_, CURLOPT_HTTPHEADER, header_list_); - } - - headers_.clear(); - - // Set up HTTP response data. - if (!response_data_stream_) - response_data_stream_ = MemoryStream::Create(nullptr); - if (method_ != request_type::kHead) { - curl_interface_->EasySetOptCallback( - curl_handle_, CURLOPT_WRITEFUNCTION, &Connection::write_callback); - curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_WRITEDATA, this); - } - - // HTTP response headers - curl_interface_->EasySetOptCallback( - curl_handle_, CURLOPT_HEADERFUNCTION, &Connection::header_callback); - curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HEADERDATA, this); -} - -bool Connection::FinishRequest(chromeos::ErrorPtr* error) { - PrepareRequest(); - CURLcode ret = curl_interface_->EasyPerform(curl_handle_); - if (ret != CURLE_OK) { - Transport::AddEasyCurlError(error, FROM_HERE, ret, curl_interface_.get()); - } else { - // Rewind our data stream to the beginning so that it can be read back. - if (response_data_stream_->CanSeek() && - !response_data_stream_->SetPosition(0, error)) - return false; - LOG(INFO) << "Response: " << GetResponseStatusCode() << " (" - << GetResponseStatusText() << ")"; - } - return (ret == CURLE_OK); -} - -RequestID Connection::FinishRequestAsync( - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - PrepareRequest(); - return transport_->StartAsyncTransfer(this, success_callback, error_callback); -} - -int Connection::GetResponseStatusCode() const { - int status_code = 0; - curl_interface_->EasyGetInfoInt( - curl_handle_, CURLINFO_RESPONSE_CODE, &status_code); - return status_code; -} - -std::string Connection::GetResponseStatusText() const { - return status_text_; -} - -std::string Connection::GetProtocolVersion() const { - return protocol_version_; -} - -std::string Connection::GetResponseHeader( - const std::string& header_name) const { - auto p = headers_.find(header_name); - return p != headers_.end() ? p->second : std::string(); -} - -StreamPtr Connection::ExtractDataStream(chromeos::ErrorPtr* error) { - if (!response_data_stream_) { - stream_utils::ErrorStreamClosed(FROM_HERE, error); - } - return std::move(response_data_stream_); -} - -size_t Connection::write_callback(char* ptr, - size_t size, - size_t num, - void* data) { - Connection* me = reinterpret_cast(data); - size_t data_len = size * num; - VLOG(1) << "Response data (" << data_len << "): " - << std::string{ptr, data_len}; - // TODO(nathanbullock): Currently we are relying on the stream not blocking, - // but if the stream is representing a pipe or some other construct that might - // block then this code will behave badly. - if (!me->response_data_stream_->WriteAllBlocking(ptr, data_len, nullptr)) { - LOG(ERROR) << "Failed to write response data"; - data_len = 0; - } - return data_len; -} - -size_t Connection::read_callback(char* ptr, - size_t size, - size_t num, - void* data) { - Connection* me = reinterpret_cast(data); - size_t data_len = size * num; - - size_t read_size = 0; - bool success = me->request_data_stream_->ReadBlocking(ptr, data_len, - &read_size, nullptr); - VLOG_IF(3, success) << "Sending data: " << std::string{ptr, read_size}; - return success ? read_size : CURL_READFUNC_ABORT; -} - -size_t Connection::header_callback(char* ptr, - size_t size, - size_t num, - void* data) { - using chromeos::string_utils::SplitAtFirst; - Connection* me = reinterpret_cast(data); - size_t hdr_len = size * num; - std::string header(ptr, hdr_len); - // Remove newlines at the end of header line. - while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) { - header.pop_back(); - } - - VLOG(2) << "Response header: " << header; - - if (!me->status_text_set_) { - // First header - response code as "HTTP/1.1 200 OK". - // Need to extract the OK part - auto pair = SplitAtFirst(header, " "); - me->protocol_version_ = pair.first; - me->status_text_ = SplitAtFirst(pair.second, " ").second; - me->status_text_set_ = true; - } else { - auto pair = SplitAtFirst(header, ":"); - if (!pair.second.empty()) - me->headers_.insert(pair); - } - return hdr_len; -} - -} // namespace curl -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_connection_curl.h b/chromeos/http/http_connection_curl.h deleted file mode 100644 index 32fd450..0000000 --- a/chromeos/http/http_connection_curl.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_CURL_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_CURL_H_ - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { -namespace curl { - -// This is a libcurl-based implementation of http::Connection. -class CHROMEOS_EXPORT Connection : public http::Connection { - public: - Connection(CURL* curl_handle, - const std::string& method, - const std::shared_ptr& curl_interface, - const std::shared_ptr& transport); - ~Connection() override; - - // Overrides from http::Connection. - // See http_connection.h for description of these methods. - bool SendHeaders(const HeaderList& headers, - chromeos::ErrorPtr* error) override; - bool SetRequestData(StreamPtr stream, chromeos::ErrorPtr* error) override; - void SetResponseData(StreamPtr stream) override; - bool FinishRequest(chromeos::ErrorPtr* error) override; - RequestID FinishRequestAsync( - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) override; - - int GetResponseStatusCode() const override; - std::string GetResponseStatusText() const override; - std::string GetProtocolVersion() const override; - std::string GetResponseHeader(const std::string& header_name) const override; - StreamPtr ExtractDataStream(chromeos::ErrorPtr* error) override; - - protected: - // Write data callback. Used by CURL when receiving response data. - CHROMEOS_PRIVATE static size_t write_callback(char* ptr, - size_t size, - size_t num, - void* data); - // Read data callback. Used by CURL when sending request body data. - CHROMEOS_PRIVATE static size_t read_callback(char* ptr, - size_t size, - size_t num, - void* data); - // Write header data callback. Used by CURL when receiving response headers. - CHROMEOS_PRIVATE static size_t header_callback(char* ptr, - size_t size, - size_t num, - void* data); - - // Helper method to set up the |curl_handle_| with all the parameters - // pertaining to the current connection. - CHROMEOS_PRIVATE void PrepareRequest(); - - // HTTP request verb, such as "GET", "POST", "PUT", ... - std::string method_; - - // Binary data for request body. - StreamPtr request_data_stream_; - - // Received response data. - StreamPtr response_data_stream_; - - // List of optional request headers provided by the caller. - // After request has been sent, contains the received response headers. - std::multimap headers_; - - // HTTP protocol version, such as HTTP/1.1 - std::string protocol_version_; - // Response status text, such as "OK" for 200, or "Forbidden" for 403 - std::string status_text_; - // Flag used when parsing response headers to separate the response status - // from the rest of response headers. - bool status_text_set_{false}; - - CURL* curl_handle_{nullptr}; - curl_slist* header_list_{nullptr}; - - std::shared_ptr curl_interface_; - - private: - friend class http::curl::Transport; - DISALLOW_COPY_AND_ASSIGN(Connection); -}; - -} // namespace curl -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_CURL_H_ diff --git a/chromeos/http/http_connection_curl_unittest.cc b/chromeos/http/http_connection_curl_unittest.cc deleted file mode 100644 index f099b0f..0000000 --- a/chromeos/http/http_connection_curl_unittest.cc +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using testing::DoAll; -using testing::Invoke; -using testing::Return; -using testing::SetArgPointee; -using testing::_; - -namespace chromeos { -namespace http { -namespace curl { - -namespace { - -using ReadWriteCallback = - size_t(char* ptr, size_t size, size_t num, void* data); - -// A helper class to simulate curl_easy_perform action. It invokes the -// read callbacks to obtain the request data from the Connection and then -// calls the header and write callbacks to "send" the response header and body. -class CurlPerformer { - public: - // During the tests, use the address of |this| as the CURL* handle. - // This allows the static Perform() method to obtain the instance pointer - // having only CURL*. - CURL* GetCurlHandle() { return reinterpret_cast(this); } - - // Callback to be invoked when mocking out curl_easy_perform() method. - static CURLcode Perform(CURL* curl) { - CurlPerformer* me = reinterpret_cast(curl); - return me->DoPerform(); - } - - // CURL callback functions and |connection| pointer needed to invoke the - // callbacks from the Connection class. - Connection* connection{nullptr}; - ReadWriteCallback* write_callback{nullptr}; - ReadWriteCallback* read_callback{nullptr}; - ReadWriteCallback* header_callback{nullptr}; - - // Request body read from the connection. - std::string request_body; - - // Response data to be sent back to connection. - std::string status_line; - HeaderList response_headers; - std::string response_body; - - private: - // The actual implementation of curl_easy_perform() fake. - CURLcode DoPerform() { - // Read request body. - char buffer[1024]; - for (;;) { - size_t size_read = read_callback(buffer, sizeof(buffer), 1, connection); - if (size_read == CURL_READFUNC_ABORT) - return CURLE_ABORTED_BY_CALLBACK; - if (size_read == CURL_READFUNC_PAUSE) - return CURLE_READ_ERROR; // Shouldn't happen. - if (size_read == 0) - break; - request_body.append(buffer, size_read); - } - - // Send the response headers. - std::vector header_lines; - header_lines.push_back(status_line + "\r\n"); - for (const auto& pair : response_headers) { - header_lines.push_back(string_utils::Join(": ", pair.first, pair.second) + - "\r\n"); - } - - for (const std::string& line : header_lines) { - CURLcode code = WriteString(header_callback, line); - if (code != CURLE_OK) - return code; - } - - // Send response body. - return WriteString(write_callback, response_body); - } - - // Helper method to send a string to a write callback. Keeps calling - // the callback until all the data is written. - CURLcode WriteString(ReadWriteCallback* callback, const std::string& str) { - size_t pos = 0; - size_t size_remaining = str.size(); - while (size_remaining) { - size_t size_written = callback( - const_cast(str.data() + pos), size_remaining, 1, connection); - if (size_written == CURL_WRITEFUNC_PAUSE) - return CURLE_WRITE_ERROR; // Shouldn't happen. - CHECK(size_written <= size_remaining) << "Unexpected size returned"; - size_remaining -= size_written; - pos += size_written; - } - return CURLE_OK; - } -}; - -// Custom matcher to validate the parameter of CURLOPT_HTTPHEADER CURL option -// which contains the request headers as curl_slist* chain. -MATCHER_P(HeadersMatch, headers, "") { - std::multiset test_headers; - for (const auto& pair : headers) - test_headers.insert(string_utils::Join(": ", pair.first, pair.second)); - - std::multiset src_headers; - const curl_slist* head = static_cast(arg); - while (head) { - src_headers.insert(head->data); - head = head->next; - } - - std::vector difference; - std::set_symmetric_difference(src_headers.begin(), src_headers.end(), - test_headers.begin(), test_headers.end(), - std::back_inserter(difference)); - return difference.empty(); -} - -// Custom action to save a CURL callback pointer into a member of CurlPerformer. -ACTION_TEMPLATE(SaveCallback, - HAS_1_TEMPLATE_PARAMS(int, k), - AND_2_VALUE_PARAMS(performer, mem_ptr)) { - performer->*mem_ptr = reinterpret_cast(std::get(args)); -} - -} // anonymous namespace - -class HttpCurlConnectionTest : public testing::Test { - public: - void SetUp() override { - curl_api_ = std::make_shared(); - transport_ = std::make_shared(); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _)) - .WillOnce(Return(CURLE_OK)); - connection_ = std::make_shared( - handle_, request_type::kPost, curl_api_, transport_); - performer_.connection = connection_.get(); - } - - void TearDown() override { - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection_.reset(); - transport_.reset(); - curl_api_.reset(); - } - - protected: - std::shared_ptr curl_api_; - std::shared_ptr transport_; - CurlPerformer performer_; - CURL* handle_{performer_.GetCurlHandle()}; - std::shared_ptr connection_; -}; - -TEST_F(HttpCurlConnectionTest, FinishRequestAsync) { - std::string request_data{"Foo Bar Baz"}; - StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr); - EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr)); - EXPECT_TRUE(connection_->SendHeaders({{"X-Foo", "bar"}}, nullptr)); - - if (VLOG_IS_ON(3)) { - EXPECT_CALL(*curl_api_, - EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1)) - .WillOnce(Return(CURLE_OK)); - } - - EXPECT_CALL( - *curl_api_, - EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size())) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _)) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, _)) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _)) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, - EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _)) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*transport_, StartAsyncTransfer(connection_.get(), _, _)) - .Times(1); - connection_->FinishRequestAsync({}, {}); -} - -MATCHER_P(MatchStringBuffer, data, "") { - return data.compare(static_cast(arg)) == 0; -} - -TEST_F(HttpCurlConnectionTest, FinishRequest) { - std::string request_data{"Foo Bar Baz"}; - std::string response_data{"OK"}; - StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr); - HeaderList headers{ - {request_header::kAccept, "*/*"}, - {request_header::kContentType, mime::text::kPlain}, - {request_header::kContentLength, std::to_string(request_data.size())}, - {"X-Foo", "bar"}, - }; - std::unique_ptr response_stream(new MockStream); - EXPECT_CALL(*response_stream, - WriteAllBlocking(MatchStringBuffer(response_data), - response_data.size(), _)) - .WillOnce(Return(true)); - EXPECT_CALL(*response_stream, CanSeek()) - .WillOnce(Return(false)); - connection_->SetResponseData(std::move(response_stream)); - EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr)); - EXPECT_TRUE(connection_->SendHeaders(headers, nullptr)); - - // Expectations for Connection::FinishRequest() call. - if (VLOG_IS_ON(3)) { - EXPECT_CALL(*curl_api_, - EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1)) - .WillOnce(Return(CURLE_OK)); - } - - EXPECT_CALL( - *curl_api_, - EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size())) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _)) - .WillOnce( - DoAll(SaveCallback<2>(&performer_, &CurlPerformer::read_callback), - Return(CURLE_OK))); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _)) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, - EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, HeadersMatch(headers))) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _)) - .WillOnce( - DoAll(SaveCallback<2>(&performer_, &CurlPerformer::write_callback), - Return(CURLE_OK))); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _)) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, - EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _)) - .WillOnce( - DoAll(SaveCallback<2>(&performer_, &CurlPerformer::header_callback), - Return(CURLE_OK))); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _)) - .WillOnce(Return(CURLE_OK)); - - EXPECT_CALL(*curl_api_, EasyPerform(handle_)) - .WillOnce(Invoke(&CurlPerformer::Perform)); - - EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _)) - .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK))); - - // Set up the CurlPerformer with the response data expected to be received. - HeaderList response_headers{ - {response_header::kContentLength, std::to_string(response_data.size())}, - {response_header::kContentType, mime::text::kHtml}, - {"X-Foo", "baz"}, - }; - performer_.status_line = "HTTP/1.1 200 OK"; - performer_.response_body = response_data; - performer_.response_headers = response_headers; - - // Perform the request. - EXPECT_TRUE(connection_->FinishRequest(nullptr)); - - // Make sure we sent out the request body correctly. - EXPECT_EQ(request_data, performer_.request_body); - - // Validate the parsed response data. - EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _)) - .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK))); - EXPECT_EQ(status_code::Ok, connection_->GetResponseStatusCode()); - EXPECT_EQ("HTTP/1.1", connection_->GetProtocolVersion()); - EXPECT_EQ("OK", connection_->GetResponseStatusText()); - EXPECT_EQ(std::to_string(response_data.size()), - connection_->GetResponseHeader(response_header::kContentLength)); - EXPECT_EQ(mime::text::kHtml, - connection_->GetResponseHeader(response_header::kContentType)); - EXPECT_EQ("baz", connection_->GetResponseHeader("X-Foo")); - auto data_stream = connection_->ExtractDataStream(nullptr); - ASSERT_NE(nullptr, data_stream.get()); -} - -} // namespace curl -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_connection_fake.cc b/chromeos/http/http_connection_fake.cc deleted file mode 100644 index e671984..0000000 --- a/chromeos/http/http_connection_fake.cc +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { -namespace fake { - -Connection::Connection(const std::string& url, - const std::string& method, - const std::shared_ptr& transport) - : http::Connection(transport), request_(url, method) { - VLOG(1) << "fake::Connection created: " << method; -} - -Connection::~Connection() { - VLOG(1) << "fake::Connection destroyed"; -} - -bool Connection::SendHeaders(const HeaderList& headers, - chromeos::ErrorPtr* error) { - request_.AddHeaders(headers); - return true; -} - -bool Connection::SetRequestData(StreamPtr stream, chromeos::ErrorPtr* error) { - request_.SetData(std::move(stream)); - return true; -} - -bool Connection::FinishRequest(chromeos::ErrorPtr* error) { - using chromeos::string_utils::ToString; - request_.AddHeaders( - {{request_header::kContentLength, ToString(request_.GetData().size())}}); - fake::Transport* transport = static_cast(transport_.get()); - CHECK(transport) << "Expecting a fake transport"; - auto handler = transport->GetHandler(request_.GetURL(), request_.GetMethod()); - if (handler.is_null()) { - LOG(ERROR) << "Received unexpected " << request_.GetMethod() - << " request at " << request_.GetURL(); - response_.ReplyText(status_code::NotFound, - "Not found", - chromeos::mime::text::kHtml); - } else { - handler.Run(request_, &response_); - } - return true; -} - -RequestID Connection::FinishRequestAsync( - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - // Make sure the produced Closure holds a reference to the instance of this - // connection. - auto connection = std::static_pointer_cast(shared_from_this()); - auto callback = [connection, success_callback, error_callback] { - connection->FinishRequestAsyncHelper(success_callback, error_callback); - }; - transport_->RunCallbackAsync(FROM_HERE, base::Bind(callback)); - return 1; -} - -void Connection::FinishRequestAsyncHelper( - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - chromeos::ErrorPtr error; - if (!FinishRequest(&error)) { - error_callback.Run(1, error.get()); - } else { - std::unique_ptr response{new Response{shared_from_this()}}; - success_callback.Run(1, std::move(response)); - } -} - -int Connection::GetResponseStatusCode() const { - return response_.GetStatusCode(); -} - -std::string Connection::GetResponseStatusText() const { - return response_.GetStatusText(); -} - -std::string Connection::GetProtocolVersion() const { - return response_.GetProtocolVersion(); -} - -std::string Connection::GetResponseHeader( - const std::string& header_name) const { - return response_.GetHeader(header_name); -} - -StreamPtr Connection::ExtractDataStream(chromeos::ErrorPtr* error) { - // HEAD requests must not return body. - if (request_.GetMethod() != request_type::kHead) { - return MemoryStream::OpenRef(response_.GetData(), error); - } else { - // Return empty data stream for HEAD requests. - return MemoryStream::OpenCopyOf(nullptr, 0, error); - } -} - -} // namespace fake -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_connection_fake.h b/chromeos/http/http_connection_fake.h deleted file mode 100644 index 710f59a..0000000 --- a/chromeos/http/http_connection_fake.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_FAKE_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_FAKE_H_ - -#include -#include -#include - -#include -#include -#include - -namespace chromeos { -namespace http { -namespace fake { - -// This is a fake implementation of http::Connection for unit testing. -class Connection : public http::Connection { - public: - Connection(const std::string& url, - const std::string& method, - const std::shared_ptr& transport); - ~Connection() override; - - // Overrides from http::Connection. - // See http_connection.h for description of these methods. - bool SendHeaders(const HeaderList& headers, - chromeos::ErrorPtr* error) override; - bool SetRequestData(StreamPtr stream, chromeos::ErrorPtr* error) override; - void SetResponseData(StreamPtr stream) override {} - bool FinishRequest(chromeos::ErrorPtr* error) override; - RequestID FinishRequestAsync(const SuccessCallback& success_callback, - const ErrorCallback& error_callback) override; - - int GetResponseStatusCode() const override; - std::string GetResponseStatusText() const override; - std::string GetProtocolVersion() const override; - std::string GetResponseHeader(const std::string& header_name) const override; - StreamPtr ExtractDataStream(chromeos::ErrorPtr* error) override; - - private: - // A helper method for FinishRequestAsync() implementation. - void FinishRequestAsyncHelper(const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - - // Request and response objects passed to the user-provided request handler - // callback. The request object contains all the request information. - // The response object is the server response that is created by - // the handler in response to the request. - ServerRequest request_; - ServerResponse response_; - - DISALLOW_COPY_AND_ASSIGN(Connection); -}; - -} // namespace fake -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_CONNECTION_FAKE_H_ diff --git a/chromeos/http/http_form_data.cc b/chromeos/http/http_form_data.cc deleted file mode 100644 index 149f544..0000000 --- a/chromeos/http/http_form_data.cc +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { - -namespace form_header { -const char kContentDisposition[] = "Content-Disposition"; -const char kContentTransferEncoding[] = "Content-Transfer-Encoding"; -const char kContentType[] = "Content-Type"; -} // namespace form_header - -const char content_disposition::kFile[] = "file"; -const char content_disposition::kFormData[] = "form-data"; - -FormField::FormField(const std::string& name, - const std::string& content_disposition, - const std::string& content_type, - const std::string& transfer_encoding) - : name_{name}, - content_disposition_{content_disposition}, - content_type_{content_type}, - transfer_encoding_{transfer_encoding} { -} - -std::string FormField::GetContentDisposition() const { - std::string disposition = content_disposition_; - if (!name_.empty()) - base::StringAppendF(&disposition, "; name=\"%s\"", name_.c_str()); - return disposition; -} - -std::string FormField::GetContentType() const { - return content_type_; -} - -std::string FormField::GetContentHeader() const { - HeaderList headers{ - {form_header::kContentDisposition, GetContentDisposition()} - }; - - if (!content_type_.empty()) - headers.emplace_back(form_header::kContentType, GetContentType()); - - if (!transfer_encoding_.empty()) { - headers.emplace_back(form_header::kContentTransferEncoding, - transfer_encoding_); - } - - std::string result; - for (const auto& pair : headers) { - base::StringAppendF( - &result, "%s: %s\r\n", pair.first.c_str(), pair.second.c_str()); - } - result += "\r\n"; - return result; -} - -TextFormField::TextFormField(const std::string& name, - const std::string& data, - const std::string& content_type, - const std::string& transfer_encoding) - : FormField{name, - content_disposition::kFormData, - content_type, - transfer_encoding}, - data_{data} { -} - -bool TextFormField::ExtractDataStreams(std::vector* streams) { - streams->push_back(MemoryStream::OpenCopyOf(data_, nullptr)); - return true; -} - -FileFormField::FileFormField(const std::string& name, - StreamPtr stream, - const std::string& file_name, - const std::string& content_disposition, - const std::string& content_type, - const std::string& transfer_encoding) - : FormField{name, content_disposition, content_type, transfer_encoding}, - stream_{std::move(stream)}, - file_name_{file_name} { -} - -std::string FileFormField::GetContentDisposition() const { - std::string disposition = FormField::GetContentDisposition(); - base::StringAppendF(&disposition, "; filename=\"%s\"", file_name_.c_str()); - return disposition; -} - -bool FileFormField::ExtractDataStreams(std::vector* streams) { - if (!stream_) - return false; - streams->push_back(std::move(stream_)); - return true; -} - -MultiPartFormField::MultiPartFormField(const std::string& name, - const std::string& content_type, - const std::string& boundary) - : FormField{name, - content_disposition::kFormData, - content_type.empty() ? mime::multipart::kMixed : content_type, - {}}, - boundary_{boundary} { - if (boundary_.empty()) - boundary_ = base::StringPrintf("%016" PRIx64, base::RandUint64()); -} - -bool MultiPartFormField::ExtractDataStreams(std::vector* streams) { - for (auto& part : parts_) { - std::string data = GetBoundaryStart() + part->GetContentHeader(); - streams->push_back(MemoryStream::OpenCopyOf(data, nullptr)); - if (!part->ExtractDataStreams(streams)) - return false; - - streams->push_back(MemoryStream::OpenRef("\r\n", nullptr)); - } - if (!parts_.empty()) { - std::string data = GetBoundaryEnd(); - streams->push_back(MemoryStream::OpenCopyOf(data, nullptr)); - } - return true; -} - -std::string MultiPartFormField::GetContentType() const { - return base::StringPrintf( - "%s; boundary=\"%s\"", content_type_.c_str(), boundary_.c_str()); -} - -void MultiPartFormField::AddCustomField(std::unique_ptr field) { - parts_.push_back(std::move(field)); -} - -void MultiPartFormField::AddTextField(const std::string& name, - const std::string& data) { - AddCustomField(std::unique_ptr{new TextFormField{name, data}}); -} - -bool MultiPartFormField::AddFileField(const std::string& name, - const base::FilePath& file_path, - const std::string& content_disposition, - const std::string& content_type, - chromeos::ErrorPtr* error) { - StreamPtr stream = FileStream::Open(file_path, Stream::AccessMode::READ, - FileStream::Disposition::OPEN_EXISTING, - error); - if (!stream) - return false; - std::string file_name = file_path.BaseName().value(); - std::unique_ptr file_field{new FileFormField{name, - std::move(stream), - file_name, - content_disposition, - content_type, - "binary"}}; - AddCustomField(std::move(file_field)); - return true; -} - -std::string MultiPartFormField::GetBoundaryStart() const { - return base::StringPrintf("--%s\r\n", boundary_.c_str()); -} - -std::string MultiPartFormField::GetBoundaryEnd() const { - return base::StringPrintf("--%s--", boundary_.c_str()); -} - -FormData::FormData() : FormData{std::string{}} { -} - -FormData::FormData(const std::string& boundary) - : form_data_{"", mime::multipart::kFormData, boundary} { -} - -void FormData::AddCustomField(std::unique_ptr field) { - form_data_.AddCustomField(std::move(field)); -} - -void FormData::AddTextField(const std::string& name, const std::string& data) { - form_data_.AddTextField(name, data); -} - -bool FormData::AddFileField(const std::string& name, - const base::FilePath& file_path, - const std::string& content_type, - chromeos::ErrorPtr* error) { - return form_data_.AddFileField( - name, file_path, content_disposition::kFormData, content_type, error); -} - -std::string FormData::GetContentType() const { - return form_data_.GetContentType(); -} - -StreamPtr FormData::ExtractDataStream() { - std::vector source_streams; - if (form_data_.ExtractDataStreams(&source_streams)) - return InputStreamSet::Create(std::move(source_streams), nullptr); - return {}; -} - -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_form_data.h b/chromeos/http/http_form_data.h deleted file mode 100644 index 9d857ba..0000000 --- a/chromeos/http/http_form_data.h +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_FORM_DATA_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_FORM_DATA_H_ - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { - -namespace content_disposition { -CHROMEOS_EXPORT extern const char kFormData[]; -CHROMEOS_EXPORT extern const char kFile[]; -} // namespace content_disposition - -// An abstract base class for all types of form fields used by FormData class. -// This class represents basic information about a form part in -// multipart/form-data and multipart/mixed content. -// For more details on multipart content, see the following RFC: -// http://www.ietf.org/rfc/rfc2388 -// For more details on MIME and content headers, see the following RFC: -// http://www.ietf.org/rfc/rfc2045 -class CHROMEOS_EXPORT FormField { - public: - // The constructor that takes the basic data part information common to - // all part types. An example of part's headers could include: - // - // Content-Disposition: form-data; name="field1" - // Content-Type: text/plain;charset=windows-1250 - // Content-Transfer-Encoding: quoted-printable - // - // The constructor parameters correspond to the basic part attributes: - // |name| = the part name ("name" parameter of Content-Disposition header; - // "field1" in the example above) - // |content_disposition| = the part disposition ("form-data" in the example) - // |content_type| = the content type ("text/plain;charset=windows-1250") - // |transfer_encoding| = the encoding type for transport ("quoted-printable") - // See http://www.ietf.org/rfc/rfc2045, section 6.1 - FormField(const std::string& name, - const std::string& content_disposition, - const std::string& content_type, - const std::string& transfer_encoding); - virtual ~FormField() = default; - - // Returns the full Content-Disposition header value. This might include the - // disposition type itself as well as the field "name" and/or "filename" - // parameters. - virtual std::string GetContentDisposition() const; - - // Returns the full content type of field data. MultiPartFormField overloads - // this method to append "boundary" parameter to it. - virtual std::string GetContentType() const; - - // Returns a string with all of the field headers, delimited by CRLF - // characters ("\r\n"). - std::string GetContentHeader() const; - - // Adds the data stream(s) to the list of streams to read from. - // This is a potentially destructive operation and can be guaranteed to - // succeed only on the first try. Subsequent calls will fail for certain - // types of form fields. - virtual bool ExtractDataStreams(std::vector* streams) = 0; - - protected: - // Form field name. If not empty, it will be appended to Content-Disposition - // field header using "name" attribute. - std::string name_; - - // Form field disposition. Most of the time this will be "form-data". But for - // nested file uploads inside "multipart/mixed" sections, this can be "file". - std::string content_disposition_; - - // Content type. If omitted (empty), "plain/text" assumed. - std::string content_type_; - - // Transfer encoding for field data. If omitted, "7bit" is assumed. For most - // binary contents (e.g. for file content), use "binary". - std::string transfer_encoding_; - - private: - DISALLOW_COPY_AND_ASSIGN(FormField); -}; - -// Simple text form field. -class CHROMEOS_EXPORT TextFormField : public FormField { - public: - // Constructor. Parameters: - // name: field name - // data: field text data - // content_type: the data content type. Empty if not specified. - // transfer_encoding: the encoding type of data. If omitted, no encoding - // is specified (and "7bit" is assumed). - TextFormField(const std::string& name, - const std::string& data, - const std::string& content_type = {}, - const std::string& transfer_encoding = {}); - - bool ExtractDataStreams(std::vector* streams) override; - - private: - std::string data_; // Buffer/reader for field data. - - DISALLOW_COPY_AND_ASSIGN(TextFormField); -}; - -// File upload form field. -class CHROMEOS_EXPORT FileFormField : public FormField { - public: - // Constructor. Parameters: - // name: field name - // stream: open stream with the contents of the file. - // file_name: just the base file name of the file, e.g. "file.txt". - // Used in "filename" parameter of Content-Disposition header. - // content_type: valid content type of the file. - // transfer_encoding: the encoding type of data. - // If omitted, "binary" is used. - FileFormField(const std::string& name, - StreamPtr stream, - const std::string& file_name, - const std::string& content_disposition, - const std::string& content_type, - const std::string& transfer_encoding = {}); - - // Override from FormField. - // Appends "filename" parameter to Content-Disposition header. - std::string GetContentDisposition() const override; - - bool ExtractDataStreams(std::vector* streams) override; - - private: - StreamPtr stream_; - std::string file_name_; - - DISALLOW_COPY_AND_ASSIGN(FileFormField); -}; - -// Multipart form field. -// This is used directly by FormData class to build the request body for -// form upload. It can also be used with multiple file uploads for a single -// file field, when the uploaded files should be sent as "multipart/mixed". -class CHROMEOS_EXPORT MultiPartFormField : public FormField { - public: - // Constructor. Parameters: - // name: field name - // content_type: valid content type. If omitted, "multipart/mixed" is used. - // boundary: multipart boundary separator. - // If omitted/empty, a random string is generated. - MultiPartFormField(const std::string& name, - const std::string& content_type = {}, - const std::string& boundary = {}); - - // Override from FormField. - // Appends "boundary" parameter to Content-Type header. - std::string GetContentType() const override; - - bool ExtractDataStreams(std::vector* streams) override; - - // Adds a form field to the form data. The |field| could be a simple text - // field, a file upload field or a multipart form field. - void AddCustomField(std::unique_ptr field); - - // Adds a simple text form field. - void AddTextField(const std::string& name, const std::string& data); - - // Adds a file upload form field using a file path. - bool AddFileField(const std::string& name, - const base::FilePath& file_path, - const std::string& content_disposition, - const std::string& content_type, - chromeos::ErrorPtr* error); - - // Returns a boundary string used to separate multipart form fields. - const std::string& GetBoundary() const { return boundary_; } - - private: - // Returns the starting boundary string: "--". - std::string GetBoundaryStart() const; - // Returns the ending boundary string: "----". - std::string GetBoundaryEnd() const; - - std::string boundary_; // Boundary string used as field separator. - std::vector> parts_; // Form field list. - - DISALLOW_COPY_AND_ASSIGN(MultiPartFormField); -}; - -// A class representing a multipart form data for sending as HTTP POST request. -class CHROMEOS_EXPORT FormData final { - public: - FormData(); - // Allows to specify a custom |boundary| separator string. - explicit FormData(const std::string& boundary); - - // Adds a form field to the form data. The |field| could be a simple text - // field, a file upload field or a multipart form field. - void AddCustomField(std::unique_ptr field); - - // Adds a simple text form field. - void AddTextField(const std::string& name, const std::string& data); - - // Adds a file upload form field using a file path. - bool AddFileField(const std::string& name, - const base::FilePath& file_path, - const std::string& content_type, - chromeos::ErrorPtr* error); - - // Returns the complete content type string to be used in HTTP requests. - std::string GetContentType() const; - - // Returns the data stream for the form data. This is a potentially - // destructive operation and can be called only once. - StreamPtr ExtractDataStream(); - - private: - MultiPartFormField form_data_; - - DISALLOW_COPY_AND_ASSIGN(FormData); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_FORM_DATA_H_ diff --git a/chromeos/http/http_form_data_unittest.cc b/chromeos/http/http_form_data_unittest.cc deleted file mode 100644 index fccef00..0000000 --- a/chromeos/http/http_form_data_unittest.cc +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { -namespace { -std::string GetFormFieldData(FormField* field) { - std::vector streams; - CHECK(field->ExtractDataStreams(&streams)); - StreamPtr stream = InputStreamSet::Create(std::move(streams), nullptr); - - std::vector data(stream->GetSize()); - EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); - return std::string{data.begin(), data.end()}; -} -} // anonymous namespace - -TEST(HttpFormData, TextFormField) { - TextFormField form_field{"field1", "abcdefg", mime::text::kPlain, "7bit"}; - const char expected_header[] = - "Content-Disposition: form-data; name=\"field1\"\r\n" - "Content-Type: text/plain\r\n" - "Content-Transfer-Encoding: 7bit\r\n" - "\r\n"; - EXPECT_EQ(expected_header, form_field.GetContentHeader()); - EXPECT_EQ("abcdefg", GetFormFieldData(&form_field)); -} - -TEST(HttpFormData, FileFormField) { - base::ScopedTempDir dir; - ASSERT_TRUE(dir.CreateUniqueTempDir()); - std::string file_content{"text line1\ntext line2\n"}; - base::FilePath file_name = dir.path().Append("sample.txt"); - ASSERT_EQ(file_content.size(), - static_cast(base::WriteFile( - file_name, file_content.data(), file_content.size()))); - - StreamPtr stream = FileStream::Open(file_name, Stream::AccessMode::READ, - FileStream::Disposition::OPEN_EXISTING, - nullptr); - ASSERT_NE(nullptr, stream); - FileFormField form_field{"test_file", - std::move(stream), - "sample.txt", - content_disposition::kFormData, - mime::text::kPlain, - ""}; - const char expected_header[] = - "Content-Disposition: form-data; name=\"test_file\";" - " filename=\"sample.txt\"\r\n" - "Content-Type: text/plain\r\n" - "\r\n"; - EXPECT_EQ(expected_header, form_field.GetContentHeader()); - EXPECT_EQ(file_content, GetFormFieldData(&form_field)); -} - -TEST(HttpFormData, MultiPartFormField) { - base::ScopedTempDir dir; - ASSERT_TRUE(dir.CreateUniqueTempDir()); - std::string file1{"text line1\ntext line2\n"}; - base::FilePath filename1 = dir.path().Append("sample.txt"); - ASSERT_EQ(file1.size(), - static_cast( - base::WriteFile(filename1, file1.data(), file1.size()))); - std::string file2{"\x01\x02\x03\x04\x05"}; - base::FilePath filename2 = dir.path().Append("test.bin"); - ASSERT_EQ(file2.size(), - static_cast( - base::WriteFile(filename2, file2.data(), file2.size()))); - - MultiPartFormField form_field{"foo", mime::multipart::kFormData, "Delimiter"}; - form_field.AddTextField("name", "John Doe"); - EXPECT_TRUE(form_field.AddFileField("file1", - filename1, - content_disposition::kFormData, - mime::text::kPlain, - nullptr)); - EXPECT_TRUE(form_field.AddFileField("file2", - filename2, - content_disposition::kFormData, - mime::application::kOctet_stream, - nullptr)); - const char expected_header[] = - "Content-Disposition: form-data; name=\"foo\"\r\n" - "Content-Type: multipart/form-data; boundary=\"Delimiter\"\r\n" - "\r\n"; - EXPECT_EQ(expected_header, form_field.GetContentHeader()); - const char expected_data[] = - "--Delimiter\r\n" - "Content-Disposition: form-data; name=\"name\"\r\n" - "\r\n" - "John Doe\r\n" - "--Delimiter\r\n" - "Content-Disposition: form-data; name=\"file1\";" - " filename=\"sample.txt\"\r\n" - "Content-Type: text/plain\r\n" - "Content-Transfer-Encoding: binary\r\n" - "\r\n" - "text line1\ntext line2\n\r\n" - "--Delimiter\r\n" - "Content-Disposition: form-data; name=\"file2\";" - " filename=\"test.bin\"\r\n" - "Content-Type: application/octet-stream\r\n" - "Content-Transfer-Encoding: binary\r\n" - "\r\n" - "\x01\x02\x03\x04\x05\r\n" - "--Delimiter--"; - EXPECT_EQ(expected_data, GetFormFieldData(&form_field)); -} - -TEST(HttpFormData, MultiPartBoundary) { - const int count = 10; - std::set boundaries; - for (int i = 0; i < count; i++) { - MultiPartFormField field{""}; - std::string boundary = field.GetBoundary(); - boundaries.insert(boundary); - // Our generated boundary must be 16 character long and contain lowercase - // hexadecimal digits only. - EXPECT_EQ(16u, boundary.size()); - EXPECT_EQ(std::string::npos, - boundary.find_first_not_of("0123456789abcdef")); - } - // Now make sure the boundary strings were generated at random, so we should - // get |count| unique boundary strings. However since the strings are random, - // there is a very slim change of generating the same string twice, so - // expect at least 90% of unique strings. 90% is picked arbitrarily here. - int expected_min_unique = count * 9 / 10; - EXPECT_GE(boundaries.size(), expected_min_unique); -} - -TEST(HttpFormData, FormData) { - base::ScopedTempDir dir; - ASSERT_TRUE(dir.CreateUniqueTempDir()); - std::string file1{"text line1\ntext line2\n"}; - base::FilePath filename1 = dir.path().Append("sample.txt"); - ASSERT_EQ(file1.size(), - static_cast( - base::WriteFile(filename1, file1.data(), file1.size()))); - std::string file2{"\x01\x02\x03\x04\x05"}; - base::FilePath filename2 = dir.path().Append("test.bin"); - ASSERT_EQ(file2.size(), - static_cast( - base::WriteFile(filename2, file2.data(), file2.size()))); - - FormData form_data{"boundary1"}; - form_data.AddTextField("name", "John Doe"); - std::unique_ptr files{ - new MultiPartFormField{"files", "", "boundary2"}}; - EXPECT_TRUE(files->AddFileField( - "", filename1, content_disposition::kFile, mime::text::kPlain, nullptr)); - EXPECT_TRUE(files->AddFileField("", - filename2, - content_disposition::kFile, - mime::application::kOctet_stream, - nullptr)); - form_data.AddCustomField(std::move(files)); - EXPECT_EQ("multipart/form-data; boundary=\"boundary1\"", - form_data.GetContentType()); - - StreamPtr stream = form_data.ExtractDataStream(); - std::vector data(stream->GetSize()); - EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); - const char expected_data[] = - "--boundary1\r\n" - "Content-Disposition: form-data; name=\"name\"\r\n" - "\r\n" - "John Doe\r\n" - "--boundary1\r\n" - "Content-Disposition: form-data; name=\"files\"\r\n" - "Content-Type: multipart/mixed; boundary=\"boundary2\"\r\n" - "\r\n" - "--boundary2\r\n" - "Content-Disposition: file; filename=\"sample.txt\"\r\n" - "Content-Type: text/plain\r\n" - "Content-Transfer-Encoding: binary\r\n" - "\r\n" - "text line1\ntext line2\n\r\n" - "--boundary2\r\n" - "Content-Disposition: file; filename=\"test.bin\"\r\n" - "Content-Type: application/octet-stream\r\n" - "Content-Transfer-Encoding: binary\r\n" - "\r\n" - "\x01\x02\x03\x04\x05\r\n" - "--boundary2--\r\n" - "--boundary1--"; - EXPECT_EQ(expected_data, (std::string{data.begin(), data.end()})); -} -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_request.cc b/chromeos/http/http_request.cc deleted file mode 100644 index f99f7d2..0000000 --- a/chromeos/http/http_request.cc +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { - -// request_type -const char request_type::kOptions[] = "OPTIONS"; -const char request_type::kGet[] = "GET"; -const char request_type::kHead[] = "HEAD"; -const char request_type::kPost[] = "POST"; -const char request_type::kPut[] = "PUT"; -const char request_type::kPatch[] = "PATCH"; -const char request_type::kDelete[] = "DELETE"; -const char request_type::kTrace[] = "TRACE"; -const char request_type::kConnect[] = "CONNECT"; -const char request_type::kCopy[] = "COPY"; -const char request_type::kMove[] = "MOVE"; - -// request_header -const char request_header::kAccept[] = "Accept"; -const char request_header::kAcceptCharset[] = "Accept-Charset"; -const char request_header::kAcceptEncoding[] = "Accept-Encoding"; -const char request_header::kAcceptLanguage[] = "Accept-Language"; -const char request_header::kAllow[] = "Allow"; -const char request_header::kAuthorization[] = "Authorization"; -const char request_header::kCacheControl[] = "Cache-Control"; -const char request_header::kConnection[] = "Connection"; -const char request_header::kContentEncoding[] = "Content-Encoding"; -const char request_header::kContentLanguage[] = "Content-Language"; -const char request_header::kContentLength[] = "Content-Length"; -const char request_header::kContentLocation[] = "Content-Location"; -const char request_header::kContentMd5[] = "Content-MD5"; -const char request_header::kContentRange[] = "Content-Range"; -const char request_header::kContentType[] = "Content-Type"; -const char request_header::kCookie[] = "Cookie"; -const char request_header::kDate[] = "Date"; -const char request_header::kExpect[] = "Expect"; -const char request_header::kExpires[] = "Expires"; -const char request_header::kFrom[] = "From"; -const char request_header::kHost[] = "Host"; -const char request_header::kIfMatch[] = "If-Match"; -const char request_header::kIfModifiedSince[] = "If-Modified-Since"; -const char request_header::kIfNoneMatch[] = "If-None-Match"; -const char request_header::kIfRange[] = "If-Range"; -const char request_header::kIfUnmodifiedSince[] = "If-Unmodified-Since"; -const char request_header::kLastModified[] = "Last-Modified"; -const char request_header::kMaxForwards[] = "Max-Forwards"; -const char request_header::kPragma[] = "Pragma"; -const char request_header::kProxyAuthorization[] = "Proxy-Authorization"; -const char request_header::kRange[] = "Range"; -const char request_header::kReferer[] = "Referer"; -const char request_header::kTE[] = "TE"; -const char request_header::kTrailer[] = "Trailer"; -const char request_header::kTransferEncoding[] = "Transfer-Encoding"; -const char request_header::kUpgrade[] = "Upgrade"; -const char request_header::kUserAgent[] = "User-Agent"; -const char request_header::kVia[] = "Via"; -const char request_header::kWarning[] = "Warning"; - -// response_header -const char response_header::kAcceptRanges[] = "Accept-Ranges"; -const char response_header::kAge[] = "Age"; -const char response_header::kAllow[] = "Allow"; -const char response_header::kCacheControl[] = "Cache-Control"; -const char response_header::kConnection[] = "Connection"; -const char response_header::kContentEncoding[] = "Content-Encoding"; -const char response_header::kContentLanguage[] = "Content-Language"; -const char response_header::kContentLength[] = "Content-Length"; -const char response_header::kContentLocation[] = "Content-Location"; -const char response_header::kContentMd5[] = "Content-MD5"; -const char response_header::kContentRange[] = "Content-Range"; -const char response_header::kContentType[] = "Content-Type"; -const char response_header::kDate[] = "Date"; -const char response_header::kETag[] = "ETag"; -const char response_header::kExpires[] = "Expires"; -const char response_header::kLastModified[] = "Last-Modified"; -const char response_header::kLocation[] = "Location"; -const char response_header::kPragma[] = "Pragma"; -const char response_header::kProxyAuthenticate[] = "Proxy-Authenticate"; -const char response_header::kRetryAfter[] = "Retry-After"; -const char response_header::kServer[] = "Server"; -const char response_header::kSetCookie[] = "Set-Cookie"; -const char response_header::kTrailer[] = "Trailer"; -const char response_header::kTransferEncoding[] = "Transfer-Encoding"; -const char response_header::kUpgrade[] = "Upgrade"; -const char response_header::kVary[] = "Vary"; -const char response_header::kVia[] = "Via"; -const char response_header::kWarning[] = "Warning"; -const char response_header::kWwwAuthenticate[] = "WWW-Authenticate"; - -// *********************************************************** -// ********************** Request Class ********************** -// *********************************************************** -Request::Request(const std::string& url, - const std::string& method, - std::shared_ptr transport) - : transport_(transport), request_url_(url), method_(method) { - VLOG(1) << "http::Request created"; - if (!transport_) - transport_ = http::Transport::CreateDefault(); -} - -Request::~Request() { - VLOG(1) << "http::Request destroyed"; -} - -void Request::AddRange(int64_t bytes) { - DCHECK(transport_) << "Request already sent"; - if (bytes < 0) { - ranges_.emplace_back(Request::range_value_omitted, -bytes); - } else { - ranges_.emplace_back(bytes, Request::range_value_omitted); - } -} - -void Request::AddRange(uint64_t from_byte, uint64_t to_byte) { - DCHECK(transport_) << "Request already sent"; - ranges_.emplace_back(from_byte, to_byte); -} - -std::unique_ptr Request::GetResponseAndBlock( - chromeos::ErrorPtr* error) { - if (!SendRequestIfNeeded(error) || !connection_->FinishRequest(error)) - return std::unique_ptr(); - std::unique_ptr response(new Response(connection_)); - connection_.reset(); - transport_.reset(); // Indicate that the response has been received - return response; -} - -RequestID Request::GetResponse(const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - ErrorPtr error; - if (!SendRequestIfNeeded(&error)) { - transport_->RunCallbackAsync( - FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); - return 0; - } - RequestID id = - connection_->FinishRequestAsync(success_callback, error_callback); - connection_.reset(); - transport_.reset(); // Indicate that the request has been dispatched. - return id; -} - -void Request::SetAccept(const std::string& accept_mime_types) { - DCHECK(transport_) << "Request already sent"; - accept_ = accept_mime_types; -} - -const std::string& Request::GetAccept() const { - return accept_; -} - -void Request::SetContentType(const std::string& contentType) { - DCHECK(transport_) << "Request already sent"; - content_type_ = contentType; -} - -const std::string& Request::GetContentType() const { - return content_type_; -} - -void Request::AddHeader(const std::string& header, const std::string& value) { - DCHECK(transport_) << "Request already sent"; - headers_.emplace(header, value); -} - -void Request::AddHeaders(const HeaderList& headers) { - DCHECK(transport_) << "Request already sent"; - headers_.insert(headers.begin(), headers.end()); -} - -bool Request::AddRequestBody(const void* data, - size_t size, - chromeos::ErrorPtr* error) { - if (!SendRequestIfNeeded(error)) - return false; - StreamPtr stream = MemoryStream::OpenCopyOf(data, size, error); - return stream && connection_->SetRequestData(std::move(stream), error); -} - -bool Request::AddRequestBody(StreamPtr stream, chromeos::ErrorPtr* error) { - return SendRequestIfNeeded(error) && - connection_->SetRequestData(std::move(stream), error); -} - -bool Request::AddRequestBodyAsFormData(std::unique_ptr form_data, - chromeos::ErrorPtr* error) { - AddHeader(request_header::kContentType, form_data->GetContentType()); - if (!SendRequestIfNeeded(error)) - return false; - return connection_->SetRequestData(form_data->ExtractDataStream(), error); -} - -bool Request::AddResponseStream(StreamPtr stream, chromeos::ErrorPtr* error) { - if (!SendRequestIfNeeded(error)) - return false; - connection_->SetResponseData(std::move(stream)); - return true; -} - -const std::string& Request::GetRequestURL() const { - return request_url_; -} - -const std::string& Request::GetRequestMethod() const { - return method_; -} - -void Request::SetReferer(const std::string& referer) { - DCHECK(transport_) << "Request already sent"; - referer_ = referer; -} - -const std::string& Request::GetReferer() const { - return referer_; -} - -void Request::SetUserAgent(const std::string& user_agent) { - DCHECK(transport_) << "Request already sent"; - user_agent_ = user_agent; -} - -const std::string& Request::GetUserAgent() const { - return user_agent_; -} - -bool Request::SendRequestIfNeeded(chromeos::ErrorPtr* error) { - if (transport_) { - if (!connection_) { - http::HeaderList headers = chromeos::MapToVector(headers_); - std::vector ranges; - if (method_ != request_type::kHead) { - ranges.reserve(ranges_.size()); - for (auto p : ranges_) { - if (p.first != range_value_omitted || - p.second != range_value_omitted) { - std::string range; - if (p.first != range_value_omitted) { - range = chromeos::string_utils::ToString(p.first); - } - range += '-'; - if (p.second != range_value_omitted) { - range += chromeos::string_utils::ToString(p.second); - } - ranges.push_back(range); - } - } - } - if (!ranges.empty()) - headers.emplace_back( - request_header::kRange, - "bytes=" + chromeos::string_utils::Join(",", ranges)); - - headers.emplace_back(request_header::kAccept, GetAccept()); - if (method_ != request_type::kGet && method_ != request_type::kHead) { - if (!content_type_.empty()) - headers.emplace_back(request_header::kContentType, content_type_); - } - connection_ = transport_->CreateConnection( - request_url_, method_, headers, user_agent_, referer_, error); - } - - if (connection_) - return true; - } else { - chromeos::Error::AddTo(error, - FROM_HERE, - http::kErrorDomain, - "response_already_received", - "HTTP response already received"); - } - return false; -} - -// ************************************************************ -// ********************** Response Class ********************** -// ************************************************************ -Response::Response(const std::shared_ptr& connection) - : connection_{connection} { - VLOG(1) << "http::Response created"; -} - -Response::~Response() { - VLOG(1) << "http::Response destroyed"; -} - -bool Response::IsSuccessful() const { - int code = GetStatusCode(); - return code >= status_code::Continue && code < status_code::BadRequest; -} - -int Response::GetStatusCode() const { - if (!connection_) - return -1; - - return connection_->GetResponseStatusCode(); -} - -std::string Response::GetStatusText() const { - if (!connection_) - return std::string(); - - return connection_->GetResponseStatusText(); -} - -std::string Response::GetContentType() const { - return GetHeader(response_header::kContentType); -} - -StreamPtr Response::ExtractDataStream(ErrorPtr* error) { - return connection_->ExtractDataStream(error); -} - -std::vector Response::ExtractData() { - std::vector data; - StreamPtr src_stream = connection_->ExtractDataStream(nullptr); - StreamPtr dest_stream = MemoryStream::CreateRef(&data, nullptr); - if (src_stream && dest_stream) { - char buffer[1024]; - size_t read = 0; - while (src_stream->ReadBlocking(buffer, sizeof(buffer), &read, nullptr) && - read > 0) { - CHECK(dest_stream->WriteAllBlocking(buffer, read, nullptr)); - } - } - return data; -} - -std::string Response::ExtractDataAsString() { - std::vector data = ExtractData(); - return std::string{data.begin(), data.end()}; -} - -std::string Response::GetHeader(const std::string& header_name) const { - if (connection_) - return connection_->GetResponseHeader(header_name); - - return std::string(); -} - -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_request.h b/chromeos/http/http_request.h deleted file mode 100644 index 5c0f086..0000000 --- a/chromeos/http/http_request.h +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_REQUEST_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_REQUEST_H_ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { - -// HTTP request verbs -namespace request_type { -CHROMEOS_EXPORT extern const char kOptions[]; -CHROMEOS_EXPORT extern const char kGet[]; -CHROMEOS_EXPORT extern const char kHead[]; -CHROMEOS_EXPORT extern const char kPost[]; -CHROMEOS_EXPORT extern const char kPut[]; -CHROMEOS_EXPORT extern const char kPatch[]; // Non-standard HTTP/1.1 verb -CHROMEOS_EXPORT extern const char kDelete[]; -CHROMEOS_EXPORT extern const char kTrace[]; -CHROMEOS_EXPORT extern const char kConnect[]; -CHROMEOS_EXPORT extern const char kCopy[]; // Non-standard HTTP/1.1 verb -CHROMEOS_EXPORT extern const char kMove[]; // Non-standard HTTP/1.1 verb -} // namespace request_type - -// HTTP request header names -namespace request_header { -CHROMEOS_EXPORT extern const char kAccept[]; -CHROMEOS_EXPORT extern const char kAcceptCharset[]; -CHROMEOS_EXPORT extern const char kAcceptEncoding[]; -CHROMEOS_EXPORT extern const char kAcceptLanguage[]; -CHROMEOS_EXPORT extern const char kAllow[]; -CHROMEOS_EXPORT extern const char kAuthorization[]; -CHROMEOS_EXPORT extern const char kCacheControl[]; -CHROMEOS_EXPORT extern const char kConnection[]; -CHROMEOS_EXPORT extern const char kContentEncoding[]; -CHROMEOS_EXPORT extern const char kContentLanguage[]; -CHROMEOS_EXPORT extern const char kContentLength[]; -CHROMEOS_EXPORT extern const char kContentLocation[]; -CHROMEOS_EXPORT extern const char kContentMd5[]; -CHROMEOS_EXPORT extern const char kContentRange[]; -CHROMEOS_EXPORT extern const char kContentType[]; -CHROMEOS_EXPORT extern const char kCookie[]; -CHROMEOS_EXPORT extern const char kDate[]; -CHROMEOS_EXPORT extern const char kExpect[]; -CHROMEOS_EXPORT extern const char kExpires[]; -CHROMEOS_EXPORT extern const char kFrom[]; -CHROMEOS_EXPORT extern const char kHost[]; -CHROMEOS_EXPORT extern const char kIfMatch[]; -CHROMEOS_EXPORT extern const char kIfModifiedSince[]; -CHROMEOS_EXPORT extern const char kIfNoneMatch[]; -CHROMEOS_EXPORT extern const char kIfRange[]; -CHROMEOS_EXPORT extern const char kIfUnmodifiedSince[]; -CHROMEOS_EXPORT extern const char kLastModified[]; -CHROMEOS_EXPORT extern const char kMaxForwards[]; -CHROMEOS_EXPORT extern const char kPragma[]; -CHROMEOS_EXPORT extern const char kProxyAuthorization[]; -CHROMEOS_EXPORT extern const char kRange[]; -CHROMEOS_EXPORT extern const char kReferer[]; -CHROMEOS_EXPORT extern const char kTE[]; -CHROMEOS_EXPORT extern const char kTrailer[]; -CHROMEOS_EXPORT extern const char kTransferEncoding[]; -CHROMEOS_EXPORT extern const char kUpgrade[]; -CHROMEOS_EXPORT extern const char kUserAgent[]; -CHROMEOS_EXPORT extern const char kVia[]; -CHROMEOS_EXPORT extern const char kWarning[]; -} // namespace request_header - -// HTTP response header names -namespace response_header { -CHROMEOS_EXPORT extern const char kAcceptRanges[]; -CHROMEOS_EXPORT extern const char kAge[]; -CHROMEOS_EXPORT extern const char kAllow[]; -CHROMEOS_EXPORT extern const char kCacheControl[]; -CHROMEOS_EXPORT extern const char kConnection[]; -CHROMEOS_EXPORT extern const char kContentEncoding[]; -CHROMEOS_EXPORT extern const char kContentLanguage[]; -CHROMEOS_EXPORT extern const char kContentLength[]; -CHROMEOS_EXPORT extern const char kContentLocation[]; -CHROMEOS_EXPORT extern const char kContentMd5[]; -CHROMEOS_EXPORT extern const char kContentRange[]; -CHROMEOS_EXPORT extern const char kContentType[]; -CHROMEOS_EXPORT extern const char kDate[]; -CHROMEOS_EXPORT extern const char kETag[]; -CHROMEOS_EXPORT extern const char kExpires[]; -CHROMEOS_EXPORT extern const char kLastModified[]; -CHROMEOS_EXPORT extern const char kLocation[]; -CHROMEOS_EXPORT extern const char kPragma[]; -CHROMEOS_EXPORT extern const char kProxyAuthenticate[]; -CHROMEOS_EXPORT extern const char kRetryAfter[]; -CHROMEOS_EXPORT extern const char kServer[]; -CHROMEOS_EXPORT extern const char kSetCookie[]; -CHROMEOS_EXPORT extern const char kTrailer[]; -CHROMEOS_EXPORT extern const char kTransferEncoding[]; -CHROMEOS_EXPORT extern const char kUpgrade[]; -CHROMEOS_EXPORT extern const char kVary[]; -CHROMEOS_EXPORT extern const char kVia[]; -CHROMEOS_EXPORT extern const char kWarning[]; -CHROMEOS_EXPORT extern const char kWwwAuthenticate[]; -} // namespace response_header - -// HTTP request status (error) codes -namespace status_code { -// OK to continue with request -static const int Continue = 100; -// Server has switched protocols in upgrade header -static const int SwitchProtocols = 101; - -// Request completed -static const int Ok = 200; -// Object created, reason = new URI -static const int Created = 201; -// Async completion (TBS) -static const int Accepted = 202; -// Partial completion -static const int Partial = 203; -// No info to return -static const int NoContent = 204; -// Request completed, but clear form -static const int ResetContent = 205; -// Partial GET fulfilled -static const int PartialContent = 206; - -// Server couldn't decide what to return -static const int Ambiguous = 300; -// Object permanently moved -static const int Moved = 301; -// Object temporarily moved -static const int Redirect = 302; -// Redirection w/ new access method -static const int RedirectMethod = 303; -// If-Modified-Since was not modified -static const int NotModified = 304; -// Redirection to proxy, location header specifies proxy to use -static const int UseProxy = 305; -// HTTP/1.1: keep same verb -static const int RedirectKeepVerb = 307; - -// Invalid syntax -static const int BadRequest = 400; -// Access denied -static const int Denied = 401; -// Payment required -static const int PaymentRequired = 402; -// Request forbidden -static const int Forbidden = 403; -// Object not found -static const int NotFound = 404; -// Method is not allowed -static const int BadMethod = 405; -// No response acceptable to client found -static const int NoneAcceptable = 406; -// Proxy authentication required -static const int ProxyAuthRequired = 407; -// Server timed out waiting for request -static const int RequestTimeout = 408; -// User should resubmit with more info -static const int Conflict = 409; -// The resource is no longer available -static const int Gone = 410; -// The server refused to accept request w/o a length -static const int LengthRequired = 411; -// Precondition given in request failed -static const int PrecondionFailed = 412; -// Request entity was too large -static const int RequestTooLarge = 413; -// Request URI too long -static const int UriTooLong = 414; -// Unsupported media type -static const int UnsupportedMedia = 415; -// Retry after doing the appropriate action. -static const int RetryWith = 449; - -// Internal server error -static const int InternalServerError = 500; -// Request not supported -static const int NotSupported = 501; -// Error response received from gateway -static const int BadGateway = 502; -// Temporarily overloaded -static const int ServiceUnavailable = 503; -// Timed out waiting for gateway -static const int GatewayTimeout = 504; -// HTTP version not supported -static const int VersionNotSupported = 505; -} // namespace status_code - -class Response; // Just a forward declaration. -class FormData; - -/////////////////////////////////////////////////////////////////////////////// -// Request class is the main object used to set up and initiate an HTTP -// communication session. It is used to specify the HTTP request method, -// request URL and many optional parameters (such as HTTP headers, user agent, -// referer URL and so on. -// -// Once everything is setup, GetResponse() method is used to send the request -// and obtain the server response. The returned Response object can be -// used to inspect the response code, HTTP headers and/or response body. -/////////////////////////////////////////////////////////////////////////////// -class CHROMEOS_EXPORT Request final { - public: - // The main constructor. |url| specifies the remote host address/path - // to send the request to. |method| is the HTTP request verb and - // |transport| is the HTTP transport implementation for server communications. - Request(const std::string& url, - const std::string& method, - std::shared_ptr transport); - ~Request(); - - // Gets/Sets "Accept:" header value. The default value is "*/*" if not set. - void SetAccept(const std::string& accept_mime_types); - const std::string& GetAccept() const; - - // Gets/Sets "Content-Type:" header value - void SetContentType(const std::string& content_type); - const std::string& GetContentType() const; - - // Adds additional HTTP request header - void AddHeader(const std::string& header, const std::string& value); - void AddHeaders(const HeaderList& headers); - - // Removes HTTP request header - void RemoveHeader(const std::string& header); - - // Adds a request body. This is not to be used with GET method - bool AddRequestBody(const void* data, size_t size, chromeos::ErrorPtr* error); - bool AddRequestBody(StreamPtr stream, chromeos::ErrorPtr* error); - - // Adds a request body. This is not to be used with GET method. - // This method also sets the correct content-type of the request, including - // the multipart data boundary. - bool AddRequestBodyAsFormData(std::unique_ptr form_data, - chromeos::ErrorPtr* error); - - // Adds a stream for the response. Otherwise a MemoryStream will be used. - bool AddResponseStream(StreamPtr stream, chromeos::ErrorPtr* error); - - // Makes a request for a subrange of data. Specifies a partial range with - // either from beginning of the data to the specified offset (if |bytes| is - // negative) or from the specified offset to the end of data (if |bytes| is - // positive). - // All individual ranges will be sent as part of "Range:" HTTP request header. - void AddRange(int64_t bytes); - - // Makes a request for a subrange of data. Specifies a full range with - // start and end bytes from the beginning of the requested data. - // All individual ranges will be sent as part of "Range:" HTTP request header. - void AddRange(uint64_t from_byte, uint64_t to_byte); - - // Returns the request URL - const std::string& GetRequestURL() const; - - // Returns the request verb. - const std::string& GetRequestMethod() const; - - // Gets/Sets a request referer URL (sent as "Referer:" request header). - void SetReferer(const std::string& referer); - const std::string& GetReferer() const; - - // Gets/Sets a user agent string (sent as "User-Agent:" request header). - void SetUserAgent(const std::string& user_agent); - const std::string& GetUserAgent() const; - - // Sends the request to the server and blocks until the response is received, - // which is returned as the response object. - // In case the server couldn't be reached for whatever reason, returns - // empty unique_ptr (null). In such a case, the additional error information - // can be returned through the optional supplied |error| parameter. - std::unique_ptr GetResponseAndBlock(chromeos::ErrorPtr* error); - - // Sends out the request and invokes the |success_callback| when the response - // is received. In case of an error, the |error_callback| is invoked. - // Returns the ID of the asynchronous request created. - RequestID GetResponse(const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - - private: - friend class HttpRequestTest; - - // Helper function to create an http::Connection and send off request headers. - CHROMEOS_PRIVATE bool SendRequestIfNeeded(chromeos::ErrorPtr* error); - - // Implementation that provides particular HTTP transport. - std::shared_ptr transport_; - - // An established connection for adding request body. This connection - // is maintained by the request object after the headers have been - // sent and before the response is requested. - std::shared_ptr connection_; - - // Full request URL, such as "http://www.host.com/path/to/object" - const std::string request_url_; - // HTTP request verb, such as "GET", "POST", "PUT", ... - const std::string method_; - - // Referrer URL, if any. Sent to the server via "Referer: " header. - std::string referer_; - // User agent string, if any. Sent to the server via "User-Agent: " header. - std::string user_agent_; - // Content type of the request body data. - // Sent to the server via "Content-Type: " header. - std::string content_type_; - // List of acceptable response data types. - // Sent to the server via "Accept: " header. - std::string accept_ = "*/*"; - - // List of optional request headers provided by the caller. - std::multimap headers_; - // List of optional data ranges to request partial content from the server. - // Sent to the server as "Range: " header. - std::vector> ranges_; - - // range_value_omitted is used in |ranges_| list to indicate omitted value. - // E.g. range (10,range_value_omitted) represents bytes from 10 to the end - // of the data stream. - const uint64_t range_value_omitted = std::numeric_limits::max(); - - DISALLOW_COPY_AND_ASSIGN(Request); -}; - -/////////////////////////////////////////////////////////////////////////////// -// Response class is returned from Request::GetResponse() and is a way -// to get to response status, error codes, response HTTP headers and response -// data (body) if available. -/////////////////////////////////////////////////////////////////////////////// -class CHROMEOS_EXPORT Response final { - public: - explicit Response(const std::shared_ptr& connection); - ~Response(); - - // Returns true if server returned a success code (status code below 400). - bool IsSuccessful() const; - - // Returns the HTTP status code (e.g. 200 for success) - int GetStatusCode() const; - - // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED"). - std::string GetStatusText() const; - - // Returns the content type of the response data. - std::string GetContentType() const; - - // Returns response data stream by transferring ownership of the data stream - // from Response class to the caller. - StreamPtr ExtractDataStream(ErrorPtr* error); - - // Extracts the data from the underlying response data stream as a byte array. - std::vector ExtractData(); - - // Extracts the data from the underlying response data stream as a string. - std::string ExtractDataAsString(); - - // Returns a value of a given response HTTP header. - std::string GetHeader(const std::string& header_name) const; - - private: - friend class HttpRequestTest; - - std::shared_ptr connection_; - - DISALLOW_COPY_AND_ASSIGN(Response); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_REQUEST_H_ diff --git a/chromeos/http/http_request_unittest.cc b/chromeos/http/http_request_unittest.cc deleted file mode 100644 index 09867ee..0000000 --- a/chromeos/http/http_request_unittest.cc +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using testing::DoAll; -using testing::Invoke; -using testing::Return; -using testing::SetArgPointee; -using testing::Unused; -using testing::WithArg; -using testing::_; - -namespace chromeos { -namespace http { - -MATCHER_P(ContainsStringData, str, "") { - if (arg->GetSize() != str.size()) - return false; - - std::string data; - char buf[100]; - size_t read = 0; - while (arg->ReadBlocking(buf, sizeof(buf), &read, nullptr) && read > 0) { - data.append(buf, read); - } - return data == str; -} - -class HttpRequestTest : public testing::Test { - public: - void SetUp() override { - transport_ = std::make_shared(); - connection_ = std::make_shared(transport_); - } - - void TearDown() override { - // Having shared pointers to mock objects (some of methods in these tests - // return shared pointers to connection and transport) could cause the - // test expectations to hold on to the mock object without releasing them - // at the end of a test, causing Mock's object leak detection to erroneously - // detect mock object "leaks". Verify and clear the expectations manually - // and explicitly to ensure the shared pointer refcounters are not - // preventing the mocks to be destroyed at the end of each test. - testing::Mock::VerifyAndClearExpectations(connection_.get()); - connection_.reset(); - testing::Mock::VerifyAndClearExpectations(transport_.get()); - transport_.reset(); - } - - protected: - std::shared_ptr transport_; - std::shared_ptr connection_; -}; - -TEST_F(HttpRequestTest, Defaults) { - Request request{"http://www.foo.bar", request_type::kPost, transport_}; - EXPECT_TRUE(request.GetContentType().empty()); - EXPECT_TRUE(request.GetReferer().empty()); - EXPECT_TRUE(request.GetUserAgent().empty()); - EXPECT_EQ("*/*", request.GetAccept()); - EXPECT_EQ("http://www.foo.bar", request.GetRequestURL()); - EXPECT_EQ(request_type::kPost, request.GetRequestMethod()); - - Request request2{"http://www.foo.bar/baz", request_type::kGet, transport_}; - EXPECT_EQ("http://www.foo.bar/baz", request2.GetRequestURL()); - EXPECT_EQ(request_type::kGet, request2.GetRequestMethod()); -} - -TEST_F(HttpRequestTest, ContentType) { - Request request{"http://www.foo.bar", request_type::kPost, transport_}; - request.SetContentType(mime::image::kJpeg); - EXPECT_EQ(mime::image::kJpeg, request.GetContentType()); -} - -TEST_F(HttpRequestTest, Referer) { - Request request{"http://www.foo.bar", request_type::kPost, transport_}; - request.SetReferer("http://www.foo.bar/baz"); - EXPECT_EQ("http://www.foo.bar/baz", request.GetReferer()); -} - -TEST_F(HttpRequestTest, UserAgent) { - Request request{"http://www.foo.bar", request_type::kPost, transport_}; - request.SetUserAgent("FooBar Browser"); - EXPECT_EQ("FooBar Browser", request.GetUserAgent()); -} - -TEST_F(HttpRequestTest, Accept) { - Request request{"http://www.foo.bar", request_type::kPost, transport_}; - request.SetAccept("text/*, text/html, text/html;level=1, */*"); - EXPECT_EQ("text/*, text/html, text/html;level=1, */*", request.GetAccept()); -} - -TEST_F(HttpRequestTest, GetResponseAndBlock) { - Request request{"http://www.foo.bar", request_type::kPost, transport_}; - request.SetUserAgent("FooBar Browser"); - request.SetReferer("http://www.foo.bar/baz"); - request.SetAccept("text/*, text/html, text/html;level=1, */*"); - request.AddHeader(request_header::kAcceptEncoding, "compress, gzip"); - request.AddHeaders({ - {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"}, - {request_header::kConnection, "close"}, - }); - request.AddRange(-10); - request.AddRange(100, 200); - request.AddRange(300); - std::string req_body{"Foo bar baz"}; - request.AddHeader(request_header::kContentType, mime::text::kPlain); - - EXPECT_CALL(*transport_, CreateConnection( - "http://www.foo.bar", - request_type::kPost, - HeaderList{ - {request_header::kAcceptEncoding, "compress, gzip"}, - {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"}, - {request_header::kConnection, "close"}, - {request_header::kContentType, mime::text::kPlain}, - {request_header::kRange, "bytes=-10,100-200,300-"}, - {request_header::kAccept, "text/*, text/html, text/html;level=1, */*"}, - }, - "FooBar Browser", - "http://www.foo.bar/baz", - nullptr)).WillOnce(Return(connection_)); - - EXPECT_CALL(*connection_, MockSetRequestData(ContainsStringData(req_body), _)) - .WillOnce(Return(true)); - - EXPECT_TRUE( - request.AddRequestBody(req_body.data(), req_body.size(), nullptr)); - - EXPECT_CALL(*connection_, FinishRequest(_)).WillOnce(Return(true)); - auto resp = request.GetResponseAndBlock(nullptr); - EXPECT_NE(nullptr, resp.get()); -} - -TEST_F(HttpRequestTest, GetResponse) { - Request request{"http://foo.bar", request_type::kGet, transport_}; - - std::string resp_data{"FooBar response body"}; - auto read_data = - [&resp_data](void* buffer, Unused, size_t* read, Unused) -> bool { - memcpy(buffer, resp_data.data(), resp_data.size()); - *read = resp_data.size(); - return true; - }; - - auto success_callback = - [this, &resp_data](RequestID request_id, std::unique_ptr resp) { - EXPECT_EQ(23, request_id); - EXPECT_CALL(*connection_, GetResponseStatusCode()) - .WillOnce(Return(status_code::Partial)); - EXPECT_EQ(status_code::Partial, resp->GetStatusCode()); - - EXPECT_CALL(*connection_, GetResponseStatusText()) - .WillOnce(Return("Partial completion")); - EXPECT_EQ("Partial completion", resp->GetStatusText()); - - EXPECT_CALL(*connection_, GetResponseHeader(response_header::kContentType)) - .WillOnce(Return(mime::text::kHtml)); - EXPECT_EQ(mime::text::kHtml, resp->GetContentType()); - - EXPECT_EQ(resp_data, resp->ExtractDataAsString()); - }; - - auto finish_request_async = - [this, &read_data, &resp_data](const SuccessCallback& success_callback) { - std::unique_ptr mock_stream{new MockStream}; - EXPECT_CALL(*mock_stream, ReadBlocking(_, _, _, _)) - .WillOnce(Invoke(read_data)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - - EXPECT_CALL(*connection_, MockExtractDataStream(_)) - .WillOnce(Return(mock_stream.release())); - std::unique_ptr resp{new Response{connection_}}; - success_callback.Run(23, std::move(resp)); - }; - - EXPECT_CALL( - *transport_, - CreateConnection("http://foo.bar", request_type::kGet, _, "", "", _)) - .WillOnce(Return(connection_)); - - EXPECT_CALL(*connection_, FinishRequestAsync(_, _)) - .WillOnce(DoAll(WithArg<0>(Invoke(finish_request_async)), Return(23))); - - EXPECT_EQ(23, request.GetResponse(base::Bind(success_callback), {})); -} - -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_transport.cc b/chromeos/http/http_transport.cc deleted file mode 100644 index 785e5fd..0000000 --- a/chromeos/http/http_transport.cc +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { -namespace http { - -const char kErrorDomain[] = "http_transport"; - -std::shared_ptr Transport::CreateDefault() { - return std::make_shared(std::make_shared()); -} - -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_transport.h b/chromeos/http/http_transport.h deleted file mode 100644 index f3f5e47..0000000 --- a/chromeos/http/http_transport.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_H_ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { - -CHROMEOS_EXPORT extern const char kErrorDomain[]; - -class Request; -class Response; -class Connection; - -using RequestID = int; - -using HeaderList = std::vector>; -using SuccessCallback = - base::Callback)>; -using ErrorCallback = base::Callback; - -/////////////////////////////////////////////////////////////////////////////// -// Transport is a base class for specific implementation of HTTP communication. -// This class (and its underlying implementation) is used by http::Request and -// http::Response classes to provide HTTP functionality to the clients. -/////////////////////////////////////////////////////////////////////////////// -class CHROMEOS_EXPORT Transport - : public std::enable_shared_from_this { - public: - Transport() = default; - virtual ~Transport() = default; - - // Creates a connection object and initializes it with the specified data. - // |transport| is a shared pointer to this transport object instance, - // used to maintain the object alive as long as the connection exists. - // The |url| here is the full URL specified in the request. It is passed - // to the underlying transport (e.g. CURL) to establish the connection. - virtual std::shared_ptr CreateConnection( - const std::string& url, - const std::string& method, - const HeaderList& headers, - const std::string& user_agent, - const std::string& referer, - chromeos::ErrorPtr* error) = 0; - - // Runs |callback| on the task runner (message loop) associated with the - // transport. For transports that do not contain references to real message - // loops (e.g. a fake transport), calls the callback immediately. - virtual void RunCallbackAsync(const tracked_objects::Location& from_here, - const base::Closure& callback) = 0; - - // Initiates an asynchronous transfer on the given |connection|. - // The actual implementation of an async I/O is transport-specific. - // Returns a request ID which can be used to cancel the request. - virtual RequestID StartAsyncTransfer( - Connection* connection, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) = 0; - - // Cancels a pending asynchronous request. This will cancel a pending request - // scheduled by the transport while the I/O operations are still in progress. - // As soon as all I/O completes for the request/response, or when an error - // occurs, the success/error callbacks are invoked and the request is - // considered complete and can no longer be canceled. - // Returns false if pending request with |request_id| is not found (e.g. it - // has already completed/its callbacks are dispatched). - virtual bool CancelRequest(RequestID request_id) = 0; - - // Set the default timeout of requests made. - virtual void SetDefaultTimeout(base::TimeDelta timeout) = 0; - - // Creates a default http::Transport (currently, using http::curl::Transport). - static std::shared_ptr CreateDefault(); - - private: - DISALLOW_COPY_AND_ASSIGN(Transport); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_H_ diff --git a/chromeos/http/http_transport_curl.cc b/chromeos/http/http_transport_curl.cc deleted file mode 100644 index 4bc6208..0000000 --- a/chromeos/http/http_transport_curl.cc +++ /dev/null @@ -1,523 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace { - -const char kCACertificatePath[] = -#ifdef __ANDROID__ - "/system/etc/security/cacerts"; -#else - "/usr/share/chromeos-ca-certificates"; -#endif - -} // namespace - -namespace chromeos { -namespace http { -namespace curl { - -// This is a class that stores connection data on particular CURL socket -// and provides file descriptor watcher to monitor read and/or write operations -// on the socket's file descriptor. -class Transport::SocketPollData : public base::MessageLoopForIO::Watcher { - public: - SocketPollData(const std::shared_ptr& curl_interface, - CURLM* curl_multi_handle, - Transport* transport, - curl_socket_t socket_fd) - : curl_interface_(curl_interface), - curl_multi_handle_(curl_multi_handle), - transport_(transport), - socket_fd_(socket_fd) {} - - // Returns the pointer for the socket-specific file descriptor watcher. - base::MessageLoopForIO::FileDescriptorWatcher* GetWatcher() { - return &file_descriptor_watcher_; - } - - private: - // Overrides from base::MessageLoopForIO::Watcher. - void OnFileCanReadWithoutBlocking(int fd) override { - OnSocketReady(fd, CURL_CSELECT_IN); - } - void OnFileCanWriteWithoutBlocking(int fd) override { - OnSocketReady(fd, CURL_CSELECT_OUT); - } - - // Data on the socket is available to be read from or written to. - // Notify CURL of the action it needs to take on the socket file descriptor. - void OnSocketReady(int fd, int action) { - CHECK_EQ(socket_fd_, fd) << "Unexpected socket file descriptor"; - int still_running_count = 0; - CURLMcode code = curl_interface_->MultiSocketAction( - curl_multi_handle_, socket_fd_, action, &still_running_count); - CHECK_NE(CURLM_CALL_MULTI_PERFORM, code) - << "CURL should no longer return CURLM_CALL_MULTI_PERFORM here"; - - if (code == CURLM_OK) - transport_->ProcessAsyncCurlMessages(); - } - - // The CURL interface to use. - std::shared_ptr curl_interface_; - // CURL multi-handle associated with the transport. - CURLM* curl_multi_handle_; - // Transport object itself. - Transport* transport_; - // The socket file descriptor for the connection. - curl_socket_t socket_fd_; - // File descriptor watcher to notify us of asynchronous I/O on the FD. - base::MessageLoopForIO::FileDescriptorWatcher file_descriptor_watcher_; - - DISALLOW_COPY_AND_ASSIGN(SocketPollData); -}; - -// The request data associated with an asynchronous operation on a particular -// connection. -struct Transport::AsyncRequestData { - // Success/error callbacks to be invoked at the end of the request. - SuccessCallback success_callback; - ErrorCallback error_callback; - // We store a connection here to make sure the object is alive for - // as long as asynchronous operation is running. - std::shared_ptr connection; - // The ID of this request. - RequestID request_id; -}; - -Transport::Transport(const std::shared_ptr& curl_interface) - : curl_interface_{curl_interface} { - VLOG(2) << "curl::Transport created"; -} - -Transport::Transport(const std::shared_ptr& curl_interface, - const std::string& proxy) - : curl_interface_{curl_interface}, proxy_{proxy} { - VLOG(2) << "curl::Transport created with proxy " << proxy; -} - -Transport::~Transport() { - ShutDownAsyncCurl(); - VLOG(2) << "curl::Transport destroyed"; -} - -std::shared_ptr Transport::CreateConnection( - const std::string& url, - const std::string& method, - const HeaderList& headers, - const std::string& user_agent, - const std::string& referer, - chromeos::ErrorPtr* error) { - std::shared_ptr connection; - CURL* curl_handle = curl_interface_->EasyInit(); - if (!curl_handle) { - LOG(ERROR) << "Failed to initialize CURL"; - chromeos::Error::AddTo(error, - FROM_HERE, - http::kErrorDomain, - "curl_init_failed", - "Failed to initialize CURL"); - return connection; - } - - LOG(INFO) << "Sending a " << method << " request to " << url; - CURLcode code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_URL, url); - - if (code == CURLE_OK) { - code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_CAPATH, - kCACertificatePath); - } - if (code == CURLE_OK) { - code = - curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1); - } - if (code == CURLE_OK) { - code = - curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2); - } - if (code == CURLE_OK && !user_agent.empty()) { - code = curl_interface_->EasySetOptStr( - curl_handle, CURLOPT_USERAGENT, user_agent); - } - if (code == CURLE_OK && !referer.empty()) { - code = - curl_interface_->EasySetOptStr(curl_handle, CURLOPT_REFERER, referer); - } - if (code == CURLE_OK && !proxy_.empty()) { - code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_PROXY, proxy_); - } - if (code == CURLE_OK) { - int64_t timeout_ms = connection_timeout_.InMillisecondsRoundedUp(); - - if (timeout_ms > 0 && timeout_ms <= std::numeric_limits::max()) { - code = curl_interface_->EasySetOptInt( - curl_handle, CURLOPT_TIMEOUT_MS, - static_cast(timeout_ms)); - } - } - - // Setup HTTP request method and optional request body. - if (code == CURLE_OK) { - if (method == request_type::kGet) { - code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_HTTPGET, 1); - } else if (method == request_type::kHead) { - code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_NOBODY, 1); - } else if (method == request_type::kPut) { - code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_UPLOAD, 1); - } else { - // POST and custom request methods - code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_POST, 1); - if (code == CURLE_OK) { - code = curl_interface_->EasySetOptPtr( - curl_handle, CURLOPT_POSTFIELDS, nullptr); - } - if (code == CURLE_OK && method != request_type::kPost) { - code = curl_interface_->EasySetOptStr( - curl_handle, CURLOPT_CUSTOMREQUEST, method); - } - } - } - - if (code != CURLE_OK) { - AddEasyCurlError(error, FROM_HERE, code, curl_interface_.get()); - curl_interface_->EasyCleanup(curl_handle); - return connection; - } - - connection = std::make_shared( - curl_handle, method, curl_interface_, shared_from_this()); - if (!connection->SendHeaders(headers, error)) { - connection.reset(); - } - return connection; -} - -void Transport::RunCallbackAsync(const tracked_objects::Location& from_here, - const base::Closure& callback) { - base::MessageLoopForIO::current()->PostTask(from_here, callback); -} - -RequestID Transport::StartAsyncTransfer(http::Connection* connection, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - chromeos::ErrorPtr error; - if (!SetupAsyncCurl(&error)) { - RunCallbackAsync( - FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); - return 0; - } - - RequestID request_id = ++last_request_id_; - - auto curl_connection = static_cast(connection); - std::unique_ptr request_data{new AsyncRequestData}; - // Add the request data to |async_requests_| before adding the CURL handle - // in case CURL feels like calling the socket callback synchronously which - // will need the data to be in |async_requests_| map already. - request_data->success_callback = success_callback; - request_data->error_callback = error_callback; - request_data->connection = - std::static_pointer_cast(curl_connection->shared_from_this()); - request_data->request_id = request_id; - async_requests_.emplace(curl_connection, std::move(request_data)); - request_id_map_.emplace(request_id, curl_connection); - - // Add the connection's CURL handle to the multi-handle. - CURLMcode code = curl_interface_->MultiAddHandle( - curl_multi_handle_, curl_connection->curl_handle_); - if (code != CURLM_OK) { - chromeos::ErrorPtr error; - AddMultiCurlError(&error, FROM_HERE, code, curl_interface_.get()); - RunCallbackAsync( - FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); - async_requests_.erase(curl_connection); - request_id_map_.erase(request_id); - return 0; - } - LOG(INFO) << "Started asynchronous HTTP request with ID " << request_id; - return request_id; -} - -bool Transport::CancelRequest(RequestID request_id) { - auto p = request_id_map_.find(request_id); - if (p == request_id_map_.end()) { - // The request must have been completed already... - // This is not necessarily an error condition, so fail gracefully. - LOG(WARNING) << "HTTP request #" << request_id << " not found"; - return false; - } - LOG(INFO) << "Canceling HTTP request #" << request_id; - CleanAsyncConnection(p->second); - return true; -} - -void Transport::SetDefaultTimeout(base::TimeDelta timeout) { - connection_timeout_ = timeout; -} - -void Transport::AddEasyCurlError(chromeos::ErrorPtr* error, - const tracked_objects::Location& location, - CURLcode code, - CurlInterface* curl_interface) { - chromeos::Error::AddTo(error, - location, - "curl_easy_error", - chromeos::string_utils::ToString(code), - curl_interface->EasyStrError(code)); -} - -void Transport::AddMultiCurlError(chromeos::ErrorPtr* error, - const tracked_objects::Location& location, - CURLMcode code, - CurlInterface* curl_interface) { - chromeos::Error::AddTo(error, - location, - "curl_multi_error", - chromeos::string_utils::ToString(code), - curl_interface->MultiStrError(code)); -} - -bool Transport::SetupAsyncCurl(chromeos::ErrorPtr* error) { - if (curl_multi_handle_) - return true; - - curl_multi_handle_ = curl_interface_->MultiInit(); - if (!curl_multi_handle_) { - LOG(ERROR) << "Failed to initialize CURL"; - chromeos::Error::AddTo(error, - FROM_HERE, - http::kErrorDomain, - "curl_init_failed", - "Failed to initialize CURL"); - return false; - } - - CURLMcode code = curl_interface_->MultiSetSocketCallback( - curl_multi_handle_, &Transport::MultiSocketCallback, this); - if (code == CURLM_OK) { - code = curl_interface_->MultiSetTimerCallback( - curl_multi_handle_, &Transport::MultiTimerCallback, this); - } - if (code != CURLM_OK) { - AddMultiCurlError(error, FROM_HERE, code, curl_interface_.get()); - return false; - } - return true; -} - -void Transport::ShutDownAsyncCurl() { - if (!curl_multi_handle_) - return; - LOG_IF(WARNING, !poll_data_map_.empty()) - << "There are pending requests at the time of transport's shutdown"; - // Make sure we are not leaking any memory here. - for (const auto& pair : poll_data_map_) - delete pair.second; - poll_data_map_.clear(); - curl_interface_->MultiCleanup(curl_multi_handle_); - curl_multi_handle_ = nullptr; -} - -int Transport::MultiSocketCallback(CURL* easy, - curl_socket_t s, - int what, - void* userp, - void* socketp) { - auto transport = static_cast(userp); - CHECK(transport) << "Transport must be set for this callback"; - auto poll_data = static_cast(socketp); - if (!poll_data) { - // We haven't attached polling data to this socket yet. Let's do this now. - poll_data = new SocketPollData{transport->curl_interface_, - transport->curl_multi_handle_, - transport, - s}; - transport->poll_data_map_.emplace(std::make_pair(easy, s), poll_data); - transport->curl_interface_->MultiAssign( - transport->curl_multi_handle_, s, poll_data); - } - - if (what == CURL_POLL_NONE) { - return 0; - } else if (what == CURL_POLL_REMOVE) { - // Remove the attached data from the socket. - transport->curl_interface_->MultiAssign( - transport->curl_multi_handle_, s, nullptr); - transport->poll_data_map_.erase(std::make_pair(easy, s)); - - // Make sure we stop watching the socket file descriptor now, before - // we schedule the SocketPollData for deletion. - poll_data->GetWatcher()->StopWatchingFileDescriptor(); - // This method can be called indirectly from SocketPollData::OnSocketReady, - // so delay destruction of SocketPollData object till the next loop cycle. - base::MessageLoopForIO::current()->DeleteSoon(FROM_HERE, poll_data); - return 0; - } - - base::MessageLoopForIO::Mode watch_mode = base::MessageLoopForIO::WATCH_READ; - switch (what) { - case CURL_POLL_IN: - watch_mode = base::MessageLoopForIO::WATCH_READ; - break; - case CURL_POLL_OUT: - watch_mode = base::MessageLoopForIO::WATCH_WRITE; - break; - case CURL_POLL_INOUT: - watch_mode = base::MessageLoopForIO::WATCH_READ_WRITE; - break; - default: - LOG(FATAL) << "Unknown CURL socket action: " << what; - break; - } - - // WatchFileDescriptor() can be called with the same controller object - // (watcher) to amend the watch mode, however this has cumulative effect. - // For example, if we were watching a file descriptor for READ operations - // and now call it to watch for WRITE, it will end up watching for both - // READ and WRITE. This is not what we want here, so stop watching the - // file descriptor on previous controller before starting with a different - // mode. - if (!poll_data->GetWatcher()->StopWatchingFileDescriptor()) - LOG(WARNING) << "Failed to stop watching the previous socket descriptor"; - CHECK(base::MessageLoopForIO::current()->WatchFileDescriptor( - s, true, watch_mode, poll_data->GetWatcher(), poll_data)) - << "Failed to watch the CURL socket."; - return 0; -} - -// CURL actually uses "long" types in callback signatures, so we must comply. -int Transport::MultiTimerCallback(CURLM* multi, - long timeout_ms, // NOLINT(runtime/int) - void* userp) { - auto transport = static_cast(userp); - // Cancel any previous timer callbacks. - transport->weak_ptr_factory_for_timer_.InvalidateWeakPtrs(); - if (timeout_ms >= 0) { - base::MessageLoopForIO::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&Transport::OnTimer, - transport->weak_ptr_factory_for_timer_.GetWeakPtr()), - base::TimeDelta::FromMilliseconds(timeout_ms)); - } - return 0; -} - -void Transport::OnTimer() { - if (curl_multi_handle_) { - int still_running_count = 0; - curl_interface_->MultiSocketAction( - curl_multi_handle_, CURL_SOCKET_TIMEOUT, 0, &still_running_count); - ProcessAsyncCurlMessages(); - } -} - -void Transport::ProcessAsyncCurlMessages() { - CURLMsg* msg = nullptr; - int msgs_left = 0; - while ((msg = curl_interface_->MultiInfoRead(curl_multi_handle_, - &msgs_left))) { - if (msg->msg == CURLMSG_DONE) { - // Async I/O complete for a connection. Invoke the user callbacks. - Connection* connection = nullptr; - CHECK_EQ(CURLE_OK, - curl_interface_->EasyGetInfoPtr( - msg->easy_handle, - CURLINFO_PRIVATE, - reinterpret_cast(&connection))); - CHECK(connection != nullptr); - OnTransferComplete(connection, msg->data.result); - } - } -} - -void Transport::OnTransferComplete(Connection* connection, CURLcode code) { - auto p = async_requests_.find(connection); - CHECK(p != async_requests_.end()) << "Unknown connection"; - AsyncRequestData* request_data = p->second.get(); - LOG(INFO) << "HTTP request # " << request_data->request_id - << " has completed " - << (code == CURLE_OK ? "successfully" : "with an error"); - if (code != CURLE_OK) { - chromeos::ErrorPtr error; - AddEasyCurlError(&error, FROM_HERE, code, curl_interface_.get()); - RunCallbackAsync(FROM_HERE, - base::Bind(request_data->error_callback, - p->second->request_id, - base::Owned(error.release()))); - } else { - LOG(INFO) << "Response: " << connection->GetResponseStatusCode() << " (" - << connection->GetResponseStatusText() << ")"; - chromeos::ErrorPtr error; - // Rewind the response data stream to the beginning so the clients can - // read the data back. - const auto& stream = request_data->connection->response_data_stream_; - if (stream && stream->CanSeek() && !stream->SetPosition(0, &error)) { - RunCallbackAsync(FROM_HERE, - base::Bind(request_data->error_callback, - p->second->request_id, - base::Owned(error.release()))); - } else { - std::unique_ptr resp{new Response{request_data->connection}}; - RunCallbackAsync(FROM_HERE, - base::Bind(request_data->success_callback, - p->second->request_id, - base::Passed(&resp))); - } - } - // In case of an error on CURL side, we would have dispatched the error - // callback and we need to clean up the current connection, however the - // error callback has no reference to the connection itself and - // |async_requests_| is the only reference to the shared pointer that - // maintains the lifetime of |connection| and possibly even this Transport - // object instance. As a result, if we call CleanAsyncConnection() directly, - // there is a chance that this object might be deleted. - // Instead, schedule an asynchronous task to clean up the connection. - RunCallbackAsync(FROM_HERE, - base::Bind(&Transport::CleanAsyncConnection, - weak_ptr_factory_.GetWeakPtr(), - connection)); -} - -void Transport::CleanAsyncConnection(Connection* connection) { - auto p = async_requests_.find(connection); - CHECK(p != async_requests_.end()) << "Unknown connection"; - // Remove the request data from the map first, since this might be the only - // reference to the Connection class and even possibly to this Transport. - auto request_data = std::move(p->second); - - // Remove associated request ID. - request_id_map_.erase(request_data->request_id); - - // Remove the connection's CURL handle from multi-handle. - curl_interface_->MultiRemoveHandle(curl_multi_handle_, - connection->curl_handle_); - - // Remove all the socket data associated with this connection. - auto iter = poll_data_map_.begin(); - while (iter != poll_data_map_.end()) { - if (iter->first.first == connection->curl_handle_) - iter = poll_data_map_.erase(iter); - else - ++iter; - } - // Remove pending asynchronous request data. - // This must be last since there is a chance of this object being - // destroyed as the result. See the comment in Transport::OnTransferComplete. - async_requests_.erase(p); -} - -} // namespace curl -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_transport_curl.h b/chromeos/http/http_transport_curl.h deleted file mode 100644 index 441e0b5..0000000 --- a/chromeos/http/http_transport_curl.h +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_CURL_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_CURL_H_ - -#include -#include -#include - -#include -#include -#include -#include - -namespace chromeos { -namespace http { -namespace curl { - -class Connection; - -/////////////////////////////////////////////////////////////////////////////// -// An implementation of http::Transport that uses libcurl for -// HTTP communications. This class (as http::Transport base) -// is used by http::Request and http::Response classes to provide HTTP -// functionality to the clients. -// See http_transport.h for more details. -/////////////////////////////////////////////////////////////////////////////// -class CHROMEOS_EXPORT Transport : public http::Transport { - public: - // Constructs the transport using the current message loop for async - // operations. - explicit Transport(const std::shared_ptr& curl_interface); - // Creates a transport object using a proxy. - // |proxy| is of the form [protocol://][user:password@]host[:port]. - // If not defined, protocol is assumed to be http://. - Transport(const std::shared_ptr& curl_interface, - const std::string& proxy); - ~Transport() override; - - // Overrides from http::Transport. - std::shared_ptr CreateConnection( - const std::string& url, - const std::string& method, - const HeaderList& headers, - const std::string& user_agent, - const std::string& referer, - chromeos::ErrorPtr* error) override; - - void RunCallbackAsync(const tracked_objects::Location& from_here, - const base::Closure& callback) override; - - RequestID StartAsyncTransfer(http::Connection* connection, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) override; - - bool CancelRequest(RequestID request_id) override; - - void SetDefaultTimeout(base::TimeDelta timeout) override; - - // Helper methods to convert CURL error codes (CURLcode and CURLMcode) - // into chromeos::Error object. - static void AddEasyCurlError(chromeos::ErrorPtr* error, - const tracked_objects::Location& location, - CURLcode code, - CurlInterface* curl_interface); - - static void AddMultiCurlError(chromeos::ErrorPtr* error, - const tracked_objects::Location& location, - CURLMcode code, - CurlInterface* curl_interface); - - private: - // Forward-declaration of internal implementation structures. - struct AsyncRequestData; - class SocketPollData; - - // Initializes CURL for async operation. - bool SetupAsyncCurl(chromeos::ErrorPtr* error); - - // Stops CURL's async operations. - void ShutDownAsyncCurl(); - - // Handles all pending async messages from CURL. - void ProcessAsyncCurlMessages(); - - // Processes the transfer completion message (success or failure). - void OnTransferComplete(http::curl::Connection* connection, - CURLcode code); - - // Cleans up internal data for a completed/canceled asynchronous operation - // on a connection. - void CleanAsyncConnection(http::curl::Connection* connection); - - // Called after a timeout delay requested by CURL has elapsed. - void OnTimer(); - - // Callback for CURL to handle curl_socket_callback() notifications. - // The parameters correspond to those of curl_socket_callback(). - static int MultiSocketCallback(CURL* easy, - curl_socket_t s, - int what, - void* userp, - void* socketp); - - // Callback for CURL to handle curl_multi_timer_callback() notifications. - // The parameters correspond to those of curl_multi_timer_callback(). - // CURL actually uses "long" types in callback signatures, so we must comply. - static int MultiTimerCallback(CURLM* multi, - long timeout_ms, // NOLINT(runtime/int) - void* userp); - - std::shared_ptr curl_interface_; - std::string proxy_; - // CURL "multi"-handle for processing requests on multiple connections. - CURLM* curl_multi_handle_{nullptr}; - // A map to find a corresponding Connection* using a request ID. - std::map request_id_map_; - // Stores the connection-specific asynchronous data (such as the success - // and error callbacks that need to be called at the end of the async - // operation). - std::map> async_requests_; - // Internal data associated with in-progress asynchronous operations. - std::map, SocketPollData*> poll_data_map_; - // The last request ID used for asynchronous operations. - RequestID last_request_id_{0}; - // The connection timeout for the requests made. - base::TimeDelta connection_timeout_; - - base::WeakPtrFactory weak_ptr_factory_for_timer_{this}; - base::WeakPtrFactory weak_ptr_factory_{this}; - DISALLOW_COPY_AND_ASSIGN(Transport); -}; - -} // namespace curl -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_CURL_H_ diff --git a/chromeos/http/http_transport_curl_unittest.cc b/chromeos/http/http_transport_curl_unittest.cc deleted file mode 100644 index 43213c0..0000000 --- a/chromeos/http/http_transport_curl_unittest.cc +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using testing::DoAll; -using testing::Invoke; -using testing::Return; -using testing::SaveArg; -using testing::SetArgPointee; -using testing::WithoutArgs; -using testing::_; - -namespace chromeos { -namespace http { -namespace curl { - -class HttpCurlTransportTest : public testing::Test { - public: - void SetUp() override { - curl_api_ = std::make_shared(); - transport_ = std::make_shared(curl_api_); - handle_ = reinterpret_cast(100); // Mock handle value. - EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_)); - EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _)) - .WillRepeatedly(Return(CURLE_OK)); - } - - void TearDown() override { - transport_.reset(); - curl_api_.reset(); - } - - protected: - std::shared_ptr curl_api_; - std::shared_ptr transport_; - CURL* handle_{nullptr}; -}; - -TEST_F(HttpCurlTransportTest, RequestGet) { - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_USERAGENT, "User Agent")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_REFERER, "http://foo.bar/baz")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) - .WillOnce(Return(CURLE_OK)); - auto connection = transport_->CreateConnection("http://foo.bar/get", - request_type::kGet, - {}, - "User Agent", - "http://foo.bar/baz", - nullptr); - EXPECT_NE(nullptr, connection.get()); - - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection.reset(); -} - -TEST_F(HttpCurlTransportTest, RequestHead) { - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/head")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_NOBODY, 1)) - .WillOnce(Return(CURLE_OK)); - auto connection = transport_->CreateConnection( - "http://foo.bar/head", request_type::kHead, {}, "", "", nullptr); - EXPECT_NE(nullptr, connection.get()); - - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection.reset(); -} - -TEST_F(HttpCurlTransportTest, RequestPut) { - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/put")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_UPLOAD, 1)) - .WillOnce(Return(CURLE_OK)); - auto connection = transport_->CreateConnection( - "http://foo.bar/put", request_type::kPut, {}, "", "", nullptr); - EXPECT_NE(nullptr, connection.get()); - - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection.reset(); -} - -TEST_F(HttpCurlTransportTest, RequestPost) { - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/post")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr)) - .WillOnce(Return(CURLE_OK)); - auto connection = transport_->CreateConnection( - "http://www.foo.bar/post", request_type::kPost, {}, "", "", nullptr); - EXPECT_NE(nullptr, connection.get()); - - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection.reset(); -} - -TEST_F(HttpCurlTransportTest, RequestPatch) { - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/patch")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL( - *curl_api_, - EasySetOptStr(handle_, CURLOPT_CUSTOMREQUEST, request_type::kPatch)) - .WillOnce(Return(CURLE_OK)); - auto connection = transport_->CreateConnection( - "http://www.foo.bar/patch", request_type::kPatch, {}, "", "", nullptr); - EXPECT_NE(nullptr, connection.get()); - - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection.reset(); -} - -TEST_F(HttpCurlTransportTest, CurlFailure) { - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) - .WillOnce(Return(CURLE_OUT_OF_MEMORY)); - EXPECT_CALL(*curl_api_, EasyStrError(CURLE_OUT_OF_MEMORY)) - .WillOnce(Return("Out of Memory")); - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - ErrorPtr error; - auto connection = transport_->CreateConnection( - "http://foo.bar/get", request_type::kGet, {}, "", "", &error); - - EXPECT_EQ(nullptr, connection.get()); - EXPECT_EQ("curl_easy_error", error->GetDomain()); - EXPECT_EQ(std::to_string(CURLE_OUT_OF_MEMORY), error->GetCode()); - EXPECT_EQ("Out of Memory", error->GetMessage()); -} - -class HttpCurlTransportAsyncTest : public testing::Test { - public: - void SetUp() override { - curl_api_ = std::make_shared(); - transport_ = std::make_shared(curl_api_); - EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_)); - EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _)) - .WillOnce(Return(CURLE_OK)); - } - - protected: - std::shared_ptr curl_api_; - std::shared_ptr transport_; - CURL* handle_{reinterpret_cast(123)}; // Mock handle value. - CURLM* multi_handle_{reinterpret_cast(456)}; // Mock handle value. - curl_socket_t dummy_socket_{789}; -}; - -TEST_F(HttpCurlTransportAsyncTest, StartAsyncTransfer) { - // This test is a bit tricky because it deals with asynchronous I/O which - // relies on a message loop to run all the async tasks. - // For this, create a temporary I/O message loop and run it ourselves for the - // duration of the test. - base::MessageLoopForIO message_loop; - base::RunLoop run_loop; - - // Initial expectations for creating a CURL connection. - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) - .WillOnce(Return(CURLE_OK)); - auto connection = transport_->CreateConnection( - "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr); - ASSERT_NE(nullptr, connection.get()); - - // Success/error callback needed to report the result of an async operation. - int success_call_count = 0; - auto success_callback = [&success_call_count, &run_loop]( - RequestID request_id, std::unique_ptr resp) { - base::MessageLoop::current()->PostTask(FROM_HERE, run_loop.QuitClosure()); - success_call_count++; - }; - - auto error_callback = [](RequestID request_id, const Error* error) { - FAIL() << "This callback shouldn't have been called"; - }; - - EXPECT_CALL(*curl_api_, MultiInit()).WillOnce(Return(multi_handle_)); - EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _)) - .WillRepeatedly(DoAll(SetArgPointee<2>(200), Return(CURLE_OK))); - - curl_socket_callback socket_callback = nullptr; - EXPECT_CALL(*curl_api_, - MultiSetSocketCallback(multi_handle_, _, transport_.get())) - .WillOnce(DoAll(SaveArg<1>(&socket_callback), Return(CURLM_OK))); - - curl_multi_timer_callback timer_callback = nullptr; - EXPECT_CALL(*curl_api_, - MultiSetTimerCallback(multi_handle_, _, transport_.get())) - .WillOnce(DoAll(SaveArg<1>(&timer_callback), Return(CURLM_OK))); - - EXPECT_CALL(*curl_api_, MultiAddHandle(multi_handle_, handle_)) - .WillOnce(Return(CURLM_OK)); - - EXPECT_EQ(1, transport_->StartAsyncTransfer(connection.get(), - base::Bind(success_callback), - base::Bind(error_callback))); - EXPECT_EQ(0, success_call_count); - - timer_callback(multi_handle_, 1, transport_.get()); - - auto do_socket_action = [&socket_callback, this] { - EXPECT_CALL(*curl_api_, MultiAssign(multi_handle_, dummy_socket_, _)) - .Times(2).WillRepeatedly(Return(CURLM_OK)); - EXPECT_EQ(0, socket_callback(handle_, dummy_socket_, CURL_POLL_REMOVE, - transport_.get(), nullptr)); - }; - - EXPECT_CALL(*curl_api_, - MultiSocketAction(multi_handle_, CURL_SOCKET_TIMEOUT, 0, _)) - .WillOnce(DoAll(SetArgPointee<3>(1), - WithoutArgs(Invoke(do_socket_action)), - Return(CURLM_OK))) - .WillRepeatedly(DoAll(SetArgPointee<3>(0), Return(CURLM_OK))); - - CURLMsg msg = {}; - msg.msg = CURLMSG_DONE; - msg.easy_handle = handle_; - msg.data.result = CURLE_OK; - - EXPECT_CALL(*curl_api_, MultiInfoRead(multi_handle_, _)) - .WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg))) - .WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(nullptr))); - EXPECT_CALL(*curl_api_, EasyGetInfoPtr(handle_, CURLINFO_PRIVATE, _)) - .WillRepeatedly(DoAll(SetArgPointee<2>(connection.get()), - Return(CURLE_OK))); - - EXPECT_CALL(*curl_api_, MultiRemoveHandle(multi_handle_, handle_)) - .WillOnce(Return(CURLM_OK)); - - // Just in case something goes wrong and |success_callback| isn't called, - // post a time-out quit closure to abort the message loop after 1 second. - message_loop.PostDelayedTask( - FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(1)); - run_loop.Run(); - EXPECT_EQ(1, success_call_count); - - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection.reset(); - - EXPECT_CALL(*curl_api_, MultiCleanup(multi_handle_)) - .WillOnce(Return(CURLM_OK)); - transport_.reset(); -} - -TEST_F(HttpCurlTransportTest, RequestGetTimeout) { - transport_->SetDefaultTimeout(base::TimeDelta::FromMilliseconds(2000)); - EXPECT_CALL(*curl_api_, - EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get")) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_TIMEOUT_MS, 2000)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1)) - .WillOnce(Return(CURLE_OK)); - auto connection = transport_->CreateConnection( - "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr); - EXPECT_NE(nullptr, connection.get()); - - EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1); - connection.reset(); -} - -} // namespace curl -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_transport_fake.cc b/chromeos/http/http_transport_fake.cc deleted file mode 100644 index a5346e3..0000000 --- a/chromeos/http/http_transport_fake.cc +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -using http::fake::Transport; -using http::fake::ServerRequestResponseBase; -using http::fake::ServerRequest; -using http::fake::ServerResponse; - -Transport::Transport() { - VLOG(1) << "fake::Transport created"; -} - -Transport::~Transport() { - VLOG(1) << "fake::Transport destroyed"; -} - -std::shared_ptr Transport::CreateConnection( - const std::string& url, - const std::string& method, - const HeaderList& headers, - const std::string& user_agent, - const std::string& referer, - chromeos::ErrorPtr* error) { - std::shared_ptr connection; - if (create_connection_error_) { - if (error) - *error = std::move(create_connection_error_); - return connection; - } - HeaderList headers_copy = headers; - if (!user_agent.empty()) { - headers_copy.push_back( - std::make_pair(http::request_header::kUserAgent, user_agent)); - } - if (!referer.empty()) { - headers_copy.push_back( - std::make_pair(http::request_header::kReferer, referer)); - } - connection = - std::make_shared(url, method, shared_from_this()); - CHECK(connection) << "Unable to create Connection object"; - if (!connection->SendHeaders(headers_copy, error)) - connection.reset(); - request_count_++; - return connection; -} - -void Transport::RunCallbackAsync(const tracked_objects::Location& from_here, - const base::Closure& callback) { - if (!async_) { - callback.Run(); - return; - } - async_callback_queue_.push(callback); -} - -bool Transport::HandleOneAsyncRequest() { - if (async_callback_queue_.empty()) - return false; - - base::Closure callback = async_callback_queue_.front(); - async_callback_queue_.pop(); - callback.Run(); - return true; -} - -void Transport::HandleAllAsyncRequests() { - while (!async_callback_queue_.empty()) - HandleOneAsyncRequest(); -} - -http::RequestID Transport::StartAsyncTransfer( - http::Connection* connection, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - // Fake transport doesn't use this method. - LOG(FATAL) << "This method should not be called on fake transport"; - return 0; -} - -bool Transport::CancelRequest(RequestID request_id) { - return false; -} - -void Transport::SetDefaultTimeout(base::TimeDelta timeout) { -} - -static inline std::string GetHandlerMapKey(const std::string& url, - const std::string& method) { - return method + ":" + url; -} - -void Transport::AddHandler(const std::string& url, - const std::string& method, - const HandlerCallback& handler) { - // Make sure we can override/replace existing handlers. - handlers_[GetHandlerMapKey(url, method)] = handler; -} - -void Transport::AddSimpleReplyHandler(const std::string& url, - const std::string& method, - int status_code, - const std::string& reply_text, - const std::string& mime_type) { - auto handler = [status_code, reply_text, mime_type]( - const ServerRequest& request, ServerResponse* response) { - response->ReplyText(status_code, reply_text, mime_type); - }; - AddHandler(url, method, base::Bind(handler)); -} - -Transport::HandlerCallback Transport::GetHandler( - const std::string& url, - const std::string& method) const { - // First try the exact combination of URL/Method - auto p = handlers_.find(GetHandlerMapKey(url, method)); - if (p != handlers_.end()) - return p->second; - // If not found, try URL/* - p = handlers_.find(GetHandlerMapKey(url, "*")); - if (p != handlers_.end()) - return p->second; - // If still not found, try */method - p = handlers_.find(GetHandlerMapKey("*", method)); - if (p != handlers_.end()) - return p->second; - // Finally, try */* - p = handlers_.find(GetHandlerMapKey("*", "*")); - return (p != handlers_.end()) ? p->second : HandlerCallback(); -} - -void ServerRequestResponseBase::SetData(StreamPtr stream) { - data_.clear(); - if (stream) { - uint8_t buffer[1024]; - size_t size = 0; - if (stream->CanGetSize()) - data_.reserve(stream->GetRemainingSize()); - - do { - CHECK(stream->ReadBlocking(buffer, sizeof(buffer), &size, nullptr)); - data_.insert(data_.end(), buffer, buffer + size); - } while (size > 0); - } -} - -std::string ServerRequestResponseBase::GetDataAsString() const { - if (data_.empty()) - return std::string(); - auto chars = reinterpret_cast(data_.data()); - return std::string(chars, data_.size()); -} - -std::unique_ptr -ServerRequestResponseBase::GetDataAsJson() const { - if (chromeos::mime::RemoveParameters( - GetHeader(request_header::kContentType)) == - chromeos::mime::application::kJson) { - auto value = base::JSONReader::Read(GetDataAsString()); - if (value) { - base::DictionaryValue* dict = nullptr; - if (value->GetAsDictionary(&dict)) { - // |value| is now owned by |dict|. - base::IgnoreResult(value.release()); - return std::unique_ptr(dict); - } - } - } - return std::unique_ptr(); -} - -std::string ServerRequestResponseBase::GetDataAsNormalizedJsonString() const { - std::string value; - // Make sure we serialize the JSON back without any pretty print so - // the string comparison works correctly. - auto json = GetDataAsJson(); - if (json) - base::JSONWriter::Write(*json, &value); - return value; -} - -void ServerRequestResponseBase::AddHeaders(const HeaderList& headers) { - for (const auto& pair : headers) { - if (pair.second.empty()) - headers_.erase(pair.first); - else - headers_.insert(pair); - } -} - -std::string ServerRequestResponseBase::GetHeader( - const std::string& header_name) const { - auto p = headers_.find(header_name); - return p != headers_.end() ? p->second : std::string(); -} - -ServerRequest::ServerRequest(const std::string& url, const std::string& method) - : method_(method) { - auto params = chromeos::url::GetQueryStringParameters(url); - url_ = chromeos::url::RemoveQueryString(url, true); - form_fields_.insert(params.begin(), params.end()); -} - -std::string ServerRequest::GetFormField(const std::string& field_name) const { - if (!form_fields_parsed_) { - std::string mime_type = chromeos::mime::RemoveParameters( - GetHeader(request_header::kContentType)); - if (mime_type == chromeos::mime::application::kWwwFormUrlEncoded && - !GetData().empty()) { - auto fields = chromeos::data_encoding::WebParamsDecode(GetDataAsString()); - form_fields_.insert(fields.begin(), fields.end()); - } - form_fields_parsed_ = true; - } - auto p = form_fields_.find(field_name); - return p != form_fields_.end() ? p->second : std::string(); -} - -void ServerResponse::Reply(int status_code, - const void* data, - size_t data_size, - const std::string& mime_type) { - data_.clear(); - status_code_ = status_code; - SetData(MemoryStream::OpenCopyOf(data, data_size, nullptr)); - AddHeaders({{response_header::kContentLength, - chromeos::string_utils::ToString(data_size)}, - {response_header::kContentType, mime_type}}); -} - -void ServerResponse::ReplyText(int status_code, - const std::string& text, - const std::string& mime_type) { - Reply(status_code, text.data(), text.size(), mime_type); -} - -void ServerResponse::ReplyJson(int status_code, const base::Value* json) { - std::string text; - base::JSONWriter::WriteWithOptions( - *json, base::JSONWriter::OPTIONS_PRETTY_PRINT, &text); - std::string mime_type = - chromeos::mime::AppendParameter(chromeos::mime::application::kJson, - chromeos::mime::parameters::kCharset, - "utf-8"); - ReplyText(status_code, text, mime_type); -} - -void ServerResponse::ReplyJson(int status_code, - const http::FormFieldList& fields) { - base::DictionaryValue json; - for (const auto& pair : fields) { - json.SetString(pair.first, pair.second); - } - ReplyJson(status_code, &json); -} - -std::string ServerResponse::GetStatusText() const { - static std::vector> status_text_map = { - {100, "Continue"}, - {101, "Switching Protocols"}, - {102, "Processing"}, - {200, "OK"}, - {201, "Created"}, - {202, "Accepted"}, - {203, "Non-Authoritative Information"}, - {204, "No Content"}, - {205, "Reset Content"}, - {206, "Partial Content"}, - {207, "Multi-Status"}, - {208, "Already Reported"}, - {226, "IM Used"}, - {300, "Multiple Choices"}, - {301, "Moved Permanently"}, - {302, "Found"}, - {303, "See Other"}, - {304, "Not Modified"}, - {305, "Use Proxy"}, - {306, "Switch Proxy"}, - {307, "Temporary Redirect"}, - {308, "Permanent Redirect"}, - {400, "Bad Request"}, - {401, "Unauthorized"}, - {402, "Payment Required"}, - {403, "Forbidden"}, - {404, "Not Found"}, - {405, "Method Not Allowed"}, - {406, "Not Acceptable"}, - {407, "Proxy Authentication Required"}, - {408, "Request Timeout"}, - {409, "Conflict"}, - {410, "Gone"}, - {411, "Length Required"}, - {412, "Precondition Failed"}, - {413, "Request Entity Too Large"}, - {414, "Request - URI Too Long"}, - {415, "Unsupported Media Type"}, - {429, "Too Many Requests"}, - {431, "Request Header Fields Too Large"}, - {500, "Internal Server Error"}, - {501, "Not Implemented"}, - {502, "Bad Gateway"}, - {503, "Service Unavailable"}, - {504, "Gateway Timeout"}, - {505, "HTTP Version Not Supported"}, - }; - - for (const auto& pair : status_text_map) { - if (pair.first == status_code_) - return pair.second; - } - return std::string(); -} - -} // namespace chromeos diff --git a/chromeos/http/http_transport_fake.h b/chromeos/http/http_transport_fake.h deleted file mode 100644 index 297294e..0000000 --- a/chromeos/http/http_transport_fake.h +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_FAKE_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_FAKE_H_ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace chromeos { -namespace http { -namespace fake { - -class ServerRequest; -class ServerResponse; -class Connection; - -/////////////////////////////////////////////////////////////////////////////// -// A fake implementation of http::Transport that simulates HTTP communication -// with a server. -/////////////////////////////////////////////////////////////////////////////// -class Transport : public http::Transport { - public: - Transport(); - ~Transport() override; - - // Server handler callback signature. - using HandlerCallback = - base::Callback; - - // This method allows the test code to provide a callback to handle requests - // for specific URL/HTTP-verb combination. When a specific |method| request - // is made on the given |url|, the |handler| will be invoked and all the - // request data will be filled in the |ServerRequest| parameter. Any server - // response should be returned through the |ServerResponse| parameter. - // Either |method| or |url| (or both) can be specified as "*" to handle - // any requests. So, ("http://localhost","*") will handle any request type - // on that URL and ("*","GET") will handle any GET requests. - // The lookup starts with the most specific data pair to the catch-all (*,*). - void AddHandler(const std::string& url, - const std::string& method, - const HandlerCallback& handler); - // Simple version of AddHandler. AddSimpleReplyHandler just returns the - // specified text response of given MIME type. - void AddSimpleReplyHandler(const std::string& url, - const std::string& method, - int status_code, - const std::string& reply_text, - const std::string& mime_type); - // Retrieve a handler for specific |url| and request |method|. - HandlerCallback GetHandler(const std::string& url, - const std::string& method) const; - - // For tests that want to assert on the number of HTTP requests sent, - // these methods can be used to do just that. - int GetRequestCount() const { return request_count_; } - void ResetRequestCount() { request_count_ = 0; } - - // For tests that wish to simulate critical transport errors, this method - // can be used to specify the error to be returned when creating a connection. - void SetCreateConnectionError(chromeos::ErrorPtr create_connection_error) { - create_connection_error_ = std::move(create_connection_error); - } - - // For tests that really need async operations with message loop, call this - // function with true. - void SetAsyncMode(bool async) { async_ = async; } - - // Pops one callback from the top of |async_callback_queue_| and invokes it. - // Returns false if the queue is empty. - bool HandleOneAsyncRequest(); - - // Invokes all the callbacks currently queued in |async_callback_queue_|. - void HandleAllAsyncRequests(); - - // Overrides from http::Transport. - std::shared_ptr CreateConnection( - const std::string& url, - const std::string& method, - const HeaderList& headers, - const std::string& user_agent, - const std::string& referer, - chromeos::ErrorPtr* error) override; - - void RunCallbackAsync(const tracked_objects::Location& from_here, - const base::Closure& callback) override; - - RequestID StartAsyncTransfer(http::Connection* connection, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) override; - - bool CancelRequest(RequestID request_id) override; - - void SetDefaultTimeout(base::TimeDelta timeout) override; - - private: - // A list of user-supplied request handlers. - std::map handlers_; - // Counter incremented each time a request is made. - int request_count_{0}; - bool async_{false}; - // A list of queued callbacks that need to be called at some point. - // Call HandleOneAsyncRequest() or HandleAllAsyncRequests() to invoke them. - std::queue async_callback_queue_; - - // Fake error to be returned from CreateConnection method. - chromeos::ErrorPtr create_connection_error_; - - DISALLOW_COPY_AND_ASSIGN(Transport); -}; - -/////////////////////////////////////////////////////////////////////////////// -// A base class for ServerRequest and ServerResponse. It provides common -// functionality to work with request/response HTTP headers and data. -/////////////////////////////////////////////////////////////////////////////// -class ServerRequestResponseBase { - public: - ServerRequestResponseBase() = default; - - // Add/retrieve request/response body data. - void SetData(StreamPtr stream); - const std::vector& GetData() const { return data_; } - std::string GetDataAsString() const; - std::unique_ptr GetDataAsJson() const; - // Parses the data into a JSON object and writes it back to JSON to normalize - // its string representation (no pretty print, extra spaces, etc). - std::string GetDataAsNormalizedJsonString() const; - - // Add/retrieve request/response HTTP headers. - void AddHeaders(const HeaderList& headers); - std::string GetHeader(const std::string& header_name) const; - const std::multimap& GetHeaders() const { - return headers_; - } - - protected: - // Data buffer. - std::vector data_; - // Header map. - std::multimap headers_; - - private: - DISALLOW_COPY_AND_ASSIGN(ServerRequestResponseBase); -}; - -/////////////////////////////////////////////////////////////////////////////// -// A container class that encapsulates all the HTTP server request information. -/////////////////////////////////////////////////////////////////////////////// -class ServerRequest : public ServerRequestResponseBase { - public: - ServerRequest(const std::string& url, const std::string& method); - - // Get the actual request URL. Does not include the query string or fragment. - const std::string& GetURL() const { return url_; } - // Get the request method. - const std::string& GetMethod() const { return method_; } - // Get the POST/GET request parameters. These are parsed query string - // parameters from the URL. In addition, for POST requests with - // application/x-www-form-urlencoded content type, the request body is also - // parsed and individual fields can be accessed through this method. - std::string GetFormField(const std::string& field_name) const; - - private: - // Request URL (without query string or URL fragment). - std::string url_; - // Request method - std::string method_; - // List of available request data form fields. - mutable std::map form_fields_; - // Flag used on first request to GetFormField to parse the body of HTTP POST - // request with application/x-www-form-urlencoded content. - mutable bool form_fields_parsed_ = false; - - DISALLOW_COPY_AND_ASSIGN(ServerRequest); -}; - -/////////////////////////////////////////////////////////////////////////////// -// A container class that encapsulates all the HTTP server response information. -// The request handler will use this class to provide a response to the caller. -// Call the Reply() or the appropriate ReplyNNN() specialization to provide -// the response data. Additional calls to AddHeaders() can be made to provide -// custom response headers. The Reply-methods will already provide the -// following response headers: -// Content-Length -// Content-Type -/////////////////////////////////////////////////////////////////////////////// -class ServerResponse : public ServerRequestResponseBase { - public: - ServerResponse() = default; - - // Generic reply method. - void Reply(int status_code, - const void* data, - size_t data_size, - const std::string& mime_type); - // Reply with text body. - void ReplyText(int status_code, - const std::string& text, - const std::string& mime_type); - // Reply with JSON object. The content type will be "application/json". - void ReplyJson(int status_code, const base::Value* json); - // Special form for JSON response for simple objects that have a flat - // list of key-value pairs of string type. - void ReplyJson(int status_code, const FormFieldList& fields); - - // Specialized overload to send the binary data as an array of simple - // data elements. Only trivial data types (scalars, POD structures, etc) - // can be used. - template - void Reply(int status_code, - const std::vector& data, - const std::string& mime_type) { - // Make sure T doesn't have virtual functions, custom constructors, etc. - static_assert(std::is_trivial::value, "Only simple data is supported"); - Reply(status_code, data.data(), data.size() * sizeof(T), mime_type); - } - - // Specialized overload to send the binary data. - // Only trivial data types (scalars, POD structures, etc) can be used. - template - void Reply(int status_code, const T& data, const std::string& mime_type) { - // Make sure T doesn't have virtual functions, custom constructors, etc. - static_assert(std::is_trivial::value, "Only simple data is supported"); - Reply(status_code, &data, sizeof(T), mime_type); - } - - // For handlers that want to simulate versions of HTTP protocol other - // than HTTP/1.1, call this method with the custom version string, - // for example "HTTP/1.0". - void SetProtocolVersion(const std::string& protocol_version) { - protocol_version_ = protocol_version; - } - - protected: - // These methods are helpers to implement corresponding functionality - // of fake::Connection. - friend class Connection; - // Helper for fake::Connection::GetResponseStatusCode(). - int GetStatusCode() const { return status_code_; } - // Helper for fake::Connection::GetResponseStatusText(). - std::string GetStatusText() const; - // Helper for fake::Connection::GetProtocolVersion(). - std::string GetProtocolVersion() const { return protocol_version_; } - - private: - int status_code_ = 0; - std::string protocol_version_ = "HTTP/1.1"; - - DISALLOW_COPY_AND_ASSIGN(ServerResponse); -}; - -} // namespace fake -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_TRANSPORT_FAKE_H_ diff --git a/chromeos/http/http_utils.cc b/chromeos/http/http_utils.cc deleted file mode 100644 index 3413817..0000000 --- a/chromeos/http/http_utils.cc +++ /dev/null @@ -1,452 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using chromeos::mime::AppendParameter; -using chromeos::mime::RemoveParameters; - -namespace chromeos { -namespace http { - -std::unique_ptr GetAndBlock(const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - return SendRequestWithNoDataAndBlock( - request_type::kGet, url, headers, transport, error); -} - -RequestID Get(const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - return SendRequestWithNoData(request_type::kGet, - url, - headers, - transport, - success_callback, - error_callback); -} - -std::unique_ptr HeadAndBlock(const std::string& url, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - return SendRequestWithNoDataAndBlock( - request_type::kHead, url, {}, transport, error); -} - -RequestID Head(const std::string& url, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - return SendRequestWithNoData(request_type::kHead, - url, - {}, - transport, - success_callback, - error_callback); -} - -std::unique_ptr PostTextAndBlock(const std::string& url, - const std::string& data, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - return PostBinaryAndBlock( - url, data.data(), data.size(), mime_type, headers, transport, error); -} - -RequestID PostText(const std::string& url, - const std::string& data, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - return PostBinary(url, - data.data(), - data.size(), - mime_type, - headers, - transport, - success_callback, - error_callback); -} - -std::unique_ptr SendRequestAndBlock( - const std::string& method, - const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - Request request(url, method, transport); - request.AddHeaders(headers); - if (data_size > 0) { - CHECK(!mime_type.empty()) << "MIME type must be specified if request body " - "message is provided"; - request.SetContentType(mime_type); - if (!request.AddRequestBody(data, data_size, error)) - return std::unique_ptr(); - } - return request.GetResponseAndBlock(error); -} - -std::unique_ptr SendRequestWithNoDataAndBlock( - const std::string& method, - const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - return SendRequestAndBlock( - method, url, nullptr, 0, {}, headers, transport, error); -} - -RequestID SendRequest(const std::string& method, - const std::string& url, - StreamPtr stream, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - Request request(url, method, transport); - request.AddHeaders(headers); - if (stream && (!stream->CanGetSize() || stream->GetRemainingSize() > 0)) { - CHECK(!mime_type.empty()) << "MIME type must be specified if request body " - "message is provided"; - request.SetContentType(mime_type); - chromeos::ErrorPtr error; - if (!request.AddRequestBody(std::move(stream), &error)) { - transport->RunCallbackAsync( - FROM_HERE, base::Bind(error_callback, - 0, base::Owned(error.release()))); - return 0; - } - } - return request.GetResponse(success_callback, error_callback); -} - -RequestID SendRequest(const std::string& method, - const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - return SendRequest(method, - url, - MemoryStream::OpenCopyOf(data, data_size, nullptr), - mime_type, - headers, - transport, - success_callback, - error_callback); -} - -RequestID SendRequestWithNoData(const std::string& method, - const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - return SendRequest(method, - url, - {}, - {}, - headers, - transport, - success_callback, - error_callback); -} - -std::unique_ptr PostBinaryAndBlock( - const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - return SendRequestAndBlock(request_type::kPost, - url, - data, - data_size, - mime_type, - headers, - transport, - error); -} - -RequestID PostBinary(const std::string& url, - StreamPtr stream, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - return SendRequest(request_type::kPost, - url, - std::move(stream), - mime_type, - headers, - transport, - success_callback, - error_callback); -} - -RequestID PostBinary(const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - return SendRequest(request_type::kPost, - url, - data, - data_size, - mime_type, - headers, - transport, - success_callback, - error_callback); -} - -std::unique_ptr PostFormDataAndBlock( - const std::string& url, - const FormFieldList& data, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - std::string encoded_data = chromeos::data_encoding::WebParamsEncode(data); - return PostBinaryAndBlock(url, - encoded_data.c_str(), - encoded_data.size(), - chromeos::mime::application::kWwwFormUrlEncoded, - headers, - transport, - error); -} - -std::unique_ptr PostFormDataAndBlock( - const std::string& url, - std::unique_ptr form_data, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - Request request(url, request_type::kPost, transport); - request.AddHeaders(headers); - if (!request.AddRequestBodyAsFormData(std::move(form_data), error)) - return std::unique_ptr(); - return request.GetResponseAndBlock(error); -} - -RequestID PostFormData(const std::string& url, - const FormFieldList& data, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - std::string encoded_data = chromeos::data_encoding::WebParamsEncode(data); - return PostBinary(url, - encoded_data.c_str(), - encoded_data.size(), - chromeos::mime::application::kWwwFormUrlEncoded, - headers, - transport, - success_callback, - error_callback); -} - -RequestID PostFormData(const std::string& url, - std::unique_ptr form_data, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - Request request(url, request_type::kPost, transport); - request.AddHeaders(headers); - chromeos::ErrorPtr error; - if (!request.AddRequestBodyAsFormData(std::move(form_data), &error)) { - transport->RunCallbackAsync( - FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release()))); - return 0; - } - return request.GetResponse(success_callback, error_callback); -} - -std::unique_ptr PostJsonAndBlock(const std::string& url, - const base::Value* json, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - std::string data; - if (json) - base::JSONWriter::Write(*json, &data); - std::string mime_type = AppendParameter(chromeos::mime::application::kJson, - chromeos::mime::parameters::kCharset, - "utf-8"); - return PostBinaryAndBlock( - url, data.c_str(), data.size(), mime_type, headers, transport, error); -} - -RequestID PostJson(const std::string& url, - std::unique_ptr json, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - std::string data; - if (json) - base::JSONWriter::Write(*json, &data); - std::string mime_type = AppendParameter(chromeos::mime::application::kJson, - chromeos::mime::parameters::kCharset, - "utf-8"); - return PostBinary(url, - data.c_str(), - data.size(), - mime_type, - headers, - transport, - success_callback, - error_callback); -} - -std::unique_ptr PatchJsonAndBlock( - const std::string& url, - const base::Value* json, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error) { - std::string data; - if (json) - base::JSONWriter::Write(*json, &data); - std::string mime_type = AppendParameter(chromeos::mime::application::kJson, - chromeos::mime::parameters::kCharset, - "utf-8"); - return SendRequestAndBlock(request_type::kPatch, - url, - data.c_str(), - data.size(), - mime_type, - headers, - transport, - error); -} - -RequestID PatchJson(const std::string& url, - std::unique_ptr json, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback) { - std::string data; - if (json) - base::JSONWriter::Write(*json, &data); - std::string mime_type = AppendParameter(chromeos::mime::application::kJson, - chromeos::mime::parameters::kCharset, - "utf-8"); - return SendRequest(request_type::kPatch, - url, - data.c_str(), - data.size(), - mime_type, - headers, - transport, - success_callback, - error_callback); -} - -std::unique_ptr ParseJsonResponse( - Response* response, - int* status_code, - chromeos::ErrorPtr* error) { - if (!response) - return std::unique_ptr(); - - if (status_code) - *status_code = response->GetStatusCode(); - - // Make sure we have a correct content type. Do not try to parse - // binary files, or HTML output. Limit to application/json and text/plain. - auto content_type = RemoveParameters(response->GetContentType()); - if (content_type != chromeos::mime::application::kJson && - content_type != chromeos::mime::text::kPlain) { - chromeos::Error::AddTo(error, - FROM_HERE, - chromeos::errors::json::kDomain, - "non_json_content_type", - "Unexpected response content type: " + content_type); - return std::unique_ptr(); - } - - std::string json = response->ExtractDataAsString(); - std::string error_message; - auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC, - nullptr, &error_message); - if (!value) { - chromeos::Error::AddToPrintf(error, - FROM_HERE, - chromeos::errors::json::kDomain, - chromeos::errors::json::kParseError, - "Error '%s' occurred parsing JSON string '%s'", - error_message.c_str(), json.c_str()); - return std::unique_ptr(); - } - base::DictionaryValue* dict_value = nullptr; - if (!value->GetAsDictionary(&dict_value)) { - chromeos::Error::AddToPrintf(error, - FROM_HERE, - chromeos::errors::json::kDomain, - chromeos::errors::json::kObjectExpected, - "Response is not a valid JSON object: '%s'", - json.c_str()); - return std::unique_ptr(); - } else { - // |value| is now owned by |dict_value|, so release the scoped_ptr now. - base::IgnoreResult(value.release()); - } - return std::unique_ptr(dict_value); -} - -std::string GetCanonicalHeaderName(const std::string& name) { - std::string canonical_name = name; - bool word_begin = true; - for (char& c : canonical_name) { - if (c == '-') { - word_begin = true; - } else { - if (word_begin) { - c = toupper(c); - } else { - c = tolower(c); - } - word_begin = false; - } - } - return canonical_name; -} - -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/http_utils.h b/chromeos/http/http_utils.h deleted file mode 100644 index 679bd6b..0000000 --- a/chromeos/http/http_utils.h +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_HTTP_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_HTTP_UTILS_H_ - -#include -#include -#include - -#include -#include -#include -#include - -namespace base { -class Value; -class DictionaryValue; -} // namespace base - -namespace chromeos { -namespace http { - -using FormFieldList = std::vector>; - -//////////////////////////////////////////////////////////////////////////////// -// The following are simple utility helper functions for common HTTP operations -// that use http::Request object behind the scenes and set it up accordingly. -// The values for request method, data MIME type, request header names should -// not be directly encoded in most cases, but use predefined constants from -// http_request.h. -// So, instead of calling: -// SendRequestAndBlock("POST", -// "http://url", -// "data", 4, -// "text/plain", -// {{"Authorization", "Bearer TOKEN"}}, -// transport, error); -// You should do use this instead: -// SendRequestAndBlock(chromeos::http::request_type::kPost, -// "http://url", -// "data", 4, -// chromeos::mime::text::kPlain, -// {{chromeos::http::request_header::kAuthorization, -// "Bearer TOKEN"}}, -// transport, error); -// -// For more advanced functionality you need to use Request/Response objects -// directly. -//////////////////////////////////////////////////////////////////////////////// - -// Performs a generic HTTP request with binary data. Success status, -// returned data and additional information (such as returned HTTP headers) -// can be obtained from the returned Response object. -CHROMEOS_EXPORT std::unique_ptr SendRequestAndBlock( - const std::string& method, - const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Same as above, but without sending the request body. -// This is especially useful for requests like "GET" and "HEAD". -CHROMEOS_EXPORT std::unique_ptr SendRequestWithNoDataAndBlock( - const std::string& method, - const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Same as above but asynchronous. On success, |success_callback| is called -// with the response object. On failure, |error_callback| is called with the -// error details. -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID SendRequest( - const std::string& method, - const std::string& url, - StreamPtr stream, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Same as above, but takes a memory buffer. The pointer should be valid only -// until the function returns. The data is copied into an internal buffer to be -// available for the duration of the asynchronous operation. -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID SendRequest( - const std::string& method, - const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Asynchronous version of SendRequestNoData(). -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID SendRequestWithNoData( - const std::string& method, - const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a GET request. Success status, returned data and additional -// information (such as returned HTTP headers) can be obtained from -// the returned Response object. -CHROMEOS_EXPORT std::unique_ptr GetAndBlock( - const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Asynchronous version of http::Get(). -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID Get( - const std::string& url, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a HEAD request. Success status and additional -// information (such as returned HTTP headers) can be obtained from -// the returned Response object. -CHROMEOS_EXPORT std::unique_ptr HeadAndBlock( - const std::string& url, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Performs an asynchronous HEAD request. -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID Head( - const std::string& url, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a POST request with binary data. Success status, returned data -// and additional information (such as returned HTTP headers) can be obtained -// from the returned Response object. -CHROMEOS_EXPORT std::unique_ptr PostBinaryAndBlock( - const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Async version of PostBinary(). -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID PostBinary( - const std::string& url, - StreamPtr stream, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Same as above, but takes a memory buffer. The pointer should be valid only -// until the function returns. The data is copied into an internal buffer -// to be available for the duration of the asynchronous operation. -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID PostBinary( - const std::string& url, - const void* data, - size_t data_size, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a POST request with text data. Success status, returned data -// and additional information (such as returned HTTP headers) can be obtained -// from the returned Response object. -CHROMEOS_EXPORT std::unique_ptr PostTextAndBlock( - const std::string& url, - const std::string& data, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Async version of PostText(). -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID PostText( - const std::string& url, - const std::string& data, - const std::string& mime_type, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a POST request with form data. Success status, returned data -// and additional information (such as returned HTTP headers) can be obtained -// from the returned Response object. The form data is a list of key/value -// pairs. The data is posed as "application/x-www-form-urlencoded". -CHROMEOS_EXPORT std::unique_ptr PostFormDataAndBlock( - const std::string& url, - const FormFieldList& data, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Async version of PostFormData() above. -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID PostFormData( - const std::string& url, - const FormFieldList& data, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a POST request with form data, including binary file uploads. -// Success status, returned data and additional information (such as returned -// HTTP headers) can be obtained from the returned Response object. -// The data is posed as "multipart/form-data". -CHROMEOS_EXPORT std::unique_ptr PostFormDataAndBlock( - const std::string& url, - std::unique_ptr form_data, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Async version of PostFormData() above. -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID PostFormData( - const std::string& url, - std::unique_ptr form_data, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a POST request with JSON data. Success status, returned data -// and additional information (such as returned HTTP headers) can be obtained -// from the returned Response object. If a JSON response is expected, -// use ParseJsonResponse() method on the returned Response object. -CHROMEOS_EXPORT std::unique_ptr PostJsonAndBlock( - const std::string& url, - const base::Value* json, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Async version of PostJson(). -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID PostJson( - const std::string& url, - std::unique_ptr json, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Performs a PATCH request with JSON data. Success status, returned data -// and additional information (such as returned HTTP headers) can be obtained -// from the returned Response object. If a JSON response is expected, -// use ParseJsonResponse() method on the returned Response object. -CHROMEOS_EXPORT std::unique_ptr PatchJsonAndBlock( - const std::string& url, - const base::Value* json, - const HeaderList& headers, - std::shared_ptr transport, - chromeos::ErrorPtr* error); - -// Async version of PatchJson(). -// Returns the ID of the request which can be used to cancel the pending -// request using Transport::CancelRequest(). -CHROMEOS_EXPORT RequestID PatchJson( - const std::string& url, - std::unique_ptr json, - const HeaderList& headers, - std::shared_ptr transport, - const SuccessCallback& success_callback, - const ErrorCallback& error_callback); - -// Given an http::Response object, parse the body data into Json object. -// Returns null if failed. Optional |error| can be passed in to -// get the extended error information as to why the parse failed. -CHROMEOS_EXPORT std::unique_ptr ParseJsonResponse( - Response* response, int* status_code, chromeos::ErrorPtr* error); - -// Converts a request header name to canonical form (lowercase with uppercase -// first letter and each letter after a hyphen ('-')). -// "content-TYPE" will be converted to "Content-Type". -CHROMEOS_EXPORT std::string GetCanonicalHeaderName(const std::string& name); - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_HTTP_UTILS_H_ diff --git a/chromeos/http/http_utils_unittest.cc b/chromeos/http/http_utils_unittest.cc deleted file mode 100644 index 3e9a7c8..0000000 --- a/chromeos/http/http_utils_unittest.cc +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { -namespace http { - -static const char kFakeUrl[] = "http://localhost"; -static const char kEchoUrl[] = "http://localhost/echo"; -static const char kMethodEchoUrl[] = "http://localhost/echo/method"; - -///////////////////// Generic helper request handlers ///////////////////////// -// Returns the request data back with the same content type. -static void EchoDataHandler(const fake::ServerRequest& request, - fake::ServerResponse* response) { - response->Reply(status_code::Ok, - request.GetData(), - request.GetHeader(request_header::kContentType)); -} - -// Returns the request method as a plain text response. -static void EchoMethodHandler(const fake::ServerRequest& request, - fake::ServerResponse* response) { - response->ReplyText( - status_code::Ok, request.GetMethod(), chromeos::mime::text::kPlain); -} - -/////////////////////////////////////////////////////////////////////////////// -TEST(HttpUtils, SendRequest_BinaryData) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler( - kEchoUrl, request_type::kPost, base::Bind(EchoDataHandler)); - - // Test binary data round-tripping. - std::vector custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; - auto response = - http::SendRequestAndBlock(request_type::kPost, - kEchoUrl, - custom_data.data(), - custom_data.size(), - chromeos::mime::application::kOctet_stream, - {}, - transport, - nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::application::kOctet_stream, - response->GetContentType()); - EXPECT_EQ(custom_data, response->ExtractData()); -} - -TEST(HttpUtils, SendRequestAsync_BinaryData) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler( - kEchoUrl, request_type::kPost, base::Bind(EchoDataHandler)); - - // Test binary data round-tripping. - std::vector custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; - auto success_callback = - [&custom_data](RequestID id, std::unique_ptr response) { - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::application::kOctet_stream, - response->GetContentType()); - EXPECT_EQ(custom_data, response->ExtractData()); - }; - auto error_callback = [](RequestID id, const Error* error) { - FAIL() << "This callback shouldn't have been called"; - }; - http::SendRequest(request_type::kPost, - kEchoUrl, - custom_data.data(), - custom_data.size(), - chromeos::mime::application::kOctet_stream, - {}, - transport, - base::Bind(success_callback), - base::Bind(error_callback)); -} - -TEST(HttpUtils, SendRequest_Post) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); - - // Test binary data round-tripping. - std::vector custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; - - // Check the correct HTTP method used. - auto response = - http::SendRequestAndBlock(request_type::kPost, - kMethodEchoUrl, - custom_data.data(), - custom_data.size(), - chromeos::mime::application::kOctet_stream, - {}, - transport, - nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::text::kPlain, response->GetContentType()); - EXPECT_EQ(request_type::kPost, response->ExtractDataAsString()); -} - -TEST(HttpUtils, SendRequest_Get) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); - - auto response = http::SendRequestAndBlock(request_type::kGet, - kMethodEchoUrl, - nullptr, - 0, - std::string{}, - {}, - transport, - nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::text::kPlain, response->GetContentType()); - EXPECT_EQ(request_type::kGet, response->ExtractDataAsString()); -} - -TEST(HttpUtils, SendRequest_Put) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); - - auto response = http::SendRequestAndBlock(request_type::kPut, - kMethodEchoUrl, - nullptr, - 0, - std::string{}, - {}, - transport, - nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::text::kPlain, response->GetContentType()); - EXPECT_EQ(request_type::kPut, response->ExtractDataAsString()); -} - -TEST(HttpUtils, SendRequest_NotFound) { - std::shared_ptr transport(new fake::Transport); - // Test failed response (URL not found). - auto response = http::SendRequestWithNoDataAndBlock( - request_type::kGet, "http://blah.com", {}, transport, nullptr); - EXPECT_FALSE(response->IsSuccessful()); - EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); -} - -TEST(HttpUtils, SendRequestAsync_NotFound) { - std::shared_ptr transport(new fake::Transport); - // Test failed response (URL not found). - auto success_callback = - [](RequestID request_id, std::unique_ptr response) { - EXPECT_FALSE(response->IsSuccessful()); - EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); - }; - auto error_callback = [](RequestID request_id, const Error* error) { - FAIL() << "This callback shouldn't have been called"; - }; - http::SendRequestWithNoData(request_type::kGet, - "http://blah.com", - {}, - transport, - base::Bind(success_callback), - base::Bind(error_callback)); -} - -TEST(HttpUtils, SendRequest_Headers) { - std::shared_ptr transport(new fake::Transport); - - static const char json_echo_url[] = "http://localhost/echo/json"; - auto JsonEchoHandler = - [](const fake::ServerRequest& request, fake::ServerResponse* response) { - base::DictionaryValue json; - json.SetString("method", request.GetMethod()); - json.SetString("data", request.GetDataAsString()); - for (const auto& pair : request.GetHeaders()) { - json.SetString("header." + pair.first, pair.second); - } - response->ReplyJson(status_code::Ok, &json); - }; - transport->AddHandler(json_echo_url, "*", base::Bind(JsonEchoHandler)); - auto response = http::SendRequestAndBlock( - request_type::kPost, json_echo_url, "abcd", 4, - chromeos::mime::application::kOctet_stream, { - {request_header::kCookie, "flavor=vanilla"}, - {request_header::kIfMatch, "*"}, - }, transport, nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::application::kJson, - chromeos::mime::RemoveParameters(response->GetContentType())); - auto json = ParseJsonResponse(response.get(), nullptr, nullptr); - std::string value; - EXPECT_TRUE(json->GetString("method", &value)); - EXPECT_EQ(request_type::kPost, value); - EXPECT_TRUE(json->GetString("data", &value)); - EXPECT_EQ("abcd", value); - EXPECT_TRUE(json->GetString("header.Cookie", &value)); - EXPECT_EQ("flavor=vanilla", value); - EXPECT_TRUE(json->GetString("header.Content-Type", &value)); - EXPECT_EQ(chromeos::mime::application::kOctet_stream, value); - EXPECT_TRUE(json->GetString("header.Content-Length", &value)); - EXPECT_EQ("4", value); - EXPECT_TRUE(json->GetString("header.If-Match", &value)); - EXPECT_EQ("*", value); -} - -TEST(HttpUtils, Get) { - // Sends back the "?test=..." portion of URL. - // So if we do GET "http://localhost?test=blah", this handler responds - // with "blah" as text/plain. - auto GetHandler = - [](const fake::ServerRequest& request, fake::ServerResponse* response) { - EXPECT_EQ(request_type::kGet, request.GetMethod()); - EXPECT_EQ("0", request.GetHeader(request_header::kContentLength)); - EXPECT_EQ("", request.GetHeader(request_header::kContentType)); - response->ReplyText(status_code::Ok, - request.GetFormField("test"), - chromeos::mime::text::kPlain); - }; - - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kFakeUrl, request_type::kGet, base::Bind(GetHandler)); - transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); - - // Make sure Get() actually does the GET request - auto response = http::GetAndBlock(kMethodEchoUrl, {}, transport, nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::text::kPlain, response->GetContentType()); - EXPECT_EQ(request_type::kGet, response->ExtractDataAsString()); - - for (std::string data : {"blah", "some data", ""}) { - std::string url = chromeos::url::AppendQueryParam(kFakeUrl, "test", data); - response = http::GetAndBlock(url, {}, transport, nullptr); - EXPECT_EQ(data, response->ExtractDataAsString()); - } -} - -TEST(HttpUtils, Head) { - auto HeadHandler = - [](const fake::ServerRequest& request, fake::ServerResponse* response) { - EXPECT_EQ(request_type::kHead, request.GetMethod()); - EXPECT_EQ("0", request.GetHeader(request_header::kContentLength)); - EXPECT_EQ("", request.GetHeader(request_header::kContentType)); - response->ReplyText(status_code::Ok, "blah", chromeos::mime::text::kPlain); - }; - - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler)); - - auto response = http::HeadAndBlock(kFakeUrl, transport, nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::text::kPlain, response->GetContentType()); - EXPECT_EQ("", response->ExtractDataAsString()); // Must not have actual body. - EXPECT_EQ("4", response->GetHeader(request_header::kContentLength)); -} - -TEST(HttpUtils, PostBinary) { - auto Handler = - [](const fake::ServerRequest& request, fake::ServerResponse* response) { - EXPECT_EQ(request_type::kPost, request.GetMethod()); - EXPECT_EQ("256", request.GetHeader(request_header::kContentLength)); - EXPECT_EQ(chromeos::mime::application::kOctet_stream, - request.GetHeader(request_header::kContentType)); - const auto& data = request.GetData(); - EXPECT_EQ(256, data.size()); - - // Sum up all the bytes. - int sum = std::accumulate(data.begin(), data.end(), 0); - EXPECT_EQ(32640, sum); // sum(i, i => [0, 255]) = 32640. - response->ReplyText(status_code::Ok, "", chromeos::mime::text::kPlain); - }; - - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(Handler)); - - /// Fill the data buffer with bytes from 0x00 to 0xFF. - std::vector data(256); - std::iota(data.begin(), data.end(), 0); - - auto response = http::PostBinaryAndBlock(kFakeUrl, - data.data(), - data.size(), - mime::application::kOctet_stream, - {}, - transport, - nullptr); - EXPECT_TRUE(response->IsSuccessful()); -} - -TEST(HttpUtils, PostText) { - std::string fake_data = "Some data"; - auto PostHandler = [fake_data](const fake::ServerRequest& request, - fake::ServerResponse* response) { - EXPECT_EQ(request_type::kPost, request.GetMethod()); - EXPECT_EQ(fake_data.size(), - std::stoul(request.GetHeader(request_header::kContentLength))); - EXPECT_EQ(chromeos::mime::text::kPlain, - request.GetHeader(request_header::kContentType)); - response->ReplyText(status_code::Ok, - request.GetDataAsString(), - chromeos::mime::text::kPlain); - }; - - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(PostHandler)); - - auto response = http::PostTextAndBlock(kFakeUrl, - fake_data, - chromeos::mime::text::kPlain, - {}, - transport, - nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::text::kPlain, response->GetContentType()); - EXPECT_EQ(fake_data, response->ExtractDataAsString()); -} - -TEST(HttpUtils, PostFormData) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler( - kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler)); - - auto response = http::PostFormDataAndBlock( - kFakeUrl, { - {"key", "value"}, - {"field", "field value"}, - }, {}, transport, nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(chromeos::mime::application::kWwwFormUrlEncoded, - response->GetContentType()); - EXPECT_EQ("key=value&field=field+value", response->ExtractDataAsString()); -} - -TEST(HttpUtils, PostMultipartFormData) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler( - kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler)); - - std::unique_ptr form_data{new FormData{"boundary123"}}; - form_data->AddTextField("key1", "value1"); - form_data->AddTextField("key2", "value2"); - std::string expected_content_type = form_data->GetContentType(); - auto response = http::PostFormDataAndBlock( - kFakeUrl, std::move(form_data), {}, transport, nullptr); - EXPECT_TRUE(response->IsSuccessful()); - EXPECT_EQ(expected_content_type, response->GetContentType()); - const char expected_value[] = - "--boundary123\r\n" - "Content-Disposition: form-data; name=\"key1\"\r\n" - "\r\n" - "value1\r\n" - "--boundary123\r\n" - "Content-Disposition: form-data; name=\"key2\"\r\n" - "\r\n" - "value2\r\n" - "--boundary123--"; - EXPECT_EQ(expected_value, response->ExtractDataAsString()); -} - -TEST(HttpUtils, PostPatchJson) { - auto JsonHandler = - [](const fake::ServerRequest& request, fake::ServerResponse* response) { - auto mime_type = chromeos::mime::RemoveParameters( - request.GetHeader(request_header::kContentType)); - EXPECT_EQ(chromeos::mime::application::kJson, mime_type); - response->ReplyJson( - status_code::Ok, - { - {"method", request.GetMethod()}, {"data", request.GetDataAsString()}, - }); - }; - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kFakeUrl, "*", base::Bind(JsonHandler)); - - base::DictionaryValue json; - json.SetString("key1", "val1"); - json.SetString("key2", "val2"); - std::string value; - - // Test POST - auto response = - http::PostJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr); - auto resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr); - EXPECT_NE(nullptr, resp_json.get()); - EXPECT_TRUE(resp_json->GetString("method", &value)); - EXPECT_EQ(request_type::kPost, value); - EXPECT_TRUE(resp_json->GetString("data", &value)); - EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value); - - // Test PATCH - response = http::PatchJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr); - resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr); - EXPECT_NE(nullptr, resp_json.get()); - EXPECT_TRUE(resp_json->GetString("method", &value)); - EXPECT_EQ(request_type::kPatch, value); - EXPECT_TRUE(resp_json->GetString("data", &value)); - EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value); -} - -TEST(HttpUtils, ParseJsonResponse) { - auto JsonHandler = - [](const fake::ServerRequest& request, fake::ServerResponse* response) { - int status_code = std::stoi(request.GetFormField("code")); - response->ReplyJson(status_code, {{"data", request.GetFormField("value")}}); - }; - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(JsonHandler)); - - // Test valid JSON responses (with success or error codes). - for (auto item : {"200;data", "400;wrong", "500;Internal Server error"}) { - auto pair = chromeos::string_utils::SplitAtFirst(item, ";"); - auto response = http::PostFormDataAndBlock( - kFakeUrl, { - {"code", pair.first}, - {"value", pair.second}, - }, {}, transport, nullptr); - int code = 0; - auto json = http::ParseJsonResponse(response.get(), &code, nullptr); - EXPECT_NE(nullptr, json.get()); - std::string value; - EXPECT_TRUE(json->GetString("data", &value)); - EXPECT_EQ(pair.first, chromeos::string_utils::ToString(code)); - EXPECT_EQ(pair.second, value); - } - - // Test invalid (non-JSON) response. - auto response = http::GetAndBlock("http://bad.url", {}, transport, nullptr); - EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); - EXPECT_EQ(chromeos::mime::text::kHtml, response->GetContentType()); - int code = 0; - auto json = http::ParseJsonResponse(response.get(), &code, nullptr); - EXPECT_EQ(nullptr, json.get()); - EXPECT_EQ(status_code::NotFound, code); -} - -TEST(HttpUtils, SendRequest_Failure) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); - ErrorPtr error; - Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message"); - transport->SetCreateConnectionError(std::move(error)); - error.reset(); // Just to make sure it is empty... - auto response = http::SendRequestWithNoDataAndBlock( - request_type::kGet, "http://blah.com", {}, transport, &error); - EXPECT_EQ(nullptr, response.get()); - EXPECT_EQ("test_domain", error->GetDomain()); - EXPECT_EQ("test_code", error->GetCode()); - EXPECT_EQ("Test message", error->GetMessage()); -} - -TEST(HttpUtils, SendRequestAsync_Failure) { - std::shared_ptr transport(new fake::Transport); - transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); - ErrorPtr error; - Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message"); - transport->SetCreateConnectionError(std::move(error)); - auto success_callback = - [](RequestID request_id, std::unique_ptr response) { - FAIL() << "This callback shouldn't have been called"; - }; - auto error_callback = [](RequestID request_id, const Error* error) { - EXPECT_EQ("test_domain", error->GetDomain()); - EXPECT_EQ("test_code", error->GetCode()); - EXPECT_EQ("Test message", error->GetMessage()); - }; - http::SendRequestWithNoData(request_type::kGet, - "http://blah.com", - {}, - transport, - base::Bind(success_callback), - base::Bind(error_callback)); -} - -TEST(HttpUtils, GetCanonicalHeaderName) { - EXPECT_EQ("Foo", GetCanonicalHeaderName("foo")); - EXPECT_EQ("Bar", GetCanonicalHeaderName("BaR")); - EXPECT_EQ("Baz", GetCanonicalHeaderName("BAZ")); - EXPECT_EQ("Foo-Bar", GetCanonicalHeaderName("foo-bar")); - EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("foo-Bar-BAZ")); - EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("FOO-BAR-BAZ")); - EXPECT_EQ("Foo-Bar-", GetCanonicalHeaderName("fOO-bAR-")); - EXPECT_EQ("-Bar", GetCanonicalHeaderName("-bAR")); - EXPECT_EQ("", GetCanonicalHeaderName("")); - EXPECT_EQ("A-B-C", GetCanonicalHeaderName("a-B-c")); -} - -} // namespace http -} // namespace chromeos diff --git a/chromeos/http/mock_connection.h b/chromeos/http/mock_connection.h deleted file mode 100644 index ccd62d9..0000000 --- a/chromeos/http/mock_connection.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CONNECTION_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CONNECTION_H_ - -#include -#include - -#include -#include -#include - -namespace chromeos { -namespace http { - -class MockConnection : public Connection { - public: - using Connection::Connection; - - MOCK_METHOD2(SendHeaders, bool(const HeaderList&, ErrorPtr*)); - MOCK_METHOD2(MockSetRequestData, bool(Stream*, ErrorPtr*)); - MOCK_METHOD1(MockSetResponseData, void(Stream*)); - MOCK_METHOD1(FinishRequest, bool(ErrorPtr*)); - MOCK_METHOD2(FinishRequestAsync, - RequestID(const SuccessCallback&, const ErrorCallback&)); - MOCK_CONST_METHOD0(GetResponseStatusCode, int()); - MOCK_CONST_METHOD0(GetResponseStatusText, std::string()); - MOCK_CONST_METHOD0(GetProtocolVersion, std::string()); - MOCK_CONST_METHOD1(GetResponseHeader, std::string(const std::string&)); - MOCK_CONST_METHOD1(MockExtractDataStream, Stream*(chromeos::ErrorPtr*)); - - private: - bool SetRequestData(StreamPtr stream, chromeos::ErrorPtr* error) override { - return MockSetRequestData(stream.get(), error); - } - void SetResponseData(StreamPtr stream) override { - MockSetResponseData(stream.get()); - } - StreamPtr ExtractDataStream(chromeos::ErrorPtr* error) override { - return StreamPtr{MockExtractDataStream(error)}; - } - - DISALLOW_COPY_AND_ASSIGN(MockConnection); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CONNECTION_H_ diff --git a/chromeos/http/mock_curl_api.h b/chromeos/http/mock_curl_api.h deleted file mode 100644 index c3cb4c9..0000000 --- a/chromeos/http/mock_curl_api.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CURL_API_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CURL_API_H_ - -#include - -#include -#include - -namespace chromeos { -namespace http { - -// This is a mock for CURL interfaces which allows to mock out the CURL's -// low-level C APIs in tests by intercepting the virtual function calls on -// the abstract CurlInterface. -class MockCurlInterface : public CurlInterface { - public: - MockCurlInterface() = default; - - MOCK_METHOD0(EasyInit, CURL*()); - MOCK_METHOD1(EasyCleanup, void(CURL*)); - MOCK_METHOD3(EasySetOptInt, CURLcode(CURL*, CURLoption, int)); - MOCK_METHOD3(EasySetOptStr, CURLcode(CURL*, CURLoption, const std::string&)); - MOCK_METHOD3(EasySetOptPtr, CURLcode(CURL*, CURLoption, void*)); - MOCK_METHOD3(EasySetOptCallback, CURLcode(CURL*, CURLoption, intptr_t)); - MOCK_METHOD3(EasySetOptOffT, CURLcode(CURL*, CURLoption, curl_off_t)); - MOCK_METHOD1(EasyPerform, CURLcode(CURL*)); - MOCK_CONST_METHOD3(EasyGetInfoInt, CURLcode(CURL*, CURLINFO, int*)); - MOCK_CONST_METHOD3(EasyGetInfoDbl, CURLcode(CURL*, CURLINFO, double*)); - MOCK_CONST_METHOD3(EasyGetInfoStr, CURLcode(CURL*, CURLINFO, std::string*)); - MOCK_CONST_METHOD3(EasyGetInfoPtr, CURLcode(CURL*, CURLINFO, void**)); - MOCK_CONST_METHOD1(EasyStrError, std::string(CURLcode)); - MOCK_METHOD0(MultiInit, CURLM*()); - MOCK_METHOD1(MultiCleanup, CURLMcode(CURLM*)); - MOCK_METHOD2(MultiInfoRead, CURLMsg*(CURLM*, int*)); - MOCK_METHOD2(MultiAddHandle, CURLMcode(CURLM*, CURL*)); - MOCK_METHOD2(MultiRemoveHandle, CURLMcode(CURLM*, CURL*)); - MOCK_METHOD3(MultiSetSocketCallback, - CURLMcode(CURLM*, curl_socket_callback, void*)); - MOCK_METHOD3(MultiSetTimerCallback, - CURLMcode(CURLM*, curl_multi_timer_callback, void*)); - MOCK_METHOD3(MultiAssign, CURLMcode(CURLM*, curl_socket_t, void*)); - MOCK_METHOD4(MultiSocketAction, CURLMcode(CURLM*, curl_socket_t, int, int*)); - MOCK_CONST_METHOD1(MultiStrError, std::string(CURLMcode)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockCurlInterface); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_MOCK_CURL_API_H_ diff --git a/chromeos/http/mock_transport.h b/chromeos/http/mock_transport.h deleted file mode 100644 index 0627e54..0000000 --- a/chromeos/http/mock_transport.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_HTTP_MOCK_TRANSPORT_H_ -#define LIBCHROMEOS_CHROMEOS_HTTP_MOCK_TRANSPORT_H_ - -#include -#include - -#include -#include -#include - -namespace chromeos { -namespace http { - -class MockTransport : public Transport { - public: - MockTransport() = default; - - MOCK_METHOD6(CreateConnection, - std::shared_ptr(const std::string&, - const std::string&, - const HeaderList&, - const std::string&, - const std::string&, - chromeos::ErrorPtr*)); - MOCK_METHOD2(RunCallbackAsync, - void(const tracked_objects::Location&, const base::Closure&)); - MOCK_METHOD3(StartAsyncTransfer, RequestID(Connection*, - const SuccessCallback&, - const ErrorCallback&)); - MOCK_METHOD1(CancelRequest, bool(RequestID)); - MOCK_METHOD1(SetDefaultTimeout, void(base::TimeDelta)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockTransport); -}; - -} // namespace http -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_HTTP_MOCK_TRANSPORT_H_ diff --git a/chromeos/key_value_store.cc b/chromeos/key_value_store.cc deleted file mode 100644 index 8d91dac..0000000 --- a/chromeos/key_value_store.cc +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/key_value_store.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using std::map; -using std::string; -using std::vector; - -namespace chromeos { - -namespace { - -// Values used for booleans. -const char kTrueValue[] = "true"; -const char kFalseValue[] = "false"; - -// Returns a copy of |key| with leading and trailing whitespace removed. -string TrimKey(const string& key) { - string trimmed_key; - base::TrimWhitespace(key, base::TRIM_ALL, &trimmed_key); - CHECK(!trimmed_key.empty()); - return trimmed_key; -} - -} // namespace - -bool KeyValueStore::Load(const base::FilePath& path) { - string file_data; - if (!base::ReadFileToString(path, &file_data)) - return false; - return LoadFromString(file_data); -} - -bool KeyValueStore::LoadFromString(const std::string& data) { - // Split along '\n', then along '='. - vector lines; - base::SplitStringDontTrim(data, '\n', &lines); - for (auto it = lines.begin(); it != lines.end(); ++it) { - std::string line; - base::TrimWhitespace(*it, base::TRIM_LEADING, &line); - if (line.empty() || line.front() == '#') - continue; - - std::string key; - std::string value; - if (!string_utils::SplitAtFirst(line, "=", &key, &value, false)) - return false; - - base::TrimWhitespace(key, base::TRIM_TRAILING, &key); - if (key.empty()) - return false; - - // Append additional lines to the value as long as we see trailing - // backslashes. - while (!value.empty() && value.back() == '\\') { - ++it; - if (it == lines.end() || it->empty()) - return false; - value.pop_back(); - value += *it; - } - - store_[key] = value; - } - return true; -} - -bool KeyValueStore::Save(const base::FilePath& path) const { - return base::ImportantFileWriter::WriteFileAtomically(path, SaveToString()); -} - -string KeyValueStore::SaveToString() const { - string data; - for (const auto& key_value : store_) - data += key_value.first + "=" + key_value.second + "\n"; - return data; -} - -bool KeyValueStore::GetString(const string& key, string* value) const { - const auto key_value = store_.find(TrimKey(key)); - if (key_value == store_.end()) - return false; - *value = key_value->second; - return true; -} - -void KeyValueStore::SetString(const string& key, const string& value) { - store_[TrimKey(key)] = value; -} - -bool KeyValueStore::GetBoolean(const string& key, bool* value) const { - string string_value; - if (!GetString(key, &string_value)) - return false; - - if (string_value == kTrueValue) { - *value = true; - return true; - } else if (string_value == kFalseValue) { - *value = false; - return true; - } - return false; -} - -void KeyValueStore::SetBoolean(const string& key, bool value) { - SetString(key, value ? kTrueValue : kFalseValue); -} - -std::vector KeyValueStore::GetKeys() const { - return GetMapKeysAsVector(store_); -} - -} // namespace chromeos diff --git a/chromeos/key_value_store.h b/chromeos/key_value_store.h deleted file mode 100644 index 10f273c..0000000 --- a/chromeos/key_value_store.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// These functions can parse a blob of data that's formatted as a simple -// key value store. Each key/value pair is stored on its own line and -// separated by the first '=' on the line. - -#ifndef LIBCHROMEOS_CHROMEOS_KEY_VALUE_STORE_H_ -#define LIBCHROMEOS_CHROMEOS_KEY_VALUE_STORE_H_ - -#include -#include -#include - -#include -#include - -namespace chromeos { - -class CHROMEOS_EXPORT KeyValueStore { - public: - // Creates an empty KeyValueStore. - KeyValueStore() = default; - virtual ~KeyValueStore() = default; - - // Loads the key=value pairs from the given |path|. Lines starting with '#' - // and empty lines are ignored, and whitespace around keys is trimmed. - // Trailing backslashes may be used to extend values across multiple lines. - // Adds all the read key=values to the store, overriding those already defined - // but persisting the ones that aren't present on the passed file. Returns - // whether reading the file succeeded. - bool Load(const base::FilePath& path); - - // Loads the key=value pairs parsing the text passed in |data|. See Load() for - // details. - // Returns whether the parsing succeeded. - bool LoadFromString(const std::string& data); - - // Saves the current store to the given |path| file. See SaveToString() for - // details on the formate of the created file. - // Returns whether the file creation succeeded. - bool Save(const base::FilePath& path) const; - - // Returns a string with the contents of the store as key=value lines. - // Calling LoadFromString() and then SaveToString() may result in different - // result if the original string contained backslash-terminated lines (i.e. - // these values will be rewritten on single lines), comments or empty lines. - std::string SaveToString() const; - - // Getter for the given key. Returns whether the key was found on the store. - bool GetString(const std::string& key, std::string* value) const; - - // Setter for the given key. It overrides the key if already exists. - void SetString(const std::string& key, const std::string& value); - - // Boolean getter. Returns whether the key was found on the store and if it - // has a valid value ("true" or "false"). - bool GetBoolean(const std::string& key, bool* value) const; - - // Boolean setter. Sets the value as "true" or "false". - void SetBoolean(const std::string& key, bool value); - - // Retrieves the keys for all values currently stored in the map. - std::vector GetKeys() const; - - private: - // The map storing all the key-value pairs. - std::map store_; - - DISALLOW_COPY_AND_ASSIGN(KeyValueStore); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_KEY_VALUE_STORE_H_ diff --git a/chromeos/key_value_store_unittest.cc b/chromeos/key_value_store_unittest.cc deleted file mode 100644 index 649832b..0000000 --- a/chromeos/key_value_store_unittest.cc +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using base::FilePath; -using base::ReadFileToString; -using std::map; -using std::string; -using std::vector; - -namespace chromeos { - -class KeyValueStoreTest : public ::testing::Test { - protected: - // Returns the value from |store_| corresponding to |key|, or an empty string - // if the key is not present. Crashes if the store returns an empty value. - string GetNonemptyStringValue(const string& key) { - string value; - if (store_.GetString(key, &value)) - CHECK(!value.empty()); - return value; - } - - KeyValueStore store_; // KeyValueStore under test. -}; - -TEST_F(KeyValueStoreTest, LoadAndSaveFromFile) { - base::ScopedTempDir temp_dir_; - CHECK(temp_dir_.CreateUniqueTempDir()); - base::FilePath temp_file_ = temp_dir_.path().Append("temp.conf"); - base::FilePath saved_temp_file_ = temp_dir_.path().Append("saved_temp.conf"); - - string blob = "A=B\n# Comment\n"; - ASSERT_EQ(blob.size(), base::WriteFile(temp_file_, blob.data(), blob.size())); - ASSERT_TRUE(store_.Load(temp_file_)); - - string value; - EXPECT_TRUE(store_.GetString("A", &value)); - EXPECT_EQ("B", value); - - ASSERT_TRUE(store_.Save(saved_temp_file_)); - string read_blob; - ASSERT_TRUE(ReadFileToString(FilePath(saved_temp_file_), &read_blob)); - EXPECT_EQ("A=B\n", read_blob); -} - -TEST_F(KeyValueStoreTest, CommentsAreIgnored) { - EXPECT_TRUE(store_.LoadFromString( - "# comment\nA=B\n\n\n#another=comment\n # leading spaces\n")); - EXPECT_EQ("A=B\n", store_.SaveToString()); -} - -TEST_F(KeyValueStoreTest, EmptyTest) { - EXPECT_TRUE(store_.LoadFromString("")); - EXPECT_EQ("", store_.SaveToString()); -} - -TEST_F(KeyValueStoreTest, LoadAndReloadTest) { - EXPECT_TRUE(store_.LoadFromString( - "A=B\nC=\nFOO=BAR=BAZ\nBAR=BAX\nMISSING=NEWLINE")); - - map expected = {{"A", "B"}, - {"C", ""}, - {"FOO", "BAR=BAZ"}, - {"BAR", "BAX"}, - {"MISSING", "NEWLINE"}}; - - // Test expected values. - string value; - for (const auto& it : expected) { - EXPECT_TRUE(store_.GetString(it.first, &value)); - EXPECT_EQ(it.second, value) << "Testing key: " << it.first; - } - - // Save, load and test again. - KeyValueStore new_store; - ASSERT_TRUE(new_store.LoadFromString(store_.SaveToString())); - - for (const auto& it : expected) { - EXPECT_TRUE(new_store.GetString(it.first, &value)) << "key: " << it.first; - EXPECT_EQ(it.second, value) << "key: " << it.first; - } -} - -TEST_F(KeyValueStoreTest, SimpleBooleanTest) { - bool result; - EXPECT_FALSE(store_.GetBoolean("A", &result)); - - store_.SetBoolean("A", true); - EXPECT_TRUE(store_.GetBoolean("A", &result)); - EXPECT_TRUE(result); - - store_.SetBoolean("A", false); - EXPECT_TRUE(store_.GetBoolean("A", &result)); - EXPECT_FALSE(result); -} - -TEST_F(KeyValueStoreTest, BooleanParsingTest) { - string blob = "TRUE=true\nfalse=false\nvar=false\nDONT_SHOUT=TRUE\n"; - EXPECT_TRUE(store_.LoadFromString(blob)); - - map expected = { - {"TRUE", true}, {"false", false}, {"var", false}}; - bool value; - EXPECT_FALSE(store_.GetBoolean("DONT_SHOUT", &value)); - string str_value; - EXPECT_TRUE(store_.GetString("DONT_SHOUT", &str_value)); - - // Test expected values. - for (const auto& it : expected) { - EXPECT_TRUE(store_.GetBoolean(it.first, &value)) << "key: " << it.first; - EXPECT_EQ(it.second, value) << "key: " << it.first; - } -} - -TEST_F(KeyValueStoreTest, TrimWhitespaceAroundKey) { - EXPECT_TRUE(store_.LoadFromString(" a=1\nb =2\n c =3\n")); - - EXPECT_EQ("1", GetNonemptyStringValue("a")); - EXPECT_EQ("2", GetNonemptyStringValue("b")); - EXPECT_EQ("3", GetNonemptyStringValue("c")); - - // Keys should also be trimmed when setting new values. - store_.SetString(" foo ", "4"); - EXPECT_EQ("4", GetNonemptyStringValue("foo")); - - store_.SetBoolean(" bar ", true); - bool value = false; - ASSERT_TRUE(store_.GetBoolean("bar", &value)); - EXPECT_TRUE(value); -} - -TEST_F(KeyValueStoreTest, IgnoreWhitespaceLine) { - EXPECT_TRUE(store_.LoadFromString("a=1\n \t \nb=2")); - - EXPECT_EQ("1", GetNonemptyStringValue("a")); - EXPECT_EQ("2", GetNonemptyStringValue("b")); -} - -TEST_F(KeyValueStoreTest, RejectEmptyKeys) { - EXPECT_FALSE(store_.LoadFromString("=1")); - EXPECT_FALSE(store_.LoadFromString(" =2")); - - // Trying to set an empty (after trimming) key should fail an assert. - EXPECT_DEATH(store_.SetString(" ", "3"), ""); - EXPECT_DEATH(store_.SetBoolean(" ", "4"), ""); -} - -TEST_F(KeyValueStoreTest, RejectBogusLines) { - EXPECT_FALSE(store_.LoadFromString("a=1\nbogus\nb=2")); -} - -TEST_F(KeyValueStoreTest, MultilineValue) { - EXPECT_TRUE(store_.LoadFromString("a=foo\nb=bar\\\n baz \\ \nc=3\n")); - - EXPECT_EQ("foo", GetNonemptyStringValue("a")); - EXPECT_EQ("bar baz \\ ", GetNonemptyStringValue("b")); - EXPECT_EQ("3", GetNonemptyStringValue("c")); -} - -TEST_F(KeyValueStoreTest, UnterminatedMultilineValue) { - EXPECT_FALSE(store_.LoadFromString("a=foo\\")); - EXPECT_FALSE(store_.LoadFromString("a=foo\\\n")); - EXPECT_FALSE(store_.LoadFromString("a=foo\\\n\n# blah\n")); -} - -TEST_F(KeyValueStoreTest, GetKeys) { - map entries = { - {"1", "apple"}, {"2", "banana"}, {"3", "cherry"} - }; - for (const auto& it : entries) { - store_.SetString(it.first, it.second); - } - - vector keys = GetMapKeysAsVector(entries); - EXPECT_EQ(keys, store_.GetKeys()); -} - -} // namespace chromeos diff --git a/chromeos/location_logging.h b/chromeos/location_logging.h deleted file mode 100644 index a90aab8..0000000 --- a/chromeos/location_logging.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_LOCATION_LOGGING_H_ -#define LIBCHROMEOS_CHROMEOS_LOCATION_LOGGING_H_ - -// These macros help to log Location objects in verbose mode. - -#include - -#define VLOG_LOC_STREAM(from_here, verbose_level) \ - logging::LogMessage(from_here.file_name(), from_here.line_number(), \ - -verbose_level).stream() - -#define VLOG_LOC(from_here, verbose_level) \ - LAZY_STREAM(VLOG_LOC_STREAM(from_here, verbose_level), \ - VLOG_IS_ON(verbose_level)) - -#define DVLOG_LOC(from_here, verbose_level) \ - LAZY_STREAM(VLOG_LOC_STREAM(from_here, verbose_level), \ - ::logging::DEBUG_MODE && VLOG_IS_ON(verbose_level)) - -#endif // LIBCHROMEOS_CHROMEOS_LOCATION_LOGGING_H_ diff --git a/chromeos/make_unique_ptr.h b/chromeos/make_unique_ptr.h deleted file mode 100644 index 0d0ec16..0000000 --- a/chromeos/make_unique_ptr.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MAKE_UNIQUE_PTR_H_ -#define LIBCHROMEOS_CHROMEOS_MAKE_UNIQUE_PTR_H_ - -#include - -namespace chromeos { - -// A function to convert T* into unique_ptr -// Doing e.g. make_unique_ptr(new FooBarBaz(arg)) is a shorter notation -// for unique_ptr>(new FooBarBaz(arg)) -// Basically the same as Chromium's make_scoped_ptr(). -// Deliberately not named "make_unique" to avoid conflicting with the similar, -// but more complex and semantically different C++14 function. -template -std::unique_ptr make_unique_ptr(T* ptr) { - return std::unique_ptr(ptr); -} - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MAKE_UNIQUE_PTR_H_ diff --git a/chromeos/map_utils.h b/chromeos/map_utils.h deleted file mode 100644 index cf4fcd6..0000000 --- a/chromeos/map_utils.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MAP_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_MAP_UTILS_H_ - -#include -#include -#include -#include - -namespace chromeos { - -// Given an STL map, returns a set containing all keys from the map. -template -inline std::set GetMapKeys(const T& map) { - std::set keys; - for (const auto& pair : map) - keys.insert(keys.end(), pair.first); // Map keys are already sorted. - return keys; -} - -// Given an STL map, returns a vector containing all keys from the map. -// The keys in the vector are sorted. -template -inline std::vector GetMapKeysAsVector(const T& map) { - std::vector keys; - keys.reserve(map.size()); - for (const auto& pair : map) - keys.push_back(pair.first); - return keys; -} - -// Given an STL map, returns a vector containing all values from the map. -template -inline std::vector GetMapValues(const T& map) { - std::vector values; - values.reserve(map.size()); - for (const auto& pair : map) - values.push_back(pair.second); - return values; -} - -// Given an STL map, returns a vector of key-value pairs from the map. -template -inline std::vector> -MapToVector(const T& map) { - std::vector> vector; - vector.reserve(map.size()); - for (const auto& pair : map) - vector.push_back(pair); - return vector; -} - -// Given an STL map, returns the value associated with a given key or a default -// value if the key is not present in the map. -template -inline typename T::mapped_type GetOrDefault( - const T& map, - typename T::key_type key, - const typename T::mapped_type& def) { - typename T::const_iterator it = map.find(key); - if (it == map.end()) - return def; - return it->second; -} - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MAP_UTILS_H_ diff --git a/chromeos/map_utils_unittest.cc b/chromeos/map_utils_unittest.cc deleted file mode 100644 index b04620e..0000000 --- a/chromeos/map_utils_unittest.cc +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include - -namespace chromeos { - -class MapUtilsTest : public ::testing::Test { - public: - void SetUp() override { - map_ = { - {"key1", 1}, {"key2", 2}, {"key3", 3}, {"key4", 4}, {"key5", 5}, - }; - } - - void TearDown() override { map_.clear(); } - - std::map map_; -}; - -TEST_F(MapUtilsTest, GetMapKeys) { - std::set keys = GetMapKeys(map_); - EXPECT_EQ((std::set{"key1", "key2", "key3", "key4", "key5"}), - keys); -} - -TEST_F(MapUtilsTest, GetMapKeysAsVector) { - std::vector keys = GetMapKeysAsVector(map_); - EXPECT_EQ((std::vector{"key1", "key2", "key3", "key4", "key5"}), - keys); -} - -TEST_F(MapUtilsTest, GetMapValues) { - std::vector values = GetMapValues(map_); - EXPECT_EQ((std::vector{1, 2, 3, 4, 5}), values); -} - -TEST_F(MapUtilsTest, MapToVector) { - std::vector> elements = MapToVector(map_); - std::vector> expected{ - {"key1", 1}, {"key2", 2}, {"key3", 3}, {"key4", 4}, {"key5", 5}, - }; - EXPECT_EQ(expected, elements); -} - -TEST_F(MapUtilsTest, Empty) { - std::map empty_map; - EXPECT_TRUE(GetMapKeys(empty_map).empty()); - EXPECT_TRUE(GetMapKeysAsVector(empty_map).empty()); - EXPECT_TRUE(GetMapValues(empty_map).empty()); - EXPECT_TRUE(MapToVector(empty_map).empty()); -} - -} // namespace chromeos diff --git a/chromeos/message_loops/base_message_loop.cc b/chromeos/message_loops/base_message_loop.cc deleted file mode 100644 index 4923138..0000000 --- a/chromeos/message_loops/base_message_loop.cc +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include -#include - -#include - -using base::Closure; - -namespace chromeos { - -BaseMessageLoop::BaseMessageLoop(base::MessageLoopForIO* base_loop) - : base_loop_(base_loop), - weak_ptr_factory_(this) {} - -BaseMessageLoop::~BaseMessageLoop() { - for (auto& io_task : io_tasks_) { - DVLOG_LOC(io_task.second.location(), 1) - << "Removing file descriptor watcher task_id " << io_task.first - << " leaked on BaseMessageLoop, scheduled from this location."; - io_task.second.StopWatching(); - } - - // Note all pending canceled delayed tasks when destroying the message loop. - size_t lazily_deleted_tasks = 0; - for (const auto& delayed_task : delayed_tasks_) { - if (delayed_task.second.closure.is_null()) { - lazily_deleted_tasks++; - } else { - DVLOG_LOC(delayed_task.second.location, 1) - << "Removing delayed task_id " << delayed_task.first - << " leaked on BaseMessageLoop, scheduled from this location."; - } - } - if (lazily_deleted_tasks) { - LOG(INFO) << "Leaking " << lazily_deleted_tasks << " canceled tasks."; - } -} - -MessageLoop::TaskId BaseMessageLoop::PostDelayedTask( - const tracked_objects::Location& from_here, - const Closure &task, - base::TimeDelta delay) { - TaskId task_id = NextTaskId(); - bool base_scheduled = base_loop_->task_runner()->PostDelayedTask( - from_here, - base::Bind(&BaseMessageLoop::OnRanPostedTask, - weak_ptr_factory_.GetWeakPtr(), - task_id), - delay); - DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id - << " to run in " << delay << "."; - if (!base_scheduled) - return MessageLoop::kTaskIdNull; - - delayed_tasks_.emplace(task_id, - DelayedTask{from_here, task_id, std::move(task)}); - return task_id; -} - -MessageLoop::TaskId BaseMessageLoop::WatchFileDescriptor( - const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const Closure &task) { - // base::MessageLoopForIO CHECKS that "fd >= 0", so we handle that case here. - if (fd < 0) - return MessageLoop::kTaskIdNull; - - base::MessageLoopForIO::Mode base_mode = base::MessageLoopForIO::WATCH_READ; - switch (mode) { - case MessageLoop::kWatchRead: - base_mode = base::MessageLoopForIO::WATCH_READ; - break; - case MessageLoop::kWatchWrite: - base_mode = base::MessageLoopForIO::WATCH_WRITE; - break; - default: - return MessageLoop::kTaskIdNull; - } - - TaskId task_id = NextTaskId(); - auto it_bool = io_tasks_.emplace( - std::piecewise_construct, - std::forward_as_tuple(task_id), - std::forward_as_tuple( - from_here, this, task_id, fd, base_mode, persistent, task)); - // This should always insert a new element. - DCHECK(it_bool.second); - bool scheduled = it_bool.first->second.StartWatching(); - DVLOG_LOC(from_here, 1) - << "Watching fd " << fd << " for " - << (mode == MessageLoop::kWatchRead ? "reading" : "writing") - << (persistent ? " persistently" : " just once") - << " as task_id " << task_id - << (scheduled ? " successfully" : " failed."); - - if (!scheduled) { - io_tasks_.erase(task_id); - return MessageLoop::kTaskIdNull; - } - return task_id; -} - -bool BaseMessageLoop::CancelTask(TaskId task_id) { - if (task_id == kTaskIdNull) - return false; - auto delayed_task_it = delayed_tasks_.find(task_id); - if (delayed_task_it == delayed_tasks_.end()) { - // This might be an IOTask then. - auto io_task_it = io_tasks_.find(task_id); - if (io_task_it == io_tasks_.end()) - return false; - return io_task_it->second.CancelTask(); - } - // A DelayedTask was found for this task_id at this point. - - // Check if the callback was already canceled but we have the entry in - // delayed_tasks_ since it didn't fire yet in the message loop. - if (delayed_task_it->second.closure.is_null()) - return false; - - DVLOG_LOC(delayed_task_it->second.location, 1) - << "Removing task_id " << task_id << " scheduled from this location."; - // We reset to closure to a null Closure to release all the resources - // used by this closure at this point, but we don't remove the task_id from - // delayed_tasks_ since we can't tell base::MessageLoopForIO to not run it. - delayed_task_it->second.closure = Closure(); - - return true; -} - -bool BaseMessageLoop::RunOnce(bool may_block) { - run_once_ = true; - base::RunLoop run_loop; // Uses the base::MessageLoopForIO implicitly. - base_run_loop_ = &run_loop; - if (!may_block) - run_loop.RunUntilIdle(); - else - run_loop.Run(); - base_run_loop_ = nullptr; - // If the flag was reset to false, it means a closure was run. - if (!run_once_) - return true; - - run_once_ = false; - return false; -} - -void BaseMessageLoop::Run() { - base::RunLoop run_loop; // Uses the base::MessageLoopForIO implicitly. - base_run_loop_ = &run_loop; - run_loop.Run(); - base_run_loop_ = nullptr; -} - -void BaseMessageLoop::BreakLoop() { - if (base_run_loop_ == nullptr) { - DVLOG(1) << "Message loop not running, ignoring BreakLoop()."; - return; // Message loop not running, nothing to do. - } - base_run_loop_->Quit(); -} - -Closure BaseMessageLoop::QuitClosure() const { - if (base_run_loop_ == nullptr) - return base::Bind(&base::DoNothing); - return base_run_loop_->QuitClosure(); -} - -MessageLoop::TaskId BaseMessageLoop::NextTaskId() { - TaskId res; - do { - res = ++last_id_; - // We would run out of memory before we run out of task ids. - } while (!res || - delayed_tasks_.find(res) != delayed_tasks_.end() || - io_tasks_.find(res) != io_tasks_.end()); - return res; -} - -void BaseMessageLoop::OnRanPostedTask(MessageLoop::TaskId task_id) { - auto task_it = delayed_tasks_.find(task_id); - DCHECK(task_it != delayed_tasks_.end()); - if (!task_it->second.closure.is_null()) { - DVLOG_LOC(task_it->second.location, 1) - << "Running delayed task_id " << task_id - << " scheduled from this location."; - // Mark the task as canceled while we are running it so CancelTask returns - // false. - Closure closure = std::move(task_it->second.closure); - task_it->second.closure = Closure(); - closure.Run(); - - // If the |run_once_| flag is set, it is because we are instructed to run - // only once callback. - if (run_once_) { - run_once_ = false; - BreakLoop(); - } - } - delayed_tasks_.erase(task_it); -} - -void BaseMessageLoop::OnFileReadyPostedTask(MessageLoop::TaskId task_id) { - auto task_it = io_tasks_.find(task_id); - // Even if this task was canceled while we were waiting in the message loop - // for this method to run, the entry in io_tasks_ should still be present, but - // won't do anything. - DCHECK(task_it != io_tasks_.end()); - task_it->second.OnFileReadyPostedTask(); -} - -BaseMessageLoop::IOTask::IOTask(const tracked_objects::Location& location, - BaseMessageLoop* loop, - MessageLoop::TaskId task_id, - int fd, - base::MessageLoopForIO::Mode base_mode, - bool persistent, - const Closure& task) - : location_(location), loop_(loop), task_id_(task_id), - fd_(fd), base_mode_(base_mode), persistent_(persistent), closure_(task) {} - -bool BaseMessageLoop::IOTask::StartWatching() { - return loop_->base_loop_->WatchFileDescriptor( - fd_, persistent_, base_mode_, &fd_watcher_, this); -} - -void BaseMessageLoop::IOTask::StopWatching() { - // This is safe to call even if we are not watching for it. - fd_watcher_.StopWatchingFileDescriptor(); -} - -void BaseMessageLoop::IOTask::OnFileCanReadWithoutBlocking(int /* fd */) { - OnFileReady(); -} - -void BaseMessageLoop::IOTask::OnFileCanWriteWithoutBlocking(int /* fd */) { - OnFileReady(); -} - -void BaseMessageLoop::IOTask::OnFileReady() { - // When the file descriptor becomes available we stop watching for it and - // schedule a task to run the callback from the main loop. The callback will - // run using the same scheduler use to run other delayed tasks, avoiding - // starvation of the available posted tasks if there are file descriptors - // always available. The new posted task will use the same TaskId as the - // current file descriptor watching task an could be canceled in either state, - // when waiting for the file descriptor or waiting in the main loop. - StopWatching(); - bool base_scheduled = loop_->base_loop_->task_runner()->PostTask( - location_, - base::Bind(&BaseMessageLoop::OnFileReadyPostedTask, - loop_->weak_ptr_factory_.GetWeakPtr(), - task_id_)); - posted_task_pending_ = true; - if (base_scheduled) { - DVLOG_LOC(location_, 1) - << "Dispatching task_id " << task_id_ << " for " - << (base_mode_ == base::MessageLoopForIO::WATCH_READ ? - "reading" : "writing") - << " file descriptor " << fd_ << ", scheduled from this location."; - } else { - // In the rare case that PostTask() fails, we fall back to run it directly. - // This would indicate a bigger problem with the message loop setup. - LOG(ERROR) << "Error on base::MessageLoopForIO::PostTask()."; - OnFileReadyPostedTask(); - } -} - -void BaseMessageLoop::IOTask::OnFileReadyPostedTask() { - // We can't access |this| after running the |closure_| since it could call - // CancelTask on its own task_id, so we copy the members we need now. - BaseMessageLoop* loop_ptr = loop_; - DCHECK(posted_task_pending_ = true); - posted_task_pending_ = false; - - // If this task was already canceled, the closure will be null and there is - // nothing else to do here. This execution doesn't count a step for RunOnce() - // unless we have a callback to run. - if (closure_.is_null()) { - loop_->io_tasks_.erase(task_id_); - return; - } - - DVLOG_LOC(location_, 1) - << "Running task_id " << task_id_ << " for " - << (base_mode_ == base::MessageLoopForIO::WATCH_READ ? - "reading" : "writing") - << " file descriptor " << fd_ << ", scheduled from this location."; - - if (persistent_) { - // In the persistent case we just run the callback. If this callback cancels - // the task id, we can't access |this| anymore, so we re-start watching the - // file descriptor before running the callback. - StartWatching(); - closure_.Run(); - } else { - // This will destroy |this|, the fd_watcher and therefore stop watching this - // file descriptor. - Closure closure_copy = std::move(closure_); - loop_->io_tasks_.erase(task_id_); - // Run the closure from the local copy we just made. - closure_copy.Run(); - } - - if (loop_ptr->run_once_) { - loop_ptr->run_once_ = false; - loop_ptr->BreakLoop(); - } -} - -bool BaseMessageLoop::IOTask::CancelTask() { - if (closure_.is_null()) - return false; - - DVLOG_LOC(location_, 1) - << "Removing task_id " << task_id_ << " scheduled from this location."; - - if (!posted_task_pending_) { - // Destroying the FileDescriptorWatcher implicitly stops watching the file - // descriptor. This will delete our instance. - loop_->io_tasks_.erase(task_id_); - return true; - } - // The IOTask is waiting for the message loop to run its delayed task, so - // it is not watching for the file descriptor. We release the closure - // resources now but keep the IOTask instance alive while we wait for the - // callback to run and delete the IOTask. - closure_ = Closure(); - return true; -} - -} // namespace chromeos diff --git a/chromeos/message_loops/base_message_loop.h b/chromeos/message_loops/base_message_loop.h deleted file mode 100644 index 902b828..0000000 --- a/chromeos/message_loops/base_message_loop.h +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_ -#define LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_ - -// BaseMessageLoop is a chromeos::MessageLoop implementation based on -// base::MessageLoopForIO. This allows to mix new code using -// chromeos::MessageLoop and legacy code using base::MessageLoopForIO in the -// same thread and share a single main loop. This disadvantage of using this -// class is a less efficient implementation of CancelTask() for delayed tasks -// since base::MessageLoopForIO doesn't provide a way to remove the event. - -#include -#include - -#include -#include -#include -#include - -#include -#include - -namespace chromeos { - -class CHROMEOS_EXPORT BaseMessageLoop : public MessageLoop { - public: - explicit BaseMessageLoop(base::MessageLoopForIO* base_loop); - ~BaseMessageLoop() override; - - // MessageLoop overrides. - TaskId PostDelayedTask(const tracked_objects::Location& from_here, - const base::Closure& task, - base::TimeDelta delay) override; - using MessageLoop::PostDelayedTask; - TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const base::Closure& task) override; - using MessageLoop::WatchFileDescriptor; - bool CancelTask(TaskId task_id) override; - bool RunOnce(bool may_block) override; - void Run() override; - void BreakLoop() override; - - // Returns a callback that will quit the current message loop. If the message - // loop is not running, an empty (null) callback is returned. - base::Closure QuitClosure() const; - - private: - // Called by base::MessageLoopForIO when is time to call the callback - // scheduled with Post*Task() of id |task_id|, even if it was canceled. - void OnRanPostedTask(MessageLoop::TaskId task_id); - - // Called from the message loop when the IOTask should run the scheduled - // callback. This is a simple wrapper of IOTask::OnFileReadyPostedTask() - // posted from the BaseMessageLoop so it is deleted when the BaseMessageLoop - // goes out of scope since we can't cancel the callback otherwise. - void OnFileReadyPostedTask(MessageLoop::TaskId task_id); - - // Return a new unused task_id. - TaskId NextTaskId(); - - struct DelayedTask { - tracked_objects::Location location; - - MessageLoop::TaskId task_id; - base::Closure closure; - }; - - std::map delayed_tasks_; - - class IOTask : public base::MessageLoopForIO::Watcher { - public: - IOTask(const tracked_objects::Location& location, - BaseMessageLoop* loop, - MessageLoop::TaskId task_id, - int fd, - base::MessageLoopForIO::Mode base_mode, - bool persistent, - const base::Closure& task); - - const tracked_objects::Location& location() const { return location_; } - - // Used to start/stop watching the file descriptor while keeping the - // IOTask entry available. - bool StartWatching(); - void StopWatching(); - - // Called from the message loop as a PostTask() when the file descriptor is - // available, scheduled to run from OnFileReady(). - void OnFileReadyPostedTask(); - - // Cancel the IOTask and returns whether it was actually canceled, with the - // same semantics as MessageLoop::CancelTask(). - bool CancelTask(); - - private: - tracked_objects::Location location_; - BaseMessageLoop* loop_; - - // These are the arguments passed in the constructor, basically forwarding - // all the arguments passed to WatchFileDescriptor() plus the assigned - // TaskId for this task. - MessageLoop::TaskId task_id_; - int fd_; - base::MessageLoopForIO::Mode base_mode_; - bool persistent_; - base::Closure closure_; - - base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_; - - // Tells whether there is a pending call to OnFileReadPostedTask(). - bool posted_task_pending_{false}; - - // base::MessageLoopForIO::Watcher overrides: - void OnFileCanReadWithoutBlocking(int fd) override; - void OnFileCanWriteWithoutBlocking(int fd) override; - - // Common implementation for both the read and write case. - void OnFileReady(); - - DISALLOW_COPY_AND_ASSIGN(IOTask); - }; - - std::map io_tasks_; - - // Flag to mark that we should run the message loop only one iteration. - bool run_once_{false}; - - // The last used TaskId. While base::MessageLoopForIO doesn't allow to cancel - // delayed tasks, we handle that functionality by not running the callback - // if it fires at a later point. - MessageLoop::TaskId last_id_{kTaskIdNull}; - - // The pointer to the libchrome base::MessageLoopForIO we are wrapping with - // this interface. - base::MessageLoopForIO* base_loop_; - - // The RunLoop instance used to run the main loop from Run(). - base::RunLoop* base_run_loop_{nullptr}; - - // We use a WeakPtrFactory to schedule tasks with the base::MessageLoopForIO - // since we can't cancel the callbacks we have scheduled there once this - // instance is destroyed. - base::WeakPtrFactory weak_ptr_factory_; - DISALLOW_COPY_AND_ASSIGN(BaseMessageLoop); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_ diff --git a/chromeos/message_loops/fake_message_loop.cc b/chromeos/message_loops/fake_message_loop.cc deleted file mode 100644 index 786cde2..0000000 --- a/chromeos/message_loops/fake_message_loop.cc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -namespace chromeos { - -FakeMessageLoop::FakeMessageLoop(base::SimpleTestClock* clock) - : test_clock_(clock) { -} - -MessageLoop::TaskId FakeMessageLoop::PostDelayedTask( - const tracked_objects::Location& from_here, - const base::Closure& task, - base::TimeDelta delay) { - // If no SimpleTestClock was provided, we use the last time we fired a - // callback. In this way, tasks scheduled from a Closure will have the right - // time. - if (test_clock_) - current_time_ = test_clock_->Now(); - MessageLoop::TaskId current_id = ++last_id_; - // FakeMessageLoop is limited to only 2^64 tasks. That should be enough. - CHECK(current_id); - tasks_.emplace(current_id, ScheduledTask{from_here, false, task}); - fire_order_.push(std::make_pair(current_time_ + delay, current_id)); - VLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << current_id - << " to run at " << current_time_ + delay - << " (in " << delay << ")."; - return current_id; -} - -MessageLoop::TaskId FakeMessageLoop::WatchFileDescriptor( - const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const base::Closure& task) { - MessageLoop::TaskId current_id = ++last_id_; - // FakeMessageLoop is limited to only 2^64 tasks. That should be enough. - CHECK(current_id); - tasks_.emplace(current_id, ScheduledTask{from_here, persistent, task}); - fds_watched_.emplace(std::make_pair(fd, mode), current_id); - return current_id; -} - -bool FakeMessageLoop::CancelTask(TaskId task_id) { - if (task_id == MessageLoop::kTaskIdNull) - return false; - bool ret = tasks_.erase(task_id) > 0; - VLOG_IF(1, ret) << "Removing task_id " << task_id; - return ret; -} - -bool FakeMessageLoop::RunOnce(bool may_block) { - if (test_clock_) - current_time_ = test_clock_->Now(); - // Try to fire ready file descriptors first. - for (const auto& fd_mode : fds_ready_) { - const auto& fd_watched = fds_watched_.find(fd_mode); - if (fd_watched == fds_watched_.end()) - continue; - // The fd_watched->second task might have been canceled and we never removed - // it from the fds_watched_, so we fix that now. - const auto& scheduled_task_ref = tasks_.find(fd_watched->second); - if (scheduled_task_ref == tasks_.end()) { - fds_watched_.erase(fd_watched); - continue; - } - VLOG_LOC(scheduled_task_ref->second.location, 1) - << "Running task_id " << fd_watched->second - << " for watching file descriptor " << fd_mode.first << " for " - << (fd_mode.second == MessageLoop::kWatchRead ? "reading" : "writing") - << (scheduled_task_ref->second.persistent ? - " persistently" : " just once") - << " scheduled from this location."; - if (scheduled_task_ref->second.persistent) { - scheduled_task_ref->second.callback.Run(); - } else { - base::Closure callback = std::move(scheduled_task_ref->second.callback); - tasks_.erase(scheduled_task_ref); - fds_watched_.erase(fd_watched); - callback.Run(); - } - return true; - } - - // Try to fire time-based callbacks afterwards. - while (!fire_order_.empty() && - (may_block || fire_order_.top().first <= current_time_)) { - const auto task_ref = fire_order_.top(); - fire_order_.pop(); - // We need to skip tasks in the priority_queue not in the |tasks_| map. - // This is normal if the task was canceled, as there is no efficient way - // to remove a task from the priority_queue. - const auto scheduled_task_ref = tasks_.find(task_ref.second); - if (scheduled_task_ref == tasks_.end()) - continue; - // Advance the clock to the task firing time, if needed. - if (current_time_ < task_ref.first) { - current_time_ = task_ref.first; - if (test_clock_) - test_clock_->SetNow(current_time_); - } - // Move the Closure out of the map before delete it. We need to delete the - // entry from the map before we call the callback, since calling CancelTask - // for the task you are running now should fail and return false. - base::Closure callback = std::move(scheduled_task_ref->second.callback); - VLOG_LOC(scheduled_task_ref->second.location, 1) - << "Running task_id " << task_ref.second - << " at time " << current_time_ << " from this location."; - tasks_.erase(scheduled_task_ref); - - callback.Run(); - return true; - } - return false; -} - -void FakeMessageLoop::SetFileDescriptorReadiness(int fd, - WatchMode mode, - bool ready) { - if (ready) - fds_ready_.emplace(fd, mode); - else - fds_ready_.erase(std::make_pair(fd, mode)); -} - -bool FakeMessageLoop::PendingTasks() { - for (const auto& task : tasks_) { - VLOG_LOC(task.second.location, 1) - << "Pending " << (task.second.persistent ? "persistent " : "") - << "task_id " << task.first << " scheduled from here."; - } - return !tasks_.empty(); -} - -} // namespace chromeos diff --git a/chromeos/message_loops/fake_message_loop.h b/chromeos/message_loops/fake_message_loop.h deleted file mode 100644 index e9f0c51..0000000 --- a/chromeos/message_loops/fake_message_loop.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_ -#define LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -namespace chromeos { - -// The FakeMessageLoop implements a message loop that doesn't block or wait for -// time based tasks to be ready. The tasks are executed in the order they should -// be executed in a real message loop implementation, but the time is advanced -// to the time when the first task should be executed instead of blocking. -// To keep a consistent notion of time for other classes, FakeMessageLoop -// optionally updates a SimpleTestClock instance when it needs to advance the -// clock. -// This message loop implementation is useful for unittests. -class CHROMEOS_EXPORT FakeMessageLoop : public MessageLoop { - public: - // Create a FakeMessageLoop optionally using a SimpleTestClock to update the - // time when Run() or RunOnce(true) are called and should block. - explicit FakeMessageLoop(base::SimpleTestClock* clock); - ~FakeMessageLoop() override = default; - - TaskId PostDelayedTask(const tracked_objects::Location& from_here, - const base::Closure& task, - base::TimeDelta delay) override; - using MessageLoop::PostDelayedTask; - TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const base::Closure& task) override; - using MessageLoop::WatchFileDescriptor; - bool CancelTask(TaskId task_id) override; - bool RunOnce(bool may_block) override; - - // FakeMessageLoop methods: - - // Pretend, for the purpose of the FakeMessageLoop watching for file - // descriptors, that the file descriptor |fd| readiness to perform the - // operation described by |mode| is |ready|. Initially, no file descriptor - // is ready for any operation. - void SetFileDescriptorReadiness(int fd, WatchMode mode, bool ready); - - // Return whether there are peding tasks. Useful to check that no - // callbacks were leaked. - bool PendingTasks(); - - private: - struct ScheduledTask { - tracked_objects::Location location; - bool persistent; - base::Closure callback; - }; - - // The sparse list of scheduled pending callbacks. - std::map tasks_; - - // Using std::greater<> for the priority_queue means that the top() of the - // queue is the lowest (earliest) time, and for the same time, the smallest - // TaskId. This determines the order in which the tasks will be fired. - std::priority_queue< - std::pair, - std::vector>, - std::greater>> fire_order_; - - // The bag of watched (fd, mode) pair associated with the TaskId that's - // watching them. - std::multimap, MessageLoop::TaskId> fds_watched_; - - // The set of (fd, mode) pairs that are faked as ready. - std::set> fds_ready_; - - base::SimpleTestClock* test_clock_ = nullptr; - base::Time current_time_ = base::Time::FromDoubleT(1246996800.); - - MessageLoop::TaskId last_id_ = kTaskIdNull; - - DISALLOW_COPY_AND_ASSIGN(FakeMessageLoop); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_ diff --git a/chromeos/message_loops/fake_message_loop_unittest.cc b/chromeos/message_loops/fake_message_loop_unittest.cc deleted file mode 100644 index cec715d..0000000 --- a/chromeos/message_loops/fake_message_loop_unittest.cc +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include - -using base::Bind; -using base::Time; -using base::TimeDelta; -using std::vector; - -namespace chromeos { - -using TaskId = MessageLoop::TaskId; - -class FakeMessageLoopTest : public ::testing::Test { - protected: - void SetUp() override { - loop_.reset(new FakeMessageLoop(nullptr)); - EXPECT_TRUE(loop_.get()); - } - void TearDown() override { - EXPECT_FALSE(loop_->PendingTasks()); - } - - base::SimpleTestClock clock_; - std::unique_ptr loop_; -}; - -TEST_F(FakeMessageLoopTest, CancelTaskInvalidValuesTest) { - EXPECT_FALSE(loop_->CancelTask(MessageLoop::kTaskIdNull)); - EXPECT_FALSE(loop_->CancelTask(1234)); -} - -TEST_F(FakeMessageLoopTest, PostDelayedTaskRunsInOrder) { - vector order; - loop_->PostDelayedTask(Bind([&order]() { order.push_back(1); }), - TimeDelta::FromSeconds(1)); - loop_->PostDelayedTask(Bind([&order]() { order.push_back(4); }), - TimeDelta::FromSeconds(4)); - loop_->PostDelayedTask(Bind([&order]() { order.push_back(3); }), - TimeDelta::FromSeconds(3)); - loop_->PostDelayedTask(Bind([&order]() { order.push_back(2); }), - TimeDelta::FromSeconds(2)); - // Run until all the tasks are run. - loop_->Run(); - EXPECT_EQ((vector{1, 2, 3, 4}), order); -} - -TEST_F(FakeMessageLoopTest, PostDelayedTaskAdvancesTheTime) { - Time start = Time::FromInternalValue(1000000); - clock_.SetNow(start); - loop_.reset(new FakeMessageLoop(&clock_)); - loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(1)); - loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(2)); - EXPECT_FALSE(loop_->RunOnce(false)); - // If the callback didn't run, the time shouldn't change. - EXPECT_EQ(start, clock_.Now()); - - // If we run only one callback, the time should be set to the time that - // callack ran. - EXPECT_TRUE(loop_->RunOnce(true)); - EXPECT_EQ(start + TimeDelta::FromSeconds(1), clock_.Now()); - - // If the clock is advanced manually, we should be able to run the - // callback without blocking, since the firing time is in the past. - clock_.SetNow(start + TimeDelta::FromSeconds(3)); - EXPECT_TRUE(loop_->RunOnce(false)); - // The time should not change even if the callback is due in the past. - EXPECT_EQ(start + TimeDelta::FromSeconds(3), clock_.Now()); -} - -TEST_F(FakeMessageLoopTest, WatchFileDescriptorWaits) { - int fd = 1234; - // We will simulate this situation. At the beginning, we will watch for a - // file descriptor that won't trigger for 10s. Then we will pretend it is - // ready after 10s and expect its callback to run just once. - int called = 0; - TaskId task_id = loop_->WatchFileDescriptor( - FROM_HERE, fd, MessageLoop::kWatchRead, false, - Bind([&called] { called++; })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - - EXPECT_NE(MessageLoop::kTaskIdNull, - loop_->PostDelayedTask(Bind([this] { this->loop_->BreakLoop(); }), - TimeDelta::FromSeconds(10))); - EXPECT_NE(MessageLoop::kTaskIdNull, - loop_->PostDelayedTask(Bind([this] { this->loop_->BreakLoop(); }), - TimeDelta::FromSeconds(20))); - loop_->Run(); - EXPECT_EQ(0, called); - - loop_->SetFileDescriptorReadiness(fd, MessageLoop::kWatchRead, true); - loop_->Run(); - EXPECT_EQ(1, called); - EXPECT_FALSE(loop_->CancelTask(task_id)); -} - -TEST_F(FakeMessageLoopTest, PendingTasksTest) { - loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(1)); - EXPECT_TRUE(loop_->PendingTasks()); - loop_->Run(); -} - -} // namespace chromeos diff --git a/chromeos/message_loops/glib_message_loop.cc b/chromeos/message_loops/glib_message_loop.cc deleted file mode 100644 index 6db3a01..0000000 --- a/chromeos/message_loops/glib_message_loop.cc +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include - -using base::Closure; - -namespace chromeos { - -GlibMessageLoop::GlibMessageLoop() { - loop_ = g_main_loop_new(g_main_context_default(), FALSE); -} - -GlibMessageLoop::~GlibMessageLoop() { - // Cancel all pending tasks when destroying the message loop. - for (const auto& task : tasks_) { - DVLOG_LOC(task.second->location, 1) - << "Removing task_id " << task.second->task_id - << " leaked on GlibMessageLoop, scheduled from this location."; - g_source_remove(task.second->source_id); - } - g_main_loop_unref(loop_); -} - -MessageLoop::TaskId GlibMessageLoop::PostDelayedTask( - const tracked_objects::Location& from_here, - const Closure &task, - base::TimeDelta delay) { - TaskId task_id = NextTaskId(); - // Note: While we store persistent = false in the ScheduledTask object, we - // don't check it in OnRanPostedTask() since it is always false for delayed - // tasks. This is only used for WatchFileDescriptor below. - ScheduledTask* scheduled_task = new ScheduledTask{ - this, from_here, task_id, 0, false, std::move(task)}; - DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id - << " to run in " << delay << "."; - scheduled_task->source_id = g_timeout_add_full( - G_PRIORITY_DEFAULT, - delay.InMillisecondsRoundedUp(), - &GlibMessageLoop::OnRanPostedTask, - reinterpret_cast(scheduled_task), - DestroyPostedTask); - tasks_[task_id] = scheduled_task; - return task_id; -} - -MessageLoop::TaskId GlibMessageLoop::WatchFileDescriptor( - const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const Closure &task) { - // Quick check to see if the fd is valid. - if (fcntl(fd, F_GETFD) == -1 && errno == EBADF) - return MessageLoop::kTaskIdNull; - - GIOCondition condition = G_IO_NVAL; - switch (mode) { - case MessageLoop::kWatchRead: - condition = static_cast(G_IO_IN | G_IO_HUP | G_IO_NVAL); - break; - case MessageLoop::kWatchWrite: - condition = static_cast(G_IO_OUT | G_IO_HUP | G_IO_NVAL); - break; - default: - return MessageLoop::kTaskIdNull; - } - - // TODO(deymo): Used g_unix_fd_add_full() instead of g_io_add_watch_full() - // when/if we switch to glib 2.36 or newer so we don't need to create a - // GIOChannel for this. - GIOChannel* io_channel = g_io_channel_unix_new(fd); - if (!io_channel) - return MessageLoop::kTaskIdNull; - GError* error = nullptr; - GIOStatus status = g_io_channel_set_encoding(io_channel, nullptr, &error); - if (status != G_IO_STATUS_NORMAL) { - LOG(ERROR) << "GError(" << error->code << "): " - << (error->message ? error->message : "(unknown)"); - g_error_free(error); - // g_io_channel_set_encoding() documentation states that this should be - // valid in this context (a new io_channel), but enforce the check in - // debug mode. - DCHECK(status == G_IO_STATUS_NORMAL); - return MessageLoop::kTaskIdNull; - } - - TaskId task_id = NextTaskId(); - ScheduledTask* scheduled_task = new ScheduledTask{ - this, from_here, task_id, 0, persistent, std::move(task)}; - scheduled_task->source_id = g_io_add_watch_full( - io_channel, - G_PRIORITY_DEFAULT, - condition, - &GlibMessageLoop::OnWatchedFdReady, - reinterpret_cast(scheduled_task), - DestroyPostedTask); - // g_io_add_watch_full() increases the reference count on the newly created - // io_channel, so we can dereference it now and it will be free'd once the - // source is removed or now if g_io_add_watch_full() failed. - g_io_channel_unref(io_channel); - - DVLOG_LOC(from_here, 1) - << "Watching fd " << fd << " for " - << (mode == MessageLoop::kWatchRead ? "reading" : "writing") - << (persistent ? " persistently" : " just once") - << " as task_id " << task_id - << (scheduled_task->source_id ? " successfully" : " failed."); - - if (!scheduled_task->source_id) { - delete scheduled_task; - return MessageLoop::kTaskIdNull; - } - tasks_[task_id] = scheduled_task; - return task_id; -} - -bool GlibMessageLoop::CancelTask(TaskId task_id) { - if (task_id == kTaskIdNull) - return false; - const auto task = tasks_.find(task_id); - // It is a programmer error to attempt to remove a non-existent source. - if (task == tasks_.end()) - return false; - DVLOG_LOC(task->second->location, 1) - << "Removing task_id " << task_id << " scheduled from this location."; - guint source_id = task->second->source_id; - // We remove here the entry from the tasks_ map, the pointer will be deleted - // by the g_source_remove() call. - tasks_.erase(task); - return g_source_remove(source_id); -} - -bool GlibMessageLoop::RunOnce(bool may_block) { - return g_main_context_iteration(nullptr, may_block); -} - -void GlibMessageLoop::Run() { - g_main_loop_run(loop_); -} - -void GlibMessageLoop::BreakLoop() { - g_main_loop_quit(loop_); -} - -MessageLoop::TaskId GlibMessageLoop::NextTaskId() { - TaskId res; - do { - res = ++last_id_; - // We would run out of memory before we run out of task ids. - } while (!res || tasks_.find(res) != tasks_.end()); - return res; -} - -gboolean GlibMessageLoop::OnRanPostedTask(gpointer user_data) { - ScheduledTask* scheduled_task = reinterpret_cast(user_data); - DVLOG_LOC(scheduled_task->location, 1) - << "Running delayed task_id " << scheduled_task->task_id - << " scheduled from this location."; - // We only need to remove this task_id from the map. DestroyPostedTask will be - // called with this same |user_data| where we can delete the ScheduledTask. - scheduled_task->loop->tasks_.erase(scheduled_task->task_id); - scheduled_task->closure.Run(); - return FALSE; // Removes the source since a callback can only be called once. -} - -gboolean GlibMessageLoop::OnWatchedFdReady(GIOChannel *source, - GIOCondition condition, - gpointer user_data) { - ScheduledTask* scheduled_task = reinterpret_cast(user_data); - DVLOG_LOC(scheduled_task->location, 1) - << "Running task_id " << scheduled_task->task_id - << " for watching a file descriptor, scheduled from this location."; - if (!scheduled_task->persistent) { - // We only need to remove this task_id from the map. DestroyPostedTask will - // be called with this same |user_data| where we can delete the - // ScheduledTask. - scheduled_task->loop->tasks_.erase(scheduled_task->task_id); - } - scheduled_task->closure.Run(); - return scheduled_task->persistent; -} - -void GlibMessageLoop::DestroyPostedTask(gpointer user_data) { - delete reinterpret_cast(user_data); -} - -} // namespace chromeos diff --git a/chromeos/message_loops/glib_message_loop.h b/chromeos/message_loops/glib_message_loop.h deleted file mode 100644 index 12d5af9..0000000 --- a/chromeos/message_loops/glib_message_loop.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_ -#define LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_ - -#include -#include - -#include -#include -#include - -#include -#include - -namespace chromeos { - -class CHROMEOS_EXPORT GlibMessageLoop : public MessageLoop { - public: - GlibMessageLoop(); - ~GlibMessageLoop() override; - - // MessageLoop overrides. - TaskId PostDelayedTask(const tracked_objects::Location& from_here, - const base::Closure& task, - base::TimeDelta delay) override; - using MessageLoop::PostDelayedTask; - TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const base::Closure& task) override; - using MessageLoop::WatchFileDescriptor; - bool CancelTask(TaskId task_id) override; - bool RunOnce(bool may_block) override; - void Run() override; - void BreakLoop() override; - - private: - // Called by the GLib's main loop when is time to call the callback scheduled - // with Post*Task(). The pointer to the callback passed when scheduling it is - // passed to this function as a gpointer on |user_data|. - static gboolean OnRanPostedTask(gpointer user_data); - - // Called by the GLib's main loop when the watched source |source| is - // ready to perform the operation given in |condition| without blocking. - static gboolean OnWatchedFdReady(GIOChannel *source, - GIOCondition condition, - gpointer user_data); - - // Called by the GLib's main loop when the scheduled callback is removed due - // to it being executed or canceled. - static void DestroyPostedTask(gpointer user_data); - - // Return a new unused task_id. - TaskId NextTaskId(); - - GMainLoop* loop_; - - struct ScheduledTask { - // A pointer to this GlibMessageLoop so we can remove the Task from the - // glib callback. - GlibMessageLoop* loop; - tracked_objects::Location location; - - MessageLoop::TaskId task_id; - guint source_id; - bool persistent; - base::Closure closure; - }; - - std::map tasks_; - - MessageLoop::TaskId last_id_ = kTaskIdNull; - - DISALLOW_COPY_AND_ASSIGN(GlibMessageLoop); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_ diff --git a/chromeos/message_loops/glib_message_loop_unittest.cc b/chromeos/message_loops/glib_message_loop_unittest.cc deleted file mode 100644 index 0563bd5..0000000 --- a/chromeos/message_loops/glib_message_loop_unittest.cc +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include - -using base::Bind; - -namespace chromeos { - -using TaskId = MessageLoop::TaskId; - -class GlibMessageLoopTest : public ::testing::Test { - protected: - void SetUp() override { - loop_.reset(new GlibMessageLoop()); - EXPECT_TRUE(loop_.get()); - } - - std::unique_ptr loop_; -}; - -// When you watch a file descriptor for reading, the guaranties are that a -// blocking call to read() on that file descriptor will not block. This should -// include the case when the other end of a pipe is closed or the file is empty. -TEST_F(GlibMessageLoopTest, WatchFileDescriptorTriggersWhenEmpty) { - int fd = HANDLE_EINTR(open("/dev/null", O_RDONLY)); - int called = 0; - TaskId task_id = loop_->WatchFileDescriptor( - FROM_HERE, fd, MessageLoop::kWatchRead, true, - Bind([&called] { called++; })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - EXPECT_NE(0, MessageLoopRunMaxIterations(loop_.get(), 10)); - EXPECT_LT(2, called); - EXPECT_TRUE(loop_->CancelTask(task_id)); -} - -// Test that an invalid file descriptor triggers the callback. -TEST_F(GlibMessageLoopTest, WatchFileDescriptorTriggersWhenInvalid) { - int fd = HANDLE_EINTR(open("/dev/zero", O_RDONLY)); - int called = 0; - TaskId task_id = loop_->WatchFileDescriptor( - FROM_HERE, fd, MessageLoop::kWatchRead, true, - Bind([&called, fd] { - if (!called) - IGNORE_EINTR(close(fd)); - called++; - })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - EXPECT_NE(0, MessageLoopRunMaxIterations(loop_.get(), 10)); - EXPECT_LT(2, called); - EXPECT_TRUE(loop_->CancelTask(task_id)); -} - -} // namespace chromeos diff --git a/chromeos/message_loops/message_loop.cc b/chromeos/message_loops/message_loop.cc deleted file mode 100644 index e913f13..0000000 --- a/chromeos/message_loops/message_loop.cc +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -namespace chromeos { - -namespace { - -// A lazily created thread local storage for quick access to a thread's message -// loop, if one exists. This should be safe and free of static constructors. -base::LazyInstance >::Leaky lazy_tls_ptr = - LAZY_INSTANCE_INITIALIZER; - -} // namespace - -const MessageLoop::TaskId MessageLoop::kTaskIdNull = 0; - -MessageLoop* MessageLoop::current() { - DCHECK(lazy_tls_ptr.Pointer()->Get() != nullptr) << - "There isn't a MessageLoop for this thread. You need to initialize it " - "first."; - return lazy_tls_ptr.Pointer()->Get(); -} - -bool MessageLoop::ThreadHasCurrent() { - return lazy_tls_ptr.Pointer()->Get() != nullptr; -} - -void MessageLoop::SetAsCurrent() { - DCHECK(lazy_tls_ptr.Pointer()->Get() == nullptr) << - "There's already a MessageLoop for this thread."; - lazy_tls_ptr.Pointer()->Set(this); -} - -void MessageLoop::ReleaseFromCurrent() { - DCHECK(lazy_tls_ptr.Pointer()->Get() == this) << - "This is not the MessageLoop bound to the current thread."; - lazy_tls_ptr.Pointer()->Set(nullptr); -} - -MessageLoop::~MessageLoop() { - if (lazy_tls_ptr.Pointer()->Get() == this) - lazy_tls_ptr.Pointer()->Set(nullptr); -} - -void MessageLoop::Run() { - // Default implementation is to call RunOnce() blocking until there aren't - // more tasks scheduled. - while (!should_exit_ && RunOnce(true)) {} - should_exit_ = false; -} - -void MessageLoop::BreakLoop() { - should_exit_ = true; -} - -} // namespace chromeos diff --git a/chromeos/message_loops/message_loop.h b/chromeos/message_loops/message_loop.h deleted file mode 100644 index 73032c8..0000000 --- a/chromeos/message_loops/message_loop.h +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MESSAGE_LOOP_H_ -#define LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MESSAGE_LOOP_H_ - -#include - -#include -#include -#include -#include - -namespace chromeos { - -class CHROMEOS_EXPORT MessageLoop { - public: - virtual ~MessageLoop(); - - // A unique task identifier used to refer to scheduled callbacks. - using TaskId = uint64_t; - - // The kNullEventId is reserved for an invalid task and will never be used - // to refer to a real task. - static const TaskId kTaskIdNull; - - // Return the MessageLoop for the current thread. It is a fatal error to - // request the current MessageLoop if SetAsCurrent() was not called on the - // current thread. If you really need to, use ThreadHasCurrent() to check if - // there is a current thread. - static MessageLoop* current(); - - // Return whether there is a MessageLoop in the current thread. - static bool ThreadHasCurrent(); - - // Set this message loop as the current thread main loop. Only one message - // loop can be set at a time. Use ReleaseFromCurrent() to release it. - void SetAsCurrent(); - - // Release this instance from the current thread. This instance must have - // been previously set with SetAsCurrent(). - void ReleaseFromCurrent(); - - // Schedule a Closure |task| to be executed after a |delay|. Returns a task - // identifier for the scheduled task that can be used to cancel the task - // before it is fired by passing it to CancelTask(). - // In case of an error scheduling the task, the kTaskIdNull is returned. - // Note that once the call is executed or canceled, the TaskId could be reused - // at a later point. - // This methond can only be called from the same thread running the main loop. - virtual TaskId PostDelayedTask(const tracked_objects::Location& from_here, - const base::Closure& task, - base::TimeDelta delay) = 0; - // Variant without the Location for easier usage. - TaskId PostDelayedTask(const base::Closure& task, - base::TimeDelta delay) { - return PostDelayedTask(tracked_objects::Location(), task, delay); - } - - // A convenience method to schedule a call with no delay. - // This methond can only be called from the same thread running the main loop. - TaskId PostTask(const base::Closure& task) { - return PostDelayedTask(task, base::TimeDelta()); - } - TaskId PostTask(const tracked_objects::Location& from_here, - const base::Closure& task) { - return PostDelayedTask(from_here, task, base::TimeDelta()); - } - - // Watch mode flag used to watch for file descriptors. - enum WatchMode { - kWatchRead, - kWatchWrite, - }; - - // Watch a file descriptor |fd| for it to be ready to perform the operation - // passed in |mode| without blocking. When that happens, the |task| closure - // will be executed. If |persistent| is true, the file descriptor will - // continue to be watched and |task| will continue to be called until the task - // is canceled with CancelTask(). - // Returns the TaskId describing this task. In case of error, returns - // kTaskIdNull. - virtual TaskId WatchFileDescriptor(const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const base::Closure& task) = 0; - - // Convenience function to call WatchFileDescriptor() without a location. - TaskId WatchFileDescriptor(int fd, - WatchMode mode, - bool persistent, - const base::Closure& task) { - return WatchFileDescriptor( - tracked_objects::Location(), fd, mode, persistent, task); - } - - // Cancel a scheduled task. Returns whether the task was canceled. For - // example, if the callback was already executed (or is being executed) or was - // already canceled this method will fail. Note that the TaskId can be reused - // after it was executed or cancelled. - virtual bool CancelTask(TaskId task_id) = 0; - - // --------------------------------------------------------------------------- - // Methods used to run and stop the message loop. - - // Run one iteration of the message loop, dispatching up to one task. The - // |may_block| tells whether this method is allowed to block waiting for a - // task to be ready to run. Returns whether it ran a task. Note that even - // if |may_block| is true, this method can return false immediately if there - // are no more tasks registered. - virtual bool RunOnce(bool may_block) = 0; - - // Run the main loop until there are no more registered tasks. - virtual void Run(); - - // Quit the running main loop immediately. This method will make the current - // running Run() method to return right after the current task returns back - // to the message loop without processing any other task. - virtual void BreakLoop(); - - protected: - MessageLoop() = default; - - private: - // Tells whether Run() should quit the message loop in the default - // implementation. - bool should_exit_ = false; - - DISALLOW_COPY_AND_ASSIGN(MessageLoop); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MESSAGE_LOOP_H_ diff --git a/chromeos/message_loops/message_loop_unittest.cc b/chromeos/message_loops/message_loop_unittest.cc deleted file mode 100644 index 73b1f96..0000000 --- a/chromeos/message_loops/message_loop_unittest.cc +++ /dev/null @@ -1,422 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -// These are the common tests for all the chromeos::MessageLoop implementations -// that should conform to this interface's contracts. For extra -// implementation-specific tests see the particular implementation unittests in -// the *_unittest.cc files. - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -using base::Bind; -using base::TimeDelta; - -namespace { -// Helper class to create and close a unidirectional pipe. Used to provide valid -// file descriptors when testing watching for a file descriptor. -class ScopedPipe { - public: - // The internal pipe size. - static const int kPipeSize; - - ScopedPipe() { - int fds[2]; - if (pipe(fds) != 0) { - PLOG(FATAL) << "Creating a pipe()"; - } - reader = fds[0]; - writer = fds[1]; - EXPECT_EQ(kPipeSize, fcntl(writer, F_SETPIPE_SZ, kPipeSize)); - } - ~ScopedPipe() { - if (reader != -1) - close(reader); - if (writer != -1) - close(writer); - } - - // The reader and writer end of the pipe. - int reader{-1}; - int writer{-1}; -}; - -const int ScopedPipe::kPipeSize = 4096; - -class ScopedSocketPair { - public: - ScopedSocketPair() { - int fds[2]; - if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) != 0) { - PLOG(FATAL) << "Creating a socketpair()"; - } - left = fds[0]; - right = fds[1]; - } - ~ScopedSocketPair() { - if (left != -1) - close(left); - if (right != -1) - close(right); - } - - // The left and right sockets are bi-directional connected and - // indistinguishable file descriptor. We named them left/right for easier - // reading. - int left{-1}; - int right{-1}; -}; -} // namespace - -namespace chromeos { - -using TaskId = MessageLoop::TaskId; - -template -class MessageLoopTest : public ::testing::Test { - protected: - void SetUp() override { - MessageLoopSetUp(); - EXPECT_TRUE(this->loop_.get()); - } - - std::unique_ptr base_loop_; - - std::unique_ptr loop_; - - private: - // These MessageLoopSetUp() methods are used to setup each MessageLoop - // according to its constructor requirements. - void MessageLoopSetUp(); -}; - -template <> -void MessageLoopTest::MessageLoopSetUp() { - loop_.reset(new GlibMessageLoop()); -} - -template <> -void MessageLoopTest::MessageLoopSetUp() { - base_loop_.reset(new base::MessageLoopForIO()); - loop_.reset(new BaseMessageLoop(base::MessageLoopForIO::current())); -} - -// This setups gtest to run each one of the following TYPED_TEST test cases on -// on each implementation. -typedef ::testing::Types< - GlibMessageLoop, - BaseMessageLoop> MessageLoopTypes; -TYPED_TEST_CASE(MessageLoopTest, MessageLoopTypes); - - -TYPED_TEST(MessageLoopTest, CancelTaskInvalidValuesTest) { - EXPECT_FALSE(this->loop_->CancelTask(MessageLoop::kTaskIdNull)); - EXPECT_FALSE(this->loop_->CancelTask(1234)); -} - -TYPED_TEST(MessageLoopTest, PostTaskTest) { - bool called = false; - TaskId task_id = this->loop_->PostTask(FROM_HERE, - Bind([&called]() { called = true; })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - MessageLoopRunMaxIterations(this->loop_.get(), 100); - EXPECT_TRUE(called); -} - -// Tests that we can cancel tasks right after we schedule them. -TYPED_TEST(MessageLoopTest, PostTaskCancelledTest) { - bool called = false; - TaskId task_id = this->loop_->PostTask(FROM_HERE, - Bind([&called]() { called = true; })); - EXPECT_TRUE(this->loop_->CancelTask(task_id)); - MessageLoopRunMaxIterations(this->loop_.get(), 100); - EXPECT_FALSE(called); - // Can't remove a task you already removed. - EXPECT_FALSE(this->loop_->CancelTask(task_id)); -} - -TYPED_TEST(MessageLoopTest, PostDelayedTaskRunsEventuallyTest) { - bool called = false; - TaskId task_id = this->loop_->PostDelayedTask( - FROM_HERE, - Bind([&called]() { called = true; }), - TimeDelta::FromMilliseconds(50)); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - MessageLoopRunUntil(this->loop_.get(), - TimeDelta::FromSeconds(10), - Bind([&called]() { return called; })); - // Check that the main loop finished before the 10 seconds timeout, so it - // finished due to the callback being called and not due to the timeout. - EXPECT_TRUE(called); -} - -// Test that you can call the overloaded version of PostDelayedTask from -// MessageLoop. This is important because only one of the two methods is -// virtual, so you need to unhide the other when overriding the virtual one. -TYPED_TEST(MessageLoopTest, PostDelayedTaskWithoutLocation) { - this->loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta()); - EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); -} - -TYPED_TEST(MessageLoopTest, WatchForInvalidFD) { - bool called = false; - EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor( - FROM_HERE, -1, MessageLoop::kWatchRead, true, - Bind([&called] { called = true; }))); - EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor( - FROM_HERE, -1, MessageLoop::kWatchWrite, true, - Bind([&called] { called = true; }))); - EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100)); - EXPECT_FALSE(called); -} - -TYPED_TEST(MessageLoopTest, CancelWatchedFileDescriptor) { - ScopedPipe pipe; - bool called = false; - TaskId task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, - Bind([&called] { called = true; })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - // The reader end is blocked because we didn't write anything to the writer - // end. - EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100)); - EXPECT_FALSE(called); - EXPECT_TRUE(this->loop_->CancelTask(task_id)); -} - -// When you watch a file descriptor for reading, the guaranties are that a -// blocking call to read() on that file descriptor will not block. This should -// include the case when the other end of a pipe is closed or the file is empty. -TYPED_TEST(MessageLoopTest, WatchFileDescriptorTriggersWhenPipeClosed) { - ScopedPipe pipe; - bool called = false; - EXPECT_EQ(0, HANDLE_EINTR(close(pipe.writer))); - pipe.writer = -1; - TaskId task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, - Bind([&called] { called = true; })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - // The reader end is not blocked because we closed the writer end so a read on - // the reader end would return 0 bytes read. - EXPECT_NE(0, MessageLoopRunMaxIterations(this->loop_.get(), 10)); - EXPECT_TRUE(called); - EXPECT_TRUE(this->loop_->CancelTask(task_id)); -} - -// When a WatchFileDescriptor task is scheduled with |persistent| = true, we -// should keep getting a call whenever the file descriptor is ready. -TYPED_TEST(MessageLoopTest, WatchFileDescriptorPersistently) { - ScopedPipe pipe; - EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1))); - - int called = 0; - TaskId task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true, - Bind([&called] { called++; })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - // We let the main loop run for 20 iterations to give it enough iterations to - // verify that our callback was called more than one. We only check that our - // callback is called more than once. - EXPECT_EQ(20, MessageLoopRunMaxIterations(this->loop_.get(), 20)); - EXPECT_LT(1, called); - EXPECT_TRUE(this->loop_->CancelTask(task_id)); -} - -TYPED_TEST(MessageLoopTest, WatchFileDescriptorNonPersistent) { - ScopedPipe pipe; - EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1))); - - int called = 0; - TaskId task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, pipe.reader, MessageLoop::kWatchRead, false, - Bind([&called] { called++; })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - // We let the main loop run for 20 iterations but we just expect it to run - // at least once. The callback should be called exactly once since we - // scheduled it non-persistently. After it ran, we shouldn't be able to cancel - // this task. - EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20)); - EXPECT_EQ(1, called); - EXPECT_FALSE(this->loop_->CancelTask(task_id)); -} - -TYPED_TEST(MessageLoopTest, WatchFileDescriptorForReadAndWriteSimultaneously) { - ScopedSocketPair socks; - EXPECT_EQ(1, HANDLE_EINTR(write(socks.right, "a", 1))); - // socks.left should be able to read this "a" and should also be able to write - // without blocking since the kernel has some buffering for it. - - TaskId read_task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, socks.left, MessageLoop::kWatchRead, true, - Bind([this, &read_task_id] { - EXPECT_TRUE(this->loop_->CancelTask(read_task_id)) - << "task_id" << read_task_id; - })); - EXPECT_NE(MessageLoop::kTaskIdNull, read_task_id); - - TaskId write_task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, socks.left, MessageLoop::kWatchWrite, true, - Bind([this, &write_task_id] { - EXPECT_TRUE(this->loop_->CancelTask(write_task_id)); - })); - EXPECT_NE(MessageLoop::kTaskIdNull, write_task_id); - - EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20)); - - EXPECT_FALSE(this->loop_->CancelTask(read_task_id)); - EXPECT_FALSE(this->loop_->CancelTask(write_task_id)); -} - -// Test that we can cancel the task we are running, and should just fail. -TYPED_TEST(MessageLoopTest, DeleteTaskFromSelf) { - bool cancel_result = true; // We would expect this to be false. - MessageLoop* loop_ptr = this->loop_.get(); - TaskId task_id; - task_id = this->loop_->PostTask( - FROM_HERE, - Bind([&cancel_result, loop_ptr, &task_id]() { - cancel_result = loop_ptr->CancelTask(task_id); - })); - EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); - EXPECT_FALSE(cancel_result); -} - -// Test that we can cancel a non-persistent file descriptor watching callback, -// which should fail. -TYPED_TEST(MessageLoopTest, DeleteNonPersistenIOTaskFromSelf) { - ScopedPipe pipe; - TaskId task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, false /* persistent */, - Bind([this, &task_id] { - EXPECT_FALSE(this->loop_->CancelTask(task_id)); - task_id = MessageLoop::kTaskIdNull; - })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); - EXPECT_EQ(MessageLoop::kTaskIdNull, task_id); -} - -// Test that we can cancel a persistent file descriptor watching callback from -// the same callback. -TYPED_TEST(MessageLoopTest, DeletePersistenIOTaskFromSelf) { - ScopedPipe pipe; - TaskId task_id = this->loop_->WatchFileDescriptor( - FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, true /* persistent */, - Bind([this, &task_id] { - EXPECT_TRUE(this->loop_->CancelTask(task_id)); - task_id = MessageLoop::kTaskIdNull; - })); - EXPECT_NE(MessageLoop::kTaskIdNull, task_id); - EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100)); - EXPECT_EQ(MessageLoop::kTaskIdNull, task_id); -} - -// Test that we can cancel several persistent file descriptor watching callbacks -// from a scheduled callback. In the BaseMessageLoop implementation, this code -// will cause us to cancel an IOTask that has a pending delayed task, but -// otherwise is a valid test case on all implementations. -TYPED_TEST(MessageLoopTest, DeleteAllPersistenIOTaskFromSelf) { - const int kNumTasks = 5; - ScopedPipe pipes[kNumTasks]; - TaskId task_ids[kNumTasks]; - - for (int i = 0; i < kNumTasks; ++i) { - task_ids[i] = this->loop_->WatchFileDescriptor( - FROM_HERE, pipes[i].writer, MessageLoop::kWatchWrite, - true /* persistent */, - Bind([this, kNumTasks, &task_ids] { - for (int j = 0; j < kNumTasks; ++j) { - // Once we cancel all the tasks, none should run, so this code runs - // only once from one callback. - EXPECT_TRUE(this->loop_->CancelTask(task_ids[j])); - task_ids[j] = MessageLoop::kTaskIdNull; - } - })); - } - MessageLoopRunMaxIterations(this->loop_.get(), 100); - for (int i = 0; i < kNumTasks; ++i) { - EXPECT_EQ(MessageLoop::kTaskIdNull, task_ids[i]); - } -} - -// Test that if there are several tasks watching for file descriptors to be -// available or simply waiting in the message loop are fairly scheduled to run. -// In other words, this test ensures that having a file descriptor always -// available doesn't prevent other file descriptors watching tasks or delayed -// tasks to be dispatched, causing starvation. -TYPED_TEST(MessageLoopTest, AllTasksAreEqual) { - int total_calls = 0; - - // First, schedule a repeating timeout callback to run from the main loop. - int timeout_called = 0; - base::Closure timeout_callback; - MessageLoop::TaskId timeout_task; - timeout_callback = base::Bind( - [this, &timeout_called, &total_calls, &timeout_callback, &timeout_task] { - timeout_called++; - total_calls++; - timeout_task = this->loop_->PostTask(FROM_HERE, Bind(timeout_callback)); - if (total_calls > 100) - this->loop_->BreakLoop(); - }); - timeout_task = this->loop_->PostTask(FROM_HERE, timeout_callback); - - // Second, schedule several file descriptor watchers. - const int kNumTasks = 3; - ScopedPipe pipes[kNumTasks]; - MessageLoop::TaskId tasks[kNumTasks]; - - int reads[kNumTasks] = {}; - auto fd_callback = [this, &pipes, &reads, &total_calls](int i) { - reads[i]++; - total_calls++; - char c; - EXPECT_EQ(1, HANDLE_EINTR(read(pipes[i].reader, &c, 1))); - if (total_calls > 100) - this->loop_->BreakLoop(); - }; - - for (int i = 0; i < kNumTasks; ++i) { - tasks[i] = this->loop_->WatchFileDescriptor( - FROM_HERE, pipes[i].reader, MessageLoop::kWatchRead, - true /* persistent */, - Bind(fd_callback, i)); - // Make enough bytes available on each file descriptor. This should not - // block because we set the size of the file descriptor buffer when - // creating it. - std::vector blob(1000, 'a'); - EXPECT_EQ(blob.size(), - HANDLE_EINTR(write(pipes[i].writer, blob.data(), blob.size()))); - } - this->loop_->Run(); - EXPECT_GT(total_calls, 100); - // We run the loop up 100 times and expect each callback to run at least 10 - // times. A good scheduler should balance these callbacks. - EXPECT_GE(timeout_called, 10); - EXPECT_TRUE(this->loop_->CancelTask(timeout_task)); - for (int i = 0; i < kNumTasks; ++i) { - EXPECT_GE(reads[i], 10) << "Reading from pipes[" << i << "], fd " - << pipes[i].reader; - EXPECT_TRUE(this->loop_->CancelTask(tasks[i])); - } -} - -} // namespace chromeos diff --git a/chromeos/message_loops/message_loop_utils.cc b/chromeos/message_loops/message_loop_utils.cc deleted file mode 100644 index cb2eb22..0000000 --- a/chromeos/message_loops/message_loop_utils.cc +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -namespace chromeos { - -void MessageLoopRunUntil( - MessageLoop* loop, - base::TimeDelta timeout, - base::Callback terminate) { - bool timeout_called = false; - MessageLoop::TaskId task_id = loop->PostDelayedTask( - FROM_HERE, - base::Bind([&timeout_called]() { timeout_called = true; }), - timeout); - while (!timeout_called && (terminate.is_null() || !terminate.Run())) - loop->RunOnce(true); - - if (!timeout_called) - loop->CancelTask(task_id); -} - -int MessageLoopRunMaxIterations(MessageLoop* loop, int iterations) { - int result; - for (result = 0; result < iterations && loop->RunOnce(false); result++) {} - return result; -} - -} // namespace chromeos diff --git a/chromeos/message_loops/message_loop_utils.h b/chromeos/message_loops/message_loop_utils.h deleted file mode 100644 index 0af0e36..0000000 --- a/chromeos/message_loops/message_loop_utils.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_ - -#include -#include - -#include -#include - -namespace chromeos { - -// Run the MessageLoop until the condition passed in |terminate| returns true -// or the timeout expires. -CHROMEOS_EXPORT void MessageLoopRunUntil( - MessageLoop* loop, - base::TimeDelta timeout, - base::Callback terminate); - -// Run the MessageLoop |loop| for up to |iterations| times without blocking. -// Return the number of tasks run. -CHROMEOS_EXPORT int MessageLoopRunMaxIterations(MessageLoop* loop, - int iterations); - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_ diff --git a/chromeos/message_loops/mock_message_loop.h b/chromeos/message_loops/mock_message_loop.h deleted file mode 100644 index a4fcd77..0000000 --- a/chromeos/message_loops/mock_message_loop.h +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_ -#define LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_ - -#include - -#include -#include -#include - -#include -#include -#include - -namespace chromeos { - -// The MockMessageLoop is a mockable MessageLoop that will by default act as a -// FakeMessageLoop. It is possible to set expectations with EXPECT_CALL without -// any action associated and they will call the same methods in the underlying -// FakeMessageLoop implementation. -// This message loop implementation is useful to check interaction with the -// message loop when running unittests. -class CHROMEOS_EXPORT MockMessageLoop : public MessageLoop { - public: - // Create a FakeMessageLoop optionally using a SimpleTestClock to update the - // time when Run() or RunOnce(true) are called and should block. - explicit MockMessageLoop(base::SimpleTestClock* clock) - : fake_loop_(clock) { - // Redirect all actions to calling the underlying FakeMessageLoop by - // default. For the overloaded methods, we need to disambiguate between the - // different options by specifying the type of the method pointer. - ON_CALL(*this, PostDelayedTask(::testing::_, ::testing::_, ::testing::_)) - .WillByDefault(::testing::Invoke( - &fake_loop_, - static_cast( - &FakeMessageLoop::PostDelayedTask))); - ON_CALL(*this, WatchFileDescriptor( - ::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) - .WillByDefault(::testing::Invoke( - &fake_loop_, - static_cast( - &FakeMessageLoop::WatchFileDescriptor))); - ON_CALL(*this, CancelTask(::testing::_)) - .WillByDefault(::testing::Invoke(&fake_loop_, - &FakeMessageLoop::CancelTask)); - ON_CALL(*this, RunOnce(::testing::_)) - .WillByDefault(::testing::Invoke(&fake_loop_, - &FakeMessageLoop::RunOnce)); - } - ~MockMessageLoop() override = default; - - MOCK_METHOD3(PostDelayedTask, - TaskId(const tracked_objects::Location& from_here, - const base::Closure& task, - base::TimeDelta delay)); - using MessageLoop::PostDelayedTask; - MOCK_METHOD5(WatchFileDescriptor, - TaskId(const tracked_objects::Location& from_here, - int fd, - WatchMode mode, - bool persistent, - const base::Closure& task)); - using MessageLoop::WatchFileDescriptor; - MOCK_METHOD1(CancelTask, bool(TaskId task_id)); - MOCK_METHOD1(RunOnce, bool(bool may_block)); - - // Returns the actual FakeMessageLoop instance so default actions can be - // override with other actions or call - FakeMessageLoop* fake_loop() { - return &fake_loop_; - } - - private: - FakeMessageLoop fake_loop_; - - DISALLOW_COPY_AND_ASSIGN(MockMessageLoop); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_ diff --git a/chromeos/mime_utils.cc b/chromeos/mime_utils.cc deleted file mode 100644 index ce67b2a..0000000 --- a/chromeos/mime_utils.cc +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -namespace chromeos { - -// *************************************************************************** -// ******************************* MIME types ******************************** -// *************************************************************************** -const char mime::types::kApplication[] = "application"; -const char mime::types::kAudio[] = "audio"; -const char mime::types::kImage[] = "image"; -const char mime::types::kMessage[] = "message"; -const char mime::types::kMultipart[] = "multipart"; -const char mime::types::kText[] = "text"; -const char mime::types::kVideo[] = "video"; - -const char mime::parameters::kCharset[] = "charset"; - -const char mime::image::kJpeg[] = "image/jpeg"; -const char mime::image::kPng[] = "image/png"; -const char mime::image::kBmp[] = "image/bmp"; -const char mime::image::kTiff[] = "image/tiff"; -const char mime::image::kGif[] = "image/gif"; - -const char mime::text::kPlain[] = "text/plain"; -const char mime::text::kHtml[] = "text/html"; -const char mime::text::kXml[] = "text/xml"; - -const char mime::application::kOctet_stream[] = "application/octet-stream"; -const char mime::application::kJson[] = "application/json"; -const char mime::application::kWwwFormUrlEncoded[] = - "application/x-www-form-urlencoded"; -const char mime::application::kProtobuf[] = "application/x-protobuf"; - -const char mime::multipart::kFormData[] = "multipart/form-data"; -const char mime::multipart::kMixed[] = "multipart/mixed"; - -// *************************************************************************** -// **************************** Utility Functions **************************** -// *************************************************************************** -static std::string EncodeParam(const std::string& param) { - // If the string contains one of "tspecials" characters as - // specified in RFC 1521, enclose it in quotes. - if (param.find_first_of("()<>@,;:\\\"/[]?=") != std::string::npos) { - return '"' + param + '"'; - } - return param; -} - -static std::string DecodeParam(const std::string& param) { - if (param.size() > 1 && param.front() == '"' && param.back() == '"') { - return param.substr(1, param.size() - 2); - } - return param; -} - -// *************************************************************************** -// ******************** Main MIME manipulation functions ********************* -// *************************************************************************** - -bool mime::Split(const std::string& mime_string, - std::string* type, - std::string* subtype, - mime::Parameters* parameters) { - std::vector parts = - chromeos::string_utils::Split(mime_string, ";"); - if (parts.empty()) - return false; - - if (!mime::Split(parts.front(), type, subtype)) - return false; - - if (parameters) { - parameters->clear(); - parameters->reserve(parts.size() - 1); - for (size_t i = 1; i < parts.size(); i++) { - auto pair = chromeos::string_utils::SplitAtFirst(parts[i], "="); - pair.second = DecodeParam(pair.second); - parameters->push_back(pair); - } - } - return true; -} - -bool mime::Split(const std::string& mime_string, - std::string* type, - std::string* subtype) { - std::string mime = mime::RemoveParameters(mime_string); - auto types = chromeos::string_utils::SplitAtFirst(mime, "/"); - - if (type) - *type = types.first; - - if (subtype) - *subtype = types.second; - - return !types.first.empty() && !types.second.empty(); -} - -std::string mime::Combine(const std::string& type, - const std::string& subtype, - const mime::Parameters& parameters) { - std::vector parts; - parts.push_back(chromeos::string_utils::Join("/", type, subtype)); - for (const auto& pair : parameters) { - parts.push_back(chromeos::string_utils::Join("=", pair.first, - EncodeParam(pair.second))); - } - return chromeos::string_utils::Join("; ", parts); -} - -std::string mime::GetType(const std::string& mime_string) { - std::string mime = mime::RemoveParameters(mime_string); - return chromeos::string_utils::SplitAtFirst(mime, "/").first; -} - -std::string mime::GetSubtype(const std::string& mime_string) { - std::string mime = mime::RemoveParameters(mime_string); - return chromeos::string_utils::SplitAtFirst(mime, "/").second; -} - -mime::Parameters mime::GetParameters(const std::string& mime_string) { - std::string type; - std::string subtype; - mime::Parameters parameters; - - if (mime::Split(mime_string, &type, &subtype, ¶meters)) - return parameters; - - return mime::Parameters(); -} - -std::string mime::RemoveParameters(const std::string& mime_string) { - return chromeos::string_utils::SplitAtFirst(mime_string, ";").first; -} - -std::string mime::AppendParameter(const std::string& mime_string, - const std::string& paramName, - const std::string& paramValue) { - std::string mime(mime_string); - mime += "; "; - mime += chromeos::string_utils::Join("=", paramName, EncodeParam(paramValue)); - return mime; -} - -std::string mime::GetParameterValue(const std::string& mime_string, - const std::string& paramName) { - mime::Parameters params = mime::GetParameters(mime_string); - for (const auto& pair : params) { - if (base::strcasecmp(pair.first.c_str(), paramName.c_str()) == 0) - return pair.second; - } - return std::string(); -} - -} // namespace chromeos diff --git a/chromeos/mime_utils.h b/chromeos/mime_utils.h deleted file mode 100644 index 46d4b88..0000000 --- a/chromeos/mime_utils.h +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MIME_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_MIME_UTILS_H_ - -#include -#include -#include - -#include -#include -#include - -namespace chromeos { -namespace mime { - -namespace types { -// Main MIME type categories -CHROMEOS_EXPORT extern const char kApplication[]; // application -CHROMEOS_EXPORT extern const char kAudio[]; // audio -CHROMEOS_EXPORT extern const char kImage[]; // image -CHROMEOS_EXPORT extern const char kMessage[]; // message -CHROMEOS_EXPORT extern const char kMultipart[]; // multipart -CHROMEOS_EXPORT extern const char kText[]; // test -CHROMEOS_EXPORT extern const char kVideo[]; // video -} // namespace types - -namespace parameters { -// Common MIME parameters -CHROMEOS_EXPORT extern const char kCharset[]; // charset=... -} // namespace parameters - -namespace image { -// Common image MIME types -CHROMEOS_EXPORT extern const char kJpeg[]; // image/jpeg -CHROMEOS_EXPORT extern const char kPng[]; // image/png -CHROMEOS_EXPORT extern const char kBmp[]; // image/bmp -CHROMEOS_EXPORT extern const char kTiff[]; // image/tiff -CHROMEOS_EXPORT extern const char kGif[]; // image/gif -} // namespace image - -namespace text { -// Common text MIME types -CHROMEOS_EXPORT extern const char kPlain[]; // text/plain -CHROMEOS_EXPORT extern const char kHtml[]; // text/html -CHROMEOS_EXPORT extern const char kXml[]; // text/xml -} // namespace text - -namespace application { -// Common application MIME types -// application/octet-stream -CHROMEOS_EXPORT extern const char kOctet_stream[]; -// application/json -CHROMEOS_EXPORT extern const char kJson[]; -// application/x-www-form-urlencoded -CHROMEOS_EXPORT extern const char kWwwFormUrlEncoded[]; -// application/x-protobuf -CHROMEOS_EXPORT extern const char kProtobuf[]; -} // namespace application - -namespace multipart { -// Common multipart MIME types -// multipart/form-data -CHROMEOS_EXPORT extern const char kFormData[]; -// multipart/mixed -CHROMEOS_EXPORT extern const char kMixed[]; -} // namespace multipart - -using Parameters = std::vector>; - -// Combine a MIME type, subtype and parameters into a MIME string. -// e.g. Combine("text", "plain", {{"charset", "utf-8"}}) will give: -// "text/plain; charset=utf-8" -CHROMEOS_EXPORT std::string Combine( - const std::string& type, - const std::string& subtype, - const Parameters& parameters = {}) WARN_UNUSED_RESULT; - -// Splits a MIME string into type and subtype. -// "text/plain;charset=utf-8" => ("text", "plain") -CHROMEOS_EXPORT bool Split(const std::string& mime_string, - std::string* type, - std::string* subtype); - -// Splits a MIME string into type, subtype, and parameters. -// "text/plain;charset=utf-8" => ("text", "plain", {{"charset","utf-8"}}) -CHROMEOS_EXPORT bool Split(const std::string& mime_string, - std::string* type, - std::string* subtype, - Parameters* parameters); - -// Returns the MIME type from MIME string. -// "text/plain;charset=utf-8" => "text" -CHROMEOS_EXPORT std::string GetType(const std::string& mime_string); - -// Returns the MIME sub-type from MIME string. -// "text/plain;charset=utf-8" => "plain" -CHROMEOS_EXPORT std::string GetSubtype(const std::string& mime_string); - -// Returns the MIME parameters from MIME string. -// "text/plain;charset=utf-8" => {{"charset","utf-8"}} -CHROMEOS_EXPORT Parameters GetParameters(const std::string& mime_string); - -// Removes parameters from a MIME string -// "text/plain;charset=utf-8" => "text/plain" -CHROMEOS_EXPORT std::string RemoveParameters( - const std::string& mime_string) WARN_UNUSED_RESULT; - -// Appends a parameter to a MIME string. -// "text/plain" => "text/plain; charset=utf-8" -CHROMEOS_EXPORT std::string AppendParameter( - const std::string& mime_string, - const std::string& paramName, - const std::string& paramValue) WARN_UNUSED_RESULT; - -// Returns the value of a parameter on a MIME string (empty string if missing). -// ("text/plain;charset=utf-8","charset") => "utf-8" -CHROMEOS_EXPORT std::string GetParameterValue(const std::string& mime_string, - const std::string& paramName); - -} // namespace mime -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MIME_UTILS_H_ diff --git a/chromeos/mime_utils_unittest.cc b/chromeos/mime_utils_unittest.cc deleted file mode 100644 index 751d7fd..0000000 --- a/chromeos/mime_utils_unittest.cc +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { - -TEST(MimeUtils, Combine) { - std::string mime_string = mime::Combine(mime::types::kText, "xml"); - EXPECT_EQ(mime::text::kXml, mime_string); - EXPECT_EQ( - "application/json; charset=utf-8", - mime::Combine(mime::types::kApplication, "json", {{"charset", "utf-8"}})); -} - -TEST(MimeUtils, Split) { - std::string s1, s2; - EXPECT_TRUE(mime::Split(mime::image::kJpeg, &s1, &s2)); - EXPECT_EQ(mime::types::kImage, s1); - EXPECT_EQ("jpeg", s2); - - mime::Parameters parameters; - EXPECT_TRUE( - mime::Split("application/json;charset=utf-8", &s1, &s2, ¶meters)); - EXPECT_EQ(mime::types::kApplication, s1); - EXPECT_EQ("json", s2); - EXPECT_EQ(mime::application::kJson, mime::Combine(s1, s2)); - EXPECT_EQ(1, parameters.size()); - EXPECT_EQ(mime::parameters::kCharset, parameters.front().first); - EXPECT_EQ("utf-8", parameters.front().second); - EXPECT_EQ("application/json; charset=utf-8", - mime::Combine(s1, s2, parameters)); -} - -TEST(MimeUtils, ExtractParts) { - mime::Parameters parameters; - - EXPECT_EQ(mime::types::kText, mime::GetType(mime::text::kPlain)); - EXPECT_EQ("plain", mime::GetSubtype(mime::text::kPlain)); - - parameters = mime::GetParameters("text/plain; charset=iso-8859-1;foo=bar"); - EXPECT_EQ(2, parameters.size()); - EXPECT_EQ(mime::parameters::kCharset, parameters[0].first); - EXPECT_EQ("iso-8859-1", parameters[0].second); - EXPECT_EQ("foo", parameters[1].first); - EXPECT_EQ("bar", parameters[1].second); -} - -TEST(MimeUtils, AppendRemoveParams) { - std::string mime_string = mime::AppendParameter( - mime::text::kXml, mime::parameters::kCharset, "utf-8"); - EXPECT_EQ("text/xml; charset=utf-8", mime_string); - mime_string = mime::AppendParameter(mime_string, "foo", "bar"); - EXPECT_EQ("text/xml; charset=utf-8; foo=bar", mime_string); - EXPECT_EQ("utf-8", - mime::GetParameterValue(mime_string, mime::parameters::kCharset)); - EXPECT_EQ("bar", mime::GetParameterValue(mime_string, "foo")); - EXPECT_EQ("", mime::GetParameterValue(mime_string, "baz")); - mime_string = mime::RemoveParameters(mime_string); - EXPECT_EQ(mime::text::kXml, mime_string); -} - -} // namespace chromeos diff --git a/chromeos/minijail/minijail.cc b/chromeos/minijail/minijail.cc deleted file mode 100644 index a3e0f57..0000000 --- a/chromeos/minijail/minijail.cc +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/minijail/minijail.h" - -#include -#include - -using std::vector; - -namespace chromeos { - -static base::LazyInstance g_minijail = LAZY_INSTANCE_INITIALIZER; - -Minijail::Minijail() {} - -Minijail::~Minijail() {} - -// static -Minijail* Minijail::GetInstance() { - return g_minijail.Pointer(); -} - -struct minijail* Minijail::New() { - return minijail_new(); -} - -void Minijail::Destroy(struct minijail* jail) { - minijail_destroy(jail); -} - -void Minijail::DropRoot(struct minijail* jail, uid_t uid, gid_t gid) { - minijail_change_uid(jail, uid); - minijail_change_gid(jail, gid); -} - -bool Minijail::DropRoot(struct minijail* jail, - const char* user, - const char* group) { - // |user| and |group| are copied so the only reason either of these - // calls can fail is ENOMEM. - return !minijail_change_user(jail, user) && - !minijail_change_group(jail, group); -} - -void Minijail::EnterNewPidNamespace(struct minijail* jail) { - minijail_namespace_pids(jail); -} - -void Minijail::MountTmp(struct minijail* jail) { - minijail_mount_tmp(jail); -} - -void Minijail::UseSeccompFilter(struct minijail* jail, const char* path) { - minijail_no_new_privs(jail); - minijail_use_seccomp_filter(jail); - minijail_parse_seccomp_filters(jail, path); -} - -void Minijail::UseCapabilities(struct minijail* jail, uint64_t capmask) { - minijail_use_caps(jail, capmask); -} - -void Minijail::Enter(struct minijail* jail) { - minijail_enter(jail); -} - -bool Minijail::Run(struct minijail* jail, vector args, pid_t* pid) { - return minijail_run_pid(jail, args[0], args.data(), pid) == 0; -} - -bool Minijail::RunSync(struct minijail* jail, vector args, int* status) { - pid_t pid; - if (Run(jail, args, &pid) && waitpid(pid, status, 0) == pid) { - return true; - } - - return false; -} - -bool Minijail::RunPipe(struct minijail* jail, - vector args, - pid_t* pid, - int* stdin) { - return minijail_run_pid_pipe(jail, args[0], args.data(), pid, stdin) == 0; -} - -bool Minijail::RunPipes(struct minijail* jail, - vector args, - pid_t* pid, - int* stdin, - int* stdout, - int* stderr) { - return minijail_run_pid_pipes( - jail, args[0], args.data(), pid, stdin, stdout, stderr) == 0; -} - -bool Minijail::RunAndDestroy(struct minijail* jail, - vector args, - pid_t* pid) { - bool res = Run(jail, args, pid); - Destroy(jail); - return res; -} - -bool Minijail::RunSyncAndDestroy(struct minijail* jail, - vector args, - int* status) { - bool res = RunSync(jail, args, status); - Destroy(jail); - return res; -} - -bool Minijail::RunPipeAndDestroy(struct minijail* jail, - vector args, - pid_t* pid, - int* stdin) { - bool res = RunPipe(jail, args, pid, stdin); - Destroy(jail); - return res; -} - -bool Minijail::RunPipesAndDestroy(struct minijail* jail, - vector args, - pid_t* pid, - int* stdin, - int* stdout, - int* stderr) { - bool res = RunPipes(jail, args, pid, stdin, stdout, stderr); - Destroy(jail); - return res; -} - -} // namespace chromeos diff --git a/chromeos/minijail/minijail.h b/chromeos/minijail/minijail.h deleted file mode 100644 index 08edeca..0000000 --- a/chromeos/minijail/minijail.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MINIJAIL_MINIJAIL_H_ -#define LIBCHROMEOS_CHROMEOS_MINIJAIL_MINIJAIL_H_ - -#include - -extern "C" { -#include -#include -} - -#include - -#include - -namespace chromeos { - -// A Minijail abstraction allowing Minijail mocking in tests. -class Minijail { - public: - virtual ~Minijail(); - - // This is a singleton -- use Minijail::GetInstance()->Foo(). - static Minijail* GetInstance(); - - // minijail_new - virtual struct minijail* New(); - // minijail_destroy - virtual void Destroy(struct minijail* jail); - - // minijail_change_uid/minijail_change_gid - virtual void DropRoot(struct minijail* jail, uid_t uid, gid_t gid); - - // minijail_change_user/minijail_change_group - virtual bool DropRoot(struct minijail* jail, - const char* user, - const char* group); - - // minijail_namespace_pids - virtual void EnterNewPidNamespace(struct minijail* jail); - - // minijail_mount_tmp - virtual void MountTmp(struct minijail* jail); - - // minijail_use_seccomp_filter/minijail_no_new_privs/ - // minijail_parse_seccomp_filters - virtual void UseSeccompFilter(struct minijail* jail, const char* path); - - // minijail_use_caps - virtual void UseCapabilities(struct minijail* jail, uint64_t capmask); - - // minijail_enter - virtual void Enter(struct minijail* jail); - - // minijail_run_pid - virtual bool Run(struct minijail* jail, std::vector args, pid_t* pid); - - // minijail_run_pid and waitpid - virtual bool RunSync(struct minijail* jail, - std::vector args, - int* status); - - // minijail_run_pid_pipe - virtual bool RunPipe(struct minijail* jail, - std::vector args, - pid_t* pid, - int* stdin); - - // minijail_run_pid_pipes - virtual bool RunPipes(struct minijail* jail, - std::vector args, - pid_t* pid, - int* stdin, - int* stdout, - int* stderr); - - // Run() and Destroy() - virtual bool RunAndDestroy(struct minijail* jail, - std::vector args, - pid_t* pid); - - // RunSync() and Destroy() - virtual bool RunSyncAndDestroy(struct minijail* jail, - std::vector args, - int* status); - - // RunPipe() and Destroy() - virtual bool RunPipeAndDestroy(struct minijail* jail, - std::vector args, - pid_t* pid, - int* stdin); - - // RunPipes() and Destroy() - virtual bool RunPipesAndDestroy(struct minijail* jail, - std::vector args, - pid_t* pid, - int* stdin, - int* stdout, - int* stderr); - - protected: - Minijail(); - - private: - friend struct base::DefaultLazyInstanceTraits; - - DISALLOW_COPY_AND_ASSIGN(Minijail); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MINIJAIL_MINIJAIL_H_ diff --git a/chromeos/minijail/mock_minijail.h b/chromeos/minijail/mock_minijail.h deleted file mode 100644 index 4a21914..0000000 --- a/chromeos/minijail/mock_minijail.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_MINIJAIL_MOCK_MINIJAIL_H_ -#define LIBCHROMEOS_CHROMEOS_MINIJAIL_MOCK_MINIJAIL_H_ - -#include - -#include -#include - -#include "chromeos/minijail/minijail.h" - -namespace chromeos { - -class MockMinijail : public chromeos::Minijail { - public: - MockMinijail() {} - virtual ~MockMinijail() {} - - MOCK_METHOD0(New, struct minijail*()); - MOCK_METHOD1(Destroy, void(struct minijail*)); - - MOCK_METHOD3(DropRoot, - bool(struct minijail* jail, - const char* user, - const char* group)); - MOCK_METHOD2(UseSeccompFilter, void(struct minijail* jail, const char* path)); - MOCK_METHOD2(UseCapabilities, void(struct minijail* jail, uint64_t capmask)); - MOCK_METHOD1(Enter, void(struct minijail* jail)); - MOCK_METHOD3(Run, - bool(struct minijail* jail, - std::vector args, - pid_t* pid)); - MOCK_METHOD3(RunSync, - bool(struct minijail* jail, - std::vector args, - int* status)); - MOCK_METHOD3(RunAndDestroy, - bool(struct minijail* jail, - std::vector args, - pid_t* pid)); - MOCK_METHOD3(RunSyncAndDestroy, - bool(struct minijail* jail, - std::vector args, - int* status)); - MOCK_METHOD4(RunPipeAndDestroy, - bool(struct minijail* jail, - std::vector args, - pid_t* pid, - int* stdin)); - MOCK_METHOD6(RunPipesAndDestroy, - bool(struct minijail* jail, - std::vector args, - pid_t* pid, - int* stdin, - int* stdout, - int* stderr)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockMinijail); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_MINIJAIL_MOCK_MINIJAIL_H_ diff --git a/chromeos/osrelease_reader.cc b/chromeos/osrelease_reader.cc deleted file mode 100644 index b75405d..0000000 --- a/chromeos/osrelease_reader.cc +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include - -namespace chromeos { - -void OsReleaseReader::Load() { - Load(base::FilePath("/")); -} - -bool OsReleaseReader::GetString(const std::string& key, - std::string* value) const { - CHECK(initialized_) << "OsReleaseReader.Load() must be called first."; - return store_.GetString(key, value); -} - -void OsReleaseReader::LoadTestingOnly(const base::FilePath& root_dir) { - Load(root_dir); -} - -void OsReleaseReader::Load(const base::FilePath& root_dir) { - base::FilePath osrelease = root_dir.Append("etc").Append("os-release"); - if (!store_.Load(osrelease)) { - // /etc/os-release might not be present (cros deploying a new configuration - // or no fields set at all). Just print a debug message and continue. - DLOG(INFO) << "Could not load fields from " << osrelease.value(); - } - - base::FilePath osreleased = root_dir.Append("etc").Append("os-release.d"); - base::FileEnumerator enumerator( - osreleased, false, base::FileEnumerator::FILES); - - for (base::FilePath path = enumerator.Next(); !path.empty(); - path = enumerator.Next()) { - std::string content; - if (!base::ReadFileToString(path, &content)) { - // The only way to fail is if a file exist in /etc/os-release.d but we - // cannot read it. - PLOG(FATAL) << "Could not read " << path.value(); - } - // There might be a trailing new line. Strip it to keep only the first line - // of the file. - content = chromeos::string_utils::SplitAtFirst(content, "\n", true).first; - store_.SetString(path.BaseName().value(), content); - } - initialized_ = true; -} - -} // namespace chromeos diff --git a/chromeos/osrelease_reader.h b/chromeos/osrelease_reader.h deleted file mode 100644 index 55d9390..0000000 --- a/chromeos/osrelease_reader.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Wrapper around /etc/os-release and /etc/os-release.d. -// Standard fields can come from both places depending on how we set them. They -// should always be accessed through this interface. - -#ifndef LIBCHROMEOS_CHROMEOS_OSRELEASE_READER_H_ -#define LIBCHROMEOS_CHROMEOS_OSRELEASE_READER_H_ - -#include - -#include -#include -#include - -namespace chromeos { - -class CHROMEOS_EXPORT OsReleaseReader final { - public: - // Create an empty reader - OsReleaseReader() = default; - - // Loads the key=value pairs from either /etc/os-release.d/ or - // /etc/os-release. - void Load(); - - // Same as the private Load method. - // This need to be public so that services can use it in testing mode (for - // autotest tests for example). - // This should not be used in production so suffix it with TestingOnly to - // make it obvious. - void LoadTestingOnly(const base::FilePath& root_dir); - - // Getter for the given key. Returns whether the key was found on the store. - bool GetString(const std::string& key, std::string* value) const; - - private: - // The map storing all the key-value pairs. - KeyValueStore store_; - - // os-release can be lazily loaded if need be. - bool initialized_; - - // Load the data from a given root_dir. - CHROMEOS_PRIVATE void Load(const base::FilePath& root_dir); - - DISALLOW_COPY_AND_ASSIGN(OsReleaseReader); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_OSRELEASE_READER_H_ diff --git a/chromeos/osrelease_reader_unittest.cc b/chromeos/osrelease_reader_unittest.cc deleted file mode 100644 index ea5ec75..0000000 --- a/chromeos/osrelease_reader_unittest.cc +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -using std::string; - -namespace chromeos { - -class OsReleaseReaderTest : public ::testing::Test { - public: - void SetUp() override { - CHECK(temp_dir_.CreateUniqueTempDir()); - osreleased_ = temp_dir_.path().Append("etc").Append("os-release.d"); - osrelease_ = temp_dir_.path().Append("etc").Append("os-release"); - base::CreateDirectory(osreleased_); - } - - protected: - base::FilePath temp_file_, osrelease_, osreleased_; - base::ScopedTempDir temp_dir_; - OsReleaseReader store_; // reader under test. -}; - -TEST_F(OsReleaseReaderTest, MissingOsReleaseTest) { - store_.LoadTestingOnly(temp_dir_.path()); -} - -TEST_F(OsReleaseReaderTest, MissingOsReleaseDTest) { - base::DeleteFile(osreleased_, true); - store_.LoadTestingOnly(temp_dir_.path()); -} - -TEST_F(OsReleaseReaderTest, CompleteTest) { - string hello = "hello"; - string ola = "ola"; - string bob = "bob"; - string osreleasecontent = "TEST_KEY=bonjour\nNAME=bob\n"; - - base::WriteFile(osreleased_.Append("TEST_KEY"), hello.data(), hello.size()); - base::WriteFile(osreleased_.Append("GREETINGS"), ola.data(), ola.size()); - base::WriteFile(osrelease_, osreleasecontent.data(), osreleasecontent.size()); - - store_.LoadTestingOnly(temp_dir_.path()); - - string test_key_value; - ASSERT_TRUE(store_.GetString("TEST_KEY", &test_key_value)); - - string greetings_value; - ASSERT_TRUE(store_.GetString("GREETINGS", &greetings_value)); - - string name_value; - ASSERT_TRUE(store_.GetString("NAME", &name_value)); - - string nonexistent_value; - // Getting the string should fail if the key does not exist. - ASSERT_FALSE(store_.GetString("DOES_NOT_EXIST", &nonexistent_value)); - - // hello in chosen (from os-release.d) instead of bonjour from os-release. - ASSERT_EQ(hello, test_key_value); - - // greetings is set to ola. - ASSERT_EQ(ola, greetings_value); - - // Name from os-release is set. - ASSERT_EQ(bob, name_value); -} - -TEST_F(OsReleaseReaderTest, NoNewLine) { - // New lines should be stripped from os-release.d files. - string hello = "hello\n"; - string bonjour = "bonjour\ngarbage"; - - base::WriteFile(osreleased_.Append("HELLO"), hello.data(), hello.size()); - base::WriteFile( - osreleased_.Append("BONJOUR"), bonjour.data(), bonjour.size()); - - store_.LoadTestingOnly(temp_dir_.path()); - - string hello_value; - string bonjour_value; - - ASSERT_TRUE(store_.GetString("HELLO", &hello_value)); - ASSERT_TRUE(store_.GetString("BONJOUR", &bonjour_value)); - - ASSERT_EQ("hello", hello_value); - ASSERT_EQ("bonjour", bonjour_value); -} - -} // namespace chromeos diff --git a/chromeos/pointer_utils.h b/chromeos/pointer_utils.h deleted file mode 100644 index d431a5d..0000000 --- a/chromeos/pointer_utils.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_POINTER_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_POINTER_UTILS_H_ - -#include -#include - -namespace chromeos { - -// AdvancePointer() is a helper function to advance void pointer by -// |byte_offset| bytes. Both const and non-const overloads are provided. -inline void* AdvancePointer(void* pointer, ssize_t byte_offset) { - return reinterpret_cast(pointer) + byte_offset; -} -inline const void* AdvancePointer(const void* pointer, ssize_t byte_offset) { - return reinterpret_cast(pointer) + byte_offset; -} - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_POINTER_UTILS_H_ diff --git a/chromeos/process.cc b/chromeos/process.cc deleted file mode 100644 index ffe1525..0000000 --- a/chromeos/process.cc +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/process.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#ifndef __linux__ -#define setresuid(_u1, _u2, _u3) setreuid(_u1, _u2) -#define setresgid(_g1, _g2, _g3) setregid(_g1, _g2) -#endif // !__linux__ - -namespace chromeos { - -bool ReturnTrue() { - return true; -} - -Process::Process() { -} - -Process::~Process() { -} - -bool Process::ProcessExists(pid_t pid) { - return base::DirectoryExists( - base::FilePath(base::StringPrintf("/proc/%d", pid))); -} - -ProcessImpl::ProcessImpl() - : pid_(0), - uid_(-1), - gid_(-1), - pre_exec_(base::Bind(&ReturnTrue)), - search_path_(false), - inherit_parent_signal_mask_(false) { -} - -ProcessImpl::~ProcessImpl() { - Reset(0); -} - -void ProcessImpl::AddArg(const std::string& arg) { - arguments_.push_back(arg); -} - -void ProcessImpl::RedirectOutput(const std::string& output_file) { - output_file_ = output_file; -} - -void ProcessImpl::RedirectUsingPipe(int child_fd, bool is_input) { - PipeInfo info; - info.is_input_ = is_input; - info.is_bound_ = false; - pipe_map_[child_fd] = info; -} - -void ProcessImpl::BindFd(int parent_fd, int child_fd) { - PipeInfo info; - info.is_bound_ = true; - - // info.child_fd_ is the 'child half' of the pipe, which gets dup2()ed into - // place over child_fd. Since we already have the child we want to dup2() into - // place, we can set info.child_fd_ to parent_fd and leave info.parent_fd_ - // invalid. - info.child_fd_ = parent_fd; - info.parent_fd_ = -1; - pipe_map_[child_fd] = info; -} - -void ProcessImpl::SetUid(uid_t uid) { - uid_ = uid; -} - -void ProcessImpl::SetGid(gid_t gid) { - gid_ = gid; -} - -void ProcessImpl::SetInheritParentSignalMask(bool inherit) { - inherit_parent_signal_mask_ = inherit; -} - -void ProcessImpl::SetPreExecCallback(const PreExecCallback& cb) { - pre_exec_ = cb; -} - -void ProcessImpl::SetSearchPath(bool search_path) { - search_path_ = search_path; -} - -int ProcessImpl::GetPipe(int child_fd) { - PipeMap::iterator i = pipe_map_.find(child_fd); - if (i == pipe_map_.end()) - return -1; - else - return i->second.parent_fd_; -} - -bool ProcessImpl::PopulatePipeMap() { - // Verify all target fds are already open. With this assumption we - // can be sure that the pipe fds created below do not overlap with - // any of the target fds which simplifies how we dup2 to them. Note - // that multi-threaded code could close i->first between this loop - // and the next. - for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { - struct stat stat_buffer; - if (fstat(i->first, &stat_buffer) < 0) { - int saved_errno = errno; - LOG(ERROR) << "Unable to fstat fd " << i->first << ": " << saved_errno; - return false; - } - } - - for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { - if (i->second.is_bound_) { - // already have a parent fd, and the child fd gets dup()ed later. - continue; - } - int pipefds[2]; - if (pipe(pipefds) < 0) { - int saved_errno = errno; - LOG(ERROR) << "pipe call failed with: " << saved_errno; - return false; - } - if (i->second.is_input_) { - // pipe is an input from the prospective of the child. - i->second.parent_fd_ = pipefds[1]; - i->second.child_fd_ = pipefds[0]; - } else { - i->second.parent_fd_ = pipefds[0]; - i->second.child_fd_ = pipefds[1]; - } - } - return true; -} - -bool ProcessImpl::Start() { - // If no arguments are provided, fail. - if (arguments_.empty()) { - return false; - } - scoped_ptr argv(new char*[arguments_.size() + 1]); - - for (size_t i = 0; i < arguments_.size(); ++i) - argv[i] = const_cast(arguments_[i].c_str()); - - argv[arguments_.size()] = nullptr; - - if (!PopulatePipeMap()) { - LOG(ERROR) << "Failing to start because pipe creation failed"; - return false; - } - - pid_t pid = fork(); - int saved_errno = errno; - if (pid < 0) { - LOG(ERROR) << "Fork failed: " << saved_errno; - Reset(0); - return false; - } - - if (pid == 0) { - // Executing inside the child process. - // Close parent's side of the child pipes. dup2 ours into place and - // then close our ends. - for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { - if (i->second.parent_fd_ != -1) - IGNORE_EINTR(close(i->second.parent_fd_)); - HANDLE_EINTR(dup2(i->second.child_fd_, i->first)); - } - // Defer the actual close() of the child fd until afterward; this lets the - // same child fd be bound to multiple fds using BindFd - for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { - IGNORE_EINTR(close(i->second.child_fd_)); - } - if (!output_file_.empty()) { - int output_handle = HANDLE_EINTR(open( - output_file_.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, - 0666)); - if (output_handle < 0) { - PLOG(ERROR) << "Could not create " << output_file_; - // Avoid exit() to avoid atexit handlers from parent. - _exit(kErrorExitStatus); - } - HANDLE_EINTR(dup2(output_handle, STDOUT_FILENO)); - HANDLE_EINTR(dup2(output_handle, STDERR_FILENO)); - // Only close output_handle if it does not happen to be one of - // the two standard file descriptors we are trying to redirect. - if (output_handle != STDOUT_FILENO && output_handle != STDERR_FILENO) { - IGNORE_EINTR(close(output_handle)); - } - } - if (gid_ != static_cast(-1) && setresgid(gid_, gid_, gid_) < 0) { - int saved_errno = errno; - LOG(ERROR) << "Unable to set GID to " << gid_ << ": " << saved_errno; - _exit(kErrorExitStatus); - } - if (uid_ != static_cast(-1) && setresuid(uid_, uid_, uid_) < 0) { - int saved_errno = errno; - LOG(ERROR) << "Unable to set UID to " << uid_ << ": " << saved_errno; - _exit(kErrorExitStatus); - } - if (!pre_exec_.Run()) { - LOG(ERROR) << "Pre-exec callback failed"; - _exit(kErrorExitStatus); - } - // Reset signal mask for the child process if not inheriting signal mask - // from the parent process. - if (!inherit_parent_signal_mask_) { - sigset_t signal_mask; - CHECK_EQ(0, sigemptyset(&signal_mask)); - CHECK_EQ(0, sigprocmask(SIG_SETMASK, &signal_mask, nullptr)); - } - if (search_path_) { - execvp(argv[0], &argv[0]); - } else { - execv(argv[0], &argv[0]); - } - PLOG(ERROR) << "Exec of " << argv[0] << " failed:"; - _exit(kErrorExitStatus); - } else { - // Still executing inside the parent process with known child pid. - arguments_.clear(); - UpdatePid(pid); - // Close our copy of child side pipes. - for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) { - IGNORE_EINTR(close(i->second.child_fd_)); - } - } - return true; -} - -int ProcessImpl::Wait() { - int status = 0; - if (pid_ == 0) { - LOG(ERROR) << "Process not running"; - return -1; - } - if (HANDLE_EINTR(waitpid(pid_, &status, 0)) < 0) { - int saved_errno = errno; - LOG(ERROR) << "Problem waiting for pid " << pid_ << ": " << saved_errno; - return -1; - } - pid_t old_pid = pid_; - // Update the pid to 0 - do not Reset as we do not want to try to - // kill the process that has just exited. - UpdatePid(0); - if (!WIFEXITED(status)) { - DCHECK(WIFSIGNALED(status)) << old_pid - << " neither exited, nor died on a signal?"; - LOG(ERROR) << "Process " << old_pid - << " did not exit normally: " << WTERMSIG(status); - return -1; - } - return WEXITSTATUS(status); -} - -int ProcessImpl::Run() { - if (!Start()) { - return -1; - } - return Wait(); -} - -pid_t ProcessImpl::pid() { - return pid_; -} - -bool ProcessImpl::Kill(int signal, int timeout) { - if (pid_ == 0) { - // Passing pid == 0 to kill is committing suicide. Check specifically. - LOG(ERROR) << "Process not running"; - return false; - } - if (kill(pid_, signal) < 0) { - int saved_errno = errno; - LOG(ERROR) << "Unable to send signal to " << pid_ << " error " - << saved_errno; - return false; - } - base::TimeTicks start_signal = base::TimeTicks::Now(); - do { - int status = 0; - pid_t w = waitpid(pid_, &status, WNOHANG); - int saved_errno = errno; - if (w < 0) { - if (saved_errno == ECHILD) - return true; - LOG(ERROR) << "Waitpid returned " << w << ", errno " << saved_errno; - return false; - } - if (w > 0) { - Reset(0); - return true; - } - usleep(100); - } while ((base::TimeTicks::Now() - start_signal).InSecondsF() <= timeout); - LOG(INFO) << "process " << pid_ << " did not exit from signal " << signal - << " in " << timeout << " seconds"; - return false; -} - -void ProcessImpl::UpdatePid(pid_t new_pid) { - pid_ = new_pid; -} - -void ProcessImpl::Reset(pid_t new_pid) { - arguments_.clear(); - // Close our side of all pipes to this child giving the child to - // handle sigpipes and shutdown nicely, though likely it won't - // have time. - for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) - IGNORE_EINTR(close(i->second.parent_fd_)); - pipe_map_.clear(); - if (pid_) - Kill(SIGKILL, 0); - UpdatePid(new_pid); -} - -bool ProcessImpl::ResetPidByFile(const std::string& pid_file) { - std::string contents; - if (!base::ReadFileToString(base::FilePath(pid_file), &contents)) { - LOG(ERROR) << "Could not read pid file" << pid_file; - return false; - } - base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents); - int64_t pid_int64 = 0; - if (!base::StringToInt64(contents, &pid_int64)) { - LOG(ERROR) << "Unexpected pid file contents"; - return false; - } - Reset(pid_int64); - return true; -} - -pid_t ProcessImpl::Release() { - pid_t old_pid = pid_; - pid_ = 0; - return old_pid; -} - -} // namespace chromeos diff --git a/chromeos/process.h b/chromeos/process.h deleted file mode 100644 index b216941..0000000 --- a/chromeos/process.h +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_PROCESS_H_ -#define LIBCHROMEOS_CHROMEOS_PROCESS_H_ - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { -// Manages a process. Can create the process, attach to an existing -// process by pid or pid file, and kill the process. Upon destruction -// any managed process is killed with SIGKILL. Use Release() to -// release the process from management. A given system process may -// only be managed by one Process at a time. -class CHROMEOS_EXPORT Process { - public: - Process(); - virtual ~Process(); - - // Adds |arg| to the executable command-line to be run. The - // executable name itself is the first argument. - virtual void AddArg(const std::string& arg) = 0; - - // Adds |option| and |value| as an option with a string value to the - // command line to be run. - inline void AddStringOption(const std::string& option, - const std::string& value) { - AddArg(option); - AddArg(value); - } - - // Adds |option| and |value| as an option which takes an integer - // value to the command line to be run. - inline void AddIntOption(const std::string& option, int value) { - AddArg(option); - AddArg(base::StringPrintf("%d", value)); - } - - // Redirects stderr and stdout to |output_file|. - virtual void RedirectOutput(const std::string& output_file) = 0; - - // Indicates we want to redirect |child_fd| in the child process's - // file table to a pipe. |child_fd| will be available for reading - // from child process's perspective iff |is_input|. - virtual void RedirectUsingPipe(int child_fd, bool is_input) = 0; - - // Binds the given file descriptor in the parent to the given file - // descriptor in the child. - virtual void BindFd(int parent_fd, int child_fd) = 0; - - // Set the real/effective/saved user ID of the child process. - virtual void SetUid(uid_t uid) = 0; - - // Set the real/effective/saved group ID of the child process. - virtual void SetGid(gid_t gid) = 0; - - // Set a flag |inherit| to indicate if the child process intend to - // inherit signal mask from the parent process. When |inherit| is - // set to true, the child process will inherit signal mask from the - // parent process. This could cause unintended side effect, where all - // the signals to the child process might be blocked if they are set - // in the parent's signal mask. - virtual void SetInheritParentSignalMask(bool inherit) = 0; - - typedef base::Callback PreExecCallback; - - // Set the pre-exec callback. This is called after all setup is complete but - // before we exec() the process. The callback may return false to cause Start - // to return false without starting the process. - virtual void SetPreExecCallback(const PreExecCallback& cb) = 0; - - // Sets whether starting the process should search the system path or not. - // By default the system path will not be searched. - virtual void SetSearchPath(bool search_path) = 0; - - // Gets the pipe file descriptor mapped to the process's |child_fd|. - virtual int GetPipe(int child_fd) = 0; - - // Starts this process, returning true if successful. - virtual bool Start() = 0; - - // Waits for this process to finish. Returns the process's exit - // status if it exited normally, or otherwise returns -1. Note - // that kErrorExitStatus may be returned if an error occurred - // after forking and before execing the child process. - virtual int Wait() = 0; - - // Start and wait for this process to finish. Returns same value as - // Wait(). - virtual int Run() = 0; - - // Returns the pid of this process or else returns 0 if there is no - // corresponding process (either because it has not yet been started - // or has since exited). - virtual pid_t pid() = 0; - - // Sends |signal| to process and wait |timeout| seconds until it - // dies. If process is not a child, returns immediately with a - // value based on whether kill was successful. If the process is a - // child and |timeout| is non-zero, returns true if the process is - // able to be reaped within the given |timeout| in seconds. - virtual bool Kill(int signal, int timeout) = 0; - - // Resets this Process object to refer to the process with |pid|. - // If |pid| is zero, this object no longer refers to a process. - virtual void Reset(pid_t new_pid) = 0; - - // Same as Reset but reads the pid from |pid_file|. Returns false - // only when the file cannot be read/parsed. - virtual bool ResetPidByFile(const std::string& pid_file) = 0; - - // Releases the process so that on destruction, the process is not killed. - virtual pid_t Release() = 0; - - // Returns if |pid| is a currently running process. - static bool ProcessExists(pid_t pid); - - // When returned from Wait or Run, indicates an error may have occurred - // creating the process. - enum { kErrorExitStatus = 127 }; -}; - -class CHROMEOS_EXPORT ProcessImpl : public Process { - public: - ProcessImpl(); - virtual ~ProcessImpl(); - - virtual void AddArg(const std::string& arg); - virtual void RedirectOutput(const std::string& output_file); - virtual void RedirectUsingPipe(int child_fd, bool is_input); - virtual void BindFd(int parent_fd, int child_fd); - virtual void SetUid(uid_t uid); - virtual void SetGid(gid_t gid); - virtual void SetInheritParentSignalMask(bool inherit); - virtual void SetPreExecCallback(const PreExecCallback& cb); - virtual void SetSearchPath(bool search_path); - virtual int GetPipe(int child_fd); - virtual bool Start(); - virtual int Wait(); - virtual int Run(); - virtual pid_t pid(); - virtual bool Kill(int signal, int timeout); - virtual void Reset(pid_t pid); - virtual bool ResetPidByFile(const std::string& pid_file); - virtual pid_t Release(); - - protected: - struct PipeInfo { - PipeInfo() : parent_fd_(-1), child_fd_(-1), is_input_(false) {} - // Parent (our) side of the pipe to the child process. - int parent_fd_; - // Child's side of the pipe to the parent. - int child_fd_; - // Is this an input or output pipe from child's perspective. - bool is_input_; - // Is this a bound (pre-existing) file descriptor? - bool is_bound_; - }; - typedef std::map PipeMap; - - void UpdatePid(pid_t new_pid); - bool PopulatePipeMap(); - - private: - FRIEND_TEST(ProcessTest, ResetPidByFile); - - // Pid of currently managed process or 0 if no currently managed - // process. pid must not be modified except by calling - // UpdatePid(new_pid). - pid_t pid_; - std::string output_file_; - std::vector arguments_; - // Map of child target file descriptors (first) to information about - // pipes created (second). - PipeMap pipe_map_; - uid_t uid_; - gid_t gid_; - PreExecCallback pre_exec_; - bool search_path_; - // Flag indicating to inherit signal mask from the parent process. It - // is set to false by default, which means by default the child process - // will not inherit signal mask from the parent process. - bool inherit_parent_signal_mask_; -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_PROCESS_H_ diff --git a/chromeos/process_information.cc b/chromeos/process_information.cc deleted file mode 100644 index 9f1d773..0000000 --- a/chromeos/process_information.cc +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/process_information.h" - -namespace chromeos { - -ProcessInformation::ProcessInformation() : cmd_line_(), process_id_(-1) { -} -ProcessInformation::~ProcessInformation() { -} - -std::string ProcessInformation::GetCommandLine() { - std::string result; - for (std::vector::iterator cmd_itr = cmd_line_.begin(); - cmd_itr != cmd_line_.end(); - cmd_itr++) { - if (result.length()) { - result.append(" "); - } - result.append((*cmd_itr)); - } - return result; -} - -} // namespace chromeos diff --git a/chromeos/process_information.h b/chromeos/process_information.h deleted file mode 100644 index bc2818e..0000000 --- a/chromeos/process_information.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_PROCESS_INFORMATION_H_ -#define LIBCHROMEOS_CHROMEOS_PROCESS_INFORMATION_H_ - -#include -#include -#include - -#include - -namespace chromeos { - -// Information for a single running process. Stores its command line, set of -// open files, process id and working directory. -class CHROMEOS_EXPORT ProcessInformation { - public: - ProcessInformation(); - virtual ~ProcessInformation(); - - std::string GetCommandLine(); - - // Set the command line array. This method DOES swap out the contents of - // |value|. The caller should expect an empty vector on return. - void set_cmd_line(std::vector* value) { - cmd_line_.clear(); - cmd_line_.swap(*value); - } - - const std::vector& get_cmd_line() { return cmd_line_; } - - // Set the command line array. This method DOES swap out the contents of - // |value|. The caller should expect an empty set on return. - void set_open_files(std::set* value) { - open_files_.clear(); - open_files_.swap(*value); - } - - const std::set& get_open_files() { return open_files_; } - - // Set the command line array. This method DOES swap out the contents of - // |value|. The caller should expect an empty string on return. - void set_cwd(std::string* value) { - cwd_.clear(); - cwd_.swap(*value); - } - - const std::string& get_cwd() { return cwd_; } - - void set_process_id(int value) { process_id_ = value; } - - int get_process_id() { return process_id_; } - - private: - std::vector cmd_line_; - std::set open_files_; - std::string cwd_; - int process_id_; -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_PROCESS_INFORMATION_H_ diff --git a/chromeos/process_mock.h b/chromeos/process_mock.h deleted file mode 100644 index c494c2d..0000000 --- a/chromeos/process_mock.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_PROCESS_MOCK_H_ -#define LIBCHROMEOS_CHROMEOS_PROCESS_MOCK_H_ - -#include - -#include - -#include "chromeos/process.h" - -namespace chromeos { - -class ProcessMock : public Process { - public: - ProcessMock() {} - virtual ~ProcessMock() {} - - MOCK_METHOD1(AddArg, void(const std::string& arg)); - MOCK_METHOD1(RedirectOutput, void(const std::string& output_file)); - MOCK_METHOD2(RedirectUsingPipe, void(int child_fd, bool is_input)); - MOCK_METHOD2(BindFd, void(int parent_fd, int child_fd)); - MOCK_METHOD1(SetUid, void(uid_t)); - MOCK_METHOD1(SetGid, void(gid_t)); - MOCK_METHOD1(SetInheritParentSignalMask, void(bool)); - MOCK_METHOD1(SetPreExecCallback, void(const PreExecCallback&)); - MOCK_METHOD1(SetSearchPath, void(bool)); - MOCK_METHOD1(GetPipe, int(int child_fd)); - MOCK_METHOD0(Start, bool()); - MOCK_METHOD0(Wait, int()); - MOCK_METHOD0(Run, int()); - MOCK_METHOD0(pid, pid_t()); - MOCK_METHOD2(Kill, bool(int signal, int timeout)); - MOCK_METHOD1(Reset, void(pid_t)); - MOCK_METHOD1(ResetPidByFile, bool(const std::string& pid_file)); - MOCK_METHOD0(Release, pid_t()); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_PROCESS_MOCK_H_ diff --git a/chromeos/process_reaper.cc b/chromeos/process_reaper.cc deleted file mode 100644 index 858e588..0000000 --- a/chromeos/process_reaper.cc +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/process_reaper.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { - -ProcessReaper::~ProcessReaper() { - Unregister(); -} - -void ProcessReaper::Register( - AsynchronousSignalHandlerInterface* async_signal_handler) { - CHECK(!async_signal_handler_); - async_signal_handler_ = async_signal_handler; - async_signal_handler->RegisterHandler( - SIGCHLD, - base::Bind(&ProcessReaper::HandleSIGCHLD, base::Unretained(this))); -} - -void ProcessReaper::Unregister() { - if (!async_signal_handler_) - return; - async_signal_handler_->UnregisterHandler(SIGCHLD); - async_signal_handler_ = nullptr; -} - -bool ProcessReaper::WatchForChild(const tracked_objects::Location& from_here, - pid_t pid, - const ChildCallback& callback) { - if (watched_processes_.find(pid) != watched_processes_.end()) - return false; - watched_processes_.emplace(pid, WatchedProcess{from_here, callback}); - return true; -} - -bool ProcessReaper::HandleSIGCHLD(const struct signalfd_siginfo& sigfd_info) { - // One SIGCHLD may correspond to multiple terminated children, so ignore - // sigfd_info and reap any available children. - while (true) { - siginfo_t info; - info.si_pid = 0; - int rc = HANDLE_EINTR(waitid(P_ALL, 0, &info, WNOHANG | WEXITED)); - - if (rc == -1) { - if (errno != ECHILD) { - PLOG(ERROR) << "waitid failed"; - } - break; - } - - if (info.si_pid == 0) { - break; - } - - auto proc = watched_processes_.find(info.si_pid); - if (proc == watched_processes_.end()) { - LOG(INFO) << "Untracked process " << info.si_pid - << " terminated with status " << info.si_status - << " (code = " << info.si_code << ")"; - } else { - DVLOG_LOC(proc->second.location, 1) - << "Process " << info.si_pid << " terminated with status " - << info.si_status << " (code = " << info.si_code << ")"; - ChildCallback callback = std::move(proc->second.callback); - watched_processes_.erase(proc); - callback.Run(info); - } - } - - // Return false to indicate that our handler should not be uninstalled. - return false; -} - -} // namespace chromeos diff --git a/chromeos/process_reaper.h b/chromeos/process_reaper.h deleted file mode 100644 index 8a8189d..0000000 --- a/chromeos/process_reaper.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_PROCESS_REAPER_H_ -#define LIBCHROMEOS_CHROMEOS_PROCESS_REAPER_H_ - -#include - -#include - -#include -#include -#include -#include -#include - -namespace chromeos { - -class CHROMEOS_EXPORT ProcessReaper final { - public: - // The callback called when a child exits. - using ChildCallback = base::Callback; - - ProcessReaper() = default; - ~ProcessReaper(); - - // Register the ProcessReaper using either the provided - // chromeos::AsynchronousSignalHandlerInterface. You can call Unregister() to - // remove this ProcessReapper or it will be called during shutdown. - // You can only register this ProcessReaper with one signal handler at a time. - void Register(AsynchronousSignalHandlerInterface* async_signal_handler); - - // Unregisters the ProcessReaper from the - // chromeos::AsynchronousSignalHandlerInterface passed in Register(). It - // doesn't do anything if not registered. - void Unregister(); - - // Watch for the child process |pid| to finish and call |callback| when the - // selected process exits or the process terminates for other reason. The - // |callback| receives the exit status and exit code of the terminated process - // as a siginfo_t. See wait(2) for details about siginfo_t. - bool WatchForChild(const tracked_objects::Location& from_here, - pid_t pid, - const ChildCallback& callback); - - private: - // SIGCHLD handler for the AsynchronousSignalHandler. Always returns false - // (meaning that the signal handler should not be unregistered). - bool HandleSIGCHLD(const signalfd_siginfo& sigfd_info); - - struct WatchedProcess { - tracked_objects::Location location; - ChildCallback callback; - }; - std::map watched_processes_; - - // The |async_signal_handler_| is owned by the caller and is |nullptr| when - // not registered. - AsynchronousSignalHandlerInterface* async_signal_handler_{nullptr}; - - DISALLOW_COPY_AND_ASSIGN(ProcessReaper); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_PROCESS_REAPER_H_ diff --git a/chromeos/process_reaper_unittest.cc b/chromeos/process_reaper_unittest.cc deleted file mode 100644 index d49ce37..0000000 --- a/chromeos/process_reaper_unittest.cc +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace { - -pid_t ForkChildAndExit(int exit_code) { - pid_t pid = fork(); - PCHECK(pid != -1); - if (pid == 0) { - _exit(exit_code); - } - return pid; -} - -pid_t ForkChildAndKill(int sig) { - pid_t pid = fork(); - PCHECK(pid != -1); - if (pid == 0) { - if (raise(sig) != 0) { - PLOG(ERROR) << "raise(" << sig << ")"; - } - _exit(0); // Not reached. This value will cause the test to fail. - } - return pid; -} - -} // namespace - -namespace chromeos { - -class ProcessReaperTest : public ::testing::Test { - public: - void SetUp() override { - chromeos_loop_.SetAsCurrent(); - async_signal_handler_.Init(); - process_reaper_.Register(&async_signal_handler_); - } - - protected: - base::MessageLoopForIO base_loop_; - chromeos::BaseMessageLoop chromeos_loop_{&base_loop_}; - chromeos::AsynchronousSignalHandler async_signal_handler_; - - // ProcessReaper under test. - ProcessReaper process_reaper_; -}; - -TEST_F(ProcessReaperTest, UnregisterWhenNotRegistered) { - ProcessReaper another_process_reaper_; - another_process_reaper_.Unregister(); -} - -TEST_F(ProcessReaperTest, UnregisterAndReregister) { - process_reaper_.Unregister(); - process_reaper_.Register(&async_signal_handler_); - // This checks that we can unregister the ProcessReaper and then destroy it. - process_reaper_.Unregister(); -} - -TEST_F(ProcessReaperTest, ReapExitedChild) { - pid_t pid = ForkChildAndExit(123); - EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind( - [this](const siginfo_t& info) { - EXPECT_EQ(CLD_EXITED, info.si_code); - EXPECT_EQ(123, info.si_status); - this->chromeos_loop_.BreakLoop(); - }))); - chromeos_loop_.Run(); -} - -// Test that simultaneous child processes fire their respective callbacks when -// exiting. -TEST_F(ProcessReaperTest, ReapedChildsMatchCallbacks) { - int running_childs = 10; - for (int i = 0; i < running_childs; ++i) { - // Different processes will have different exit values. - int exit_value = 1 + i; - pid_t pid = ForkChildAndExit(exit_value); - EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind( - [this, exit_value, &running_childs](const siginfo_t& info) { - EXPECT_EQ(CLD_EXITED, info.si_code); - EXPECT_EQ(exit_value, info.si_status); - running_childs--; - if (running_childs == 0) - this->chromeos_loop_.BreakLoop(); - }))); - } - // This sleep is optional. It helps to have more processes exit before we - // start watching for them in the message loop. - usleep(10 * 1000); - chromeos_loop_.Run(); - EXPECT_EQ(0, running_childs); -} - -TEST_F(ProcessReaperTest, ReapKilledChild) { - pid_t pid = ForkChildAndKill(SIGKILL); - EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind( - [this](const siginfo_t& info) { - EXPECT_EQ(CLD_KILLED, info.si_code); - EXPECT_EQ(SIGKILL, info.si_status); - this->chromeos_loop_.BreakLoop(); - }))); - chromeos_loop_.Run(); -} - -} // namespace chromeos diff --git a/chromeos/process_unittest.cc b/chromeos/process_unittest.cc deleted file mode 100644 index 1d83cb7..0000000 --- a/chromeos/process_unittest.cc +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/process.h" - -#include - -#include -#include -#include -#include - -#include "chromeos/process_mock.h" -#include "chromeos/test_helpers.h" - -using base::FilePath; - -// This test assumes the following standard binaries are installed. -#if defined(__ANDROID__) -# define SYSTEM_PREFIX "/system" -#else -# define SYSTEM_PREFIX "" -#endif - -static const char kBinSh[] = SYSTEM_PREFIX "/bin/sh"; -static const char kBinCat[] = SYSTEM_PREFIX "/bin/cat"; -static const char kBinCp[] = SYSTEM_PREFIX "/bin/cp"; -static const char kBinEcho[] = SYSTEM_PREFIX "/bin/echo"; -static const char kBinFalse[] = SYSTEM_PREFIX "/bin/false"; -static const char kBinSleep[] = SYSTEM_PREFIX "/bin/sleep"; -static const char kBinTrue[] = SYSTEM_PREFIX "/bin/true"; - -namespace chromeos { - -// Test that the mock has all the functions of the interface by -// instantiating it. This variable is not used elsewhere. -struct CompileMocks { - ProcessMock process_mock; -}; - -TEST(SimpleProcess, Basic) { - // Log must be cleared before running this test, just as ProcessTest::SetUp. - ClearLog(); - ProcessImpl process; - process.AddArg(kBinEcho); - EXPECT_EQ(0, process.Run()); - EXPECT_EQ("", GetLog()); -} - -TEST(SimpleProcess, NoSearchPath) { - ProcessImpl process; - process.AddArg("echo"); - EXPECT_EQ(127, process.Run()); -} - -TEST(SimpleProcess, SearchPath) { - ProcessImpl process; - process.AddArg("echo"); - process.SetSearchPath(true); - EXPECT_EQ(EXIT_SUCCESS, process.Run()); -} - -TEST(SimpleProcess, BindFd) { - int fds[2]; - char buf[16]; - static const char* kMsg = "hello, world!"; - ProcessImpl process; - EXPECT_EQ(0, pipe(fds)); - process.AddArg(kBinEcho); - process.AddArg(kMsg); - process.BindFd(fds[1], 1); - process.Run(); - memset(buf, 0, sizeof(buf)); - EXPECT_EQ(read(fds[0], buf, sizeof(buf) - 1), strlen(kMsg) + 1); - EXPECT_EQ(std::string(kMsg) + "\n", std::string(buf)); -} - -class ProcessTest : public ::testing::Test { - public: - void SetUp() { - CHECK(temp_dir_.CreateUniqueTempDir()); - output_file_ = temp_dir_.path().Append("fork_out").value(); - process_.RedirectOutput(output_file_); - ClearLog(); - } - - static void SetUpTestCase() { - base::CommandLine::Init(0, nullptr); - ::chromeos::InitLog(chromeos::kLogToStderr); - ::chromeos::LogToString(true); - } - - protected: - void CheckStderrCaptured(); - FilePath GetFdPath(int fd); - - ProcessImpl process_; - std::vector args_; - std::string output_file_; - base::ScopedTempDir temp_dir_; -}; - -TEST_F(ProcessTest, Basic) { - process_.AddArg(kBinEcho); - process_.AddArg("hello world"); - EXPECT_EQ(0, process_.Run()); - ExpectFileEquals("hello world\n", output_file_.c_str()); - EXPECT_EQ("", GetLog()); -} - -TEST_F(ProcessTest, AddStringOption) { - process_.AddArg(kBinEcho); - process_.AddStringOption("--hello", "world"); - EXPECT_EQ(0, process_.Run()); - ExpectFileEquals("--hello world\n", output_file_.c_str()); -} - -TEST_F(ProcessTest, AddIntValue) { - process_.AddArg(kBinEcho); - process_.AddIntOption("--answer", 42); - EXPECT_EQ(0, process_.Run()); - ExpectFileEquals("--answer 42\n", output_file_.c_str()); -} - -TEST_F(ProcessTest, NonZeroReturnValue) { - process_.AddArg(kBinFalse); - EXPECT_EQ(1, process_.Run()); - ExpectFileEquals("", output_file_.c_str()); - EXPECT_EQ("", GetLog()); -} - -TEST_F(ProcessTest, BadOutputFile) { - process_.AddArg(kBinEcho); - process_.RedirectOutput("/bad/path"); - EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); -} - -TEST_F(ProcessTest, BadExecutable) { - process_.AddArg("false"); - EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); -} - -void ProcessTest::CheckStderrCaptured() { - std::string contents; - process_.AddArg(kBinSh); - process_.AddArg("-c"); - process_.AddArg("echo errormessage 1>&2 && exit 1"); - EXPECT_EQ(1, process_.Run()); - EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); - EXPECT_NE(std::string::npos, contents.find("errormessage")); - EXPECT_EQ("", GetLog()); -} - -TEST_F(ProcessTest, StderrCaptured) { - CheckStderrCaptured(); -} - -TEST_F(ProcessTest, StderrCapturedWhenPreviouslyClosed) { - int saved_stderr = dup(STDERR_FILENO); - close(STDERR_FILENO); - CheckStderrCaptured(); - dup2(saved_stderr, STDERR_FILENO); -} - -FilePath ProcessTest::GetFdPath(int fd) { - return FilePath(base::StringPrintf("/proc/self/fd/%d", fd)); -} - -TEST_F(ProcessTest, RedirectStderrUsingPipe) { - std::string contents; - process_.RedirectOutput(""); - process_.AddArg(kBinSh); - process_.AddArg("-c"); - process_.AddArg("echo errormessage >&2 && exit 1"); - process_.RedirectUsingPipe(STDERR_FILENO, false); - EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); - EXPECT_EQ(1, process_.Run()); - int pipe_fd = process_.GetPipe(STDERR_FILENO); - EXPECT_GE(pipe_fd, 0); - EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO)); - EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO)); - EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents)); - EXPECT_NE(std::string::npos, contents.find("errormessage")); - EXPECT_EQ("", GetLog()); -} - -TEST_F(ProcessTest, RedirectStderrUsingPipeWhenPreviouslyClosed) { - int saved_stderr = dup(STDERR_FILENO); - close(STDERR_FILENO); - process_.RedirectOutput(""); - process_.AddArg(kBinCp); - process_.RedirectUsingPipe(STDERR_FILENO, false); - EXPECT_FALSE(process_.Start()); - EXPECT_TRUE(FindLog("Unable to fstat fd 2:")); - dup2(saved_stderr, STDERR_FILENO); -} - -TEST_F(ProcessTest, RedirectStdoutUsingPipe) { - std::string contents; - process_.RedirectOutput(""); - process_.AddArg(kBinEcho); - process_.AddArg("hello world\n"); - process_.RedirectUsingPipe(STDOUT_FILENO, false); - EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO)); - EXPECT_EQ(0, process_.Run()); - int pipe_fd = process_.GetPipe(STDOUT_FILENO); - EXPECT_GE(pipe_fd, 0); - EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); - EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO)); - EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents)); - EXPECT_NE(std::string::npos, contents.find("hello world\n")); - EXPECT_EQ("", GetLog()); -} - -TEST_F(ProcessTest, RedirectStdinUsingPipe) { - std::string contents; - const char kMessage[] = "made it!\n"; - process_.AddArg(kBinCat); - process_.RedirectUsingPipe(STDIN_FILENO, true); - process_.RedirectOutput(output_file_); - EXPECT_TRUE(process_.Start()); - int write_fd = process_.GetPipe(STDIN_FILENO); - EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); - EXPECT_TRUE(base::WriteFile(GetFdPath(write_fd), kMessage, strlen(kMessage))); - close(write_fd); - EXPECT_EQ(0, process_.Wait()); - ExpectFileEquals(kMessage, output_file_.c_str()); -} - -TEST_F(ProcessTest, WithSameUid) { - gid_t uid = geteuid(); - process_.AddArg(kBinEcho); - process_.SetUid(uid); - EXPECT_EQ(0, process_.Run()); -} - -TEST_F(ProcessTest, WithSameGid) { - gid_t gid = getegid(); - process_.AddArg(kBinEcho); - process_.SetGid(gid); - EXPECT_EQ(0, process_.Run()); -} - -TEST_F(ProcessTest, WithIllegalUid) { - ASSERT_NE(0, geteuid()); - process_.AddArg(kBinEcho); - process_.SetUid(0); - EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); - std::string contents; - EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); - EXPECT_NE(std::string::npos, contents.find("Unable to set UID to 0: 1\n")); -} - -TEST_F(ProcessTest, WithIllegalGid) { - ASSERT_NE(0, getegid()); - process_.AddArg(kBinEcho); - process_.SetGid(0); - EXPECT_EQ(static_cast(Process::kErrorExitStatus), process_.Run()); - std::string contents; - EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); - EXPECT_NE(std::string::npos, contents.find("Unable to set GID to 0: 1\n")); -} - -TEST_F(ProcessTest, NoParams) { - EXPECT_EQ(-1, process_.Run()); -} - -#if !defined(__BIONIC__) // Bionic intercepts the segfault on Android. -TEST_F(ProcessTest, SegFaultHandling) { - process_.AddArg(kBinSh); - process_.AddArg("-c"); - process_.AddArg("kill -SEGV $$"); - EXPECT_EQ(-1, process_.Run()); - EXPECT_TRUE(FindLog("did not exit normally: 11")); -} -#endif - -TEST_F(ProcessTest, KillHandling) { - process_.AddArg(kBinSh); - process_.AddArg("-c"); - process_.AddArg("kill -KILL $$"); - EXPECT_EQ(-1, process_.Run()); - EXPECT_TRUE(FindLog("did not exit normally: 9")); -} - - -TEST_F(ProcessTest, KillNoPid) { - process_.Kill(SIGTERM, 0); - EXPECT_TRUE(FindLog("Process not running")); -} - -TEST_F(ProcessTest, ProcessExists) { - EXPECT_FALSE(Process::ProcessExists(0)); - EXPECT_TRUE(Process::ProcessExists(1)); - EXPECT_TRUE(Process::ProcessExists(getpid())); -} - -TEST_F(ProcessTest, ResetPidByFile) { - FilePath pid_path = temp_dir_.path().Append("pid"); - EXPECT_FALSE(process_.ResetPidByFile(pid_path.value())); - EXPECT_TRUE(base::WriteFile(pid_path, "456\n", 4)); - EXPECT_TRUE(process_.ResetPidByFile(pid_path.value())); - EXPECT_EQ(456, process_.pid()); - // The purpose of this unit test is to check if Process::ResetPidByFile() can - // properly read a pid from a file. We don't really want to kill the process - // with pid 456, so update the pid to 0 to prevent the Process destructor from - // killing any innocent process. - process_.UpdatePid(0); -} - -TEST_F(ProcessTest, KillSleeper) { - process_.AddArg(kBinSleep); - process_.AddArg("10000"); - ASSERT_TRUE(process_.Start()); - pid_t pid = process_.pid(); - ASSERT_GT(pid, 1); - EXPECT_TRUE(process_.Kill(SIGTERM, 1)); - EXPECT_EQ(0, process_.pid()); -} - -TEST_F(ProcessTest, Reset) { - process_.AddArg(kBinFalse); - process_.Reset(0); - process_.AddArg(kBinEcho); - EXPECT_EQ(0, process_.Run()); -} - -bool ReturnFalse() { return false; } - -TEST_F(ProcessTest, PreExecCallback) { - process_.AddArg(kBinTrue); - process_.SetPreExecCallback(base::Bind(&ReturnFalse)); - ASSERT_NE(0, process_.Run()); -} - -} // namespace chromeos diff --git a/chromeos/secure_blob.cc b/chromeos/secure_blob.cc deleted file mode 100644 index b7e215f..0000000 --- a/chromeos/secure_blob.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include // memcpy - -#include - -#include "chromeos/secure_blob.h" - -namespace chromeos { - -SecureBlob::SecureBlob(const std::string& data) - : SecureBlob(data.begin(), data.end()) {} - -SecureBlob::~SecureBlob() { - clear(); -} - -void SecureBlob::resize(size_type count) { - if (count < size()) { - SecureMemset(data() + count, 0, capacity() - count); - } - Blob::resize(count); -} - -void SecureBlob::resize(size_type count, const value_type& value) { - if (count < size()) { - SecureMemset(data() + count, 0, capacity() - count); - } - Blob::resize(count, value); -} - -void SecureBlob::clear() { - SecureMemset(data(), 0, capacity()); - Blob::clear(); -} - -std::string SecureBlob::to_string() const { - return std::string(data(), data() + size()); -} - -SecureBlob SecureBlob::Combine(const SecureBlob& blob1, - const SecureBlob& blob2) { - SecureBlob result; - result.reserve(blob1.size() + blob2.size()); - result.insert(result.end(), blob1.begin(), blob1.end()); - result.insert(result.end(), blob2.begin(), blob2.end()); - return result; -} - -void* SecureMemset(void* v, int c, size_t n) { - volatile uint8_t* p = reinterpret_cast(v); - while (n--) - *p++ = c; - return v; -} - -int SecureMemcmp(const void* s1, const void* s2, size_t n) { - const uint8_t* us1 = reinterpret_cast(s1); - const uint8_t* us2 = reinterpret_cast(s2); - int result = 0; - - if (0 == n) - return 1; - - /* Code snippet without data-dependent branch due to - * Nate Lawson (nate@root.org) of Root Labs. */ - while (n--) - result |= *us1++ ^ *us2++; - - return result != 0; -} - -} // namespace chromeos diff --git a/chromeos/secure_blob.h b/chromeos/secure_blob.h deleted file mode 100644 index e92658e..0000000 --- a/chromeos/secure_blob.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_SECURE_BLOB_H_ -#define LIBCHROMEOS_CHROMEOS_SECURE_BLOB_H_ - -#include -#include - -#include - -namespace chromeos { - -using Blob = std::vector; - -// SecureBlob erases the contents on destruction. It does not guarantee erasure -// on resize, assign, etc. -class CHROMEOS_EXPORT SecureBlob : public Blob { - public: - SecureBlob() = default; - using Blob::vector; // Inherit standard constructors from vector. - explicit SecureBlob(const std::string& data); - ~SecureBlob(); - - void resize(size_type count); - void resize(size_type count, const value_type& value); - void clear(); - - std::string to_string() const; - char* char_data() { return reinterpret_cast(data()); } - const char* char_data() const { - return reinterpret_cast(data()); - } - static SecureBlob Combine(const SecureBlob& blob1, const SecureBlob& blob2); -}; - -// Secure memset(). This function is guaranteed to fill in the whole buffer -// and is not subject to compiler optimization as allowed by Sub-clause 5.1.2.3 -// of C Standard [ISO/IEC 9899:2011] which states: -// In the abstract machine, all expressions are evaluated as specified by the -// semantics. An actual implementation need not evaluate part of an expression -// if it can deduce that its value is not used and that no needed side effects -// are produced (including any caused by calling a function or accessing -// a volatile object). -// While memset() can be optimized out in certain situations (since most -// compilers implement this function as intrinsic and know of its side effects), -// this function will not be optimized out. -CHROMEOS_EXPORT void* SecureMemset(void* v, int c, size_t n); - -// Compare [n] bytes starting at [s1] with [s2] and return 0 if they match, -// 1 if they don't. Time taken to perform the comparison is only dependent on -// [n] and not on the relationship of the match between [s1] and [s2]. -CHROMEOS_EXPORT int SecureMemcmp(const void* s1, const void* s2, size_t n); - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_SECURE_BLOB_H_ diff --git a/chromeos/secure_blob_unittest.cc b/chromeos/secure_blob_unittest.cc deleted file mode 100644 index e85e2af..0000000 --- a/chromeos/secure_blob_unittest.cc +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Unit tests for SecureBlob. - -#include "chromeos/secure_blob.h" - -#include -#include -#include - -#include -#include - -namespace chromeos { -using std::string; - -class SecureBlobTest : public ::testing::Test { - public: - SecureBlobTest() {} - virtual ~SecureBlobTest() {} - - static bool FindBlobInBlob(const chromeos::Blob& haystack, - const chromeos::Blob& needle) { - auto pos = std::search( - haystack.begin(), haystack.end(), needle.begin(), needle.end()); - return (pos != haystack.end()); - } - - static int FindBlobIndexInBlob(const chromeos::Blob& haystack, - const chromeos::Blob& needle) { - auto pos = std::search( - haystack.begin(), haystack.end(), needle.begin(), needle.end()); - if (pos == haystack.end()) { - return -1; - } - return std::distance(haystack.begin(), pos); - } - - private: - DISALLOW_COPY_AND_ASSIGN(SecureBlobTest); -}; - -TEST_F(SecureBlobTest, AllocationSizeTest) { - // Check that allocating a SecureBlob of a specified size works - SecureBlob blob(32); - - EXPECT_EQ(32, blob.size()); -} - -TEST_F(SecureBlobTest, AllocationCopyTest) { - // Check that allocating a SecureBlob with an iterator works - unsigned char from_data[32]; - std::iota(std::begin(from_data), std::end(from_data), 0); - - SecureBlob blob(std::begin(from_data), std::end(from_data)); - - EXPECT_EQ(sizeof(from_data), blob.size()); - - for (unsigned int i = 0; i < sizeof(from_data); i++) { - EXPECT_EQ(from_data[i], blob[i]); - } -} - -TEST_F(SecureBlobTest, IteratorConstructorTest) { - // Check that allocating a SecureBlob with an iterator works - chromeos::Blob from_blob(32); - for (unsigned int i = 0; i < from_blob.size(); i++) { - from_blob[i] = i; - } - - SecureBlob blob(from_blob.begin(), from_blob.end()); - - EXPECT_EQ(from_blob.size(), blob.size()); - EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(from_blob, blob)); -} - -TEST_F(SecureBlobTest, ResizeTest) { - // Check that resizing a SecureBlob wipes the excess memory. The test assumes - // that resize() down by one will not re-allocate the memory, so the last byte - // will still be part of the SecureBlob's allocation - size_t length = 1024; - SecureBlob blob(length); - void* original_data = blob.data(); - for (size_t i = 0; i < length; i++) { - blob[i] = i; - } - - blob.resize(length - 1); - - EXPECT_EQ(original_data, blob.data()); - EXPECT_EQ(length - 1, blob.size()); - EXPECT_EQ(0, blob.data()[length - 1]); -} - -TEST_F(SecureBlobTest, CombineTest) { - SecureBlob blob1(32); - SecureBlob blob2(32); - std::iota(blob1.begin(), blob1.end(), 0); - std::iota(blob2.begin(), blob2.end(), 32); - SecureBlob combined_blob = SecureBlob::Combine(blob1, blob2); - EXPECT_EQ(combined_blob.size(), (blob1.size() + blob2.size())); - EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob1)); - EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob2)); - int blob1_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob1); - int blob2_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob2); - EXPECT_EQ(blob1_index, 0); - EXPECT_EQ(blob2_index, 32); -} - -TEST_F(SecureBlobTest, BlobToStringTest) { - std::string test_string("Test String"); - SecureBlob blob = SecureBlob(test_string.begin(), test_string.end()); - EXPECT_EQ(blob.size(), test_string.length()); - std::string result_string = blob.to_string(); - EXPECT_EQ(test_string.compare(result_string), 0); -} - -} // namespace chromeos diff --git a/chromeos/streams/fake_stream.cc b/chromeos/streams/fake_stream.cc deleted file mode 100644 index c1ccacd..0000000 --- a/chromeos/streams/fake_stream.cc +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include - -namespace chromeos { - -namespace { - -// Gets a delta between the two times, makes sure that the delta is positive. -base::TimeDelta CalculateDelay(const base::Time& now, - const base::Time& delay_until) { - const base::TimeDelta zero_delay; - if (delay_until.is_null() || now >= delay_until) { - return zero_delay; - } - - base::TimeDelta delay = delay_until - now; - if (delay < zero_delay) - delay = zero_delay; - return delay; -} - -// Given the current clock time, and expected delays for read and write -// operations calculates the smaller wait delay of the two and sets the -// resulting operation to |*mode| and the delay to wait for into |*delay|. -void GetMinDelayAndMode(const base::Time& now, - bool read, const base::Time& delay_read_until, - bool write, const base::Time& delay_write_until, - Stream::AccessMode* mode, base::TimeDelta* delay) { - base::TimeDelta read_delay = base::TimeDelta::Max(); - base::TimeDelta write_delay = base::TimeDelta::Max(); - - if (read) - read_delay = CalculateDelay(now, delay_read_until); - if (write) - write_delay = CalculateDelay(now, delay_write_until); - - if (read_delay > write_delay) { - read = false; - } else if (read_delay < write_delay) { - write = false; - } - *mode = stream_utils::MakeAccessMode(read, write); - *delay = std::min(read_delay, write_delay); -} - -} // anonymous namespace - -FakeStream::FakeStream(Stream::AccessMode mode, - base::Clock* clock) - : mode_{mode}, clock_{clock} {} - -void FakeStream::AddReadPacketData(base::TimeDelta delay, - const void* data, - size_t size) { - auto* byte_ptr = static_cast(data); - AddReadPacketData(delay, chromeos::Blob{byte_ptr, byte_ptr + size}); -} - -void FakeStream::AddReadPacketData(base::TimeDelta delay, chromeos::Blob data) { - InputDataPacket packet; - packet.data = std::move(data); - packet.delay_before = delay; - incoming_queue_.push(std::move(packet)); -} - -void FakeStream::AddReadPacketString(base::TimeDelta delay, - const std::string& data) { - AddReadPacketData(delay, chromeos::Blob{data.begin(), data.end()}); -} - -void FakeStream::QueueReadError(base::TimeDelta delay) { - QueueReadErrorWithMessage(delay, std::string{}); -} - -void FakeStream::QueueReadErrorWithMessage(base::TimeDelta delay, - const std::string& message) { - InputDataPacket packet; - packet.data.assign(message.begin(), message.end()); - packet.delay_before = delay; - packet.read_error = true; - incoming_queue_.push(std::move(packet)); -} - -void FakeStream::ClearReadQueue() { - std::queue().swap(incoming_queue_); - delay_input_until_ = base::Time{}; - input_buffer_.clear(); - input_ptr_ = 0; - report_read_error_ = 0; -} - -void FakeStream::ExpectWritePacketSize(base::TimeDelta delay, - size_t data_size) { - OutputDataPacket packet; - packet.expected_size = data_size; - packet.delay_before = delay; - outgoing_queue_.push(std::move(packet)); -} - -void FakeStream::ExpectWritePacketData(base::TimeDelta delay, - const void* data, - size_t size) { - auto* byte_ptr = static_cast(data); - ExpectWritePacketData(delay, chromeos::Blob{byte_ptr, byte_ptr + size}); -} - -void FakeStream::ExpectWritePacketData(base::TimeDelta delay, - chromeos::Blob data) { - OutputDataPacket packet; - packet.expected_size = data.size(); - packet.data = std::move(data); - packet.delay_before = delay; - outgoing_queue_.push(std::move(packet)); -} - -void FakeStream::ExpectWritePacketString(base::TimeDelta delay, - const std::string& data) { - ExpectWritePacketData(delay, chromeos::Blob{data.begin(), data.end()}); -} - -void FakeStream::QueueWriteError(base::TimeDelta delay) { - QueueWriteErrorWithMessage(delay, std::string{}); -} - -void FakeStream::QueueWriteErrorWithMessage(base::TimeDelta delay, - const std::string& message) { - OutputDataPacket packet; - packet.expected_size = 0; - packet.data.assign(message.begin(), message.end()); - packet.delay_before = delay; - packet.write_error = true; - outgoing_queue_.push(std::move(packet)); -} - -void FakeStream::ClearWriteQueue() { - std::queue().swap(outgoing_queue_); - delay_output_until_ = base::Time{}; - output_buffer_.clear(); - expected_output_data_.clear(); - max_output_buffer_size_ = 0; - all_output_data_.clear(); - report_write_error_ = 0; -} - -const chromeos::Blob& FakeStream::GetFlushedOutputData() const { - return all_output_data_; -} - -std::string FakeStream::GetFlushedOutputDataAsString() const { - return std::string{all_output_data_.begin(), all_output_data_.end()}; -} - -bool FakeStream::CanRead() const { - return stream_utils::IsReadAccessMode(mode_); -} - -bool FakeStream::CanWrite() const { - return stream_utils::IsWriteAccessMode(mode_); -} - -bool FakeStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); -} - -bool FakeStream::Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) { - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); -} - -bool FakeStream::IsReadBufferEmpty() const { - return input_ptr_ >= input_buffer_.size(); -} - -bool FakeStream::PopReadPacket() { - if (incoming_queue_.empty()) - return false; - const InputDataPacket& packet = incoming_queue_.front(); - input_ptr_ = 0; - input_buffer_ = std::move(packet.data); - delay_input_until_ = clock_->Now() + packet.delay_before; - incoming_queue_.pop(); - report_read_error_ = packet.read_error; - return true; -} - -bool FakeStream::ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) { - if (!CanRead()) - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); - - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - for (;;) { - if (!delay_input_until_.is_null() && clock_->Now() < delay_input_until_) { - *size_read = 0; - if (end_of_stream) - *end_of_stream = false; - break; - } - - if (report_read_error_) { - report_read_error_ = false; - std::string message{input_buffer_.begin(), input_buffer_.end()}; - if (message.empty()) - message = "Simulating read error for tests"; - input_buffer_.clear(); - Error::AddTo(error, FROM_HERE, "fake_stream", "read_error", message); - return false; - } - - if (!IsReadBufferEmpty()) { - size_to_read = std::min(size_to_read, input_buffer_.size() - input_ptr_); - std::memcpy(buffer, input_buffer_.data() + input_ptr_, size_to_read); - input_ptr_ += size_to_read; - *size_read = size_to_read; - if (end_of_stream) - *end_of_stream = false; - break; - } - - if (!PopReadPacket()) { - *size_read = 0; - if (end_of_stream) - *end_of_stream = true; - break; - } - } - return true; -} - -bool FakeStream::IsWriteBufferFull() const { - return output_buffer_.size() >= max_output_buffer_size_; -} - -bool FakeStream::PopWritePacket() { - if (outgoing_queue_.empty()) - return false; - const OutputDataPacket& packet = outgoing_queue_.front(); - expected_output_data_ = std::move(packet.data); - delay_output_until_ = clock_->Now() + packet.delay_before; - max_output_buffer_size_ = packet.expected_size; - report_write_error_ = packet.write_error; - outgoing_queue_.pop(); - return true; -} - -bool FakeStream::WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) { - if (!CanWrite()) - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); - - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - for (;;) { - if (!delay_output_until_.is_null() && clock_->Now() < delay_output_until_) { - *size_written = 0; - return true; - } - - if (report_write_error_) { - report_write_error_ = false; - std::string message{expected_output_data_.begin(), - expected_output_data_.end()}; - if (message.empty()) - message = "Simulating write error for tests"; - output_buffer_.clear(); - max_output_buffer_size_ = 0; - expected_output_data_.clear(); - Error::AddTo(error, FROM_HERE, "fake_stream", "write_error", message); - return false; - } - - if (!IsWriteBufferFull()) { - bool success = true; - size_to_write = std::min(size_to_write, - max_output_buffer_size_ - output_buffer_.size()); - auto byte_ptr = static_cast(buffer); - output_buffer_.insert(output_buffer_.end(), - byte_ptr, byte_ptr + size_to_write); - if (output_buffer_.size() == max_output_buffer_size_) { - if (!expected_output_data_.empty() && - expected_output_data_ != output_buffer_) { - // We expected different data to be written, report an error. - Error::AddTo(error, FROM_HERE, "fake_stream", "data_mismatch", - "Unexpected data written"); - success = false; - } - - all_output_data_.insert(all_output_data_.end(), - output_buffer_.begin(), output_buffer_.end()); - - output_buffer_.clear(); - max_output_buffer_size_ = 0; - expected_output_data_.clear(); - } - *size_written = size_to_write; - return success; - } - - if (!PopWritePacket()) { - // No more data expected. - Error::AddTo(error, FROM_HERE, "fake_stream", "full", - "No more output data expected"); - return false; - } - } -} - -bool FakeStream::FlushBlocking(ErrorPtr* error) { - if (!CanWrite()) - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); - - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - bool success = true; - if (!output_buffer_.empty()) { - if (!expected_output_data_.empty() && - expected_output_data_ != output_buffer_) { - // We expected different data to be written, report an error. - Error::AddTo(error, FROM_HERE, "fake_stream", "data_mismatch", - "Unexpected data written"); - success = false; - } - all_output_data_.insert(all_output_data_.end(), - output_buffer_.begin(), output_buffer_.end()); - - output_buffer_.clear(); - max_output_buffer_size_ = 0; - expected_output_data_.clear(); - } - return success; -} - -bool FakeStream::CloseBlocking(ErrorPtr* error) { - is_open_ = false; - return true; -} - -bool FakeStream::WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) { - bool read_requested = stream_utils::IsReadAccessMode(mode); - bool write_requested = stream_utils::IsWriteAccessMode(mode); - - if ((read_requested && !CanRead()) || (write_requested && !CanWrite())) - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); - - if (read_requested && IsReadBufferEmpty()) - PopReadPacket(); - if (write_requested && IsWriteBufferFull()) - PopWritePacket(); - - base::TimeDelta delay; - GetMinDelayAndMode(clock_->Now(), read_requested, delay_input_until_, - write_requested, delay_output_until_, &mode, &delay); - MessageLoop::current()->PostDelayedTask( - FROM_HERE, base::Bind(callback, mode), delay); - return true; -} - -bool FakeStream::WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) { - const base::TimeDelta zero_delay; - bool read_requested = stream_utils::IsReadAccessMode(in_mode); - bool write_requested = stream_utils::IsWriteAccessMode(in_mode); - - if ((read_requested && !CanRead()) || (write_requested && !CanWrite())) - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); - - base::TimeDelta delay; - GetMinDelayAndMode(clock_->Now(), read_requested, delay_input_until_, - write_requested, delay_output_until_, out_mode, &delay); - - if (timeout < delay) - return stream_utils::ErrorOperationTimeout(FROM_HERE, error); - - LOG(INFO) << "TEST: Would have blocked for " << delay.InMilliseconds() - << " ms."; - - return true; -} - -} // namespace chromeos diff --git a/chromeos/streams/fake_stream.h b/chromeos/streams/fake_stream.h deleted file mode 100644 index 2f8567d..0000000 --- a/chromeos/streams/fake_stream.h +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_FAKE_STREAM_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_FAKE_STREAM_H_ - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -// Fake stream implementation for testing. -// This class allows to provide data for the stream in tests that can be later -// read through the Stream interface. Also, data written into the stream can be -// later inspected and verified. -// -// NOTE: This class provides a fake implementation for streams with separate -// input and output channels. That is, read and write operations do not affect -// each other. Also, the stream implementation is sequential only (no seeking). -// Good examples of a use case for fake stream are: -// - read-only sequential streams (file, memory, pipe, ...) -// - write-only sequential streams (same as above) -// - independent channel read-write streams (sockets, ...) -// -// For more complex read/write stream test scenarios using a real MemoryStream -// or temporary FileStream is probably a better choice. -class FakeStream : public Stream { - public: - // Construct a new instance of the fake stream. - // mode - expected read/write mode supported by the stream. - // clock - the clock to use to get the current time. - FakeStream(Stream::AccessMode mode, - base::Clock* clock); - - // Add data packets to the read queue of the stream. - // Optional |delay| indicates that the data packet should be delayed. - void AddReadPacketData(base::TimeDelta delay, const void* data, size_t size); - void AddReadPacketData(base::TimeDelta delay, chromeos::Blob data); - void AddReadPacketString(base::TimeDelta delay, const std::string& data); - - // Schedule a read error by adding a special error packet to the queue. - void QueueReadError(base::TimeDelta delay); - void QueueReadErrorWithMessage(base::TimeDelta delay, - const std::string& message); - - // Resets read queue and clears any input data buffers. - void ClearReadQueue(); - - // Add expectations for output data packets to be written by the stream. - // Optional |delay| indicates that the initial write operation for the data in - // the packet should be delayed. - // ExpectWritePacketSize just limits the size of output packet while - // ExpectWritePacketData also validates that the data matches that of |data|. - void ExpectWritePacketSize(base::TimeDelta delay, size_t data_size); - void ExpectWritePacketData(base::TimeDelta delay, - const void* data, - size_t size); - void ExpectWritePacketData(base::TimeDelta delay, chromeos::Blob data); - void ExpectWritePacketString(base::TimeDelta delay, const std::string& data); - - // Schedule a write error by adding a special error packet to the queue. - void QueueWriteError(base::TimeDelta delay); - void QueueWriteErrorWithMessage(base::TimeDelta delay, - const std::string& message); - - // Resets write queue and clears any output data buffers. - void ClearWriteQueue(); - - // Returns the output data accumulated so far by all complete write packets, - // or explicitly flushed. - const chromeos::Blob& GetFlushedOutputData() const; - std::string GetFlushedOutputDataAsString() const; - - // Overrides from chromeos::Stream. - bool IsOpen() const override { return is_open_; } - bool CanRead() const override; - bool CanWrite() const override; - bool CanSeek() const override { return false; } - bool CanGetSize() const override { return false; } - uint64_t GetSize() const override { return 0; } - bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; - uint64_t GetRemainingSize() const override { return 0; } - uint64_t GetPosition() const override { return 0; } - bool Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) override; - - bool ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) override; - bool WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) override; - bool FlushBlocking(ErrorPtr* error) override; - bool CloseBlocking(ErrorPtr* error) override; - bool WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) override; - bool WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) override; - - private: - // Input data packet to be placed on the read queue. - struct InputDataPacket { - chromeos::Blob data; // Data to be read. - base::TimeDelta delay_before; // Possible delay for the first read. - bool read_error{false}; // Set to true if this packet generates an error. - }; - - // Output data packet to be placed on the write queue. - struct OutputDataPacket { - size_t expected_size{0}; // Output packet size - chromeos::Blob data; // Possible data to verify the output with. - base::TimeDelta delay_before; // Possible delay for the first write. - bool write_error{false}; // Set to true if this packet generates an error. - }; - - // Check if there is any pending read data in the input buffer. - bool IsReadBufferEmpty() const; - // Pops the next read packet from the queue and sets its data into the - // internal input buffer. - bool PopReadPacket(); - - // Check if the output buffer is full. - bool IsWriteBufferFull() const; - - // Moves the current full output buffer into |all_output_data_|, clears the - // buffer, and pops the information about the next expected output packet - // from the write queue. - bool PopWritePacket(); - - bool is_open_{true}; - Stream::AccessMode mode_; - base::Clock* clock_; - - // Internal data for read operations. - std::queue incoming_queue_; - base::Time delay_input_until_; - chromeos::Blob input_buffer_; - size_t input_ptr_{0}; - bool report_read_error_{false}; - - // Internal data for write operations. - std::queue outgoing_queue_; - base::Time delay_output_until_; - chromeos::Blob output_buffer_; - chromeos::Blob expected_output_data_; - size_t max_output_buffer_size_{0}; - bool report_write_error_{false}; - chromeos::Blob all_output_data_; - - DISALLOW_COPY_AND_ASSIGN(FakeStream); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_FAKE_STREAM_H_ diff --git a/chromeos/streams/fake_stream_unittest.cc b/chromeos/streams/fake_stream_unittest.cc deleted file mode 100644 index 89bada0..0000000 --- a/chromeos/streams/fake_stream_unittest.cc +++ /dev/null @@ -1,510 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include - -using testing::AnyNumber; -using testing::InSequence; -using testing::_; - -namespace chromeos { - -class FakeStreamTest : public testing::Test { - public: - void SetUp() override { - mock_loop_.SetAsCurrent(); - // Ignore calls to RunOnce(). - EXPECT_CALL(mock_loop_, RunOnce(true)).Times(AnyNumber()); - } - - void CreateStream(Stream::AccessMode mode) { - stream_.reset(new FakeStream{mode, &clock_}); - } - - // Performs non-blocking read on the stream and returns the read data - // as a string in |out_buffer|. Returns true if the read was successful or - // false when an error occurs. |*eos| is set to true when end of stream is - // reached. - bool ReadString(size_t size_to_read, std::string* out_buffer, bool* eos) { - std::vector data; - data.resize(size_to_read); - size_t size_read = 0; - bool ok = stream_->ReadNonBlocking(data.data(), data.size(), &size_read, - eos, nullptr); - if (ok) { - out_buffer->assign(data.data(), data.data() + size_read); - } else { - out_buffer->clear(); - } - return ok; - } - - // Writes a string to a stream. Returns the number of bytes written or -1 - // in case an error occurred. - int WriteString(const std::string& data) { - size_t written = 0; - if (!stream_->WriteNonBlocking(data.data(), data.size(), &written, nullptr)) - return -1; - return static_cast(written); - } - - protected: - base::SimpleTestClock clock_; - MockMessageLoop mock_loop_{&clock_}; - std::unique_ptr stream_; - const base::TimeDelta zero_delay; -}; - -TEST_F(FakeStreamTest, InitReadOnly) { - CreateStream(Stream::AccessMode::READ); - EXPECT_TRUE(stream_->IsOpen()); - EXPECT_TRUE(stream_->CanRead()); - EXPECT_FALSE(stream_->CanWrite()); - EXPECT_FALSE(stream_->CanSeek()); - EXPECT_FALSE(stream_->CanGetSize()); - EXPECT_EQ(0, stream_->GetSize()); - EXPECT_EQ(0, stream_->GetRemainingSize()); - EXPECT_EQ(0, stream_->GetPosition()); -} - -TEST_F(FakeStreamTest, InitWriteOnly) { - CreateStream(Stream::AccessMode::WRITE); - EXPECT_TRUE(stream_->IsOpen()); - EXPECT_FALSE(stream_->CanRead()); - EXPECT_TRUE(stream_->CanWrite()); - EXPECT_FALSE(stream_->CanSeek()); - EXPECT_FALSE(stream_->CanGetSize()); - EXPECT_EQ(0, stream_->GetSize()); - EXPECT_EQ(0, stream_->GetRemainingSize()); - EXPECT_EQ(0, stream_->GetPosition()); -} - -TEST_F(FakeStreamTest, InitReadWrite) { - CreateStream(Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->IsOpen()); - EXPECT_TRUE(stream_->CanRead()); - EXPECT_TRUE(stream_->CanWrite()); - EXPECT_FALSE(stream_->CanSeek()); - EXPECT_FALSE(stream_->CanGetSize()); - EXPECT_EQ(0, stream_->GetSize()); - EXPECT_EQ(0, stream_->GetRemainingSize()); - EXPECT_EQ(0, stream_->GetPosition()); -} - -TEST_F(FakeStreamTest, ReadEmpty) { - CreateStream(Stream::AccessMode::READ); - std::string data; - bool eos = false; - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_TRUE(eos); - EXPECT_TRUE(data.empty()); -} - -TEST_F(FakeStreamTest, ReadFullPacket) { - CreateStream(Stream::AccessMode::READ); - stream_->AddReadPacketString({}, "foo"); - std::string data; - bool eos = false; - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("foo", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_TRUE(eos); - EXPECT_TRUE(data.empty()); -} - -TEST_F(FakeStreamTest, ReadPartialPacket) { - CreateStream(Stream::AccessMode::READ); - stream_->AddReadPacketString({}, "foobar"); - std::string data; - bool eos = false; - EXPECT_TRUE(ReadString(3, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("foo", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("bar", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_TRUE(eos); - EXPECT_TRUE(data.empty()); -} - -TEST_F(FakeStreamTest, ReadMultiplePackets) { - CreateStream(Stream::AccessMode::READ); - stream_->AddReadPacketString({}, "foobar"); - stream_->AddReadPacketString({}, "baz"); - stream_->AddReadPacketString({}, "quux"); - std::string data; - bool eos = false; - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("foobar", data); - - EXPECT_TRUE(ReadString(2, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("ba", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("z", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("quux", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_TRUE(eos); - EXPECT_TRUE(data.empty()); - - stream_->AddReadPacketString({}, "foo-bar"); - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("foo-bar", data); -} - -TEST_F(FakeStreamTest, ReadPacketsWithDelay) { - CreateStream(Stream::AccessMode::READ); - stream_->AddReadPacketString({}, "foobar"); - stream_->AddReadPacketString(base::TimeDelta::FromSeconds(1), "baz"); - std::string data; - bool eos = false; - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("foobar", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_TRUE(data.empty()); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_TRUE(data.empty()); - - clock_.Advance(base::TimeDelta::FromSeconds(1)); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("baz", data); -} - -TEST_F(FakeStreamTest, ReadPacketsWithError) { - CreateStream(Stream::AccessMode::READ); - stream_->AddReadPacketString({}, "foobar"); - stream_->QueueReadErrorWithMessage(base::TimeDelta::FromSeconds(1), - "Dummy error"); - stream_->AddReadPacketString({}, "baz"); - - std::string data; - bool eos = false; - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("foobar", data); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_TRUE(data.empty()); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_TRUE(data.empty()); - - clock_.Advance(base::TimeDelta::FromSeconds(1)); - - EXPECT_FALSE(ReadString(100, &data, &eos)); - - EXPECT_TRUE(ReadString(100, &data, &eos)); - EXPECT_FALSE(eos); - EXPECT_EQ("baz", data); -} - -TEST_F(FakeStreamTest, WaitForDataRead) { - CreateStream(Stream::AccessMode::READ); - - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(2); - - int call_count = 0; - auto callback = [&call_count](Stream::AccessMode mode) { - call_count++; - EXPECT_EQ(Stream::AccessMode::READ, mode); - }; - - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ, - base::Bind(callback), nullptr)); - mock_loop_.Run(); - EXPECT_EQ(1, call_count); - - stream_->AddReadPacketString({}, "foobar"); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ, - base::Bind(callback), nullptr)); - mock_loop_.Run(); - EXPECT_EQ(2, call_count); - - stream_->ClearReadQueue(); - - auto one_sec_delay = base::TimeDelta::FromSeconds(1); - stream_->AddReadPacketString(one_sec_delay, "baz"); - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ, - base::Bind(callback), nullptr)); - mock_loop_.Run(); - EXPECT_EQ(3, call_count); -} - -TEST_F(FakeStreamTest, ReadAsync) { - CreateStream(Stream::AccessMode::READ); - std::string input_data = "foobar-baz"; - size_t split_pos = input_data.find('-'); - - auto one_sec_delay = base::TimeDelta::FromSeconds(1); - stream_->AddReadPacketString({}, input_data.substr(0, split_pos)); - stream_->AddReadPacketString(one_sec_delay, input_data.substr(split_pos)); - - { - InSequence seq; - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1); - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); - } - - std::vector buffer; - buffer.resize(input_data.size()); - - int success_count = 0; - int error_count = 0; - auto on_success = [&success_count] { success_count++; }; - auto on_failure = [&error_count](const Error* error) { error_count++; }; - - EXPECT_TRUE(stream_->ReadAllAsync(buffer.data(), buffer.size(), - base::Bind(on_success), - base::Bind(on_failure), - nullptr)); - mock_loop_.Run(); - EXPECT_EQ(1, success_count); - EXPECT_EQ(0, error_count); - EXPECT_EQ(input_data, (std::string{buffer.begin(), buffer.end()})); -} - -TEST_F(FakeStreamTest, WriteEmpty) { - CreateStream(Stream::AccessMode::WRITE); - EXPECT_EQ(-1, WriteString("foo")); -} - -TEST_F(FakeStreamTest, WritePartial) { - CreateStream(Stream::AccessMode::WRITE); - stream_->ExpectWritePacketSize({}, 6); - EXPECT_EQ(3, WriteString("foo")); - EXPECT_EQ(3, WriteString("bar")); - EXPECT_EQ(-1, WriteString("baz")); - - EXPECT_EQ("foobar", stream_->GetFlushedOutputDataAsString()); -} - -TEST_F(FakeStreamTest, WriteFullPackets) { - CreateStream(Stream::AccessMode::WRITE); - - stream_->ExpectWritePacketSize({}, 3); - EXPECT_EQ(3, WriteString("foo")); - EXPECT_EQ(-1, WriteString("bar")); - - stream_->ExpectWritePacketSize({}, 3); - EXPECT_EQ(3, WriteString("bar")); - - stream_->ExpectWritePacketSize({}, 3); - EXPECT_EQ(3, WriteString("quux")); - - EXPECT_EQ("foobarquu", stream_->GetFlushedOutputDataAsString()); -} - -TEST_F(FakeStreamTest, WriteAndVerifyData) { - CreateStream(Stream::AccessMode::WRITE); - - stream_->ExpectWritePacketString({}, "foo"); - stream_->ExpectWritePacketString({}, "bar"); - EXPECT_EQ(3, WriteString("foobar")); - EXPECT_EQ(3, WriteString("bar")); - - stream_->ExpectWritePacketString({}, "foo"); - stream_->ExpectWritePacketString({}, "baz"); - EXPECT_EQ(3, WriteString("foobar")); - EXPECT_EQ(-1, WriteString("bar")); - - stream_->ExpectWritePacketString({}, "foobar"); - EXPECT_EQ(3, WriteString("foo")); - EXPECT_EQ(2, WriteString("ba")); - EXPECT_EQ(-1, WriteString("z")); -} - -TEST_F(FakeStreamTest, WriteWithDelay) { - CreateStream(Stream::AccessMode::WRITE); - - const auto delay = base::TimeDelta::FromMilliseconds(500); - - stream_->ExpectWritePacketSize({}, 3); - stream_->ExpectWritePacketSize(delay, 3); - EXPECT_EQ(3, WriteString("foobar")); - - EXPECT_EQ(0, WriteString("bar")); - EXPECT_EQ(0, WriteString("bar")); - clock_.Advance(delay); - EXPECT_EQ(3, WriteString("bar")); - - EXPECT_EQ("foobar", stream_->GetFlushedOutputDataAsString()); -} - -TEST_F(FakeStreamTest, WriteWithError) { - CreateStream(Stream::AccessMode::WRITE); - - const auto delay = base::TimeDelta::FromMilliseconds(500); - - stream_->ExpectWritePacketSize({}, 3); - stream_->QueueWriteError({}); - stream_->ExpectWritePacketSize({}, 3); - stream_->QueueWriteErrorWithMessage(delay, "Dummy message"); - stream_->ExpectWritePacketString({}, "foobar"); - - const std::string data = "foobarbaz"; - EXPECT_EQ(3, WriteString(data)); - EXPECT_EQ(-1, WriteString(data)); // Simulated error #1. - EXPECT_EQ(3, WriteString(data)); - EXPECT_EQ(0, WriteString(data)); // Waiting for data... - clock_.Advance(delay); - EXPECT_EQ(-1, WriteString(data)); // Simulated error #2. - EXPECT_EQ(6, WriteString(data)); - EXPECT_EQ(-1, WriteString(data)); // No more data expected. -} - -TEST_F(FakeStreamTest, WaitForDataWrite) { - CreateStream(Stream::AccessMode::WRITE); - - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(2); - - int call_count = 0; - auto callback = [&call_count](Stream::AccessMode mode) { - call_count++; - EXPECT_EQ(Stream::AccessMode::WRITE, mode); - }; - - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::WRITE, - base::Bind(callback), nullptr)); - mock_loop_.Run(); - EXPECT_EQ(1, call_count); - - stream_->ExpectWritePacketString({}, "foobar"); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::WRITE, - base::Bind(callback), nullptr)); - mock_loop_.Run(); - EXPECT_EQ(2, call_count); - - stream_->ClearWriteQueue(); - - auto one_sec_delay = base::TimeDelta::FromSeconds(1); - stream_->ExpectWritePacketString(one_sec_delay, "baz"); - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::WRITE, - base::Bind(callback), nullptr)); - mock_loop_.Run(); - EXPECT_EQ(3, call_count); -} - -TEST_F(FakeStreamTest, WriteAsync) { - CreateStream(Stream::AccessMode::WRITE); - std::string output_data = "foobar-baz"; - size_t split_pos = output_data.find('-'); - - auto one_sec_delay = base::TimeDelta::FromSeconds(1); - stream_->ExpectWritePacketString({}, output_data.substr(0, split_pos)); - stream_->ExpectWritePacketString(one_sec_delay, - output_data.substr(split_pos)); - - { - InSequence seq; - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1); - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); - } - - int success_count = 0; - int error_count = 0; - auto on_success = [&success_count] { success_count++; }; - auto on_failure = [&error_count](const Error* error) { error_count++; }; - - EXPECT_TRUE(stream_->WriteAllAsync(output_data.data(), output_data.size(), - base::Bind(on_success), - base::Bind(on_failure), - nullptr)); - mock_loop_.Run(); - EXPECT_EQ(1, success_count); - EXPECT_EQ(0, error_count); - EXPECT_EQ(output_data, stream_->GetFlushedOutputDataAsString()); -} - -TEST_F(FakeStreamTest, WaitForDataReadWrite) { - CreateStream(Stream::AccessMode::READ_WRITE); - auto one_sec_delay = base::TimeDelta::FromSeconds(1); - auto two_sec_delay = base::TimeDelta::FromSeconds(2); - - int call_count = 0; - auto callback = [&call_count](Stream::AccessMode mode, - Stream::AccessMode expected_mode) { - call_count++; - EXPECT_EQ(static_cast(expected_mode), static_cast(mode)); - }; - - stream_->AddReadPacketString(one_sec_delay, "foo"); - stream_->ExpectWritePacketString(two_sec_delay, "bar"); - - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, - base::Bind(callback, - Stream::AccessMode::READ), - nullptr)); - mock_loop_.Run(); - EXPECT_EQ(1, call_count); - - // The above step has adjusted the clock by 1 second already. - stream_->ClearReadQueue(); - stream_->AddReadPacketString(two_sec_delay, "foo"); - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, - base::Bind(callback, - Stream::AccessMode::WRITE), - nullptr)); - mock_loop_.Run(); - EXPECT_EQ(2, call_count); - - clock_.Advance(one_sec_delay); - - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, - base::Bind(callback, - Stream::AccessMode::READ_WRITE), - nullptr)); - mock_loop_.Run(); - EXPECT_EQ(3, call_count); - - stream_->ClearReadQueue(); - stream_->ClearWriteQueue(); - stream_->AddReadPacketString(one_sec_delay, "foo"); - stream_->ExpectWritePacketString(one_sec_delay, "bar"); - - EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1); - EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE, - base::Bind(callback, - Stream::AccessMode::READ_WRITE), - nullptr)); - mock_loop_.Run(); - EXPECT_EQ(4, call_count); -} - -} // namespace chromeos diff --git a/chromeos/streams/file_stream.cc b/chromeos/streams/file_stream.cc deleted file mode 100644 index 35fae6d..0000000 --- a/chromeos/streams/file_stream.cc +++ /dev/null @@ -1,549 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -// FileDescriptor is a helper class that serves two purposes: -// 1. It wraps low-level system APIs (as FileDescriptorInterface) to allow -// mocking calls to them in tests. -// 2. It provides file descriptor watching services using FileDescriptorWatcher -// and MessageLoopForIO::Watcher interface. -// The real FileStream uses this class to perform actual file I/O on the -// contained file descriptor. -class FileDescriptor : public FileStream::FileDescriptorInterface { - public: - FileDescriptor(int fd, bool own) : fd_{fd}, own_{own} {} - ~FileDescriptor() override { - if (IsOpen()) { - Close(); - } - } - - // Overrides for FileStream::FileDescriptorInterface methods. - bool IsOpen() const override { return fd_ >= 0; } - - ssize_t Read(void* buf, size_t nbyte) override { - return HANDLE_EINTR(read(fd_, buf, nbyte)); - } - - ssize_t Write(const void* buf, size_t nbyte) override { - return HANDLE_EINTR(write(fd_, buf, nbyte)); - } - - off64_t Seek(off64_t offset, int whence) override { - return lseek64(fd_, offset, whence); - } - - mode_t GetFileMode() const override { - struct stat file_stat; - if (fstat(fd_, &file_stat) < 0) - return 0; - return file_stat.st_mode; - } - - uint64_t GetSize() const override { - struct stat file_stat; - if (fstat(fd_, &file_stat) < 0) - return 0; - return file_stat.st_size; - } - - int Truncate(off64_t length) const override { - return HANDLE_EINTR(ftruncate(fd_, length)); - } - - int Close() override { - int fd = -1; - // The stream may or may not own the file descriptor stored in |fd_|. - // Despite that, we will need to set |fd_| to -1 when Close() finished. - // So, here we set it to -1 first and if we own the old descriptor, close - // it before exiting. - std::swap(fd, fd_); - CancelPendingAsyncOperations(); - return own_ ? IGNORE_EINTR(close(fd)) : 0; - } - - bool WaitForData(Stream::AccessMode mode, - const DataCallback& data_callback, - ErrorPtr* error) override { - if (stream_utils::IsReadAccessMode(mode)) { - CHECK(read_data_callback_.is_null()); - MessageLoop::current()->CancelTask(read_watcher_); - read_watcher_ = MessageLoop::current()->WatchFileDescriptor( - FROM_HERE, - fd_, - MessageLoop::WatchMode::kWatchRead, - false, // persistent - base::Bind(&FileDescriptor::OnFileCanReadWithoutBlocking, - base::Unretained(this))); - if (read_watcher_ == MessageLoop::kTaskIdNull) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kInvalidParameter, - "File descriptor doesn't support watching for reading."); - return false; - } - read_data_callback_ = data_callback; - } - if (stream_utils::IsWriteAccessMode(mode)) { - CHECK(write_data_callback_.is_null()); - MessageLoop::current()->CancelTask(write_watcher_); - write_watcher_ = MessageLoop::current()->WatchFileDescriptor( - FROM_HERE, - fd_, - MessageLoop::WatchMode::kWatchWrite, - false, // persistent - base::Bind(&FileDescriptor::OnFileCanWriteWithoutBlocking, - base::Unretained(this))); - if (write_watcher_ == MessageLoop::kTaskIdNull) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kInvalidParameter, - "File descriptor doesn't support watching for writing."); - return false; - } - write_data_callback_ = data_callback; - } - return true; - } - - int WaitForDataBlocking(Stream::AccessMode in_mode, - base::TimeDelta timeout, - Stream::AccessMode* out_mode) override { - fd_set read_fds; - fd_set write_fds; - fd_set error_fds; - - FD_ZERO(&read_fds); - FD_ZERO(&write_fds); - FD_ZERO(&error_fds); - - if (stream_utils::IsReadAccessMode(in_mode)) - FD_SET(fd_, &read_fds); - - if (stream_utils::IsWriteAccessMode(in_mode)) - FD_SET(fd_, &write_fds); - - FD_SET(fd_, &error_fds); - timeval timeout_val = {}; - if (!timeout.is_max()) { - const timespec ts = timeout.ToTimeSpec(); - TIMESPEC_TO_TIMEVAL(&timeout_val, &ts); - } - int res = HANDLE_EINTR(select(fd_ + 1, &read_fds, &write_fds, &error_fds, - timeout.is_max() ? nullptr : &timeout_val)); - if (res > 0 && out_mode) { - *out_mode = stream_utils::MakeAccessMode(FD_ISSET(fd_, &read_fds), - FD_ISSET(fd_, &write_fds)); - } - return res; - } - - void CancelPendingAsyncOperations() override { - read_data_callback_.Reset(); - if (read_watcher_ != MessageLoop::kTaskIdNull) { - MessageLoop::current()->CancelTask(read_watcher_); - read_watcher_ = MessageLoop::kTaskIdNull; - } - - write_data_callback_.Reset(); - if (write_watcher_ != MessageLoop::kTaskIdNull) { - MessageLoop::current()->CancelTask(write_watcher_); - write_watcher_ = MessageLoop::kTaskIdNull; - } - } - - // Called from the chromeos::MessageLoop when the file descriptor is available - // for reading. - void OnFileCanReadWithoutBlocking() { - CHECK(!read_data_callback_.is_null()); - DataCallback cb = read_data_callback_; - read_data_callback_.Reset(); - cb.Run(Stream::AccessMode::READ); - } - - void OnFileCanWriteWithoutBlocking() { - CHECK(!write_data_callback_.is_null()); - DataCallback cb = write_data_callback_; - write_data_callback_.Reset(); - cb.Run(Stream::AccessMode::WRITE); - } - - private: - // The actual file descriptor we are working with. Will contain -1 if the - // file stream has been closed. - int fd_; - - // |own_| is set to true if the file stream owns the file descriptor |fd_| and - // must close it when the stream is closed. This will be false for file - // descriptors that shouldn't be closed (e.g. stdin, stdout, stderr). - bool own_; - - // Stream callbacks to be called when read and/or write operations can be - // performed on the file descriptor without blocking. - DataCallback read_data_callback_; - DataCallback write_data_callback_; - - // MessageLoop tasks monitoring read/write operations on the file descriptor. - MessageLoop::TaskId read_watcher_{MessageLoop::kTaskIdNull}; - MessageLoop::TaskId write_watcher_{MessageLoop::kTaskIdNull}; - - DISALLOW_COPY_AND_ASSIGN(FileDescriptor); -}; - - -StreamPtr FileStream::Open(const base::FilePath& path, - AccessMode mode, - Disposition disposition, - ErrorPtr* error) { - StreamPtr stream; - int open_flags = O_CLOEXEC; - switch (mode) { - case AccessMode::READ: - open_flags |= O_RDONLY; - break; - case AccessMode::WRITE: - open_flags |= O_WRONLY; - break; - case AccessMode::READ_WRITE: - open_flags |= O_RDWR; - break; - } - - switch (disposition) { - case Disposition::OPEN_EXISTING: - // Nothing else to do. - break; - case Disposition::CREATE_ALWAYS: - open_flags |= O_CREAT | O_TRUNC; - break; - case Disposition::CREATE_NEW_ONLY: - open_flags |= O_CREAT | O_EXCL; - break; - case Disposition::TRUNCATE_EXISTING: - open_flags |= O_TRUNC; - break; - } - - mode_t creation_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - int fd = HANDLE_EINTR(open(path.value().c_str(), open_flags, creation_mode)); - if (fd < 0) { - chromeos::errors::system::AddSystemError(error, FROM_HERE, errno); - return stream; - } - if (HANDLE_EINTR(fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK)) < 0) { - chromeos::errors::system::AddSystemError(error, FROM_HERE, errno); - IGNORE_EINTR(close(fd)); - return stream; - } - - std::unique_ptr fd_interface{ - new FileDescriptor{fd, true}}; - - stream.reset(new FileStream{std::move(fd_interface), mode}); - return stream; -} - -StreamPtr FileStream::CreateTemporary(ErrorPtr* error) { - StreamPtr stream; - base::FilePath path; - // The "proper" solution would be here to add O_TMPFILE flag to |open_flags| - // below and pass just the temp directory path to open(), so the actual file - // name isn't even needed. However this is supported only as of Linux kernel - // 3.11 and not all our configurations have that. So, for now just create - // a temp file first and then open it. - if (!base::CreateTemporaryFile(&path)) { - chromeos::errors::system::AddSystemError(error, FROM_HERE, errno); - return stream; - } - int open_flags = O_CLOEXEC | O_RDWR | O_CREAT | O_TRUNC; - mode_t creation_mode = S_IRUSR | S_IWUSR; - int fd = HANDLE_EINTR(open(path.value().c_str(), open_flags, creation_mode)); - if (fd < 0) { - chromeos::errors::system::AddSystemError(error, FROM_HERE, errno); - return stream; - } - unlink(path.value().c_str()); - - stream = FromFileDescriptor(fd, true, error); - if (!stream) - IGNORE_EINTR(close(fd)); - return stream; -} - -StreamPtr FileStream::FromFileDescriptor(int file_descriptor, - bool own_descriptor, - ErrorPtr* error) { - StreamPtr stream; - if (file_descriptor < 0 || file_descriptor >= FD_SETSIZE) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kInvalidParameter, - "Invalid file descriptor value"); - return stream; - } - - int fd_flags = HANDLE_EINTR(fcntl(file_descriptor, F_GETFL)); - if (fd_flags < 0) { - chromeos::errors::system::AddSystemError(error, FROM_HERE, errno); - return stream; - } - int file_access_mode = (fd_flags & O_ACCMODE); - AccessMode access_mode = AccessMode::READ_WRITE; - if (file_access_mode == O_RDONLY) - access_mode = AccessMode::READ; - else if (file_access_mode == O_WRONLY) - access_mode = AccessMode::WRITE; - - // Make sure the file descriptor is set to perform non-blocking operations - // if not enabled already. - if ((fd_flags & O_NONBLOCK) == 0) { - fd_flags |= O_NONBLOCK; - if (HANDLE_EINTR(fcntl(file_descriptor, F_SETFL, fd_flags)) < 0) { - chromeos::errors::system::AddSystemError(error, FROM_HERE, errno); - return stream; - } - } - - std::unique_ptr fd_interface{ - new FileDescriptor{file_descriptor, own_descriptor}}; - - stream.reset(new FileStream{std::move(fd_interface), access_mode}); - return stream; -} - -FileStream::FileStream(std::unique_ptr fd_interface, - AccessMode mode) - : fd_interface_(std::move(fd_interface)), - access_mode_(mode) { - switch (fd_interface_->GetFileMode() & S_IFMT) { - case S_IFCHR: // Character device - case S_IFSOCK: // Socket - case S_IFIFO: // FIFO/pipe - // We know that these devices are not seekable and stream size is unknown. - seekable_ = false; - can_get_size_ = false; - break; - - case S_IFBLK: // Block device - case S_IFDIR: // Directory - case S_IFREG: // Normal file - case S_IFLNK: // Symbolic link - default: - // The above devices support seek. Also, if not sure/in doubt, err on the - // side of "allowable". - seekable_ = true; - can_get_size_ = true; - break; - } -} - -bool FileStream::IsOpen() const { - return fd_interface_->IsOpen(); -} - -bool FileStream::CanRead() const { - return IsOpen() && stream_utils::IsReadAccessMode(access_mode_); -} - -bool FileStream::CanWrite() const { - return IsOpen() && stream_utils::IsWriteAccessMode(access_mode_); -} - -bool FileStream::CanSeek() const { - return IsOpen() && seekable_; -} - -bool FileStream::CanGetSize() const { - return IsOpen() && can_get_size_; -} - -uint64_t FileStream::GetSize() const { - return IsOpen() ? fd_interface_->GetSize() : 0; -} - -bool FileStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - if (!stream_utils::CheckInt64Overflow(FROM_HERE, size, 0, error)) - return false; - - if (fd_interface_->Truncate(size) >= 0) - return true; - - errors::system::AddSystemError(error, FROM_HERE, errno); - return false; -} - -uint64_t FileStream::GetRemainingSize() const { - if (!CanGetSize()) - return 0; - uint64_t pos = GetPosition(); - uint64_t size = GetSize(); - return (pos < size) ? (size - pos) : 0; -} - -uint64_t FileStream::GetPosition() const { - if (!CanSeek()) - return 0; - - off64_t pos = fd_interface_->Seek(0, SEEK_CUR); - const off64_t min_pos = 0; - return std::max(min_pos, pos); -} - -bool FileStream::Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - int raw_whence = 0; - switch (whence) { - case Whence::FROM_BEGIN: - raw_whence = SEEK_SET; - break; - case Whence::FROM_CURRENT: - raw_whence = SEEK_CUR; - break; - case Whence::FROM_END: - raw_whence = SEEK_END; - break; - default: - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kInvalidParameter, "Invalid whence"); - return false; - } - off64_t pos = fd_interface_->Seek(offset, raw_whence); - if (pos < 0) { - errors::system::AddSystemError(error, FROM_HERE, errno); - return false; - } - - if (new_position) - *new_position = static_cast(pos); - return true; -} - -bool FileStream::ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - ssize_t read = fd_interface_->Read(buffer, size_to_read); - if (read < 0) { - // If read() fails, check if this is due to no data being currently - // available and we do non-blocking I/O. - if (errno == EWOULDBLOCK || errno == EAGAIN) { - if (end_of_stream) - *end_of_stream = false; - *size_read = 0; - return true; - } - // Otherwise a real problem occurred. - errors::system::AddSystemError(error, FROM_HERE, errno); - return false; - } - if (end_of_stream) - *end_of_stream = (read == 0 && size_to_read != 0); - *size_read = read; - return true; -} - -bool FileStream::WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - ssize_t written = fd_interface_->Write(buffer, size_to_write); - if (written < 0) { - // If write() fails, check if this is due to the fact that no data - // can be presently written and we do non-blocking I/O. - if (errno == EWOULDBLOCK || errno == EAGAIN) { - *size_written = 0; - return true; - } - // Otherwise a real problem occurred. - errors::system::AddSystemError(error, FROM_HERE, errno); - return false; - } - *size_written = written; - return true; -} - -bool FileStream::FlushBlocking(ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - // File descriptors don't have an internal buffer to flush. - return true; -} - -bool FileStream::CloseBlocking(ErrorPtr* error) { - if (!IsOpen()) - return true; - - if (fd_interface_->Close() < 0) { - errors::system::AddSystemError(error, FROM_HERE, errno); - return false; - } - - return true; -} - -bool FileStream::WaitForData( - AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - return fd_interface_->WaitForData(mode, callback, error); -} - -bool FileStream::WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - int ret = fd_interface_->WaitForDataBlocking(in_mode, timeout, out_mode); - if (ret < 0) { - errors::system::AddSystemError(error, FROM_HERE, errno); - return false; - } - if (ret == 0) - return stream_utils::ErrorOperationTimeout(FROM_HERE, error); - - return true; -} - -void FileStream::CancelPendingAsyncOperations() { - if (IsOpen()) { - fd_interface_->CancelPendingAsyncOperations(); - } - Stream::CancelPendingAsyncOperations(); -} - -} // namespace chromeos diff --git a/chromeos/streams/file_stream.h b/chromeos/streams/file_stream.h deleted file mode 100644 index a59405c..0000000 --- a/chromeos/streams/file_stream.h +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_FILE_STREAM_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_FILE_STREAM_H_ - -#include -#include -#include -#include - -namespace chromeos { - -// FileStream class provides the implementation of chromeos::Stream for files -// and file-descriptor-based streams, such as pipes and sockets. -// The FileStream class cannot be instantiated by clients directly. However -// they should use the static factory methods such as: -// - FileStream::Open(): to open a file by name. -// - FileStream::CreateTemporary(): to create a temporary file stream. -// - FileStream::FromFileDescriptor(): to create a stream using an existing -// file descriptor. -class CHROMEOS_EXPORT FileStream : public Stream { - public: - // See comments for FileStream::Open() for detailed description of this enum. - enum class Disposition { - OPEN_EXISTING, // Open existing file only. Fail if doesn't exist. - CREATE_ALWAYS, // Create empty file, possibly overwriting existing file. - CREATE_NEW_ONLY, // Create new file if doesn't exist already. - TRUNCATE_EXISTING, // Open/truncate existing file. Fail if doesn't exist. - }; - - // Simple interface to wrap native library calls so that they can be mocked - // out for testing. - struct FileDescriptorInterface { - using DataCallback = base::Callback; - - virtual ~FileDescriptorInterface() = default; - - virtual bool IsOpen() const = 0; - virtual ssize_t Read(void* buf, size_t nbyte) = 0; - virtual ssize_t Write(const void* buf, size_t nbyte) = 0; - virtual off64_t Seek(off64_t offset, int whence) = 0; - virtual mode_t GetFileMode() const = 0; - virtual uint64_t GetSize() const = 0; - virtual int Truncate(off64_t length) const = 0; - virtual int Close() = 0; - virtual bool WaitForData(AccessMode mode, - const DataCallback& data_callback, - ErrorPtr* error) = 0; - virtual int WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode) = 0; - virtual void CancelPendingAsyncOperations() = 0; - }; - - // == Construction ========================================================== - - // Opens a file at specified |path| for reading, writing or both as indicated - // by |mode|. The |disposition| specifies how the file must be opened/created: - // - OPEN_EXISTING - opens the existing file and keeps its content intact. - // The seek pointer is at the beginning of the file. - // - CREATE_ALWAYS - creates the file always. If it exists, the file is - // truncated. - // - CREATE_NEW_ONLY - creates a new file only if it doesn't exist. Fails - // otherwise. This can be useful for creating lock files. - // - TRUNCATE_EXISTING - opens existing file and truncates it to zero length. - // Fails if the file doesn't already exist. - // If successful, the open file stream is returned. Otherwise returns the - // stream pointer containing nullptr and fills in the details of the error - // in |error| object, if provided. - static StreamPtr Open(const base::FilePath& path, - AccessMode mode, - Disposition disposition, - ErrorPtr* error); - - // Creates a temporary unnamed file and returns a stream to it. The file will - // be deleted when the stream is destroyed. - static StreamPtr CreateTemporary(ErrorPtr* error); - - // Creates a file stream based on existing file descriptor. The file - // descriptor will be set into non-blocking mode and will be owned by the - // resulting stream (and closed when the stream is destroyed). - // If the function fails, returns a null stream pointer and sets the error - // details to |error| object. Also note that it is the caller's responsibility - // to close the file descriptor if this function fails, since the stream - // hasn't been created yet and didn't take ownership of the file descriptor. - // |own_descriptor| indicates whether the stream must close the underlying - // file descriptor when its CloseBlocking() method is called. This should be - // set to false for file descriptors that shouldn't be closed (e.g. stdin). - static StreamPtr FromFileDescriptor(int file_descriptor, - bool own_descriptor, - ErrorPtr* error); - - // == Stream capabilities =================================================== - bool IsOpen() const override; - bool CanRead() const override; - bool CanWrite() const override; - bool CanSeek() const override; - bool CanGetSize() const override; - - // == Stream size operations ================================================ - uint64_t GetSize() const override; - bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; - uint64_t GetRemainingSize() const override; - - // == Seek operations ======================================================= - uint64_t GetPosition() const override; - bool Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) override; - - // == Read operations ======================================================= - bool ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) override; - - // == Write operations ====================================================== - bool WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) override; - - // == Finalizing/closing streams =========================================== - bool FlushBlocking(ErrorPtr* error) override; - bool CloseBlocking(ErrorPtr* error) override; - - // == Data availability monitoring ========================================== - - // Override for Stream::WaitForData to start watching the associated file - // descriptor for non-blocking read/write operations. - bool WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) override; - - // Runs select() on the file descriptor to wait until we can do non-blocking - // I/O on it. - bool WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) override; - - // Cancels pending asynchronous read/write operations. - void CancelPendingAsyncOperations() override; - - private: - friend class FileStreamTest; - - // Internal constructor used by the factory methods Open(), CreateTemporary(), - // and FromFileDescriptor(). - FileStream(std::unique_ptr fd_interface, - AccessMode mode); - - // Wrapper for the file descriptor. Used in testing to mock out the real - // file system APIs. - std::unique_ptr fd_interface_; - - // The access mode this stream is open with. - AccessMode access_mode_{AccessMode::READ_WRITE}; - - // Set to false for streams that are guaranteed non-seekable. - bool seekable_{true}; - - // Set to false for streams that have unknown size. - bool can_get_size_{false}; - - DISALLOW_COPY_AND_ASSIGN(FileStream); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_FILE_STREAM_H_ diff --git a/chromeos/streams/file_stream_unittest.cc b/chromeos/streams/file_stream_unittest.cc deleted file mode 100644 index 7f01067..0000000 --- a/chromeos/streams/file_stream_unittest.cc +++ /dev/null @@ -1,1117 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using testing::InSequence; -using testing::Return; -using testing::ReturnArg; -using testing::SaveArg; -using testing::SetErrnoAndReturn; -using testing::_; - -namespace chromeos { - -namespace { - -// gmock action that would return a blocking situation from a read() or write(). -ACTION(ReturnWouldBlock) { - errno = EWOULDBLOCK; - return -1; -} - -// Helper function to read one byte from the stream. -inline int ReadByte(Stream* stream) { - uint8_t byte = 0; - return stream->ReadAllBlocking(&byte, sizeof(byte), nullptr) ? byte : -1; -} - -// Helper function to write one byte from the stream. -inline bool WriteByte(Stream* stream, uint8_t byte) { - return stream->WriteAllBlocking(&byte, sizeof(byte), nullptr); -} - -// Helper function to test file stream workflow on newly created file. -void TestCreateFile(Stream* stream) { - ASSERT_TRUE(stream->IsOpen()); - - // Set up a sample data buffer. - std::vector in_buffer(256); - std::iota(in_buffer.begin(), in_buffer.end(), 0); - - // Initial assumptions about empty file stream. - EXPECT_TRUE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(0, stream->GetSize()); - - // Write sample data. - EXPECT_TRUE(stream->WriteAllBlocking(in_buffer.data(), in_buffer.size(), - nullptr)); - EXPECT_EQ(in_buffer.size(), stream->GetPosition()); - EXPECT_EQ(in_buffer.size(), stream->GetSize()); - - // Rewind the stream to the beginning. - uint64_t pos = 0; - EXPECT_TRUE(stream->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr)); - EXPECT_EQ(0, pos); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(in_buffer.size(), stream->GetSize()); - - // Read the file contents back. - std::vector out_buffer(256); - EXPECT_TRUE(stream->ReadAllBlocking(out_buffer.data(), out_buffer.size(), - nullptr)); - EXPECT_EQ(out_buffer.size(), stream->GetPosition()); - EXPECT_EQ(out_buffer.size(), stream->GetSize()); - - // Make sure the data read matches those written. - EXPECT_EQ(in_buffer, out_buffer); - - // Random read/write - EXPECT_TRUE(stream->Seek(10, Stream::Whence::FROM_BEGIN, &pos, nullptr)); - EXPECT_EQ(10, pos); - - // Since our data buffer contained values from 0 to 255, the byte at position - // 10 will contain the value of 10. - EXPECT_EQ(10, ReadByte(stream)); - EXPECT_EQ(11, ReadByte(stream)); - EXPECT_EQ(12, ReadByte(stream)); - EXPECT_TRUE(stream->Seek(7, Stream::Whence::FROM_CURRENT, nullptr, nullptr)); - EXPECT_EQ(20, ReadByte(stream)); - - EXPECT_EQ(21, stream->GetPosition()); - EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, &pos, nullptr)); - EXPECT_EQ(19, pos); - EXPECT_TRUE(WriteByte(stream, 100)); - EXPECT_EQ(20, ReadByte(stream)); - EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, nullptr, nullptr)); - EXPECT_EQ(100, ReadByte(stream)); - EXPECT_EQ(20, ReadByte(stream)); - EXPECT_TRUE(stream->Seek(-1, Stream::Whence::FROM_END, &pos, nullptr)); - EXPECT_EQ(255, pos); - EXPECT_EQ(255, ReadByte(stream)); - EXPECT_EQ(-1, ReadByte(stream)); -} - -} // anonymous namespace - -// A mock file descriptor wrapper to test low-level file API used by FileStream. -class MockFileDescriptor : public FileStream::FileDescriptorInterface { - public: - MOCK_CONST_METHOD0(IsOpen, bool()); - MOCK_METHOD2(Read, ssize_t(void*, size_t)); - MOCK_METHOD2(Write, ssize_t(const void*, size_t)); - MOCK_METHOD2(Seek, off64_t(off64_t, int)); - MOCK_CONST_METHOD0(GetFileMode, mode_t()); - MOCK_CONST_METHOD0(GetSize, uint64_t()); - MOCK_CONST_METHOD1(Truncate, int(off64_t)); - MOCK_METHOD0(Flush, int()); - MOCK_METHOD0(Close, int()); - MOCK_METHOD3(WaitForData, - bool(Stream::AccessMode, const DataCallback&, ErrorPtr*)); - MOCK_METHOD3(WaitForDataBlocking, - int(Stream::AccessMode, base::TimeDelta, Stream::AccessMode*)); - MOCK_METHOD0(CancelPendingAsyncOperations, void()); -}; - -class FileStreamTest : public testing::Test { - public: - void SetUp() override { - CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); - } - - MockFileDescriptor& fd_mock() { - return *static_cast(stream_->fd_interface_.get()); - } - - void CreateStream(mode_t file_mode, Stream::AccessMode access_mode) { - std::unique_ptr fd{new MockFileDescriptor{}}; - EXPECT_CALL(*fd, GetFileMode()).WillOnce(Return(file_mode)); - stream_.reset(new FileStream(std::move(fd), access_mode)); - EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(true)); - } - - void ExpectStreamClosed(const ErrorPtr& error) const { - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kStreamClosed, error->GetCode()); - EXPECT_EQ("Stream is closed", error->GetMessage()); - } - - void ExpectStreamOffsetTooLarge(const ErrorPtr& error) const { - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); - EXPECT_EQ("The stream offset value is out of range", error->GetMessage()); - } - - inline static char* IntToPtr(int addr) { - return reinterpret_cast(addr); - } - - inline static const char* IntToConstPtr(int addr) { - return reinterpret_cast(addr); - } - - bool CallWaitForData(Stream::AccessMode mode, ErrorPtr* error) { - return stream_->WaitForData(mode, {}, error); - } - - std::unique_ptr stream_; - - const uint64_t kMaxSize = std::numeric_limits::max(); - const uint64_t kTooLargeSize = std::numeric_limits::max(); - - // Dummy buffer pointer values to make sure that input pointer values - // are delegated to the file interface without a change. - char* const test_read_buffer_ = IntToPtr(12345); - const char* const test_write_buffer_ = IntToConstPtr(67890); -}; - -TEST_F(FileStreamTest, IsOpen) { - EXPECT_TRUE(stream_->IsOpen()); - EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); - EXPECT_FALSE(stream_->IsOpen()); -} - -TEST_F(FileStreamTest, CanRead) { - CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanRead()); - EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false)); - EXPECT_FALSE(stream_->CanRead()); - CreateStream(S_IFREG, Stream::AccessMode::READ); - EXPECT_TRUE(stream_->CanRead()); - CreateStream(S_IFREG, Stream::AccessMode::WRITE); - EXPECT_FALSE(stream_->CanRead()); -} - -TEST_F(FileStreamTest, CanWrite) { - CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanWrite()); - EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false)); - EXPECT_FALSE(stream_->CanWrite()); - CreateStream(S_IFREG, Stream::AccessMode::READ); - EXPECT_FALSE(stream_->CanWrite()); - CreateStream(S_IFREG, Stream::AccessMode::WRITE); - EXPECT_TRUE(stream_->CanWrite()); -} - -TEST_F(FileStreamTest, CanSeek) { - CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanSeek()); - CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanSeek()); - CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanSeek()); - CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanSeek()); - CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE); - EXPECT_FALSE(stream_->CanSeek()); - CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE); - EXPECT_FALSE(stream_->CanSeek()); - CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE); - EXPECT_FALSE(stream_->CanSeek()); - - CreateStream(S_IFREG, Stream::AccessMode::READ); - EXPECT_TRUE(stream_->CanSeek()); - CreateStream(S_IFREG, Stream::AccessMode::WRITE); - EXPECT_TRUE(stream_->CanSeek()); -} - -TEST_F(FileStreamTest, CanGetSize) { - CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanGetSize()); - CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanGetSize()); - CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanGetSize()); - CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE); - EXPECT_TRUE(stream_->CanGetSize()); - CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE); - EXPECT_FALSE(stream_->CanGetSize()); - CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE); - EXPECT_FALSE(stream_->CanGetSize()); - CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE); - EXPECT_FALSE(stream_->CanGetSize()); - - CreateStream(S_IFREG, Stream::AccessMode::READ); - EXPECT_TRUE(stream_->CanGetSize()); - CreateStream(S_IFREG, Stream::AccessMode::WRITE); - EXPECT_TRUE(stream_->CanGetSize()); -} - -TEST_F(FileStreamTest, GetSize) { - EXPECT_CALL(fd_mock(), GetSize()).WillRepeatedly(Return(12345)); - EXPECT_EQ(12345u, stream_->GetSize()); - EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); - EXPECT_EQ(0u, stream_->GetSize()); -} - -TEST_F(FileStreamTest, SetSizeBlocking) { - EXPECT_CALL(fd_mock(), Truncate(0)).WillOnce(Return(0)); - EXPECT_TRUE(stream_->SetSizeBlocking(0, nullptr)); - - EXPECT_CALL(fd_mock(), Truncate(123)).WillOnce(Return(0)); - EXPECT_TRUE(stream_->SetSizeBlocking(123, nullptr)); - - EXPECT_CALL(fd_mock(), Truncate(kMaxSize)).WillOnce(Return(0)); - EXPECT_TRUE(stream_->SetSizeBlocking(kMaxSize, nullptr)); -} - -TEST_F(FileStreamTest, SetSizeBlocking_Fail) { - chromeos::ErrorPtr error; - - EXPECT_CALL(fd_mock(), Truncate(1235)).WillOnce(SetErrnoAndReturn(EIO, -1)); - EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EIO", error->GetCode()); - - error.reset(); - EXPECT_FALSE(stream_->SetSizeBlocking(kTooLargeSize, &error)); - ExpectStreamOffsetTooLarge(error); - - error.reset(); - EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); - EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error)); - ExpectStreamClosed(error); -} - -TEST_F(FileStreamTest, GetRemainingSize) { - EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(234)); - EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1234)); - EXPECT_EQ(1000u, stream_->GetRemainingSize()); - - EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(1234)); - EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1000)); - EXPECT_EQ(0u, stream_->GetRemainingSize()); -} - -TEST_F(FileStreamTest, Seek_Set) { - uint64_t pos = 0; - - EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET)).WillOnce(Return(0)); - EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr)); - EXPECT_EQ(0u, pos); - - EXPECT_CALL(fd_mock(), Seek(123456, SEEK_SET)).WillOnce(Return(123456)); - EXPECT_TRUE(stream_->Seek(123456, Stream::Whence::FROM_BEGIN, &pos, nullptr)); - EXPECT_EQ(123456u, pos); - - EXPECT_CALL(fd_mock(), Seek(kMaxSize, SEEK_SET)) - .WillRepeatedly(Return(kMaxSize)); - EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, &pos, - nullptr)); - EXPECT_EQ(kMaxSize, pos); - EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, nullptr, - nullptr)); -} - -TEST_F(FileStreamTest, Seek_Cur) { - uint64_t pos = 0; - - EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(100)); - EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_CURRENT, &pos, nullptr)); - EXPECT_EQ(100u, pos); - - EXPECT_CALL(fd_mock(), Seek(234, SEEK_CUR)).WillOnce(Return(1234)); - EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_CURRENT, &pos, nullptr)); - EXPECT_EQ(1234u, pos); - - EXPECT_CALL(fd_mock(), Seek(-100, SEEK_CUR)).WillOnce(Return(900)); - EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_CURRENT, &pos, nullptr)); - EXPECT_EQ(900u, pos); -} - -TEST_F(FileStreamTest, Seek_End) { - uint64_t pos = 0; - - EXPECT_CALL(fd_mock(), Seek(0, SEEK_END)).WillOnce(Return(1000)); - EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_END, &pos, nullptr)); - EXPECT_EQ(1000u, pos); - - EXPECT_CALL(fd_mock(), Seek(234, SEEK_END)).WillOnce(Return(10234)); - EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_END, &pos, nullptr)); - EXPECT_EQ(10234u, pos); - - EXPECT_CALL(fd_mock(), Seek(-100, SEEK_END)).WillOnce(Return(9900)); - EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_END, &pos, nullptr)); - EXPECT_EQ(9900u, pos); -} - -TEST_F(FileStreamTest, Seek_Fail) { - chromeos::ErrorPtr error; - EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET)) - .WillOnce(SetErrnoAndReturn(EPIPE, -1)); - EXPECT_FALSE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, nullptr, &error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EPIPE", error->GetCode()); -} - -TEST_F(FileStreamTest, ReadAsync) { - size_t read_size = 0; - bool failed = false; - auto success_callback = [&read_size](size_t size) { read_size = size; }; - auto error_callback = [&failed](const Error* error) { failed = true; }; - FileStream::FileDescriptorInterface::DataCallback data_callback; - - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)) - .WillOnce(ReturnWouldBlock()); - EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - EXPECT_TRUE(stream_->ReadAsync(test_read_buffer_, 100, - base::Bind(success_callback), - base::Bind(error_callback), - nullptr)); - EXPECT_EQ(0u, read_size); - EXPECT_FALSE(failed); - - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(83)); - data_callback.Run(Stream::AccessMode::READ); - EXPECT_EQ(83u, read_size); - EXPECT_FALSE(failed); -} - -TEST_F(FileStreamTest, ReadNonBlocking) { - size_t size = 0; - bool eos = false; - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) - .WillRepeatedly(ReturnArg<1>()); - EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, - nullptr)); - EXPECT_EQ(100u, size); - EXPECT_FALSE(eos); - - EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 0, &size, &eos, - nullptr)); - EXPECT_EQ(0u, size); - EXPECT_FALSE(eos); - - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)).WillOnce(Return(0)); - EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, - nullptr)); - EXPECT_EQ(0u, size); - EXPECT_TRUE(eos); - - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos, - nullptr)); - EXPECT_EQ(0u, size); - EXPECT_FALSE(eos); -} - -TEST_F(FileStreamTest, ReadNonBlocking_Fail) { - size_t size = 0; - chromeos::ErrorPtr error; - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)) - .WillOnce(SetErrnoAndReturn(EACCES, -1)); - EXPECT_FALSE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, nullptr, - &error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EACCES", error->GetCode()); -} - -TEST_F(FileStreamTest, ReadBlocking) { - size_t size = 0; - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); - EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 100, &size, nullptr)); - EXPECT_EQ(20u, size); - - { - InSequence seq; - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)).WillOnce(Return(45)); - } - EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 80, &size, nullptr)); - EXPECT_EQ(45u, size); - - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 50)).WillOnce(Return(0)); - EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 50, &size, nullptr)); - EXPECT_EQ(0u, size); -} - -TEST_F(FileStreamTest, ReadBlocking_Fail) { - { - InSequence seq; - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) - .WillOnce(SetErrnoAndReturn(EBADF, -1)); - } - chromeos::ErrorPtr error; - size_t size = 0; - EXPECT_FALSE(stream_->ReadBlocking(test_read_buffer_, 80, &size, &error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EBADF", error->GetCode()); -} - -TEST_F(FileStreamTest, ReadAllBlocking) { - { - InSequence seq; - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); - EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) - .WillOnce(Return(45)); - EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35)) - .WillOnce(Return(35)); - } - EXPECT_TRUE(stream_->ReadAllBlocking(test_read_buffer_, 100, nullptr)); -} - -TEST_F(FileStreamTest, ReadAllBlocking_Fail) { - { - InSequence seq; - EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20)); - EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80)) - .WillOnce(Return(0)); - } - chromeos::ErrorPtr error; - EXPECT_FALSE(stream_->ReadAllBlocking(test_read_buffer_, 100, &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); - EXPECT_EQ("Reading past the end of stream", error->GetMessage()); -} - -TEST_F(FileStreamTest, WriteAsync) { - size_t write_size = 0; - bool failed = false; - auto success_callback = [&write_size](size_t size) { write_size = size; }; - auto error_callback = [&failed](const Error* error) { failed = true; }; - FileStream::FileDescriptorInterface::DataCallback data_callback; - - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)) - .WillOnce(ReturnWouldBlock()); - EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - EXPECT_TRUE(stream_->WriteAsync(test_write_buffer_, 100, - base::Bind(success_callback), - base::Bind(error_callback), - nullptr)); - EXPECT_EQ(0u, write_size); - EXPECT_FALSE(failed); - - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(87)); - data_callback.Run(Stream::AccessMode::WRITE); - EXPECT_EQ(87u, write_size); - EXPECT_FALSE(failed); -} - -TEST_F(FileStreamTest, WriteNonBlocking) { - size_t size = 0; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) - .WillRepeatedly(ReturnArg<1>()); - EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, - nullptr)); - EXPECT_EQ(100u, size); - - EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 0, &size, nullptr)); - EXPECT_EQ(0u, size); - - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)).WillOnce(Return(0)); - EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, - nullptr)); - EXPECT_EQ(0u, size); - - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, - nullptr)); - EXPECT_EQ(0u, size); -} - -TEST_F(FileStreamTest, WriteNonBlocking_Fail) { - size_t size = 0; - chromeos::ErrorPtr error; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)) - .WillOnce(SetErrnoAndReturn(EACCES, -1)); - EXPECT_FALSE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size, - &error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EACCES", error->GetCode()); -} - -TEST_F(FileStreamTest, WriteBlocking) { - size_t size = 0; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20)); - EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 100, &size, nullptr)); - EXPECT_EQ(20u, size); - - { - InSequence seq; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)).WillOnce(Return(45)); - } - EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 80, &size, nullptr)); - EXPECT_EQ(45u, size); - - { - InSequence seq; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(0)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(1)); - } - EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 50, &size, nullptr)); - EXPECT_EQ(1u, size); -} - -TEST_F(FileStreamTest, WriteBlocking_Fail) { - { - InSequence seq; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(SetErrnoAndReturn(EBADF, -1)); - } - chromeos::ErrorPtr error; - size_t size = 0; - EXPECT_FALSE(stream_->WriteBlocking(test_write_buffer_, 80, &size, &error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EBADF", error->GetCode()); -} - -TEST_F(FileStreamTest, WriteAllBlocking) { - { - InSequence seq; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20)); - EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80)) - .WillOnce(Return(45)); - EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(Return(1)); - EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35)) - .WillOnce(Return(35)); - } - EXPECT_TRUE(stream_->WriteAllBlocking(test_write_buffer_, 100, nullptr)); -} - -TEST_F(FileStreamTest, WriteAllBlocking_Fail) { - { - InSequence seq; - EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)) - .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(SetErrnoAndReturn(EBADF, -1)); - } - chromeos::ErrorPtr error; - EXPECT_FALSE(stream_->WriteAllBlocking(test_write_buffer_, 80, &error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EBADF", error->GetCode()); -} - -TEST_F(FileStreamTest, WaitForDataBlocking_Timeout) { - EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _)) - .WillOnce(Return(0)); - chromeos::ErrorPtr error; - EXPECT_FALSE(stream_->WaitForDataBlocking(Stream::AccessMode::WRITE, {}, - nullptr, &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kTimeout, error->GetCode()); -} - -TEST_F(FileStreamTest, FlushBlocking) { - EXPECT_TRUE(stream_->FlushBlocking(nullptr)); -} - -TEST_F(FileStreamTest, CloseBlocking) { - EXPECT_CALL(fd_mock(), Close()).WillOnce(Return(0)); - EXPECT_TRUE(stream_->CloseBlocking(nullptr)); - - EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false)); - EXPECT_TRUE(stream_->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, CloseBlocking_Fail) { - chromeos::ErrorPtr error; - EXPECT_CALL(fd_mock(), Close()).WillOnce(SetErrnoAndReturn(EFBIG, -1)); - EXPECT_FALSE(stream_->CloseBlocking(&error)); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EFBIG", error->GetCode()); -} - -TEST_F(FileStreamTest, WaitForData) { - EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _)) - .WillOnce(Return(true)); - EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ, nullptr)); - - EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _)) - .WillOnce(Return(true)); - EXPECT_TRUE(CallWaitForData(Stream::AccessMode::WRITE, nullptr)); - - EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _)) - .WillOnce(Return(true)); - EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr)); - - EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _)) - .WillOnce(Return(false)); - EXPECT_FALSE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr)); -} - -TEST_F(FileStreamTest, CreateTemporary) { - StreamPtr stream = FileStream::CreateTemporary(nullptr); - ASSERT_NE(nullptr, stream.get()); - TestCreateFile(stream.get()); -} - -TEST_F(FileStreamTest, OpenRead) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - std::vector buffer(1024 * 1024); - base::RandBytes(buffer.data(), buffer.size()); - int file_size = buffer.size(); // Stupid base::WriteFile taking "int" size. - ASSERT_EQ(file_size, base::WriteFile(path, buffer.data(), file_size)); - - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::READ, - FileStream::Disposition::OPEN_EXISTING, - nullptr); - ASSERT_NE(nullptr, stream.get()); - ASSERT_TRUE(stream->IsOpen()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_FALSE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0u, stream->GetPosition()); - EXPECT_EQ(buffer.size(), stream->GetSize()); - - - std::vector buffer2(buffer.size()); - EXPECT_TRUE(stream->ReadAllBlocking(buffer2.data(), buffer2.size(), nullptr)); - EXPECT_EQ(buffer2, buffer); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, OpenWrite) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - std::vector buffer(1024 * 1024); - base::RandBytes(buffer.data(), buffer.size()); - - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::WRITE, - FileStream::Disposition::CREATE_ALWAYS, - nullptr); - ASSERT_NE(nullptr, stream.get()); - ASSERT_TRUE(stream->IsOpen()); - EXPECT_FALSE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0u, stream->GetPosition()); - EXPECT_EQ(0u, stream->GetSize()); - - EXPECT_TRUE(stream->WriteAllBlocking(buffer.data(), buffer.size(), nullptr)); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); - - std::vector buffer2(buffer.size()); - int file_size = buffer2.size(); // Stupid base::ReadFile taking "int" size. - ASSERT_EQ(file_size, base::ReadFile(path, buffer2.data(), file_size)); - EXPECT_EQ(buffer2, buffer); -} - -TEST_F(FileStreamTest, Open_OpenExisting) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - std::string data{"Lorem ipsum dolor sit amet ..."}; - int data_size = data.size(); // I hate ints for data size... - ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); - - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::OPEN_EXISTING, - nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0u, stream->GetPosition()); - EXPECT_EQ(data.size(), stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, Open_OpenExisting_Fail) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - - ErrorPtr error; - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::OPEN_EXISTING, - &error); - ASSERT_EQ(nullptr, stream.get()); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("ENOENT", error->GetCode()); -} - -TEST_F(FileStreamTest, Open_CreateAlways_New) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::CREATE_ALWAYS, - nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0u, stream->GetPosition()); - EXPECT_EQ(0u, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, Open_CreateAlways_Existing) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - std::string data{"Lorem ipsum dolor sit amet ..."}; - int data_size = data.size(); // I hate ints for data size... - ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); - - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::CREATE_ALWAYS, - nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0u, stream->GetPosition()); - EXPECT_EQ(0u, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, Open_CreateNewOnly_New) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::CREATE_NEW_ONLY, - nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0u, stream->GetPosition()); - EXPECT_EQ(0u, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, Open_CreateNewOnly_Existing) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - std::string data{"Lorem ipsum dolor sit amet ..."}; - int data_size = data.size(); // I hate ints for data size... - ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); - - ErrorPtr error; - StreamPtr stream = FileStream::Open(path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::CREATE_NEW_ONLY, - &error); - ASSERT_EQ(nullptr, stream.get()); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("EEXIST", error->GetCode()); -} - -TEST_F(FileStreamTest, Open_TruncateExisting_New) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - - ErrorPtr error; - StreamPtr stream = FileStream::Open( - path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::TRUNCATE_EXISTING, - &error); - ASSERT_EQ(nullptr, stream.get()); - EXPECT_EQ(errors::system::kDomain, error->GetDomain()); - EXPECT_EQ("ENOENT", error->GetCode()); -} - -TEST_F(FileStreamTest, Open_TruncateExisting_Existing) { - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"}); - std::string data{"Lorem ipsum dolor sit amet ..."}; - int data_size = data.size(); // I hate ints for data size... - ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size)); - - StreamPtr stream = FileStream::Open( - path, - Stream::AccessMode::READ_WRITE, - FileStream::Disposition::TRUNCATE_EXISTING, - nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_TRUE(stream->CanSeek()); - EXPECT_TRUE(stream->CanGetSize()); - EXPECT_EQ(0u, stream->GetPosition()); - EXPECT_EQ(0u, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, FromFileDescriptor_StdIn) { - StreamPtr stream = - FileStream::FromFileDescriptor(STDIN_FILENO, false, nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->IsOpen()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_FALSE(stream->CanSeek()); - EXPECT_FALSE(stream->CanGetSize()); -} - -TEST_F(FileStreamTest, FromFileDescriptor_StdOut) { - StreamPtr stream = - FileStream::FromFileDescriptor(STDOUT_FILENO, false, nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->IsOpen()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_FALSE(stream->CanSeek()); - EXPECT_FALSE(stream->CanGetSize()); -} - -TEST_F(FileStreamTest, FromFileDescriptor_StdErr) { - StreamPtr stream = - FileStream::FromFileDescriptor(STDERR_FILENO, false, nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->IsOpen()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_FALSE(stream->CanSeek()); - EXPECT_FALSE(stream->CanGetSize()); -} - -TEST_F(FileStreamTest, FromFileDescriptor_ReadNonBlocking) { - int fds[2] = {-1, -1}; - ASSERT_EQ(0, pipe(fds)); - - StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->IsOpen()); - EXPECT_TRUE(stream->CanRead()); - EXPECT_FALSE(stream->CanWrite()); - EXPECT_FALSE(stream->CanSeek()); - EXPECT_FALSE(stream->CanGetSize()); - - char buf[10]; - size_t read = 0; - bool eos = true; - EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); - EXPECT_EQ(0, read); - EXPECT_FALSE(eos); - - std::string data{"foo_bar"}; - EXPECT_TRUE(base::WriteFileDescriptor(fds[1], data.data(), data.size())); - EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); - EXPECT_EQ(data.size(), read); - EXPECT_FALSE(eos); - EXPECT_EQ(data, (std::string{buf, read})); - - EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); - EXPECT_EQ(0, read); - EXPECT_FALSE(eos); - - close(fds[1]); - - EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr)); - EXPECT_EQ(0, read); - EXPECT_TRUE(eos); - - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, FromFileDescriptor_WriteNonBlocking) { - int fds[2] = {-1, -1}; - ASSERT_EQ(0, pipe(fds)); - - StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr); - ASSERT_NE(nullptr, stream.get()); - EXPECT_TRUE(stream->IsOpen()); - EXPECT_FALSE(stream->CanRead()); - EXPECT_TRUE(stream->CanWrite()); - EXPECT_FALSE(stream->CanSeek()); - EXPECT_FALSE(stream->CanGetSize()); - - // Pipe buffer is generally 64K, so 128K should be more than enough. - std::vector buffer(128 * 1024); - base::RandBytes(buffer.data(), buffer.size()); - size_t written = 0; - size_t total_size = 0; - - // Fill the output buffer of the pipe until we can no longer write any data - // to it. - do { - ASSERT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, - nullptr)); - total_size += written; - } while (written == buffer.size()); - - EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, - nullptr)); - EXPECT_EQ(0, written); - - std::vector out_buffer(total_size); - EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size())); - - EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written, - nullptr)); - EXPECT_GT(written, 0); - out_buffer.resize(written); - EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size())); - EXPECT_TRUE(std::equal(out_buffer.begin(), out_buffer.end(), buffer.begin())); - - close(fds[0]); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, FromFileDescriptor_ReadAsync) { - int fds[2] = {-1, -1}; - bool succeeded = false; - bool failed = false; - char buffer[100]; - base::MessageLoopForIO base_loop; - BaseMessageLoop chromeos_loop{&base_loop}; - chromeos_loop.SetAsCurrent(); - - auto success_callback = [&succeeded, &buffer](size_t size) { - std::string data{buffer, buffer + size}; - ASSERT_EQ("abracadabra", data); - succeeded = true; - }; - - auto error_callback = [&failed](const Error* error) { - failed = true; - }; - - auto write_data_callback = [](int write_fd) { - std::string data{"abracadabra"}; - EXPECT_TRUE(base::WriteFileDescriptor(write_fd, data.data(), data.size())); - }; - - ASSERT_EQ(0, pipe(fds)); - - StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr); - - // Write to the pipe with a bit of delay. - chromeos_loop.PostDelayedTask( - FROM_HERE, - base::Bind(write_data_callback, fds[1]), - base::TimeDelta::FromMilliseconds(10)); - - EXPECT_TRUE(stream->ReadAsync(buffer, 100, base::Bind(success_callback), - base::Bind(error_callback), nullptr)); - - auto end_condition = [&failed, &succeeded] { return failed || succeeded; }; - MessageLoopRunUntil(&chromeos_loop, - base::TimeDelta::FromSeconds(1), - base::Bind(end_condition)); - - EXPECT_TRUE(succeeded); - EXPECT_FALSE(failed); - - close(fds[1]); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -TEST_F(FileStreamTest, FromFileDescriptor_WriteAsync) { - int fds[2] = {-1, -1}; - bool succeeded = false; - bool failed = false; - const std::string data{"abracadabra"}; - base::MessageLoopForIO base_loop; - BaseMessageLoop chromeos_loop{&base_loop}; - chromeos_loop.SetAsCurrent(); - - ASSERT_EQ(0, pipe(fds)); - - auto success_callback = [&succeeded, &data](int read_fd, size_t size) { - char buffer[100]; - EXPECT_TRUE(base::ReadFromFD(read_fd, buffer, data.size())); - EXPECT_EQ(data, (std::string{buffer, buffer + data.size()})); - succeeded = true; - }; - - auto error_callback = [&failed](const Error* error) { - failed = true; - }; - - StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr); - - EXPECT_TRUE(stream->WriteAsync(data.data(), data.size(), - base::Bind(success_callback, fds[0]), - base::Bind(error_callback), nullptr)); - - auto end_condition = [&failed, &succeeded] { return failed || succeeded; }; - MessageLoopRunUntil(&chromeos_loop, - base::TimeDelta::FromSeconds(1), - base::Bind(end_condition)); - - EXPECT_TRUE(succeeded); - EXPECT_FALSE(failed); - - close(fds[0]); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); -} - -} // namespace chromeos diff --git a/chromeos/streams/input_stream_set.cc b/chromeos/streams/input_stream_set.cc deleted file mode 100644 index 745b964..0000000 --- a/chromeos/streams/input_stream_set.cc +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include - -namespace chromeos { - -InputStreamSet::InputStreamSet( - std::vector source_streams, - std::vector owned_source_streams, - uint64_t initial_stream_size) - : source_streams_{std::move(source_streams)}, - owned_source_streams_{std::move(owned_source_streams)}, - initial_stream_size_{initial_stream_size} {} - -StreamPtr InputStreamSet::Create(std::vector source_streams, - std::vector owned_source_streams, - ErrorPtr* error) { - StreamPtr stream; - - if (source_streams.empty()) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kInvalidParameter, - "Source stream list is empty"); - return stream; - } - - // Make sure we have only readable streams. - for (Stream* src_stream : source_streams) { - if (!src_stream->CanRead()) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kInvalidParameter, - "The stream list must contain only readable streams"); - return stream; - } - } - - // We are using remaining size here because the multiplexed stream is not - // seekable and the bytes already read are essentially "lost" as far as this - // stream is concerned. - uint64_t initial_stream_size = 0; - for (const Stream* stream : source_streams) - initial_stream_size += stream->GetRemainingSize(); - - stream.reset(new InputStreamSet{std::move(source_streams), - std::move(owned_source_streams), - initial_stream_size}); - return stream; -} - -StreamPtr InputStreamSet::Create(std::vector source_streams, - ErrorPtr* error) { - return Create(std::move(source_streams), {}, error); -} - -StreamPtr InputStreamSet::Create(std::vector owned_source_streams, - ErrorPtr* error) { - std::vector source_streams; - source_streams.reserve(owned_source_streams.size()); - for (const StreamPtr& stream : owned_source_streams) - source_streams.push_back(stream.get()); - return Create(std::move(source_streams), std::move(owned_source_streams), - error); -} - -bool InputStreamSet::IsOpen() const { - return !closed_; -} - -bool InputStreamSet::CanGetSize() const { - bool can_get_size = IsOpen(); - for (const Stream* stream : source_streams_) { - if (!stream->CanGetSize()) { - can_get_size = false; - break; - } - } - return can_get_size; -} - -uint64_t InputStreamSet::GetSize() const { - return initial_stream_size_; -} - -bool InputStreamSet::SetSizeBlocking(uint64_t size, ErrorPtr* error) { - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); -} - -uint64_t InputStreamSet::GetRemainingSize() const { - uint64_t size = 0; - for (const Stream* stream : source_streams_) - size += stream->GetRemainingSize(); - return size; -} - -bool InputStreamSet::Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) { - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); -} - -bool InputStreamSet::ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - while (!source_streams_.empty()) { - Stream* stream = source_streams_.front(); - bool eos = false; - if (!stream->ReadNonBlocking(buffer, size_to_read, size_read, &eos, error)) - return false; - - if (*size_read > 0 || !eos) { - if (end_of_stream) - *end_of_stream = false; - return true; - } - - source_streams_.erase(source_streams_.begin()); - } - *size_read = 0; - if (end_of_stream) - *end_of_stream = true; - return true; -} - -bool InputStreamSet::WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) { - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); -} - -bool InputStreamSet::CloseBlocking(ErrorPtr* error) { - bool success = true; - // We want to close only the owned streams. - for (StreamPtr& stream_ptr : owned_source_streams_) { - if (!stream_ptr->CloseBlocking(error)) - success = false; // Keep going for other streams... - } - owned_source_streams_.clear(); - source_streams_.clear(); - initial_stream_size_ = 0; - closed_ = true; - return success; -} - -bool InputStreamSet::WaitForData( - AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - if (stream_utils::IsWriteAccessMode(mode)) - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); - - if (!source_streams_.empty()) { - Stream* stream = source_streams_.front(); - return stream->WaitForData(mode, callback, error); - } - - MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, mode)); - return true; -} - -bool InputStreamSet::WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) { - if (!IsOpen()) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - - if (stream_utils::IsWriteAccessMode(in_mode)) - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); - - if (!source_streams_.empty()) { - Stream* stream = source_streams_.front(); - return stream->WaitForDataBlocking(in_mode, timeout, out_mode, error); - } - - if (out_mode) - *out_mode = in_mode; - return true; -} - -void InputStreamSet::CancelPendingAsyncOperations() { - if (IsOpen() && !source_streams_.empty()) { - Stream* stream = source_streams_.front(); - stream->CancelPendingAsyncOperations(); - } - Stream::CancelPendingAsyncOperations(); -} - -} // namespace chromeos diff --git a/chromeos/streams/input_stream_set.h b/chromeos/streams/input_stream_set.h deleted file mode 100644 index a37ab08..0000000 --- a/chromeos/streams/input_stream_set.h +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_INPUT_STREAM_SET_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_INPUT_STREAM_SET_H_ - -#include - -#include -#include -#include - -namespace chromeos { - -// Multiplexer stream allows to bundle a bunch of secondary streams in one -// logical stream and simulate a read operation across data concatenated from -// all those source streams. -// -// When created on a set of source streams like stream1, stream2, stream3, etc., -// reading from the multiplexer stream will read all the data from stream1 until -// end-of-stream is reached, then keep reading from stream2, stream3 and so on. -// -// InputStreamSet has an option of owning the underlying source streams -// or just referencing them. Owned streams are passed to InputStreamSet -// with exclusive ownership transfer (using StreamPtr) and those streams will -// be closed/destroyed when InputStreamSet is closed/destroyed. -// Referenced source streams' life time is maintained elsewhere and they must -// be valid for the duration of InputStreamSet's life. Closing the -// muliplexer stream does not close the referenced streams. -class CHROMEOS_EXPORT InputStreamSet : public Stream { - public: - // == Construction ========================================================== - - // Generic method that constructs a multiplexer stream on a list of source - // streams. |source_streams| is the list of all source stream references - // in the order they need to be read from. |owned_source_streams| is a list - // of source stream instances that the multiplexer stream will own. - // Note that the streams from |owned_source_streams| should still be - // referenced in |source_streams| if you need their data to be read from. - // |owned_source_streams| could be empty (in which case none of the source - // streams are not owned), or contain fewer items than in |source_streams|. - static StreamPtr Create(std::vector source_streams, - std::vector owned_source_streams, - ErrorPtr* error); - - // Simple helper method to create a multiplexer stream with a list of - // referenced streams. None of the streams will be owned. - // Effectively calls Create(source_streams, {}, error); - static StreamPtr Create(std::vector source_streams, - ErrorPtr* error); - - // Simple helper method to create a multiplexer stream with a list of - // referenced streams. None of the streams will be owned. - // Effectively calls Create(source_streams, owned_source_streams, error) - // with |source_streams| containing pointers to the streams from - // |owned_source_streams| list. - static StreamPtr Create(std::vector owned_source_streams, - ErrorPtr* error); - - // == Stream capabilities =================================================== - bool IsOpen() const override; - bool CanRead() const override { return true; } - bool CanWrite() const override { return false; } - bool CanSeek() const override { return false; } - bool CanGetSize() const override; - - // == Stream size operations ================================================ - uint64_t GetSize() const override; - bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; - uint64_t GetRemainingSize() const override; - - // == Seek operations ======================================================= - uint64_t GetPosition() const override { return 0; } - bool Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) override; - - // == Read operations ======================================================= - bool ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) override; - - // == Write operations ====================================================== - bool WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) override; - - // == Finalizing/closing streams =========================================== - bool FlushBlocking(ErrorPtr* error) override { return true; } - bool CloseBlocking(ErrorPtr* error) override; - - // == Data availability monitoring ========================================== - bool WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) override; - - bool WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) override; - - void CancelPendingAsyncOperations() override; - - private: - friend class InputStreamSetTest; - - // Internal constructor used by the Create() factory methods. - InputStreamSet(std::vector source_streams, - std::vector owned_source_streams, - uint64_t initial_stream_size); - - // List of streams to read data from. - std::vector source_streams_; - - // List of source streams this stream owns. Owned source streams will be - // closed when InputStreamSet::CloseBlocking() is called and will be - // destroyed when this stream is destroyed. - std::vector owned_source_streams_; - - uint64_t initial_stream_size_{0}; - bool closed_{false}; - - DISALLOW_COPY_AND_ASSIGN(InputStreamSet); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_INPUT_STREAM_SET_H_ diff --git a/chromeos/streams/input_stream_set_unittest.cc b/chromeos/streams/input_stream_set_unittest.cc deleted file mode 100644 index 82ac60c..0000000 --- a/chromeos/streams/input_stream_set_unittest.cc +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include - -using testing::An; -using testing::DoAll; -using testing::InSequence; -using testing::Return; -using testing::SetArgPointee; -using testing::StrictMock; -using testing::_; - -namespace chromeos { - -class InputStreamSetTest : public testing::Test { - public: - void SetUp() override { - itf1_.reset(new StrictMock{}); - itf2_.reset(new StrictMock{}); - stream_.reset(new InputStreamSet({itf1_.get(), itf2_.get()}, {}, 100)); - } - - void TearDown() override { - stream_.reset(); - itf2_.reset(); - itf1_.reset(); - } - - std::unique_ptr> itf1_; - std::unique_ptr> itf2_; - std::unique_ptr stream_; - - inline static void* IntToPtr(int addr) { - return reinterpret_cast(addr); - } -}; - -TEST_F(InputStreamSetTest, InitialFalseAssumptions) { - // Methods that should just succeed/fail without calling underlying streams. - EXPECT_TRUE(stream_->CanRead()); - EXPECT_FALSE(stream_->CanWrite()); - EXPECT_FALSE(stream_->CanSeek()); - EXPECT_EQ(100, stream_->GetSize()); - EXPECT_FALSE(stream_->SetSizeBlocking(0, nullptr)); - EXPECT_FALSE(stream_->GetPosition()); - EXPECT_FALSE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, nullptr, nullptr)); - char buffer[100]; - size_t size = 0; - EXPECT_FALSE(stream_->WriteAsync(buffer, sizeof(buffer), {}, {}, nullptr)); - EXPECT_FALSE(stream_->WriteAllAsync(buffer, sizeof(buffer), {}, {}, nullptr)); - EXPECT_FALSE(stream_->WriteNonBlocking(buffer, sizeof(buffer), &size, - nullptr)); - EXPECT_FALSE(stream_->WriteBlocking(buffer, sizeof(buffer), &size, nullptr)); - EXPECT_FALSE(stream_->WriteAllBlocking(buffer, sizeof(buffer), nullptr)); - EXPECT_TRUE(stream_->FlushBlocking(nullptr)); - EXPECT_TRUE(stream_->CloseBlocking(nullptr)); -} - -TEST_F(InputStreamSetTest, InitialTrueAssumptions) { - // Methods that redirect calls to underlying streams. - EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true)); - EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(true)); - EXPECT_TRUE(stream_->CanGetSize()); - - // Reading from the first stream fails, so the second one shouldn't be used. - EXPECT_CALL(*itf1_, ReadNonBlocking(_, _, _, _, _)) - .WillOnce(Return(false)); - EXPECT_CALL(*itf2_, ReadNonBlocking(_, _, _, _, _)).Times(0); - char buffer[100]; - size_t size = 0; - EXPECT_FALSE(stream_->ReadBlocking(buffer, sizeof(buffer), &size, nullptr)); -} - -TEST_F(InputStreamSetTest, CanGetSize) { - EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true)); - EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(true)); - EXPECT_TRUE(stream_->CanGetSize()); - - EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(false)); - EXPECT_FALSE(stream_->CanGetSize()); - - EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true)); - EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(false)); - EXPECT_FALSE(stream_->CanGetSize()); -} - -TEST_F(InputStreamSetTest, GetRemainingSize) { - EXPECT_CALL(*itf1_, GetRemainingSize()).WillOnce(Return(10)); - EXPECT_CALL(*itf2_, GetRemainingSize()).WillOnce(Return(32)); - EXPECT_EQ(42, stream_->GetRemainingSize()); -} - -TEST_F(InputStreamSetTest, ReadNonBlocking) { - size_t read = 0; - bool eos = false; - - InSequence s; - EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(10), - SetArgPointee<3>(false), - Return(true))); - EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos, - nullptr)); - EXPECT_EQ(10, read); - EXPECT_FALSE(eos); - - EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), SetArgPointee<3>(true), Return(true))); - EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100 , _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(100), - SetArgPointee<3>(false), - Return(true))); - EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos, - nullptr)); - EXPECT_EQ(100, read); - EXPECT_FALSE(eos); - - EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), SetArgPointee<3>(true), Return(true))); - EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos, - nullptr)); - EXPECT_EQ(0, read); - EXPECT_TRUE(eos); -} - -TEST_F(InputStreamSetTest, ReadBlocking) { - size_t read = 0; - - InSequence s; - EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(10), - SetArgPointee<3>(false), - Return(true))); - EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr)); - EXPECT_EQ(10, read); - - EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(true), - Return(true))); - EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(false), - Return(true))); - EXPECT_CALL(*itf2_, WaitForDataBlocking(Stream::AccessMode::READ, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(100), - SetArgPointee<3>(false), - Return(true))); - EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr)); - EXPECT_EQ(100, read); - - EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(true), - Return(true))); - EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr)); - EXPECT_EQ(0, read); -} - -} // namespace chromeos diff --git a/chromeos/streams/memory_containers.cc b/chromeos/streams/memory_containers.cc deleted file mode 100644 index 5a0b653..0000000 --- a/chromeos/streams/memory_containers.cc +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -namespace chromeos { -namespace data_container { - -namespace { - -bool ErrorStreamReadOnly(const tracked_objects::Location& location, - ErrorPtr* error) { - Error::AddTo(error, - location, - errors::stream::kDomain, - errors::stream::kOperationNotSupported, - "Stream is read-only"); - return false; -} - -} // anonymous namespace - -void ContiguousBufferBase::CopyMemoryBlock(void* dest, - const void* src, - size_t size) const { - memcpy(dest, src, size); -} - -bool ContiguousBufferBase::Read(void* buffer, - size_t size_to_read, - size_t offset, - size_t* size_read, - ErrorPtr* error) { - size_t buf_size = GetSize(); - if (offset < buf_size) { - size_t remaining = buf_size - offset; - if (size_to_read >= remaining) { - size_to_read = remaining; - } - const void* src_buffer = GetReadOnlyBuffer(offset, error); - if (!src_buffer) - return false; - - CopyMemoryBlock(buffer, src_buffer, size_to_read); - } else { - size_to_read = 0; - } - if (size_read) - *size_read = size_to_read; - return true; -} - -bool ContiguousBufferBase::Write(const void* buffer, - size_t size_to_write, - size_t offset, - size_t* size_written, - ErrorPtr* error) { - if (size_to_write) { - size_t new_size = offset + size_to_write; - if (GetSize() < new_size && !Resize(new_size, error)) - return false; - void* ptr = GetBuffer(offset, error); - if (!ptr) - return false; - CopyMemoryBlock(ptr, buffer, size_to_write); - if (size_written) - *size_written = size_to_write; - } - return true; -} - -bool ContiguousReadOnlyBufferBase::Write(const void* buffer, - size_t size_to_write, - size_t offset, - size_t* size_written, - ErrorPtr* error) { - return ErrorStreamReadOnly(FROM_HERE, error); -} - -bool ContiguousReadOnlyBufferBase::Resize(size_t new_size, ErrorPtr* error) { - return ErrorStreamReadOnly(FROM_HERE, error); -} - -void* ContiguousReadOnlyBufferBase::GetBuffer(size_t offset, ErrorPtr* error) { - ErrorStreamReadOnly(FROM_HERE, error); - return nullptr; -} - -ByteBuffer::ByteBuffer(size_t reserve_size) - : VectorPtr(new std::vector()) { - vector_ptr_->reserve(reserve_size); -} - -ByteBuffer::~ByteBuffer() { - delete vector_ptr_; -} - -StringPtr::StringPtr(std::string* string) : string_ptr_(string) {} - -bool StringPtr::Resize(size_t new_size, ErrorPtr* error) { - string_ptr_->resize(new_size); - return true; -} - -const void* StringPtr::GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const { - return string_ptr_->data() + offset; -} - -void* StringPtr::GetBuffer(size_t offset, ErrorPtr* error) { - return &(*string_ptr_)[offset]; -} - -ReadOnlyStringRef::ReadOnlyStringRef(const std::string& string) - : string_ref_(string) {} - -const void* ReadOnlyStringRef::GetReadOnlyBuffer(size_t offset, - ErrorPtr* error) const { - return string_ref_.data() + offset; -} - -ReadOnlyStringCopy::ReadOnlyStringCopy(std::string string) - : ReadOnlyStringRef(string_copy_), string_copy_(std::move(string)) {} - -} // namespace data_container -} // namespace chromeos diff --git a/chromeos/streams/memory_containers.h b/chromeos/streams/memory_containers.h deleted file mode 100644 index 3b8d216..0000000 --- a/chromeos/streams/memory_containers.h +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_MEMORY_CONTAINERS_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_MEMORY_CONTAINERS_H_ - -#include -#include - -#include -#include -#include - -namespace chromeos { -namespace data_container { - -// MemoryStream class relies on helper classes defined below to support data -// storage in various types of containers. -// A particular implementation of container type (e.g. based on raw memory -// buffers, std::vector, std::string or others) need to implement the container -// interface provided by data_container::DataContainerInterface. -// Low-level functionality such as reading data from and writing data to the -// container, getting and changing the buffer size, and so on, must be provided. -// Not all methods must be provided. For example, for read-only containers, only -// read operations can be provided. -class CHROMEOS_EXPORT DataContainerInterface { - public: - DataContainerInterface() = default; - virtual ~DataContainerInterface() = default; - - // Read the data from the container into |buffer|. Up to |size_to_read| bytes - // must be read at a time. The container can return fewer bytes. The actual - // size of data read is provided in |size_read|. - // If the read operation fails, the function must return false and provide - // additional information about the error in |error| object. - virtual bool Read(void* buffer, - size_t size_to_read, - size_t offset, - size_t* size_read, - ErrorPtr* error) = 0; - - // Writes |size_to_write| bytes of data from |buffer| into the container. - // The container may accept fewer bytes of data. The actual size of data - // written is provided in |size_written|. - // If the read operation fails, the function must return false and provide - // additional information about the error in |error| object. - virtual bool Write(const void* buffer, - size_t size_to_write, - size_t offset, - size_t* size_written, - ErrorPtr* error) = 0; - // Resizes the container to the new size specified in |new_size|. - virtual bool Resize(size_t new_size, ErrorPtr* error) = 0; - // Returns the current size of the container. - virtual size_t GetSize() const = 0; - // Returns true if the container is read-only. - virtual bool IsReadOnly() const = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(DataContainerInterface); -}; - -// ContiguousBufferBase is a helper base class for memory containers that -// employ contiguous memory for all of their data. This class provides the -// default implementation for Read() and Write() functions and requires the -// implementations to provide GetBuffer() and/or ReadOnlyBuffer() functions. -class CHROMEOS_EXPORT ContiguousBufferBase : public DataContainerInterface { - public: - ContiguousBufferBase() = default; - // Implementation of DataContainerInterface::Read(). - bool Read(void* buffer, - size_t size_to_read, - size_t offset, - size_t* size_read, - ErrorPtr* error) override; - // Implementation of DataContainerInterface::Write(). - bool Write(const void* buffer, - size_t size_to_write, - size_t offset, - size_t* size_written, - ErrorPtr* error) override; - - // Overload to provide the pointer to the read-only data for the container at - // the specified |offset|. In case of an error, this function must return - // nullptr and provide error details in |error| object if provided. - virtual const void* GetReadOnlyBuffer(size_t offset, - ErrorPtr* error) const = 0; - // Overload to provide the pointer to the read/write data for the container at - // the specified |offset|. In case of an error, this function must return - // nullptr and provide error details in |error| object if provided. - virtual void* GetBuffer(size_t offset, ErrorPtr* error) = 0; - - protected: - // Wrapper around memcpy which can be mocked out in tests. - virtual void CopyMemoryBlock(void* dest, const void* src, size_t size) const; - - private: - DISALLOW_COPY_AND_ASSIGN(ContiguousBufferBase); -}; - -// ContiguousReadOnlyBufferBase is a specialization of ContiguousBufferBase for -// read-only containers. -class CHROMEOS_EXPORT ContiguousReadOnlyBufferBase - : public ContiguousBufferBase { - public: - ContiguousReadOnlyBufferBase() = default; - // Fails with an error "operation_not_supported" (Stream is read-only) error. - bool Write(const void* buffer, - size_t size_to_write, - size_t offset, - size_t* size_written, - ErrorPtr* error) override; - // Fails with an error "operation_not_supported" (Stream is read-only) error. - bool Resize(size_t new_size, ErrorPtr* error) override; - // Fails with an error "operation_not_supported" (Stream is read-only) error. - bool IsReadOnly() const override { return true; } - // Fails with an error "operation_not_supported" (Stream is read-only) error. - void* GetBuffer(size_t offset, ErrorPtr* error) override; - - private: - DISALLOW_COPY_AND_ASSIGN(ContiguousReadOnlyBufferBase); -}; - -// ReadOnlyBuffer implements a read-only container based on raw memory block. -class CHROMEOS_EXPORT ReadOnlyBuffer : public ContiguousReadOnlyBufferBase { - public: - // Constructs the container based at the pointer to memory |buffer| and its - // |size|. The pointer to the memory must be valid throughout life-time of - // the stream using this container. - ReadOnlyBuffer(const void* buffer, size_t size) - : buffer_(buffer), size_(size) {} - - // Returns the pointer to data at |offset|. - const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { - return reinterpret_cast(buffer_) + offset; - } - // Returns the size of the container. - size_t GetSize() const override { return size_; } - - private: - // Raw memory pointer to the data block and its size. - const void* buffer_; - size_t size_; - - DISALLOW_COPY_AND_ASSIGN(ReadOnlyBuffer); -}; - -// VectorPtr is a read/write container based on a vector pointer. -// This is a template class to allow usage of both vector and -// vector without duplicating the implementation. -template -class VectorPtr : public ContiguousBufferBase { - public: - static_assert(sizeof(T) == 1, "Only char/byte is supported"); - explicit VectorPtr(std::vector* vector) : vector_ptr_(vector) {} - - bool Resize(size_t new_size, ErrorPtr* error) override { - vector_ptr_->resize(new_size); - return true; - } - size_t GetSize() const override { return vector_ptr_->size(); } - bool IsReadOnly() const override { return false; } - const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { - return reinterpret_cast(vector_ptr_->data()) + offset; - } - void* GetBuffer(size_t offset, ErrorPtr* error) override { - return reinterpret_cast(vector_ptr_->data()) + offset; - } - - protected: - std::vector* vector_ptr_; - - private: - DISALLOW_COPY_AND_ASSIGN(VectorPtr); -}; - -// ReadOnlyVectorRef is a read-only container based on a vector reference. -// This is a template class to allow usage of both vector and -// vector without duplicating the implementation. -template -class ReadOnlyVectorRef : public ContiguousReadOnlyBufferBase { - public: - static_assert(sizeof(T) == 1, "Only char/byte is supported"); - explicit ReadOnlyVectorRef(const std::vector& vector) - : vector_ref_(vector) {} - - const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { - return reinterpret_cast(vector_ref_.data()) + offset; - } - size_t GetSize() const override { return vector_ref_.size(); } - - protected: - const std::vector& vector_ref_; - - private: - DISALLOW_COPY_AND_ASSIGN(ReadOnlyVectorRef); -}; - -// ReadOnlyVectorCopy is a read-only container based on a copy of vector. -// This container actually owns the data stored in the vector. -// This is a template class to allow usage of both vector and -// vector without duplicating the implementation. -template -class ReadOnlyVectorCopy : public ContiguousReadOnlyBufferBase { - public: - static_assert(sizeof(T) == 1, "Only char/byte is supported"); - explicit ReadOnlyVectorCopy(std::vector vector) - : vector_copy_(std::move(vector)) {} - - ReadOnlyVectorCopy(const T* buffer, size_t size) - : vector_copy_(buffer, buffer + size) {} - - const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override { - return reinterpret_cast(vector_copy_.data()) + offset; - } - size_t GetSize() const override { return vector_copy_.size(); } - - protected: - std::vector vector_copy_; - - private: - DISALLOW_COPY_AND_ASSIGN(ReadOnlyVectorCopy); -}; - -// ByteBuffer is a read/write container that manages the data and underlying -// storage. -class CHROMEOS_EXPORT ByteBuffer : public VectorPtr { - public: - explicit ByteBuffer(size_t reserve_size); - ~ByteBuffer() override; - - private: - DISALLOW_COPY_AND_ASSIGN(ByteBuffer); -}; - -// StringPtr is a read/write container based on external std::string storage. -class CHROMEOS_EXPORT StringPtr : public ContiguousBufferBase { - public: - explicit StringPtr(std::string* string); - - bool Resize(size_t new_size, ErrorPtr* error) override; - size_t GetSize() const override { return string_ptr_->size(); } - bool IsReadOnly() const override { return false; } - const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override; - void* GetBuffer(size_t offset, ErrorPtr* error) override; - - protected: - std::string* string_ptr_; - - private: - DISALLOW_COPY_AND_ASSIGN(StringPtr); -}; - -// ReadOnlyStringRef is a read-only container based on external std::string. -class CHROMEOS_EXPORT ReadOnlyStringRef : public ContiguousReadOnlyBufferBase { - public: - explicit ReadOnlyStringRef(const std::string& string); - const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override; - size_t GetSize() const override { return string_ref_.size(); } - - protected: - const std::string& string_ref_; - - private: - DISALLOW_COPY_AND_ASSIGN(ReadOnlyStringRef); -}; - -// ReadOnlyStringCopy is a read-only container based on a copy of a std::string. -// This container actually owns the data stored in the string. -class CHROMEOS_EXPORT ReadOnlyStringCopy : public ReadOnlyStringRef { - public: - explicit ReadOnlyStringCopy(std::string string); - - protected: - std::string string_copy_; - - private: - DISALLOW_COPY_AND_ASSIGN(ReadOnlyStringCopy); -}; - -} // namespace data_container -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_MEMORY_CONTAINERS_H_ diff --git a/chromeos/streams/memory_containers_unittest.cc b/chromeos/streams/memory_containers_unittest.cc deleted file mode 100644 index 1882ac6..0000000 --- a/chromeos/streams/memory_containers_unittest.cc +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include -#include -#include -#include - -using testing::DoAll; -using testing::Invoke; -using testing::InSequence; -using testing::Return; -using testing::WithArgs; -using testing::_; - -namespace chromeos { - -namespace { -class MockContiguousBuffer : public data_container::ContiguousBufferBase { - public: - MockContiguousBuffer() = default; - - MOCK_METHOD2(Resize, bool(size_t, ErrorPtr*)); - MOCK_CONST_METHOD0(GetSize, size_t()); - MOCK_CONST_METHOD0(IsReadOnly, bool()); - - MOCK_CONST_METHOD2(GetReadOnlyBuffer, const void*(size_t, ErrorPtr*)); - MOCK_METHOD2(GetBuffer, void*(size_t, ErrorPtr*)); - - MOCK_CONST_METHOD3(CopyMemoryBlock, void(void*, const void*, size_t)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockContiguousBuffer); -}; -} // anonymous namespace - -class MemoryContainerTest : public testing::Test { - public: - inline static void* IntToPtr(int addr) { - return reinterpret_cast(addr); - } - - inline static const void* IntToConstPtr(int addr) { - return reinterpret_cast(addr); - } - - // Dummy buffer pointer values used as external data source/destination for - // read/write operations. - void* const test_read_buffer_ = IntToPtr(12345); - const void* const test_write_buffer_ = IntToConstPtr(67890); - - // Dummy buffer pointer values used for internal buffer owned by the - // memory buffer container class. - const void* const const_buffer_ = IntToConstPtr(123); - void* const buffer_ = IntToPtr(456); - - MockContiguousBuffer container_; -}; - -TEST_F(MemoryContainerTest, Read_WithinBuffer) { - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, GetReadOnlyBuffer(10, _)) - .WillOnce(Return(const_buffer_)); - EXPECT_CALL(container_, - CopyMemoryBlock(test_read_buffer_, const_buffer_, 50)).Times(1); - } - size_t read = 0; - ErrorPtr error; - EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 10, &read, &error)); - EXPECT_EQ(50, read); - EXPECT_EQ(nullptr, error.get()); -} - -TEST_F(MemoryContainerTest, Read_PastEndOfBuffer) { - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, GetReadOnlyBuffer(80, _)) - .WillOnce(Return(const_buffer_)); - EXPECT_CALL(container_, - CopyMemoryBlock(test_read_buffer_, const_buffer_, 20)).Times(1); - } - size_t read = 0; - EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 80, &read, nullptr)); - EXPECT_EQ(20, read); -} - -TEST_F(MemoryContainerTest, Read_OutsideBuffer) { - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - size_t read = 0; - EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 100, &read, nullptr)); - EXPECT_EQ(0, read); -} - -TEST_F(MemoryContainerTest, Read_Error) { - auto OnReadError = [](ErrorPtr* error) { - Error::AddTo(error, FROM_HERE, "domain", "read_error", "read error"); - }; - - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, GetReadOnlyBuffer(0, _)) - .WillOnce(DoAll(WithArgs<1>(Invoke(OnReadError)), Return(nullptr))); - } - size_t read = 0; - ErrorPtr error; - EXPECT_FALSE(container_.Read(test_read_buffer_, 10, 0, &read, &error)); - EXPECT_EQ(0, read); - EXPECT_NE(nullptr, error.get()); - EXPECT_EQ("domain", error->GetDomain()); - EXPECT_EQ("read_error", error->GetCode()); - EXPECT_EQ("read error", error->GetMessage()); -} - -TEST_F(MemoryContainerTest, Write_WithinBuffer) { - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, GetBuffer(10, _)) - .WillOnce(Return(buffer_)); - EXPECT_CALL(container_, - CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1); - } - size_t written = 0; - ErrorPtr error; - EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 10, &written, &error)); - EXPECT_EQ(50, written); - EXPECT_EQ(nullptr, error.get()); -} - -TEST_F(MemoryContainerTest, Write_PastEndOfBuffer) { - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, Resize(130, _)).WillOnce(Return(true)); - EXPECT_CALL(container_, GetBuffer(80, _)) - .WillOnce(Return(buffer_)); - EXPECT_CALL(container_, - CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1); - } - size_t written = 0; - EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 80, &written, nullptr)); - EXPECT_EQ(50, written); -} - -TEST_F(MemoryContainerTest, Write_OutsideBuffer) { - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, Resize(160, _)).WillOnce(Return(true)); - EXPECT_CALL(container_, GetBuffer(110, _)) - .WillOnce(Return(buffer_)); - EXPECT_CALL(container_, - CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1); - } - size_t written = 0; - EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 110, &written, nullptr)); - EXPECT_EQ(50, written); -} - -TEST_F(MemoryContainerTest, Write_Error_Resize) { - auto OnWriteError = [](ErrorPtr* error) { - Error::AddTo(error, FROM_HERE, "domain", "write_error", "resize error"); - }; - - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, Resize(160, _)) - .WillOnce(DoAll(WithArgs<1>(Invoke(OnWriteError)), Return(false))); - } - size_t written = 0; - ErrorPtr error; - EXPECT_FALSE(container_.Write(test_write_buffer_, 50, 110, &written, &error)); - EXPECT_EQ(0, written); - EXPECT_NE(nullptr, error.get()); - EXPECT_EQ("domain", error->GetDomain()); - EXPECT_EQ("write_error", error->GetCode()); - EXPECT_EQ("resize error", error->GetMessage()); -} - -TEST_F(MemoryContainerTest, Write_Error) { - auto OnWriteError = [](ErrorPtr* error) { - Error::AddTo(error, FROM_HERE, "domain", "write_error", "write error"); - }; - - { - InSequence s; - EXPECT_CALL(container_, GetSize()).WillOnce(Return(100)); - EXPECT_CALL(container_, Resize(160, _)).WillOnce(Return(true)); - EXPECT_CALL(container_, GetBuffer(110, _)) - .WillOnce(DoAll(WithArgs<1>(Invoke(OnWriteError)), Return(nullptr))); - } - size_t written = 0; - ErrorPtr error; - EXPECT_FALSE(container_.Write(test_write_buffer_, 50, 110, &written, &error)); - EXPECT_EQ(0, written); - EXPECT_NE(nullptr, error.get()); - EXPECT_EQ("domain", error->GetDomain()); - EXPECT_EQ("write_error", error->GetCode()); - EXPECT_EQ("write error", error->GetMessage()); -} - -} // namespace chromeos - diff --git a/chromeos/streams/memory_stream.cc b/chromeos/streams/memory_stream.cc deleted file mode 100644 index 6bc5756..0000000 --- a/chromeos/streams/memory_stream.cc +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include - -namespace chromeos { - -MemoryStream::MemoryStream( - std::unique_ptr container, - size_t stream_position) - : container_{std::move(container)}, stream_position_{stream_position} {} - -StreamPtr MemoryStream::OpenRef(const void* buffer, - size_t size, - ErrorPtr* error) { - std::unique_ptr container{ - new data_container::ReadOnlyBuffer{buffer, size}}; - return CreateEx(std::move(container), 0, error); -} - -StreamPtr MemoryStream::OpenCopyOf(const void* buffer, - size_t size, - ErrorPtr* error) { - std::unique_ptr> container{ - new data_container::ReadOnlyVectorCopy{ - reinterpret_cast(buffer), size}}; - return CreateEx(std::move(container), 0, error); -} - -StreamPtr MemoryStream::OpenRef(const std::string& buffer, ErrorPtr* error) { - std::unique_ptr container{ - new data_container::ReadOnlyStringRef{buffer}}; - return CreateEx(std::move(container), 0, error); -} - -StreamPtr MemoryStream::OpenCopyOf(std::string buffer, ErrorPtr* error) { - std::unique_ptr container{ - new data_container::ReadOnlyStringCopy{std::move(buffer)}}; - return CreateEx(std::move(container), 0, error); -} - -StreamPtr MemoryStream::OpenRef(const char* buffer, ErrorPtr* error) { - return OpenRef(buffer, std::strlen(buffer), error); -} - -StreamPtr MemoryStream::OpenCopyOf(const char* buffer, ErrorPtr* error) { - return OpenCopyOf(buffer, std::strlen(buffer), error); -} - -StreamPtr MemoryStream::Create(size_t reserve_size, ErrorPtr* error) { - std::unique_ptr container{ - new data_container::ByteBuffer{reserve_size}}; - return CreateEx(std::move(container), 0, error); -} - -StreamPtr MemoryStream::CreateRef(std::string* buffer, ErrorPtr* error) { - std::unique_ptr container{ - new data_container::StringPtr{buffer}}; - return CreateEx(std::move(container), 0, error); -} - -StreamPtr MemoryStream::CreateRefForAppend(std::string* buffer, - ErrorPtr* error) { - std::unique_ptr container{ - new data_container::StringPtr{buffer}}; - return CreateEx(std::move(container), buffer->size(), error); -} - -StreamPtr MemoryStream::CreateEx( - std::unique_ptr container, - size_t stream_position, - ErrorPtr* error) { - ignore_result(error); // Unused. - return StreamPtr{new MemoryStream(std::move(container), stream_position)}; -} - -bool MemoryStream::IsOpen() const { return container_ != nullptr; } -bool MemoryStream::CanRead() const { return IsOpen(); } - -bool MemoryStream::CanWrite() const { - return IsOpen() && !container_->IsReadOnly(); -} - -bool MemoryStream::CanSeek() const { return IsOpen(); } -bool MemoryStream::CanGetSize() const { return IsOpen(); } - -uint64_t MemoryStream::GetSize() const { - return IsOpen() ? container_->GetSize() : 0; -} - -bool MemoryStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { - if (!CheckContainer(error)) - return false; - return container_->Resize(size, error); -} - -uint64_t MemoryStream::GetRemainingSize() const { - uint64_t pos = GetPosition(); - uint64_t size = GetSize(); - return (pos < size) ? size - pos : 0; -} - -uint64_t MemoryStream::GetPosition() const { - return IsOpen() ? stream_position_ : 0; -} - -bool MemoryStream::Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) { - uint64_t pos = 0; - if (!CheckContainer(error) || - !stream_utils::CalculateStreamPosition(FROM_HERE, offset, whence, - stream_position_, GetSize(), &pos, - error)) { - return false; - } - if (pos > static_cast(std::numeric_limits::max())) { - // This can only be the case on 32 bit systems. - chromeos::Error::AddTo(error, - FROM_HERE, - errors::stream::kDomain, - errors::stream::kInvalidParameter, - "Stream pointer position is outside allowed limits"); - return false; - } - - stream_position_ = static_cast(pos); - if (new_position) - *new_position = stream_position_; - return true; -} - -bool MemoryStream::ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) { - if (!CheckContainer(error)) - return false; - size_t read = 0; - if (!container_->Read(buffer, size_to_read, stream_position_, &read, error)) - return false; - stream_position_ += read; - *size_read = read; - if (end_of_stream) - *end_of_stream = (read == 0) && (size_to_read != 0); - return true; -} - -bool MemoryStream::WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) { - if (!CheckContainer(error)) - return false; - if (!container_->Write(buffer, size_to_write, stream_position_, size_written, - error)) { - return false; - } - stream_position_ += *size_written; - return true; -} - -bool MemoryStream::FlushBlocking(ErrorPtr* error) { - return CheckContainer(error); -} - -bool MemoryStream::CloseBlocking(ErrorPtr* error) { - ignore_result(error); // Unused. - container_.reset(); - return true; -} - -bool MemoryStream::CheckContainer(ErrorPtr* error) const { - return container_ || stream_utils::ErrorStreamClosed(FROM_HERE, error); -} - -bool MemoryStream::WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) { - MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, mode)); - return true; -} - -bool MemoryStream::WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) { - if (out_mode) - *out_mode = in_mode; - return true; -} - -} // namespace chromeos diff --git a/chromeos/streams/memory_stream.h b/chromeos/streams/memory_stream.h deleted file mode 100644 index ac03440..0000000 --- a/chromeos/streams/memory_stream.h +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_MEMORY_STREAM_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_MEMORY_STREAM_H_ - -#include -#include - -#include -#include -#include -#include -#include - -namespace chromeos { - -// MemoryStream is a chromeos::Stream implementation for memory buffer. A number -// of memory containers are supported, such as raw memory pointers, data stored -// in std::vector and std::string. -// MemoryStream offers support for constant read-only memory buffers as well as -// for writable buffers that can grow when needed. -// A memory stream is created by using the OpenNNN and CreateNNN factory methods -// to construct a read-only and writable streams respectively. -// The following factory methods overloads are provided: -// - OpenRef - overloads for constructing the stream on a constant read-only -// memory buffer that is not owned by the stream. The buffer -// pointer/reference must remain valid throughout the lifetime -// of the constructed stream object. The benefit of this is that -// no data copying is performed and the underlying container can -// be manipulated outside of the stream. -// - OpenCopyOf - overloads to construct a stream that copies the data from the -// memory buffer and maintains the copied data until the stream -// is closed or destroyed. This makes it possible to construct -// a read-only streams on transient data or for cases where -// it is not possible or necessary to maintain the lifetime of -// the underlying memory buffer. -// - Create - creates a new internal memory buffer that can be written to -// or read from using the stream I/O interface. -// - CreateRef - constructs a read/write stream on a reference of data -// container such as std::vector or std::string which must -// remain valid throughout the lifetime of the memory stream. -// The data already stored in the container is maintained, -// however the stream pointer is set to the beginning of the -// data when the stream is created. -// - CreateRefForAppend - similar to CreateRef except that it automatically -// positions the stream seek pointer at the end of the data, -// which makes it possible to append more data to the existing -// container. -class CHROMEOS_EXPORT MemoryStream : public Stream { - public: - // == Construction ========================================================== - - // Constructs a read-only stream on a generic memory buffer. The data - // pointed to by |buffer| will be copied and owned by the stream object. - static StreamPtr OpenCopyOf(const void* buffer, size_t size, ErrorPtr* error); - static StreamPtr OpenCopyOf(std::string buffer, ErrorPtr* error); - static StreamPtr OpenCopyOf(const char* buffer, ErrorPtr* error); - // Only vectors of char and uint8_t are supported. - template - inline static StreamPtr OpenCopyOf(std::vector buffer, - ErrorPtr* error) { - std::unique_ptr> container{ - new data_container::ReadOnlyVectorCopy{std::move(buffer)}}; - return CreateEx(std::move(container), 0, error); - } - - // Constructs a read-only stream on a generic memory buffer which is owned - // by the caller. - // ***WARNING***: The |buffer| pointer must be valid for as long as the stream - // object is alive. The stream does not do any additional lifetime management - // for the data pointed to by |buffer| and destroying that buffer before - // the stream is closed will lead to unexpected behavior. - static StreamPtr OpenRef(const void* buffer, size_t size, ErrorPtr* error); - static StreamPtr OpenRef(const std::string& buffer, ErrorPtr* error); - static StreamPtr OpenRef(const char* buffer, ErrorPtr* error); - // Only vectors of char and uint8_t are supported. - template - inline static StreamPtr OpenRef(const std::vector& buffer, - ErrorPtr* error) { - std::unique_ptr> container{ - new data_container::ReadOnlyVectorRef{buffer}}; - return CreateEx(std::move(container), 0, error); - } - - ///------------------------------------------------------------------------ - // Creates new stream for reading/writing. This method creates an internal - // memory buffer and maintains it until the stream is closed. |reserve_size| - // parameter is a hint of the buffer size to pre-allocate. This does not - // affect the memory buffer reported size. The buffer can grow past that - // amount if needed. - static StreamPtr Create(size_t reserve_size, ErrorPtr* error); - - inline static StreamPtr Create(ErrorPtr* error) { return Create(0, error); } - - // Creates new stream for reading/writing stored in a string. The string - // |buffer| must remain valid during the lifetime of the stream. - // The stream pointer will be at the beginning of the string and the string's - // content is preserved. - static StreamPtr CreateRef(std::string* buffer, ErrorPtr* error); - - // Creates new stream for reading/writing stored in a vector. The vector - // |buffer| must remain valid during the lifetime of the stream. - // The stream pointer will be at the beginning of the data and the vector's - // content is preserved. - // Only vectors of char and uint8_t are supported. - template - static StreamPtr CreateRef(std::vector* buffer, ErrorPtr* error) { - std::unique_ptr> container{ - new data_container::VectorPtr{buffer}}; - return CreateEx(std::move(container), 0, error); - } - - // Creates new stream for reading/writing stored in a string. The string - // |buffer| must remain valid during the lifetime of the stream. - // The stream pointer will be at the end of the string and the string's - // content is preserved. - static StreamPtr CreateRefForAppend(std::string* buffer, ErrorPtr* error); - - // Creates new stream for reading/writing stored in a vector. The vector - // |buffer| must remain valid during the lifetime of the stream. - // The stream pointer will be at the end of the data and the vector's - // content is preserved. - // Only vectors of char and uint8_t are supported. - template - static StreamPtr CreateRefForAppend(std::vector* buffer, ErrorPtr* error) { - std::unique_ptr> container{ - new data_container::VectorPtr{buffer}}; - return CreateEx(std::move(container), buffer->size() * sizeof(T), error); - } - - ///------------------------------------------------------------------------ - // Generic stream creation on a data container. Takes an arbitrary |container| - // and constructs a stream using it. The container determines the traits of - // the stream (e.g. whether it is read-only, what operations are supported - // and so on). |stream_position| is the current stream pointer position at - // creation time. - static StreamPtr CreateEx( - std::unique_ptr container, - size_t stream_position, - ErrorPtr* error); - - // == Stream capabilities =================================================== - bool IsOpen() const override; - bool CanRead() const override; - bool CanWrite() const override; - bool CanSeek() const override; - bool CanGetSize() const override; - - // == Stream size operations ================================================ - uint64_t GetSize() const override; - bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; - uint64_t GetRemainingSize() const override; - - // == Seek operations ======================================================= - uint64_t GetPosition() const override; - bool Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) override; - - // == Read operations ======================================================= - bool ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) override; - - // == Write operations ====================================================== - bool WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) override; - - // == Finalizing/closing streams =========================================== - bool FlushBlocking(ErrorPtr* error) override; - bool CloseBlocking(ErrorPtr* error) override; - - // == Data availability monitoring ========================================== - bool WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) override; - - bool WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) override; - - private: - friend class MemoryStreamTest; - - // Private constructor used by MemoryStream::OpenNNNN() and - // MemoryStream::CreateNNNN() factory methods. - MemoryStream( - std::unique_ptr container, - size_t stream_position); - - // Checks if the stream has a valid container. - bool CheckContainer(ErrorPtr* error) const; - - // Data container the stream is using to write and/or read data. - std::unique_ptr container_; - - // The current stream pointer position. - size_t stream_position_{0}; - - DISALLOW_COPY_AND_ASSIGN(MemoryStream); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_MEMORY_STREAM_H_ diff --git a/chromeos/streams/memory_stream_unittest.cc b/chromeos/streams/memory_stream_unittest.cc deleted file mode 100644 index 423681a..0000000 --- a/chromeos/streams/memory_stream_unittest.cc +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -using testing::DoAll; -using testing::Return; -using testing::SetArgPointee; -using testing::_; - -namespace chromeos { - -namespace { - -int ReadByte(Stream* stream, chromeos::ErrorPtr* error) { - uint8_t byte = 0; - return stream->ReadAllBlocking(&byte, sizeof(byte), error) ? byte : -1; -} - -class MockMemoryContainer : public data_container::DataContainerInterface { - public: - MockMemoryContainer() = default; - - MOCK_METHOD5(Read, bool(void*, size_t, size_t, size_t*, ErrorPtr*)); - MOCK_METHOD5(Write, bool(const void*, size_t, size_t, size_t*, ErrorPtr*)); - MOCK_METHOD2(Resize, bool(size_t, ErrorPtr*)); - MOCK_CONST_METHOD0(GetSize, size_t()); - MOCK_CONST_METHOD0(IsReadOnly, bool()); - - private: - DISALLOW_COPY_AND_ASSIGN(MockMemoryContainer); -}; - -} // anonymous namespace - -class MemoryStreamTest : public testing::Test { - public: - void SetUp() override { - std::unique_ptr container{new MockMemoryContainer{}}; - stream_.reset(new MemoryStream{std::move(container), 0}); - } - - MockMemoryContainer& container_mock() { - return *static_cast(stream_->container_.get()); - } - - inline static void* IntToPtr(int addr) { - return reinterpret_cast(addr); - } - - inline static const void* IntToConstPtr(int addr) { - return reinterpret_cast(addr); - } - - std::unique_ptr stream_; - // Dummy buffer pointer values to make sure that input pointer values - // are delegated to the stream interface without a change. - void* const test_read_buffer_ = IntToPtr(12345); - const void* const test_write_buffer_ = IntToConstPtr(67890); - // We limit the size of memory streams to be the maximum size of either of - // size_t (on 32 bit platforms) or the size of signed 64 bit integer. - const size_t kSizeMax = - std::min(std::numeric_limits::max(), - std::numeric_limits::max()); -}; - -TEST_F(MemoryStreamTest, CanRead) { - EXPECT_TRUE(stream_->CanRead()); -} - -TEST_F(MemoryStreamTest, CanWrite) { - EXPECT_CALL(container_mock(), IsReadOnly()) - .WillOnce(Return(true)) - .WillOnce(Return(false)); - - EXPECT_FALSE(stream_->CanWrite()); - EXPECT_TRUE(stream_->CanWrite()); -} - -TEST_F(MemoryStreamTest, CanSeek) { - EXPECT_TRUE(stream_->CanSeek()); -} - -TEST_F(MemoryStreamTest, GetSize) { - EXPECT_CALL(container_mock(), GetSize()) - .WillOnce(Return(0)) - .WillOnce(Return(1234)) - .WillOnce(Return(kSizeMax)); - - EXPECT_EQ(0, stream_->GetSize()); - EXPECT_EQ(1234, stream_->GetSize()); - EXPECT_EQ(kSizeMax, stream_->GetSize()); -} - -TEST_F(MemoryStreamTest, SetSizeBlocking) { - EXPECT_CALL(container_mock(), Resize(0, _)).WillOnce(Return(true)); - - ErrorPtr error; - EXPECT_TRUE(stream_->SetSizeBlocking(0, &error)); - EXPECT_EQ(nullptr, error.get()); - - EXPECT_CALL(container_mock(), Resize(kSizeMax, nullptr)) - .WillOnce(Return(true)); - - EXPECT_TRUE(stream_->SetSizeBlocking(kSizeMax, nullptr)); -} - -TEST_F(MemoryStreamTest, SeekAndGetPosition) { - EXPECT_EQ(0, stream_->GetPosition()); - - EXPECT_CALL(container_mock(), GetSize()).WillRepeatedly(Return(200)); - - ErrorPtr error; - uint64_t new_pos = 0; - EXPECT_TRUE(stream_->Seek(2, Stream::Whence::FROM_BEGIN, &new_pos, &error)); - EXPECT_EQ(nullptr, error.get()); - EXPECT_EQ(2, new_pos); - EXPECT_EQ(2, stream_->GetPosition()); - EXPECT_TRUE(stream_->Seek(2, Stream::Whence::FROM_CURRENT, &new_pos, &error)); - EXPECT_EQ(nullptr, error.get()); - EXPECT_EQ(4, new_pos); - EXPECT_EQ(4, stream_->GetPosition()); - - EXPECT_TRUE(stream_->Seek(-2, Stream::Whence::FROM_END, nullptr, nullptr)); - EXPECT_EQ(198, stream_->GetPosition()); - - EXPECT_CALL(container_mock(), GetSize()).WillOnce(Return(kSizeMax)); - EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_END, nullptr, nullptr)); - EXPECT_EQ(kSizeMax, stream_->GetPosition()); -} - -TEST_F(MemoryStreamTest, ReadNonBlocking) { - size_t read = 0; - bool eos = false; - - EXPECT_CALL(container_mock(), Read(test_read_buffer_, 10, 0, _, nullptr)) - .WillOnce(DoAll(SetArgPointee<3>(5), Return(true))); - - EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 10, &read, &eos, - nullptr)); - EXPECT_EQ(5, read); - EXPECT_EQ(5, stream_->GetPosition()); - EXPECT_FALSE(eos); - - EXPECT_CALL(container_mock(), Read(test_read_buffer_, 100, 5, _, nullptr)) - .WillOnce(DoAll(SetArgPointee<3>(100), Return(true))); - - EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &read, &eos, - nullptr)); - EXPECT_EQ(100, read); - EXPECT_EQ(105, stream_->GetPosition()); - EXPECT_FALSE(eos); - - EXPECT_CALL(container_mock(), Read(test_read_buffer_, 10, 105, _, nullptr)) - .WillOnce(DoAll(SetArgPointee<3>(0), Return(true))); - - EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 10, &read, &eos, - nullptr)); - EXPECT_EQ(0, read); - EXPECT_EQ(105, stream_->GetPosition()); - EXPECT_TRUE(eos); -} - -TEST_F(MemoryStreamTest, WriteNonBlocking) { - size_t written = 0; - - EXPECT_CALL(container_mock(), Write(test_write_buffer_, 10, 0, _, nullptr)) - .WillOnce(DoAll(SetArgPointee<3>(5), Return(true))); - - EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 10, &written, - nullptr)); - EXPECT_EQ(5, written); - EXPECT_EQ(5, stream_->GetPosition()); - - EXPECT_CALL(container_mock(), Write(test_write_buffer_, 100, 5, _, nullptr)) - .WillOnce(DoAll(SetArgPointee<3>(100), Return(true))); - - EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &written, - nullptr)); - EXPECT_EQ(100, written); - EXPECT_EQ(105, stream_->GetPosition()); - - EXPECT_CALL(container_mock(), Write(test_write_buffer_, 10, 105, _, nullptr)) - .WillOnce(DoAll(SetArgPointee<3>(10), Return(true))); - - EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 10, &written, - nullptr)); - EXPECT_EQ(115, stream_->GetPosition()); -} - -////////////////////////////////////////////////////////////////////////////// -// Factory method tests. -TEST(MemoryStream, OpenBinary) { - char buffer[] = {1, 2, 3}; - StreamPtr stream = MemoryStream::OpenRef(buffer, sizeof(buffer), nullptr); - buffer[0] = 5; - EXPECT_EQ(3, stream->GetSize()); - EXPECT_EQ(5, ReadByte(stream.get(), nullptr)); - EXPECT_EQ(2, ReadByte(stream.get(), nullptr)); - EXPECT_EQ(3, ReadByte(stream.get(), nullptr)); - chromeos::ErrorPtr error; - EXPECT_EQ(-1, ReadByte(stream.get(), &error)); - EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); - EXPECT_EQ("Reading past the end of stream", error->GetMessage()); -} - -TEST(MemoryStream, OpenBinaryCopy) { - char buffer[] = {1, 2, 3}; - StreamPtr stream = MemoryStream::OpenCopyOf(buffer, sizeof(buffer), nullptr); - buffer[0] = 5; - EXPECT_EQ(3, stream->GetSize()); - EXPECT_EQ(1, ReadByte(stream.get(), nullptr)); - EXPECT_EQ(2, ReadByte(stream.get(), nullptr)); - EXPECT_EQ(3, ReadByte(stream.get(), nullptr)); - chromeos::ErrorPtr error; - EXPECT_EQ(-1, ReadByte(stream.get(), &error)); - EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); - EXPECT_EQ("Reading past the end of stream", error->GetMessage()); -} - -TEST(MemoryStream, OpenString) { - std::string str("abcd"); - StreamPtr stream = MemoryStream::OpenRef(str, nullptr); - str[0] = 'A'; - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ('A', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); - EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); -} - -TEST(MemoryStream, OpenStringCopy) { - std::string str("abcd"); - StreamPtr stream = MemoryStream::OpenCopyOf(str, nullptr); - str[0] = 'A'; - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ('a', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); - EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); -} - -TEST(MemoryStream, OpenCharBuf) { - char str[] = "abcd"; - StreamPtr stream = MemoryStream::OpenRef(str, nullptr); - str[0] = 'A'; - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ('A', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); - EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); -} - -TEST(MemoryStream, OpenCharBufCopy) { - char str[] = "abcd"; - StreamPtr stream = MemoryStream::OpenCopyOf(str, nullptr); - str[0] = 'A'; - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ('a', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); - EXPECT_EQ(-1, ReadByte(stream.get(), nullptr)); -} - -TEST(MemoryStream, OpenVector) { - std::vector data = {'a', 'b', 'c', 'd'}; - StreamPtr stream = MemoryStream::OpenRef(data, nullptr); - data[0] = 'A'; - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(4, stream->GetRemainingSize()); - EXPECT_EQ('A', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); - EXPECT_EQ(4, stream->GetPosition()); - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ(0, stream->GetRemainingSize()); -} - -TEST(MemoryStream, OpenVectorCopy) { - std::vector data = {'a', 'b', 'c', 'd'}; - StreamPtr stream = MemoryStream::OpenCopyOf(data, nullptr); - data[0] = 'A'; - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(4, stream->GetRemainingSize()); - EXPECT_EQ('a', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('b', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('c', ReadByte(stream.get(), nullptr)); - EXPECT_EQ('d', ReadByte(stream.get(), nullptr)); - EXPECT_EQ(4, stream->GetPosition()); - EXPECT_EQ(4, stream->GetSize()); - EXPECT_EQ(0, stream->GetRemainingSize()); -} - -TEST(MemoryStream, CreateVector) { - std::vector buffer; - StreamPtr stream = MemoryStream::CreateRef(&buffer, nullptr); - EXPECT_TRUE(buffer.empty()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(0, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); - - buffer.resize(5); - std::iota(buffer.begin(), buffer.end(), 0); - stream = MemoryStream::CreateRef(&buffer, nullptr); - EXPECT_FALSE(buffer.empty()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(5, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); - - stream = MemoryStream::CreateRefForAppend(&buffer, nullptr); - EXPECT_FALSE(buffer.empty()); - EXPECT_EQ(5, stream->GetPosition()); - EXPECT_EQ(5, stream->GetSize()); - EXPECT_TRUE(stream->WriteAllBlocking("abcde", 5, nullptr)); - EXPECT_FALSE(buffer.empty()); - EXPECT_EQ(10, stream->GetPosition()); - EXPECT_EQ(10, stream->GetSize()); - EXPECT_TRUE(stream->SetPosition(0, nullptr)); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(10, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); - - EXPECT_EQ(10, buffer.size()); - EXPECT_EQ((std::vector{0, 1, 2, 3, 4, 'a', 'b', 'c', 'd', 'e'}), - buffer); - - stream = MemoryStream::OpenRef(buffer, nullptr); - EXPECT_FALSE(buffer.empty()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(10, stream->GetSize()); -} - -TEST(MemoryStream, CreateString) { - std::string buffer; - StreamPtr stream = MemoryStream::CreateRef(&buffer, nullptr); - EXPECT_TRUE(buffer.empty()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(0, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); - - buffer = "abc"; - stream = MemoryStream::CreateRef(&buffer, nullptr); - EXPECT_FALSE(buffer.empty()); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(3, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); - - stream = MemoryStream::CreateRefForAppend(&buffer, nullptr); - EXPECT_FALSE(buffer.empty()); - EXPECT_EQ(3, stream->GetPosition()); - EXPECT_EQ(3, stream->GetSize()); - EXPECT_TRUE(stream->WriteAllBlocking("d_1234", 6, nullptr)); - EXPECT_FALSE(buffer.empty()); - EXPECT_EQ(9, stream->GetPosition()); - EXPECT_EQ(9, stream->GetSize()); - EXPECT_TRUE(stream->SetPosition(0, nullptr)); - EXPECT_EQ(0, stream->GetPosition()); - EXPECT_EQ(9, stream->GetSize()); - EXPECT_TRUE(stream->CloseBlocking(nullptr)); - EXPECT_EQ(9, buffer.size()); - EXPECT_EQ("abcd_1234", buffer); -} - -} // namespace chromeos diff --git a/chromeos/streams/mock_stream.h b/chromeos/streams/mock_stream.h deleted file mode 100644 index a1303c2..0000000 --- a/chromeos/streams/mock_stream.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_MOCK_STREAM_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_MOCK_STREAM_H_ - -#include - -#include - -namespace chromeos { - -// Mock Stream implementation for testing. -class MockStream : public Stream { - public: - MockStream() = default; - - MOCK_CONST_METHOD0(IsOpen, bool()); - MOCK_CONST_METHOD0(CanRead, bool()); - MOCK_CONST_METHOD0(CanWrite, bool()); - MOCK_CONST_METHOD0(CanSeek, bool()); - MOCK_CONST_METHOD0(CanGetSize, bool()); - - MOCK_CONST_METHOD0(GetSize, uint64_t()); - MOCK_METHOD2(SetSizeBlocking, bool(uint64_t, ErrorPtr*)); - MOCK_CONST_METHOD0(GetRemainingSize, uint64_t()); - - MOCK_CONST_METHOD0(GetPosition, uint64_t()); - MOCK_METHOD4(Seek, bool(int64_t, Whence, uint64_t*, ErrorPtr*)); - - MOCK_METHOD5(ReadAsync, bool(void*, - size_t, - const base::Callback&, - const ErrorCallback&, - ErrorPtr*)); - MOCK_METHOD5(ReadAllAsync, bool(void*, - size_t, - const base::Closure&, - const ErrorCallback&, - ErrorPtr*)); - MOCK_METHOD5(ReadNonBlocking, bool(void*, size_t, size_t*, bool*, ErrorPtr*)); - MOCK_METHOD4(ReadBlocking, bool(void*, size_t, size_t*, ErrorPtr*)); - MOCK_METHOD3(ReadAllBlocking, bool(void*, size_t, ErrorPtr*)); - - MOCK_METHOD5(WriteAsync, bool(const void*, - size_t, - const base::Callback&, - const ErrorCallback&, - ErrorPtr*)); - MOCK_METHOD5(WriteAllAsync, bool(const void*, - size_t, - const base::Closure&, - const ErrorCallback&, - ErrorPtr*)); - MOCK_METHOD4(WriteNonBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); - MOCK_METHOD4(WriteBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); - MOCK_METHOD3(WriteAllBlocking, bool(const void*, size_t, ErrorPtr*)); - - MOCK_METHOD1(FlushBlocking, bool(ErrorPtr*)); - MOCK_METHOD1(CloseBlocking, bool(ErrorPtr*)); - - MOCK_METHOD3(WaitForData, bool(AccessMode, - const base::Callback&, - ErrorPtr*)); - MOCK_METHOD4(WaitForDataBlocking, - bool(AccessMode, base::TimeDelta, AccessMode*, ErrorPtr*)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockStream); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_MOCK_STREAM_H_ diff --git a/chromeos/streams/openssl_stream_bio.cc b/chromeos/streams/openssl_stream_bio.cc deleted file mode 100644 index 7c40b18..0000000 --- a/chromeos/streams/openssl_stream_bio.cc +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include - -namespace chromeos { - -namespace { - -// Internal functions for implementing OpenSSL BIO on chromeos::Stream. -int stream_write(BIO* bio, const char* buf, int size) { - chromeos::Stream* stream = static_cast(bio->ptr); - size_t written = 0; - BIO_clear_retry_flags(bio); - if (!stream->WriteNonBlocking(buf, size, &written, nullptr)) - return -1; - - if (written == 0) { - // Socket's output buffer is full, try again later. - BIO_set_retry_write(bio); - return -1; - } - return base::checked_cast(written); -} - -int stream_read(BIO* bio, char* buf, int size) { - chromeos::Stream* stream = static_cast(bio->ptr); - size_t read = 0; - BIO_clear_retry_flags(bio); - bool eos = false; - if (!stream->ReadNonBlocking(buf, size, &read, &eos, nullptr)) - return -1; - - if (read == 0 && !eos) { - // If no data is available on the socket and it is still not closed, - // ask OpenSSL to try again later. - BIO_set_retry_read(bio); - return -1; - } - return base::checked_cast(read); -} - -// NOLINTNEXTLINE(runtime/int) -long stream_ctrl(BIO* bio, int cmd, long num, void* ptr) { - if (cmd == BIO_CTRL_FLUSH) { - chromeos::Stream* stream = static_cast(bio->ptr); - return stream->FlushBlocking(nullptr) ? 1 : 0; - } - return 0; -} - -int stream_new(BIO* bio) { - bio->shutdown = 0; // By default do not close underlying stream on shutdown. - bio->init = 0; - bio->num = -1; // not used. - return 1; -} - -int stream_free(BIO* bio) { - if (!bio) - return 0; - - if (bio->init) { - bio->ptr = nullptr; - bio->init = 0; - } - return 1; -} - -// BIO_METHOD structure describing the BIO built on top of chromeos::Stream. -BIO_METHOD stream_method = { - 0x7F | BIO_TYPE_SOURCE_SINK, // type: 0x7F is an arbitrary unused type ID. - "stream", // name - stream_write, // write function - stream_read, // read function - nullptr, // puts function, not implemented - nullptr, // gets function, not implemented - stream_ctrl, // control function - stream_new, // creation - stream_free, // free - nullptr, // callback function, not used -}; - -} // anonymous namespace - -BIO* BIO_new_stream(chromeos::Stream* stream) { - BIO* bio = BIO_new(&stream_method); - if (bio) { - bio->ptr = stream; - bio->init = 1; - } - return bio; -} - -} // namespace chromeos diff --git a/chromeos/streams/openssl_stream_bio.h b/chromeos/streams/openssl_stream_bio.h deleted file mode 100644 index dba2b1c..0000000 --- a/chromeos/streams/openssl_stream_bio.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_OPENSSL_STREAM_BIO_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_OPENSSL_STREAM_BIO_H_ - -#include - -// Forward-declare BIO as an alias to OpenSSL's internal bio_st structure. -using BIO = struct bio_st; - -namespace chromeos { - -class Stream; - -// Creates a new BIO that uses the chromeos::Stream as the back-end storage. -// The created BIO does *NOT* own the |stream| and the stream must out-live -// the BIO. -// At the moment, only BIO_read and BIO_write operations are supported as well -// as BIO_flush. More functionality could be added to this when/if needed. -// The returned BIO performs *NON-BLOCKING* IO on the underlying stream. -CHROMEOS_EXPORT BIO* BIO_new_stream(chromeos::Stream* stream); - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_OPENSSL_STREAM_BIO_H_ diff --git a/chromeos/streams/openssl_stream_bio_unittests.cc b/chromeos/streams/openssl_stream_bio_unittests.cc deleted file mode 100644 index 5deb521..0000000 --- a/chromeos/streams/openssl_stream_bio_unittests.cc +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include - -#include -#include -#include - -using testing::DoAll; -using testing::Return; -using testing::SetArgPointee; -using testing::StrictMock; -using testing::_; - -namespace chromeos { - -class StreamBIOTest : public testing::Test { - public: - void SetUp() override { - stream_.reset(new StrictMock{}); - bio_ = BIO_new_stream(stream_.get()); - } - - void TearDown() override { - BIO_free(bio_); - bio_ = nullptr; - stream_.reset(); - } - - std::unique_ptr> stream_; - BIO* bio_{nullptr}; -}; - -TEST_F(StreamBIOTest, ReadFull) { - char buffer[10]; - EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(10), - SetArgPointee<3>(false), - Return(true))); - EXPECT_EQ(10, BIO_read(bio_, buffer, sizeof(buffer))); -} - -TEST_F(StreamBIOTest, ReadPartial) { - char buffer[10]; - EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(3), - SetArgPointee<3>(false), - Return(true))); - EXPECT_EQ(3, BIO_read(bio_, buffer, sizeof(buffer))); -} - -TEST_F(StreamBIOTest, ReadWouldBlock) { - char buffer[10]; - EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(false), - Return(true))); - EXPECT_EQ(-1, BIO_read(bio_, buffer, sizeof(buffer))); - EXPECT_TRUE(BIO_should_retry(bio_)); -} - -TEST_F(StreamBIOTest, ReadEndOfStream) { - char buffer[10]; - EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(true), - Return(true))); - EXPECT_EQ(0, BIO_read(bio_, buffer, sizeof(buffer))); - EXPECT_FALSE(BIO_should_retry(bio_)); -} - -TEST_F(StreamBIOTest, ReadError) { - char buffer[10]; - EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _)) - .WillOnce(Return(false)); - EXPECT_EQ(-1, BIO_read(bio_, buffer, sizeof(buffer))); - EXPECT_FALSE(BIO_should_retry(bio_)); -} - -TEST_F(StreamBIOTest, WriteFull) { - char buffer[10] = {}; - EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(10), Return(true))); - EXPECT_EQ(10, BIO_write(bio_, buffer, sizeof(buffer))); -} - -TEST_F(StreamBIOTest, WritePartial) { - char buffer[10] = {}; - EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(3), Return(true))); - EXPECT_EQ(3, BIO_write(bio_, buffer, sizeof(buffer))); -} - -TEST_F(StreamBIOTest, WriteWouldBlock) { - char buffer[10] = {}; - EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_EQ(-1, BIO_write(bio_, buffer, sizeof(buffer))); - EXPECT_TRUE(BIO_should_retry(bio_)); -} - -TEST_F(StreamBIOTest, WriteError) { - char buffer[10] = {}; - EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _)) - .WillOnce(Return(false)); - EXPECT_EQ(-1, BIO_write(bio_, buffer, sizeof(buffer))); - EXPECT_FALSE(BIO_should_retry(bio_)); -} - -TEST_F(StreamBIOTest, FlushSuccess) { - EXPECT_CALL(*stream_, FlushBlocking(_)).WillOnce(Return(true)); - EXPECT_EQ(1, BIO_flush(bio_)); -} - -TEST_F(StreamBIOTest, FlushError) { - EXPECT_CALL(*stream_, FlushBlocking(_)).WillOnce(Return(false)); - EXPECT_EQ(0, BIO_flush(bio_)); -} - -} // namespace chromeos diff --git a/chromeos/streams/stream.cc b/chromeos/streams/stream.cc deleted file mode 100644 index 64f9428..0000000 --- a/chromeos/streams/stream.cc +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include - -namespace chromeos { - -bool Stream::TruncateBlocking(ErrorPtr* error) { - return SetSizeBlocking(GetPosition(), error); -} - -bool Stream::SetPosition(uint64_t position, ErrorPtr* error) { - if (!stream_utils::CheckInt64Overflow(FROM_HERE, position, 0, error)) - return false; - return Seek(position, Whence::FROM_BEGIN, nullptr, error); -} - -bool Stream::ReadAsync(void* buffer, - size_t size_to_read, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error) { - if (is_async_read_pending_) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kOperationNotSupported, - "Another asynchronous operation is still pending"); - return false; - } - - auto callback = base::Bind(&Stream::IgnoreEOSCallback, success_callback); - // If we can read some data right away non-blocking we should still run the - // callback from the main loop, so we pass true here for force_async_callback. - return ReadAsyncImpl(buffer, size_to_read, callback, error_callback, error, - true); -} - -bool Stream::ReadAllAsync(void* buffer, - size_t size_to_read, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error) { - if (is_async_read_pending_) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kOperationNotSupported, - "Another asynchronous operation is still pending"); - return false; - } - - auto callback = base::Bind(&Stream::ReadAllAsyncCallback, - weak_ptr_factory_.GetWeakPtr(), buffer, - size_to_read, success_callback, error_callback); - return ReadAsyncImpl(buffer, size_to_read, callback, error_callback, error, - true); -} - -bool Stream::ReadBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - ErrorPtr* error) { - for (;;) { - bool eos = false; - if (!ReadNonBlocking(buffer, size_to_read, size_read, &eos, error)) - return false; - - if (*size_read > 0 || eos) - break; - - if (!WaitForDataBlocking(AccessMode::READ, base::TimeDelta::Max(), nullptr, - error)) { - return false; - } - } - return true; -} - -bool Stream::ReadAllBlocking(void* buffer, - size_t size_to_read, - ErrorPtr* error) { - while (size_to_read > 0) { - size_t size_read = 0; - if (!ReadBlocking(buffer, size_to_read, &size_read, error)) - return false; - - if (size_read == 0) - return stream_utils::ErrorReadPastEndOfStream(FROM_HERE, error); - - size_to_read -= size_read; - buffer = AdvancePointer(buffer, size_read); - } - return true; -} - -bool Stream::WriteAsync(const void* buffer, - size_t size_to_write, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error) { - if (is_async_write_pending_) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kOperationNotSupported, - "Another asynchronous operation is still pending"); - return false; - } - // If we can read some data right away non-blocking we should still run the - // callback from the main loop, so we pass true here for force_async_callback. - return WriteAsyncImpl(buffer, size_to_write, success_callback, error_callback, - error, true); -} - -bool Stream::WriteAllAsync(const void* buffer, - size_t size_to_write, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error) { - if (is_async_write_pending_) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kOperationNotSupported, - "Another asynchronous operation is still pending"); - return false; - } - - auto callback = base::Bind(&Stream::WriteAllAsyncCallback, - weak_ptr_factory_.GetWeakPtr(), buffer, - size_to_write, success_callback, error_callback); - return WriteAsyncImpl(buffer, size_to_write, callback, error_callback, error, - true); -} - -bool Stream::WriteBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) { - for (;;) { - if (!WriteNonBlocking(buffer, size_to_write, size_written, error)) - return false; - - if (*size_written > 0 || size_to_write == 0) - break; - - if (!WaitForDataBlocking(AccessMode::WRITE, base::TimeDelta::Max(), nullptr, - error)) { - return false; - } - } - return true; -} - -bool Stream::WriteAllBlocking(const void* buffer, - size_t size_to_write, - ErrorPtr* error) { - while (size_to_write > 0) { - size_t size_written = 0; - if (!WriteBlocking(buffer, size_to_write, &size_written, error)) - return false; - - if (size_written == 0) { - Error::AddTo(error, FROM_HERE, errors::stream::kDomain, - errors::stream::kPartialData, - "Failed to write all the data"); - return false; - } - size_to_write -= size_written; - buffer = AdvancePointer(buffer, size_written); - } - return true; -} - -bool Stream::FlushAsync(const base::Closure& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* /* error */) { - auto callback = base::Bind(&Stream::FlushAsyncCallback, - weak_ptr_factory_.GetWeakPtr(), - success_callback, error_callback); - MessageLoop::current()->PostTask(FROM_HERE, callback); - return true; -} - -void Stream::IgnoreEOSCallback( - const base::Callback& success_callback, - size_t bytes, - bool eos) { - success_callback.Run(bytes); -} - -bool Stream::ReadAsyncImpl( - void* buffer, - size_t size_to_read, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error, - bool force_async_callback) { - CHECK(!is_async_read_pending_); - // We set this value to true early in the function so calling others will - // prevent us from calling WaitForData() to make calls to - // ReadAsync() fail while we run WaitForData(). - is_async_read_pending_ = true; - - size_t read = 0; - bool eos = false; - if (!ReadNonBlocking(buffer, size_to_read, &read, &eos, error)) - return false; - - if (read > 0 || eos) { - if (force_async_callback) { - MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(&Stream::OnReadAsyncDone, weak_ptr_factory_.GetWeakPtr(), - success_callback, read, eos)); - } else { - is_async_read_pending_ = false; - success_callback.Run(read, eos); - } - return true; - } - - is_async_read_pending_ = WaitForData( - AccessMode::READ, - base::Bind(&Stream::OnReadAvailable, weak_ptr_factory_.GetWeakPtr(), - buffer, size_to_read, success_callback, error_callback), - error); - return is_async_read_pending_; -} - -void Stream::OnReadAsyncDone( - const base::Callback& success_callback, - size_t bytes_read, - bool eos) { - is_async_read_pending_ = false; - success_callback.Run(bytes_read, eos); -} - -void Stream::OnReadAvailable( - void* buffer, - size_t size_to_read, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - AccessMode mode) { - CHECK(stream_utils::IsReadAccessMode(mode)); - CHECK(is_async_read_pending_); - is_async_read_pending_ = false; - ErrorPtr error; - // Just reschedule the read operation but don't need to run the callback from - // the main loop since we are already running on a callback. - if (!ReadAsyncImpl(buffer, size_to_read, success_callback, error_callback, - &error, false)) { - error_callback.Run(error.get()); - } -} - -bool Stream::WriteAsyncImpl( - const void* buffer, - size_t size_to_write, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error, - bool force_async_callback) { - CHECK(!is_async_write_pending_); - // We set this value to true early in the function so calling others will - // prevent us from calling WaitForData() to make calls to - // ReadAsync() fail while we run WaitForData(). - is_async_write_pending_ = true; - - size_t written = 0; - if (!WriteNonBlocking(buffer, size_to_write, &written, error)) - return false; - - if (written > 0) { - if (force_async_callback) { - MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(&Stream::OnWriteAsyncDone, weak_ptr_factory_.GetWeakPtr(), - success_callback, written)); - } else { - is_async_write_pending_ = false; - success_callback.Run(written); - } - return true; - } - is_async_write_pending_ = WaitForData( - AccessMode::WRITE, - base::Bind(&Stream::OnWriteAvailable, weak_ptr_factory_.GetWeakPtr(), - buffer, size_to_write, success_callback, error_callback), - error); - return is_async_write_pending_; -} - -void Stream::OnWriteAsyncDone( - const base::Callback& success_callback, - size_t size_written) { - is_async_write_pending_ = false; - success_callback.Run(size_written); -} - -void Stream::OnWriteAvailable( - const void* buffer, - size_t size, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - AccessMode mode) { - CHECK(stream_utils::IsWriteAccessMode(mode)); - CHECK(is_async_write_pending_); - is_async_write_pending_ = false; - ErrorPtr error; - // Just reschedule the read operation but don't need to run the callback from - // the main loop since we are already running on a callback. - if (!WriteAsyncImpl(buffer, size, success_callback, error_callback, &error, - false)) { - error_callback.Run(error.get()); - } -} - -void Stream::ReadAllAsyncCallback(void* buffer, - size_t size_to_read, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - size_t size_read, - bool eos) { - ErrorPtr error; - size_to_read -= size_read; - if (size_to_read != 0 && eos) { - stream_utils::ErrorReadPastEndOfStream(FROM_HERE, &error); - error_callback.Run(error.get()); - return; - } - - if (size_to_read) { - buffer = AdvancePointer(buffer, size_read); - auto callback = base::Bind(&Stream::ReadAllAsyncCallback, - weak_ptr_factory_.GetWeakPtr(), buffer, - size_to_read, success_callback, error_callback); - if (!ReadAsyncImpl(buffer, size_to_read, callback, error_callback, &error, - false)) { - error_callback.Run(error.get()); - } - } else { - success_callback.Run(); - } -} - -void Stream::WriteAllAsyncCallback(const void* buffer, - size_t size_to_write, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - size_t size_written) { - ErrorPtr error; - if (size_to_write != 0 && size_written == 0) { - Error::AddTo(&error, FROM_HERE, errors::stream::kDomain, - errors::stream::kPartialData, "Failed to write all the data"); - error_callback.Run(error.get()); - return; - } - size_to_write -= size_written; - if (size_to_write) { - buffer = AdvancePointer(buffer, size_written); - auto callback = base::Bind(&Stream::WriteAllAsyncCallback, - weak_ptr_factory_.GetWeakPtr(), buffer, - size_to_write, success_callback, error_callback); - if (!WriteAsyncImpl(buffer, size_to_write, callback, error_callback, &error, - false)) { - error_callback.Run(error.get()); - } - } else { - success_callback.Run(); - } -} - -void Stream::FlushAsyncCallback(const base::Closure& success_callback, - const ErrorCallback& error_callback) { - ErrorPtr error; - if (FlushBlocking(&error)) { - success_callback.Run(); - } else { - error_callback.Run(error.get()); - } -} - -void Stream::CancelPendingAsyncOperations() { - weak_ptr_factory_.InvalidateWeakPtrs(); - is_async_read_pending_ = false; - is_async_write_pending_ = false; -} - -} // namespace chromeos diff --git a/chromeos/streams/stream.h b/chromeos/streams/stream.h deleted file mode 100644 index f1ad75a..0000000 --- a/chromeos/streams/stream.h +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_H_ - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace chromeos { - -// Stream is a base class that specific stream storage implementations must -// derive from to provide I/O facilities. -// The stream class provides general streaming I/O primitives to read, write and -// seek within a stream. It has methods for asynchronous (callback-based) as -// well as synchronous (both blocking and non-blocking) operations. -// The Stream class is abstract and cannot be created by itself. -// In order to construct a stream, you must use one of the derived classes' -// factory methods which return a stream smart pointer (StreamPtr): -// -// StreamPtr input_stream = FileStream::Open(path, AccessMode::READ); -// StreamPtr output_stream = MemoryStream::Create(); -// uint8_t buf[1000]; -// size_t read = 0; -// while (input_stream->ReadBlocking(buf, sizeof(buf), &read, nullptr)) { -// if (read == 0) break; -// output_stream->WriteAllBlocking(buf, read, nullptr); -// } -// -// NOTE ABOUT ASYNCHRONOUS OPERATIONS: Asynchronous I/O relies on a MessageLoop -// instance to be present on the current thread. Using Stream::ReadAsync(), -// Stream::WriteAsync() and similar will call MessageLoop::current() to access -// the current message loop and abort if there isn't one for the current thread. -// Also, only one outstanding asynchronous operation of particular kind (reading -// or writing) at a time is supported. Trying to call ReadAsync() while another -// asynchronous read operation is pending will fail with an error -// ("operation_not_supported"). -// -// NOTE ABOUT READING FROM/WRITING TO STREAMS: In many cases underlying streams -// use buffered I/O. Using all read/write methods other than ReadAllAsync(), -// ReadAllBlocking(), WriteAllAsync(), WriteAllBlocking() will return -// immediately if there is any data available in the underlying buffer. That is, -// trying to read 1000 bytes while the internal buffer contains only 100 will -// return immediately with just those 100 bytes and no blocking or other I/O -// traffic will be incurred. This guarantee is important for efficient and -// correct implementation of duplex communication over pipes and sockets. -// -// NOTE TO IMPLEMENTERS: When creating new stream types, you must derive -// from this class and provide the implementation for its pure virtual methods. -// For operations that do not apply to your stream, make sure the corresponding -// methods return "false" and set the error to "operation_not_supported". -// You should use stream_utils::ErrorOperationNotSupported() for this. Also -// Make sure the stream capabilities functions like CanRead(), etc return -// correct values: -// -// bool MyReadOnlyStream::CanRead() const { return true; } -// bool MyReadOnlyStream::CanWrite() const { return false; } -// bool MyReadOnlyStream::WriteBlocking(const void* buffer, -// size_t size_to_write, -// size_t* size_written, -// ErrorPtr* error) { -// return stream_utils::ErrorOperationNotSupported(error); -// } -// -// The class should also provide a static factory methods to create/open -// a new stream: -// -// static StreamPtr MyReadOnlyStream::Open(..., ErrorPtr* error) { -// auto my_stream = std::make_unique(...); -// if (!my_stream->Initialize(..., error)) -// my_stream.reset(); -// } -// return my_stream; -// } -// -class CHROMEOS_EXPORT Stream { - public: - // When seeking in streams, whence specifies the origin of the seek operation. - enum class Whence { FROM_BEGIN, FROM_CURRENT, FROM_END }; - // Stream access mode for open operations (used in derived classes). - enum class AccessMode { READ, WRITE, READ_WRITE }; - - // Standard error callback for asynchronous operations. - using ErrorCallback = base::Callback; - - virtual ~Stream() = default; - - // == Stream capabilities =================================================== - - // Returns true while stream is open. Closing the last reference to the stream - // will make this method return false. - virtual bool IsOpen() const = 0; - - // Called to determine if read operations are supported on the stream (stream - // is readable). This method does not check if there is actually any data to - // read, only the fact that the stream is open in read mode and can be read - // from in general. - // If CanRead() returns false, it is guaranteed that the stream can't be - // read from. However, if it returns true, there is no guarantee that the - // subsequent read operation will actually succeed (for example, the stream - // position could be at the end of the data stream, or the access mode of - // the stream is unknown beforehand). - virtual bool CanRead() const = 0; - - // Called to determine if write operations are supported on the stream (stream - // is writable). - // If CanWrite() returns false, it is guaranteed that the stream can't be - // written to. However, if it returns true, the subsequent write operation - // is not guaranteed to succeed (e.g. the output media could be out of free - // space or a transport error could occur). - virtual bool CanWrite() const = 0; - - // Called to determine if random access I/O operations are supported on - // the stream. Sequential streams should return false. - // If CanSeek() returns false, it is guaranteed that the stream can't use - // Seek(). However, if it returns true, it might be possible to seek, but this - // is not guaranteed since the actual underlying stream capabilities might - // not be known. - // Note that non-seekable streams might still maintain the current stream - // position and GetPosition method might still be used even if CanSeek() - // returns false. However SetPosition() will almost always fail in such - // a case. - virtual bool CanSeek() const = 0; - - // Called to determine if the size of the stream is known. Size of some - // sequential streams (e.g. based on pipes) is unknown beforehand, so this - // method can be used to check how reliable a call to GetSize() is. - virtual bool CanGetSize() const = 0; - - // == Stream size operations ================================================ - - // Returns the size of stream data. - // If the stream size is unavailable/unknown, it returns 0. - virtual uint64_t GetSize() const = 0; - - // Resizes the stream storage to |size|. Stream must be writable and support - // this operation. - virtual bool SetSizeBlocking(uint64_t size, ErrorPtr* error) = 0; - - // Truncates the stream at the current stream pointer. - // Calls SetSizeBlocking(GetPosition(), ...). - bool TruncateBlocking(ErrorPtr* error); - - // Returns the amount of data remaining in the stream. If the size of the - // stream is unknown, or if the stream pointer is at or past the end of the - // stream, the function returns 0. - virtual uint64_t GetRemainingSize() const = 0; - - // == Seek operations ======================================================= - - // Gets the position of the stream I/O pointer from the beginning of the - // stream. If the stream position is unavailable/unknown, it returns 0. - virtual uint64_t GetPosition() const = 0; - - // Moves the stream pointer to the specified position, relative to the - // beginning of the stream. This calls Seek(position, Whence::FROM_BEGIN), - // however it also provides proper |position| validation to ensure that - // it doesn't overflow the range of signed int64_t used by Seek. - bool SetPosition(uint64_t position, ErrorPtr* error); - - // Moves the stream pointer by |offset| bytes relative to |whence|. - // When successful, returns true and sets the new pointer position from the - // beginning of the stream to |new_position|. If |new_position| is nullptr, - // new stream position is not returned. - // On error, returns false and specifies additional details in |error| if it - // is not nullptr. - virtual bool Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) = 0; - - // == Read operations ======================================================= - - // -- Asynchronous ---------------------------------------------------------- - - // Reads up to |size_to_read| bytes from the stream asynchronously. It is not - // guaranteed that all requested data will be read. It is not an error for - // this function to read fewer bytes than requested. If the function reads - // zero bytes, it means that the end of stream is reached. - // Upon successful read, the |success_callback| will be invoked with the - // actual number of bytes read. - // If an error occurs during the asynchronous operation, the |error_callback| - // is invoked with the error details. The error object pointer passed in as a - // parameter to the |error_callback| is valid only for the duration of that - // callback. - // If this function successfully schedules an asynchronous operation, it - // returns true. If it fails immediately, it will return false and set the - // error details to |error| object and will not call the success or error - // callbacks. - // The |buffer| must be at least |size_to_read| in size and must remain - // valid for the duration of the asynchronous operation (until either - // |success_callback| or |error_callback| is called). - // Only one asynchronous operation at a time is allowed on the stream (read - // and/or write) - // Uses ReadNonBlocking() and MonitorDataAvailable(). - virtual bool ReadAsync(void* buffer, - size_t size_to_read, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error); - - // Similar to ReadAsync() operation above but reads exactly |size_to_read| - // bytes from the stream into the |buffer|. Attempt to read past the end of - // the stream is considered an error in this case and will trigger the - // |error_callback|. The rest of restrictions and conditions of ReadAsync() - // method applies to ReadAllAsync() as well. - // Uses ReadNonBlocking() and MonitorDataAvailable(). - virtual bool ReadAllAsync(void* buffer, - size_t size_to_read, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error); - - // -- Synchronous non-blocking ---------------------------------------------- - - // Reads up to |size_to_read| bytes from the stream without blocking. - // The |buffer| must be at least |size_to_read| in size. It is not an error - // for this function to return without reading all (or any) the data. - // The actual amount of data read (which could be 0 bytes) is returned in - // |size_read|. - // On error, the function returns false and specifies additional error details - // in |error|. - // If end of stream is reached or if no data is currently available to be read - // without blocking, |size_read| will contain 0 and the function will still - // return true (success). In case of end-of-stream scenario, |end_of_stream| - // will also be set to true to indicate that no more data is available. - virtual bool ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) = 0; - - // -- Synchronous blocking -------------------------------------------------- - - // Reads up to |size_to_read| bytes from the stream. This function will block - // until at least one byte is read or the end of stream is reached or until - // the stream is closed. - // The |buffer| must be at least |size_to_read| in size. It is not an error - // for this function to return without reading all the data. The actual amount - // of data read (which could be 0 bytes) is returned in |size_read|. - // On error, the function returns false and specifies additional error details - // in |error|. In this case, the state of the stream pointer is undefined, - // since some bytes might have been read successfully (and the pointer moved) - // before the error has occurred and |size_read| is not updated. - // If end of stream is reached, |size_read| will contain 0 and the function - // will still return true (success). - virtual bool ReadBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - ErrorPtr* error); - - // Reads exactly |size_to_read| bytes to |buffer|. Returns false on error - // (reading fewer than requested bytes is treated as an error as well). - // Calls ReadAllBlocking() repeatedly until all the data is read. - virtual bool ReadAllBlocking(void* buffer, - size_t size_to_read, - ErrorPtr* error); - - // == Write operations ====================================================== - - // -- Asynchronous ---------------------------------------------------------- - - // Writes up to |size_to_write| bytes from |buffer| to the stream - // asynchronously. It is not guaranteed that all requested data will be - // written. It is not an error for this function to write fewer bytes than - // requested. - // Upon successful write, the |success_callback| will be invoked with the - // actual number of bytes written. - // If an error occurs during the asynchronous operation, the |error_callback| - // is invoked with the error details. The error object pointer is valid only - // for the duration of the error callback. - // If this function successfully schedules an asynchronous operation, it - // returns true. If it fails immediately, it will return false and set the - // error details to |error| object and will not call the success or error - // callbacks. - // The |buffer| must be at least |size_to_write| in size and must remain - // valid for the duration of the asynchronous operation (until either - // |success_callback| or |error_callback| is called). - // Only one asynchronous operation at a time is allowed on the stream (read - // and/or write). - // Uses WriteNonBlocking() and MonitorDataAvailable(). - virtual bool WriteAsync(const void* buffer, - size_t size_to_write, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error); - - // Similar to WriteAsync() operation above but writes exactly |size_to_write| - // bytes from |buffet| to the stream. When all the data is written - // successfully, the |success_callback| is invoked. - // The rest of restrictions and conditions of WriteAsync() method applies to - // WriteAllAsync() as well. - // Uses WriteNonBlocking() and MonitorDataAvailable(). - virtual bool WriteAllAsync(const void* buffer, - size_t size_to_write, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error); - - // -- Synchronous non-blocking ---------------------------------------------- - - // Writes up to |size_to_write| bytes to the stream. The |buffer| must be at - // least |size_to_write| in size. It is not an error for this function to - // return without writing all the data requested (or any data at all). - // The actual amount of data written is returned in |size_written|. - // On error, the function returns false and specifies additional error details - // in |error|. - virtual bool WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) = 0; - - // -- Synchronous blocking -------------------------------------------------- - - // Writes up to |size_to_write| bytes to the stream. The |buffer| must be at - // least |size_to_write| in size. It is not an error for this function to - // return without writing all the data requested. The actual amount of data - // written is returned in |size_written|. - // On error, the function returns false and specifies additional error details - // in |error|. - virtual bool WriteBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error); - - // Writes exactly |size_to_write| bytes to |buffer|. Returns false on error - // (writing fewer than requested bytes is treated as an error as well). - // Calls WriteBlocking() repeatedly until all the data is written. - virtual bool WriteAllBlocking(const void* buffer, - size_t size_to_write, - ErrorPtr* error); - - // == Finalizing/closing streams =========================================== - - // Flushes all the user-space data from cache output buffers to storage - // medium. For read-only streams this is a no-op, however it is still valid - // to call this method on read-only streams. - // If an error occurs, the function returns false and specifies additional - // error details in |error|. - virtual bool FlushBlocking(ErrorPtr* error) = 0; - - // Flushes all the user-space data from the cache output buffer - // asynchronously. When all the data is successfully flushed, the - // |success_callback| is invoked. If an error occurs while flushing, partial - // data might be flushed and |error_callback| is invoked. If there's an error - // scheduling the flush operation, it returns false and neither callback will - // be called. - virtual bool FlushAsync(const base::Closure& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error); - - // Closes the underlying stream. The stream is also automatically closed - // when the stream object is destroyed, but since closing a stream is - // an operation that may fail, in situations when it is important to detect - // the failure to close the stream, CloseBlocking() should be used explicitly - // before destroying the stream object. - virtual bool CloseBlocking(ErrorPtr* error) = 0; - - // == Data availability monitoring ========================================== - - // Overloaded by derived classes to provide stream monitoring for read/write - // data availability for the stream. Calls |callback| when data can be read - // and/or written without blocking. - // |mode| specifies the type of operation to monitor for (read, write, both). - virtual bool WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) = 0; - - // Helper function for implementing blocking I/O. Blocks until the - // non-blocking operation specified by |in_mode| can be performed. - // If |out_mode| is not nullptr, it receives the actual operation that can be - // performed. For example, watching a stream for READ_WRITE while only - // READ can be performed, |out_mode| would contain READ even though |in_mode| - // was set to READ_WRITE. - // |timeout| is the maximum amount of time to wait. Set it to TimeDelta::Max() - // to wait indefinitely. - virtual bool WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) = 0; - - // Cancels pending asynchronous read/write operations. - virtual void CancelPendingAsyncOperations(); - - protected: - Stream() = default; - - private: - // Simple wrapper to call the externally exposed |success_callback| that only - // receives a size_t. - CHROMEOS_PRIVATE static void IgnoreEOSCallback( - const base::Callback& success_callback, - size_t read, - bool eos); - - // The internal implementation of ReadAsync() and ReadAllAsync(). - // Calls ReadNonBlocking and if there's no data available waits for it calling - // WaitForData(). The extra |force_async_callback| tell whether the success - // callback should be called from the main loop instead of directly from this - // method. This method only calls WaitForData() if ReadNonBlocking() returns a - // situation in which it would block (bytes_read = 0 and eos = false), - // preventing us from calling WaitForData() on streams that don't support such - // feature. - CHROMEOS_PRIVATE bool ReadAsyncImpl( - void* buffer, - size_t size_to_read, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error, - bool force_async_callback); - - // Called from the main loop when the ReadAsyncImpl finished right away - // without waiting for data. We use this callback to call the - // |sucess_callback| but invalidate the callback if the Stream is destroyed - // while this call is waiting in the main loop. - CHROMEOS_PRIVATE void OnReadAsyncDone( - const base::Callback& success_callback, - size_t bytes_read, - bool eos); - - // Called from WaitForData() when read operations can be performed - // without blocking (the type of operation is provided in |mode|). - CHROMEOS_PRIVATE void OnReadAvailable( - void* buffer, - size_t size_to_read, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - AccessMode mode); - - // The internal implementation of WriteAsync() and WriteAllAsync(). - // Calls WriteNonBlocking and if the write would block for it to not block - // calling WaitForData(). The extra |force_async_callback| tell whether the - // success callback should be called from the main loop instead of directly - // from this method. This method only calls WaitForData() if - // WriteNonBlocking() returns a situation in which it would block - // (size_written = 0 and eos = false), preventing us from calling - // WaitForData() on streams that don't support such feature. - CHROMEOS_PRIVATE bool WriteAsyncImpl( - const void* buffer, - size_t size_to_write, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - ErrorPtr* error, - bool force_async_callback); - - // Called from the main loop when the WriteAsyncImpl finished right away - // without waiting for data. We use this callback to call the - // |sucess_callback| but invalidate the callback if the Stream is destroyed - // while this call is waiting in the main loop. - CHROMEOS_PRIVATE void OnWriteAsyncDone( - const base::Callback& success_callback, - size_t size_written); - - // Called from WaitForData() when write operations can be performed - // without blocking (the type of operation is provided in |mode|). - CHROMEOS_PRIVATE void OnWriteAvailable( - const void* buffer, - size_t size, - const base::Callback& success_callback, - const ErrorCallback& error_callback, - AccessMode mode); - - // Helper callbacks to implement ReadAllAsync/WriteAllAsync. - CHROMEOS_PRIVATE void ReadAllAsyncCallback( - void* buffer, - size_t size_to_read, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - size_t size_read, - bool eos); - CHROMEOS_PRIVATE void WriteAllAsyncCallback( - const void* buffer, - size_t size_to_write, - const base::Closure& success_callback, - const ErrorCallback& error_callback, - size_t size_written); - - // Helper callbacks to implement FlushAsync(). - CHROMEOS_PRIVATE void FlushAsyncCallback( - const base::Closure& success_callback, - const ErrorCallback& error_callback); - - // Data members for asynchronous read operations. - bool is_async_read_pending_{false}; - - // Data members for asynchronous write operations. - bool is_async_write_pending_{false}; - - base::WeakPtrFactory weak_ptr_factory_{this}; - DISALLOW_COPY_AND_ASSIGN(Stream); -}; - -// A smart pointer to the stream used to pass the stream object around. -using StreamPtr = std::unique_ptr; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_H_ diff --git a/chromeos/streams/stream_errors.cc b/chromeos/streams/stream_errors.cc deleted file mode 100644 index 35911c5..0000000 --- a/chromeos/streams/stream_errors.cc +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -namespace chromeos { -namespace errors { -namespace stream { - -const char kDomain[] = "stream.io"; - -const char kStreamClosed[] = "stream_closed"; -const char kOperationNotSupported[] = "operation_not_supported"; -const char kPartialData[] = "partial_data"; -const char kInvalidParameter[] = "invalid_parameter"; -const char kTimeout[] = "time_out"; - -} // namespace stream -} // namespace errors -} // namespace chromeos diff --git a/chromeos/streams/stream_errors.h b/chromeos/streams/stream_errors.h deleted file mode 100644 index 61f8a6f..0000000 --- a/chromeos/streams/stream_errors.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_ERRORS_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_ERRORS_H_ - -#include - -namespace chromeos { -namespace errors { -namespace stream { - -// Error domain for generic stream-based errors. -CHROMEOS_EXPORT extern const char kDomain[]; - -CHROMEOS_EXPORT extern const char kStreamClosed[]; -CHROMEOS_EXPORT extern const char kOperationNotSupported[]; -CHROMEOS_EXPORT extern const char kPartialData[]; -CHROMEOS_EXPORT extern const char kInvalidParameter[]; -CHROMEOS_EXPORT extern const char kTimeout[]; - -} // namespace stream -} // namespace errors -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_ERRORS_H_ diff --git a/chromeos/streams/stream_unittest.cc b/chromeos/streams/stream_unittest.cc deleted file mode 100644 index 6485a17..0000000 --- a/chromeos/streams/stream_unittest.cc +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include - -#include -#include -#include - -using testing::DoAll; -using testing::InSequence; -using testing::Return; -using testing::SaveArg; -using testing::SetArgPointee; -using testing::_; - -namespace chromeos { - -using AccessMode = Stream::AccessMode; -using Whence = Stream::Whence; - -// To verify "non-trivial" methods implemented in Stream, mock out the -// "trivial" methods to make sure the ones we are interested in testing -// actually end up calling the expected methods with right parameters. -class MockStreamImpl : public Stream { - public: - MockStreamImpl() = default; - - MOCK_CONST_METHOD0(IsOpen, bool()); - MOCK_CONST_METHOD0(CanRead, bool()); - MOCK_CONST_METHOD0(CanWrite, bool()); - MOCK_CONST_METHOD0(CanSeek, bool()); - MOCK_CONST_METHOD0(CanGetSize, bool()); - - MOCK_CONST_METHOD0(GetSize, uint64_t()); - MOCK_METHOD2(SetSizeBlocking, bool(uint64_t, ErrorPtr*)); - MOCK_CONST_METHOD0(GetRemainingSize, uint64_t()); - - MOCK_CONST_METHOD0(GetPosition, uint64_t()); - MOCK_METHOD4(Seek, bool(int64_t, Whence, uint64_t*, ErrorPtr*)); - - // Omitted: ReadAsync - // Omitted: ReadAllAsync - MOCK_METHOD5(ReadNonBlocking, bool(void*, size_t, size_t*, bool*, ErrorPtr*)); - // Omitted: ReadBlocking - // Omitted: ReadAllBlocking - - // Omitted: WriteAsync - // Omitted: WriteAllAsync - MOCK_METHOD4(WriteNonBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); - // Omitted: WriteBlocking - // Omitted: WriteAllBlocking - - MOCK_METHOD1(FlushBlocking, bool(ErrorPtr*)); - MOCK_METHOD1(CloseBlocking, bool(ErrorPtr*)); - - MOCK_METHOD3(WaitForData, bool(AccessMode, - const base::Callback&, - ErrorPtr*)); - MOCK_METHOD4(WaitForDataBlocking, - bool(AccessMode, base::TimeDelta, AccessMode*, ErrorPtr*)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockStreamImpl); -}; - -TEST(Stream, TruncateBlocking) { - MockStreamImpl stream_mock; - EXPECT_CALL(stream_mock, GetPosition()).WillOnce(Return(123)); - EXPECT_CALL(stream_mock, SetSizeBlocking(123, _)).WillOnce(Return(true)); - EXPECT_TRUE(stream_mock.TruncateBlocking(nullptr)); -} - -TEST(Stream, SetPosition) { - MockStreamImpl stream_mock; - EXPECT_CALL(stream_mock, Seek(12345, Whence::FROM_BEGIN, _, _)) - .WillOnce(Return(true)); - EXPECT_TRUE(stream_mock.SetPosition(12345, nullptr)); - - // Test too large an offset (that doesn't fit in signed 64 bit value). - ErrorPtr error; - uint64_t max_offset = std::numeric_limits::max(); - EXPECT_CALL(stream_mock, Seek(max_offset, _, _, _)) - .WillOnce(Return(true)); - EXPECT_TRUE(stream_mock.SetPosition(max_offset, nullptr)); - - EXPECT_FALSE(stream_mock.SetPosition(max_offset + 1, &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); -} - -TEST(Stream, ReadAsync) { - size_t read_size = 0; - bool succeeded = false; - bool failed = false; - auto success_callback = [&read_size, &succeeded](size_t size) { - read_size = size; - succeeded = true; - }; - auto error_callback = [&failed](const Error* error) { failed = true; }; - - MockStreamImpl stream_mock; - base::Callback data_callback; - char buf[10]; - - // This sets up an initial non blocking read that would block, so ReadAsync() - // should wait for more data. - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) - .WillOnce( - DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - EXPECT_TRUE(stream_mock.ReadAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), nullptr)); - EXPECT_EQ(0u, read_size); - EXPECT_FALSE(succeeded); - EXPECT_FALSE(failed); - - // Since the previous call is waiting for the data to be available, we can't - // schedule another read. - ErrorPtr error; - EXPECT_FALSE(stream_mock.ReadAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); - EXPECT_EQ("Another asynchronous operation is still pending", - error->GetMessage()); - - // Making the data available via data_callback should not schedule the - // success callback from the main loop and run it directly instead. - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(7), - SetArgPointee<3>(false), - Return(true))); - data_callback.Run(AccessMode::READ); - EXPECT_EQ(7u, read_size); - EXPECT_FALSE(failed); -} - -TEST(Stream, ReadAsync_DontWaitForData) { - bool succeeded = false; - bool failed = false; - auto success_callback = [&succeeded](size_t size) { succeeded = true; }; - auto error_callback = [&failed](const Error* error) { failed = true; }; - - MockStreamImpl stream_mock; - char buf[10]; - FakeMessageLoop fake_loop_{nullptr}; - fake_loop_.SetAsCurrent(); - - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) - .WillOnce( - DoAll(SetArgPointee<2>(5), SetArgPointee<3>(false), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(_, _, _)).Times(0); - EXPECT_TRUE(stream_mock.ReadAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), nullptr)); - // Even if ReadNonBlocking() returned some data without waiting, the - // |success_callback| should not run yet. - EXPECT_TRUE(fake_loop_.PendingTasks()); - EXPECT_FALSE(succeeded); - EXPECT_FALSE(failed); - - // Since the previous callback is still waiting in the main loop, we can't - // schedule another read yet. - ErrorPtr error; - EXPECT_FALSE(stream_mock.ReadAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); - EXPECT_EQ("Another asynchronous operation is still pending", - error->GetMessage()); - - fake_loop_.Run(); - EXPECT_TRUE(succeeded); - EXPECT_FALSE(failed); -} - -TEST(Stream, ReadAllAsync) { - bool succeeded = false; - bool failed = false; - auto success_callback = [&succeeded]() { succeeded = true; }; - auto error_callback = [&failed](const Error* error) { failed = true; }; - - MockStreamImpl stream_mock; - base::Callback data_callback; - char buf[10]; - - // This sets up an initial non blocking read that would block, so - // ReadAllAsync() should wait for more data. - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) - .WillOnce( - DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - EXPECT_TRUE(stream_mock.ReadAllAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), - nullptr)); - EXPECT_FALSE(succeeded); - EXPECT_FALSE(failed); - testing::Mock::VerifyAndClearExpectations(&stream_mock); - - // ReadAllAsync() will try to read non blocking until the read would block - // before it waits for the data to be available again. - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(7), - SetArgPointee<3>(false), - Return(true))); - EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _)) - .WillOnce( - DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - data_callback.Run(AccessMode::READ); - EXPECT_FALSE(succeeded); - EXPECT_FALSE(failed); - testing::Mock::VerifyAndClearExpectations(&stream_mock); - - EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(3), - SetArgPointee<3>(true), - Return(true))); - data_callback.Run(AccessMode::READ); - EXPECT_TRUE(succeeded); - EXPECT_FALSE(failed); -} - -TEST(Stream, ReadAllAsync_EOS) { - bool succeeded = false; - bool failed = false; - auto success_callback = [&succeeded]() { succeeded = true; }; - auto error_callback = [&failed](const Error* error) { - ASSERT_EQ(errors::stream::kDomain, error->GetDomain()); - ASSERT_EQ(errors::stream::kPartialData, error->GetCode()); - failed = true; - }; - - MockStreamImpl stream_mock; - base::Callback data_callback; - char buf[10]; - - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) - .WillOnce( - DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - EXPECT_TRUE(stream_mock.ReadAllAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), - nullptr)); - - // ReadAsyncAll() should finish and fail once ReadNonBlocking() returns an - // end-of-stream condition. - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(7), - SetArgPointee<3>(true), - Return(true))); - data_callback.Run(AccessMode::READ); - EXPECT_FALSE(succeeded); - EXPECT_TRUE(failed); -} - -TEST(Stream, ReadBlocking) { - MockStreamImpl stream_mock; - char buf[1024]; - size_t read = 0; - - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(24), - SetArgPointee<3>(false), - Return(true))); - EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); - EXPECT_EQ(24, read); - - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(true), - Return(true))); - EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); - EXPECT_EQ(0, read); - - { - InSequence seq; - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(false), - Return(true))); - EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(false), - Return(true))); - EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(124), - SetArgPointee<3>(false), - Return(true))); - } - EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); - EXPECT_EQ(124, read); - - { - InSequence seq; - EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), - SetArgPointee<3>(false), - Return(true))); - EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _)) - .WillOnce(Return(false)); - } - EXPECT_FALSE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr)); -} - -TEST(Stream, ReadAllBlocking) { - class MockReadBlocking : public MockStreamImpl { - public: - MOCK_METHOD4(ReadBlocking, bool(void*, size_t, size_t*, ErrorPtr*)); - } stream_mock; - - char buf[1024]; - - EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); - EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true))); - EXPECT_TRUE(stream_mock.ReadAllBlocking(buf, sizeof(buf), nullptr)); - - ErrorPtr error; - EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); - EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_FALSE(stream_mock.ReadAllBlocking(buf, sizeof(buf), &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); -} - -TEST(Stream, WriteAsync) { - size_t write_size = 0; - bool failed = false; - auto success_callback = [&write_size](size_t size) { write_size = size; }; - auto error_callback = [&failed](const Error* error) { failed = true; }; - - MockStreamImpl stream_mock; - InSequence s; - base::Callback data_callback; - char buf[10] = {}; - - // WriteNonBlocking returns a blocking situation (size_written = 0) so the - // WaitForData() is run. - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - EXPECT_TRUE(stream_mock.WriteAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), nullptr)); - EXPECT_EQ(0u, write_size); - EXPECT_FALSE(failed); - - ErrorPtr error; - EXPECT_FALSE(stream_mock.WriteAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); - EXPECT_EQ("Another asynchronous operation is still pending", - error->GetMessage()); - - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(7), Return(true))); - data_callback.Run(AccessMode::WRITE); - EXPECT_EQ(7u, write_size); - EXPECT_FALSE(failed); -} - -TEST(Stream, WriteAllAsync) { - bool succeeded = false; - bool failed = false; - auto success_callback = [&succeeded]() { succeeded = true; }; - auto error_callback = [&failed](const Error* error) { failed = true; }; - - MockStreamImpl stream_mock; - base::Callback data_callback; - char buf[10] = {}; - - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - EXPECT_TRUE(stream_mock.WriteAllAsync(buf, sizeof(buf), - base::Bind(success_callback), - base::Bind(error_callback), - nullptr)); - testing::Mock::VerifyAndClearExpectations(&stream_mock); - EXPECT_FALSE(succeeded); - EXPECT_FALSE(failed); - - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(7), Return(true))); - EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _)) - .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true))); - data_callback.Run(AccessMode::WRITE); - testing::Mock::VerifyAndClearExpectations(&stream_mock); - EXPECT_FALSE(succeeded); - EXPECT_FALSE(failed); - - EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(3), Return(true))); - data_callback.Run(AccessMode::WRITE); - EXPECT_TRUE(succeeded); - EXPECT_FALSE(failed); -} - -TEST(Stream, WriteBlocking) { - MockStreamImpl stream_mock; - char buf[1024]; - size_t written = 0; - - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); - EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr)); - EXPECT_EQ(24, written); - - { - InSequence seq; - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(124), Return(true))); - } - EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr)); - EXPECT_EQ(124, written); - - { - InSequence seq; - EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(0), Return(true))); - EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _)) - .WillOnce(Return(false)); - } - EXPECT_FALSE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr)); -} - -TEST(Stream, WriteAllBlocking) { - class MockWritelocking : public MockStreamImpl { - public: - MOCK_METHOD4(WriteBlocking, bool(const void*, size_t, size_t*, ErrorPtr*)); - } stream_mock; - - char buf[1024]; - - EXPECT_CALL(stream_mock, WriteBlocking(buf, 1024, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(24), Return(true))); - EXPECT_CALL(stream_mock, WriteBlocking(buf + 24, 1000, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true))); - EXPECT_TRUE(stream_mock.WriteAllBlocking(buf, sizeof(buf), nullptr)); -} - -} // namespace chromeos diff --git a/chromeos/streams/stream_utils.cc b/chromeos/streams/stream_utils.cc deleted file mode 100644 index b9e9847..0000000 --- a/chromeos/streams/stream_utils.cc +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include - -namespace chromeos { -namespace stream_utils { - -namespace { - -// Status of asynchronous CopyData operation. -struct CopyDataState { - chromeos::StreamPtr in_stream; - chromeos::StreamPtr out_stream; - std::vector buffer; - uint64_t remaining_to_copy; - uint64_t size_copied; - CopyDataSuccessCallback success_callback; - CopyDataErrorCallback error_callback; -}; - -// Async CopyData I/O error callback. -void OnCopyDataError(const std::shared_ptr& state, - const chromeos::Error* error) { - state->error_callback.Run(std::move(state->in_stream), - std::move(state->out_stream), error); -} - -// Forward declaration. -void PerformRead(const std::shared_ptr& state); - -// Callback from read operation for CopyData. Writes the read data to the output -// stream and invokes PerformRead when done to restart the copy cycle. -void PerformWrite(const std::shared_ptr& state, size_t size) { - if (size == 0) { - state->success_callback.Run(std::move(state->in_stream), - std::move(state->out_stream), - state->size_copied); - return; - } - state->size_copied += size; - CHECK_GE(state->remaining_to_copy, size); - state->remaining_to_copy -= size; - - chromeos::ErrorPtr error; - bool success = state->out_stream->WriteAllAsync( - state->buffer.data(), size, base::Bind(&PerformRead, state), - base::Bind(&OnCopyDataError, state), &error); - - if (!success) - OnCopyDataError(state, error.get()); -} - -// Performs the read part of asynchronous CopyData operation. Reads the data -// from input stream and invokes PerformWrite when done to write the data to -// the output stream. -void PerformRead(const std::shared_ptr& state) { - chromeos::ErrorPtr error; - const uint64_t buffer_size = state->buffer.size(); - // |buffer_size| is guaranteed to fit in size_t, so |size_to_read| value will - // also not overflow size_t, so the static_cast below is safe. - size_t size_to_read = - static_cast(std::min(buffer_size, state->remaining_to_copy)); - if (size_to_read == 0) - return PerformWrite(state, 0); // Nothing more to read. Finish operation. - bool success = state->in_stream->ReadAsync( - state->buffer.data(), size_to_read, base::Bind(PerformWrite, state), - base::Bind(OnCopyDataError, state), &error); - - if (!success) - OnCopyDataError(state, error.get()); -} - -} // anonymous namespace - -bool ErrorStreamClosed(const tracked_objects::Location& location, - ErrorPtr* error) { - Error::AddTo(error, - location, - errors::stream::kDomain, - errors::stream::kStreamClosed, - "Stream is closed"); - return false; -} - -bool ErrorOperationNotSupported(const tracked_objects::Location& location, - ErrorPtr* error) { - Error::AddTo(error, - location, - errors::stream::kDomain, - errors::stream::kOperationNotSupported, - "Stream operation not supported"); - return false; -} - -bool ErrorReadPastEndOfStream(const tracked_objects::Location& location, - ErrorPtr* error) { - Error::AddTo(error, - location, - errors::stream::kDomain, - errors::stream::kPartialData, - "Reading past the end of stream"); - return false; -} - -bool ErrorOperationTimeout(const tracked_objects::Location& location, - ErrorPtr* error) { - Error::AddTo(error, - location, - errors::stream::kDomain, - errors::stream::kTimeout, - "Operation timed out"); - return false; -} - - -bool CheckInt64Overflow(const tracked_objects::Location& location, - uint64_t position, - int64_t offset, - ErrorPtr* error) { - if (offset < 0) { - // Subtracting the offset. Make sure we do not underflow. - uint64_t unsigned_offset = static_cast(-offset); - if (position >= unsigned_offset) - return true; - } else { - // Adding the offset. Make sure we do not overflow unsigned 64 bits first. - if (position <= std::numeric_limits::max() - offset) { - // We definitely will not overflow the unsigned 64 bit integer. - // Now check that we end up within the limits of signed 64 bit integer. - uint64_t new_position = position + offset; - uint64_t max = std::numeric_limits::max(); - if (new_position <= max) - return true; - } - } - Error::AddTo(error, - location, - errors::stream::kDomain, - errors::stream::kInvalidParameter, - "The stream offset value is out of range"); - return false; -} - -bool CalculateStreamPosition(const tracked_objects::Location& location, - int64_t offset, - Stream::Whence whence, - uint64_t current_position, - uint64_t stream_size, - uint64_t* new_position, - ErrorPtr* error) { - uint64_t pos = 0; - switch (whence) { - case Stream::Whence::FROM_BEGIN: - pos = 0; - break; - - case Stream::Whence::FROM_CURRENT: - pos = current_position; - break; - - case Stream::Whence::FROM_END: - pos = stream_size; - break; - - default: - Error::AddTo(error, - location, - errors::stream::kDomain, - errors::stream::kInvalidParameter, - "Invalid stream position whence"); - return false; - } - - if (!CheckInt64Overflow(location, pos, offset, error)) - return false; - - *new_position = static_cast(pos + offset); - return true; -} - -void CopyData(StreamPtr in_stream, - StreamPtr out_stream, - const CopyDataSuccessCallback& success_callback, - const CopyDataErrorCallback& error_callback) { - CopyData(std::move(in_stream), std::move(out_stream), - std::numeric_limits::max(), 4096, success_callback, - error_callback); -} - -void CopyData(StreamPtr in_stream, - StreamPtr out_stream, - uint64_t max_size_to_copy, - size_t buffer_size, - const CopyDataSuccessCallback& success_callback, - const CopyDataErrorCallback& error_callback) { - auto state = std::make_shared(); - state->in_stream = std::move(in_stream); - state->out_stream = std::move(out_stream); - state->buffer.resize(buffer_size); - state->remaining_to_copy = max_size_to_copy; - state->size_copied = 0; - state->success_callback = success_callback; - state->error_callback = error_callback; - chromeos::MessageLoop::current()->PostTask(FROM_HERE, - base::Bind(&PerformRead, state)); -} - -} // namespace stream_utils -} // namespace chromeos diff --git a/chromeos/streams/stream_utils.h b/chromeos/streams/stream_utils.h deleted file mode 100644 index 0e85887..0000000 --- a/chromeos/streams/stream_utils.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_UTILS_H_ - -#include -#include -#include - -namespace chromeos { -namespace stream_utils { - -// Generates "Stream closed" error and returns false. -CHROMEOS_EXPORT bool ErrorStreamClosed( - const tracked_objects::Location& location, ErrorPtr* error); - -// Generates "Not supported" error and returns false. -CHROMEOS_EXPORT bool ErrorOperationNotSupported( - const tracked_objects::Location& location, ErrorPtr* error); - -// Generates "Read past end of stream" error and returns false. -CHROMEOS_EXPORT bool ErrorReadPastEndOfStream( - const tracked_objects::Location& location, ErrorPtr* error); - -// Generates "Operation time out" error and returns false. -CHROMEOS_EXPORT bool ErrorOperationTimeout( - const tracked_objects::Location& location, ErrorPtr* error); - -// Checks if |position| + |offset| fit within the constraint of positive -// signed int64_t type. We use uint64_t for absolute stream pointer positions, -// however many implementations, including file-descriptor-based I/O do not -// support the full extent of unsigned 64 bit numbers. So we restrict the file -// positions to what can fit in the signed 64 bit value (that is, we support -// "only" up to 9 exabytes, instead of the possible 18). -// The |location| parameter will be used to report the origin of the error -// if one is generated/triggered. -CHROMEOS_EXPORT bool CheckInt64Overflow( - const tracked_objects::Location& location, - uint64_t position, - int64_t offset, - ErrorPtr* error); - -// Helper function to calculate the stream position based on the current -// stream position and offset. Returns true and the new calculated stream -// position in |new_position| if successful. In case of invalid stream -// position (negative values or out of range of signed 64 bit values), returns -// false and "invalid_parameter" |error|. -// The |location| parameter will be used to report the origin of the error -// if one is generated/triggered. -CHROMEOS_EXPORT bool CalculateStreamPosition( - const tracked_objects::Location& location, - int64_t offset, - Stream::Whence whence, - uint64_t current_position, - uint64_t stream_size, - uint64_t* new_position, - ErrorPtr* error); - -// Checks if |mode| allows read access. -inline bool IsReadAccessMode(Stream::AccessMode mode) { - return mode == Stream::AccessMode::READ || - mode == Stream::AccessMode::READ_WRITE; -} - -// Checks if |mode| allows write access. -inline bool IsWriteAccessMode(Stream::AccessMode mode) { - return mode == Stream::AccessMode::WRITE || - mode == Stream::AccessMode::READ_WRITE; -} - -// Make the access mode based on read/write rights requested. -inline Stream::AccessMode MakeAccessMode(bool read, bool write) { - CHECK(read || write); // Either read or write (or both) must be specified. - if (read && write) - return Stream::AccessMode::READ_WRITE; - return write ? Stream::AccessMode::WRITE : Stream::AccessMode::READ; -} - -using CopyDataSuccessCallback = - base::Callback; -using CopyDataErrorCallback = - base::Callback; - -// Asynchronously copies data from input stream to output stream until all the -// data from the input stream is read. The function takes ownership of both -// streams for the duration of the operation and then gives them back when -// either the |success_callback| or |error_callback| is called. -// |success_callback| also provides the number of bytes actually copied. -// This variant of CopyData uses internal buffer of 4 KiB for the operation. -CHROMEOS_EXPORT void CopyData(StreamPtr in_stream, - StreamPtr out_stream, - const CopyDataSuccessCallback& success_callback, - const CopyDataErrorCallback& error_callback); - -// Asynchronously copies data from input stream to output stream until the -// maximum amount of data specified in |max_size_to_copy| is copied or the end -// of the input stream is encountered. The function takes ownership of both -// streams for the duration of the operation and then gives them back when -// either the |success_callback| or |error_callback| is called. -// |success_callback| also provides the number of bytes actually copied. -// |buffer_size| specifies the size of the read buffer to use for the operation. -CHROMEOS_EXPORT void CopyData(StreamPtr in_stream, - StreamPtr out_stream, - uint64_t max_size_to_copy, - size_t buffer_size, - const CopyDataSuccessCallback& success_callback, - const CopyDataErrorCallback& error_callback); - -} // namespace stream_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_STREAM_UTILS_H_ diff --git a/chromeos/streams/stream_utils_unittest.cc b/chromeos/streams/stream_utils_unittest.cc deleted file mode 100644 index 1de4fc3..0000000 --- a/chromeos/streams/stream_utils_unittest.cc +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -using testing::DoAll; -using testing::InSequence; -using testing::Return; -using testing::StrictMock; -using testing::_; - -ACTION_TEMPLATE(InvokeAsyncCallback, - HAS_1_TEMPLATE_PARAMS(int, k), - AND_1_VALUE_PARAMS(size)) { - chromeos::MessageLoop::current()->PostTask( - FROM_HERE, base::Bind(std::get(args), size)); - return true; -} - -ACTION_TEMPLATE(InvokeAsyncCallback, - HAS_1_TEMPLATE_PARAMS(int, k), - AND_0_VALUE_PARAMS()) { - chromeos::MessageLoop::current()->PostTask(FROM_HERE, std::get(args)); - return true; -} - -ACTION_TEMPLATE(InvokeAsyncErrorCallback, - HAS_1_TEMPLATE_PARAMS(int, k), - AND_1_VALUE_PARAMS(code)) { - chromeos::ErrorPtr error; - chromeos::Error::AddTo(&error, FROM_HERE, "test", code, "message"); - chromeos::MessageLoop::current()->PostTask( - FROM_HERE, base::Bind(std::get(args), base::Owned(error.release()))); - return true; -} - -namespace chromeos { - -TEST(StreamUtils, ErrorStreamClosed) { - ErrorPtr error; - EXPECT_FALSE(stream_utils::ErrorStreamClosed(FROM_HERE, &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kStreamClosed, error->GetCode()); - EXPECT_EQ("Stream is closed", error->GetMessage()); -} - -TEST(StreamUtils, ErrorOperationNotSupported) { - ErrorPtr error; - EXPECT_FALSE(stream_utils::ErrorOperationNotSupported(FROM_HERE, &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode()); - EXPECT_EQ("Stream operation not supported", error->GetMessage()); -} - -TEST(StreamUtils, ErrorReadPastEndOfStream) { - ErrorPtr error; - EXPECT_FALSE(stream_utils::ErrorReadPastEndOfStream(FROM_HERE, &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kPartialData, error->GetCode()); - EXPECT_EQ("Reading past the end of stream", error->GetMessage()); -} - -TEST(StreamUtils, CheckInt64Overflow) { - const int64_t max_int64 = std::numeric_limits::max(); - const uint64_t max_uint64 = std::numeric_limits::max(); - EXPECT_TRUE(stream_utils::CheckInt64Overflow(FROM_HERE, 0, 0, nullptr)); - EXPECT_TRUE(stream_utils::CheckInt64Overflow( - FROM_HERE, 0, max_int64, nullptr)); - EXPECT_TRUE(stream_utils::CheckInt64Overflow( - FROM_HERE, max_int64, 0, nullptr)); - EXPECT_TRUE(stream_utils::CheckInt64Overflow(FROM_HERE, 100, -90, nullptr)); - EXPECT_TRUE(stream_utils::CheckInt64Overflow( - FROM_HERE, 1000, -1000, nullptr)); - - ErrorPtr error; - EXPECT_FALSE(stream_utils::CheckInt64Overflow(FROM_HERE, 100, -101, &error)); - EXPECT_EQ(errors::stream::kDomain, error->GetDomain()); - EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); - EXPECT_EQ("The stream offset value is out of range", error->GetMessage()); - - EXPECT_FALSE(stream_utils::CheckInt64Overflow( - FROM_HERE, max_int64, 1, nullptr)); - EXPECT_FALSE(stream_utils::CheckInt64Overflow( - FROM_HERE, max_uint64, 0, nullptr)); - EXPECT_FALSE(stream_utils::CheckInt64Overflow( - FROM_HERE, max_uint64, max_int64, nullptr)); -} - -TEST(StreamUtils, CalculateStreamPosition) { - using Whence = Stream::Whence; - const uint64_t current_pos = 1234; - const uint64_t end_pos = 2000; - uint64_t pos = 0; - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, 0, Whence::FROM_BEGIN, current_pos, end_pos, &pos, nullptr)); - EXPECT_EQ(0u, pos); - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, 0, Whence::FROM_CURRENT, current_pos, end_pos, &pos, nullptr)); - EXPECT_EQ(current_pos, pos); - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, 0, Whence::FROM_END, current_pos, end_pos, &pos, nullptr)); - EXPECT_EQ(end_pos, pos); - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, 10, Whence::FROM_BEGIN, current_pos, end_pos, &pos, nullptr)); - EXPECT_EQ(10u, pos); - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, 10, Whence::FROM_CURRENT, current_pos, end_pos, &pos, - nullptr)); - EXPECT_EQ(current_pos + 10, pos); - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, 10, Whence::FROM_END, current_pos, end_pos, &pos, nullptr)); - EXPECT_EQ(end_pos + 10, pos); - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, -10, Whence::FROM_CURRENT, current_pos, end_pos, &pos, - nullptr)); - EXPECT_EQ(current_pos - 10, pos); - - EXPECT_TRUE(stream_utils::CalculateStreamPosition( - FROM_HERE, -10, Whence::FROM_END, current_pos, end_pos, &pos, nullptr)); - EXPECT_EQ(end_pos - 10, pos); - - ErrorPtr error; - EXPECT_FALSE(stream_utils::CalculateStreamPosition( - FROM_HERE, -1, Whence::FROM_BEGIN, current_pos, end_pos, &pos, &error)); - EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode()); - EXPECT_EQ("The stream offset value is out of range", error->GetMessage()); - - EXPECT_FALSE(stream_utils::CalculateStreamPosition( - FROM_HERE, -1001, Whence::FROM_CURRENT, 1000, end_pos, &pos, nullptr)); - - const uint64_t max_int64 = std::numeric_limits::max(); - EXPECT_FALSE(stream_utils::CalculateStreamPosition( - FROM_HERE, 1, Whence::FROM_CURRENT, max_int64, end_pos, &pos, nullptr)); -} - -class CopyStreamDataTest : public testing::Test { - public: - void SetUp() override { - fake_loop_.SetAsCurrent(); - in_stream_.reset(new StrictMock{}); - out_stream_.reset(new StrictMock{}); - } - - FakeMessageLoop fake_loop_{nullptr}; - std::unique_ptr> in_stream_; - std::unique_ptr> out_stream_; - bool succeeded_{false}; - bool failed_{false}; - - void OnSuccess(uint64_t expected, - StreamPtr in_stream, - StreamPtr out_stream, - uint64_t copied) { - EXPECT_EQ(expected, copied); - succeeded_ = true; - } - - void OnError(const std::string& expected_error, - StreamPtr in_stream, - StreamPtr out_stream, - const Error* error) { - EXPECT_EQ(expected_error, error->GetCode()); - failed_ = true; - } - - void ExpectSuccess() { - EXPECT_TRUE(succeeded_); - EXPECT_FALSE(failed_); - } - - void ExpectFailure() { - EXPECT_FALSE(succeeded_); - EXPECT_TRUE(failed_); - } -}; - -TEST_F(CopyStreamDataTest, CopyAllAtOnce) { - { - InSequence seq; - EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(100)); - EXPECT_CALL(*out_stream_, WriteAllAsync(_, 100, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>()); - } - stream_utils::CopyData( - std::move(in_stream_), std::move(out_stream_), 100, 4096, - base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100), - base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); - fake_loop_.Run(); - ExpectSuccess(); -} - -TEST_F(CopyStreamDataTest, CopyInBlocks) { - { - InSequence seq; - EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(60)); - EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>()); - EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(40)); - EXPECT_CALL(*out_stream_, WriteAllAsync(_, 40, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>()); - } - stream_utils::CopyData( - std::move(in_stream_), std::move(out_stream_), 100, 4096, - base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100), - base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); - fake_loop_.Run(); - ExpectSuccess(); -} - -TEST_F(CopyStreamDataTest, CopyTillEndOfStream) { - { - InSequence seq; - EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(60)); - EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>()); - EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(0)); - } - stream_utils::CopyData( - std::move(in_stream_), std::move(out_stream_), 100, 4096, - base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 60), - base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); - fake_loop_.Run(); - ExpectSuccess(); -} - -TEST_F(CopyStreamDataTest, CopyInSmallBlocks) { - { - InSequence seq; - EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(60)); - EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>()); - EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(40)); - EXPECT_CALL(*out_stream_, WriteAllAsync(_, 40, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>()); - } - stream_utils::CopyData( - std::move(in_stream_), std::move(out_stream_), 100, 60, - base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100), - base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "")); - fake_loop_.Run(); - ExpectSuccess(); -} - -TEST_F(CopyStreamDataTest, ErrorRead) { - { - InSequence seq; - EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _)) - .WillOnce(InvokeAsyncErrorCallback<3>("read")); - } - stream_utils::CopyData( - std::move(in_stream_), std::move(out_stream_), 100, 60, - base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 0), - base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "read")); - fake_loop_.Run(); - ExpectFailure(); -} - -TEST_F(CopyStreamDataTest, ErrorWrite) { - { - InSequence seq; - EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _)) - .WillOnce(InvokeAsyncCallback<2>(60)); - EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _)) - .WillOnce(InvokeAsyncErrorCallback<3>("write")); - } - stream_utils::CopyData( - std::move(in_stream_), std::move(out_stream_), 100, 60, - base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 0), - base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), - "write")); - fake_loop_.Run(); - ExpectFailure(); -} - -} // namespace chromeos diff --git a/chromeos/streams/tls_stream.cc b/chromeos/streams/tls_stream.cc deleted file mode 100644 index 6dc5ef5..0000000 --- a/chromeos/streams/tls_stream.cc +++ /dev/null @@ -1,546 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace { - -// SSL info callback which is called by OpenSSL when we enable logging level of -// at least 3. This logs the information about the internal TLS handshake. -void TlsInfoCallback(const SSL *ssl, int where, int ret) { - std::string reason; - std::vector info; - if (where & SSL_CB_LOOP) - info.push_back("loop"); - if (where & SSL_CB_EXIT) - info.push_back("exit"); - if (where & SSL_CB_READ) - info.push_back("read"); - if (where & SSL_CB_WRITE) - info.push_back("write"); - if (where & SSL_CB_ALERT) { - info.push_back("alert"); - reason = ", reason: "; - reason += SSL_alert_type_string_long(ret); - reason += "/"; - reason += SSL_alert_desc_string_long(ret); - } - if (where & SSL_CB_HANDSHAKE_START) - info.push_back("handshake_start"); - if (where & SSL_CB_HANDSHAKE_DONE) - info.push_back("handshake_done"); - - VLOG(3) << "TLS progress info: " << chromeos::string_utils::Join(",", info) - << ", with status: " << ret << reason; -} - -// Static variable to store the index of TlsStream private data in SSL context -// used to store custom data for OnCertVerifyResults(). -int ssl_ctx_private_data_index = -1; - -// Default trusted certificate store location. -const char kCACertificatePath[] = -#ifdef __ANDROID__ - "/system/etc/security/cacerts"; -#else - "/usr/share/chromeos-ca-certificates"; -#endif - -} // anonymous namespace - -namespace chromeos { - -// Helper implementation of TLS stream used to hide most of OpenSSL inner -// workings from the users of chromeos::TlsStream. -class TlsStream::TlsStreamImpl { - public: - TlsStreamImpl(); - ~TlsStreamImpl(); - - bool Init(StreamPtr socket, - const std::string& host, - const base::Closure& success_callback, - const Stream::ErrorCallback& error_callback, - ErrorPtr* error); - - bool ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error); - - bool WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error); - - bool Flush(ErrorPtr* error); - bool Close(ErrorPtr* error); - bool WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error); - bool WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error); - void CancelPendingAsyncOperations(); - - private: - bool ReportError(ErrorPtr* error, - const tracked_objects::Location& location, - const std::string& message); - void DoHandshake(const base::Closure& success_callback, - const Stream::ErrorCallback& error_callback); - void RetryHandshake(const base::Closure& success_callback, - const Stream::ErrorCallback& error_callback, - Stream::AccessMode mode); - - int OnCertVerifyResults(int ok, X509_STORE_CTX* ctx); - static int OnCertVerifyResultsStatic(int ok, X509_STORE_CTX* ctx); - - StreamPtr socket_; - std::unique_ptr ctx_{nullptr, SSL_CTX_free}; - std::unique_ptr ssl_{nullptr, SSL_free}; - BIO* stream_bio_{nullptr}; - bool need_more_read_{false}; - bool need_more_write_{false}; - - base::WeakPtrFactory weak_ptr_factory_{this}; - DISALLOW_COPY_AND_ASSIGN(TlsStreamImpl); -}; - -TlsStream::TlsStreamImpl::TlsStreamImpl() { - SSL_load_error_strings(); - SSL_library_init(); - if (ssl_ctx_private_data_index < 0) { - ssl_ctx_private_data_index = - SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); - } -} - -TlsStream::TlsStreamImpl::~TlsStreamImpl() { - ssl_.reset(); - ctx_.reset(); -} - -bool TlsStream::TlsStreamImpl::ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) { - const size_t max_int = std::numeric_limits::max(); - int size_int = static_cast(std::min(size_to_read, max_int)); - int ret = SSL_read(ssl_.get(), buffer, size_int); - if (ret > 0) { - *size_read = static_cast(ret); - if (end_of_stream) - *end_of_stream = false; - return true; - } - - int err = SSL_get_error(ssl_.get(), ret); - if (err == SSL_ERROR_ZERO_RETURN) { - *size_read = 0; - if (end_of_stream) - *end_of_stream = true; - return true; - } - - if (err == SSL_ERROR_WANT_READ) { - need_more_read_ = true; - } else if (err == SSL_ERROR_WANT_WRITE) { - // Writes might be required for SSL_read() because of possible TLS - // re-negotiations which can happen at any time. - need_more_write_ = true; - } else { - return ReportError(error, FROM_HERE, "Error reading from TLS socket"); - } - *size_read = 0; - if (end_of_stream) - *end_of_stream = false; - return true; -} - -bool TlsStream::TlsStreamImpl::WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) { - const size_t max_int = std::numeric_limits::max(); - int size_int = static_cast(std::min(size_to_write, max_int)); - int ret = SSL_write(ssl_.get(), buffer, size_int); - if (ret > 0) { - *size_written = static_cast(ret); - return true; - } - - int err = SSL_get_error(ssl_.get(), ret); - if (err == SSL_ERROR_WANT_READ) { - // Reads might be required for SSL_write() because of possible TLS - // re-negotiations which can happen at any time. - need_more_read_ = true; - } else if (err == SSL_ERROR_WANT_WRITE) { - need_more_write_ = true; - } else { - return ReportError(error, FROM_HERE, "Error writing to TLS socket"); - } - *size_written = 0; - return true; -} - -bool TlsStream::TlsStreamImpl::Flush(ErrorPtr* error) { - return socket_->FlushBlocking(error); -} - -bool TlsStream::TlsStreamImpl::Close(ErrorPtr* error) { - // 2 seconds should be plenty here. - const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(2); - // The retry count of 4 below is just arbitrary, to ensure we don't get stuck - // here forever. We should rarely need to repeat SSL_shutdown anyway. - for (int retry_count = 0; retry_count < 4; retry_count++) { - int ret = SSL_shutdown(ssl_.get()); - // We really don't care for bi-directional shutdown here. - // Just make sure we only send the "close notify" alert to the remote peer. - if (ret >= 0) - break; - - int err = SSL_get_error(ssl_.get(), ret); - if (err == SSL_ERROR_WANT_READ) { - if (!socket_->WaitForDataBlocking(AccessMode::READ, kTimeout, nullptr, - error)) { - break; - } - } else if (err == SSL_ERROR_WANT_WRITE) { - if (!socket_->WaitForDataBlocking(AccessMode::WRITE, kTimeout, nullptr, - error)) { - break; - } - } else { - LOG(ERROR) << "SSL_shutdown returned error #" << err; - ReportError(error, FROM_HERE, "Failed to shut down TLS socket"); - break; - } - } - return socket_->CloseBlocking(error); -} - -bool TlsStream::TlsStreamImpl::WaitForData( - AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) { - bool is_read = stream_utils::IsReadAccessMode(mode); - bool is_write = stream_utils::IsWriteAccessMode(mode); - is_read |= need_more_read_; - is_write |= need_more_write_; - need_more_read_ = false; - need_more_write_ = false; - if (is_read && SSL_pending(ssl_.get()) > 0) { - callback.Run(AccessMode::READ); - return true; - } - mode = stream_utils::MakeAccessMode(is_read, is_write); - return socket_->WaitForData(mode, callback, error); -} - -bool TlsStream::TlsStreamImpl::WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) { - bool is_read = stream_utils::IsReadAccessMode(in_mode); - bool is_write = stream_utils::IsWriteAccessMode(in_mode); - is_read |= need_more_read_; - is_write |= need_more_write_; - need_more_read_ = need_more_write_ = false; - if (is_read && SSL_pending(ssl_.get()) > 0) { - if (out_mode) - *out_mode = AccessMode::READ; - return true; - } - in_mode = stream_utils::MakeAccessMode(is_read, is_write); - return socket_->WaitForDataBlocking(in_mode, timeout, out_mode, error); -} - -void TlsStream::TlsStreamImpl::CancelPendingAsyncOperations() { - socket_->CancelPendingAsyncOperations(); - weak_ptr_factory_.InvalidateWeakPtrs(); -} - -bool TlsStream::TlsStreamImpl::ReportError( - ErrorPtr* error, - const tracked_objects::Location& location, - const std::string& message) { - const char* file = nullptr; - int line = 0; - const char* data = 0; - int flags = 0; - while (auto errnum = ERR_get_error_line_data(&file, &line, &data, &flags)) { - char buf[256]; - ERR_error_string_n(errnum, buf, sizeof(buf)); - tracked_objects::Location ssl_location{"Unknown", file, line, nullptr}; - std::string ssl_message = buf; - if (flags & ERR_TXT_STRING) { - ssl_message += ": "; - ssl_message += data; - } - Error::AddTo(error, ssl_location, "openssl", std::to_string(errnum), - ssl_message); - } - Error::AddTo(error, location, "tls_stream", "failed", message); - return false; -} - -int TlsStream::TlsStreamImpl::OnCertVerifyResults(int ok, X509_STORE_CTX* ctx) { - // OpenSSL already performs a comprehensive check of the certificate chain - // (using X509_verify_cert() function) and calls back with the result of its - // verification. - // |ok| is set to 1 if the verification passed and 0 if an error was detected. - // Here we can perform some additional checks if we need to, or simply log - // the issues found. - - // For now, just log an error if it occurred. - if (!ok) { - LOG(ERROR) << "Server certificate validation failed: " - << X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)); - } - return ok; -} - -int TlsStream::TlsStreamImpl::OnCertVerifyResultsStatic(int ok, - X509_STORE_CTX* ctx) { - // Obtain the pointer to the instance of TlsStream::TlsStreamImpl from the - // SSL CTX object referenced by |ctx|. - SSL* ssl = static_cast(X509_STORE_CTX_get_ex_data( - ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - SSL_CTX* ssl_ctx = ssl ? SSL_get_SSL_CTX(ssl) : nullptr; - TlsStream::TlsStreamImpl* self = nullptr; - if (ssl_ctx) { - self = static_cast(SSL_CTX_get_ex_data( - ssl_ctx, ssl_ctx_private_data_index)); - } - return self ? self->OnCertVerifyResults(ok, ctx) : ok; -} - -bool TlsStream::TlsStreamImpl::Init(StreamPtr socket, - const std::string& host, - const base::Closure& success_callback, - const Stream::ErrorCallback& error_callback, - ErrorPtr* error) { - ctx_.reset(SSL_CTX_new(TLSv1_2_client_method())); - if (!ctx_) - return ReportError(error, FROM_HERE, "Cannot create SSL_CTX"); - - // Top cipher suites supported by both Google GFEs and OpenSSL (in server - // preferred order). - int res = SSL_CTX_set_cipher_list(ctx_.get(), - "ECDHE-ECDSA-AES128-GCM-SHA256:" - "ECDHE-ECDSA-AES256-GCM-SHA384:" - "ECDHE-RSA-AES128-GCM-SHA256:" - "ECDHE-RSA-AES256-GCM-SHA384"); - if (res != 1) - return ReportError(error, FROM_HERE, "Cannot set the cipher list"); - - res = SSL_CTX_load_verify_locations(ctx_.get(), nullptr, - kCACertificatePath); - if (res != 1) { - return ReportError(error, FROM_HERE, - "Failed to specify trusted certificate location"); - } - - // Store a pointer to "this" into SSL_CTX instance. - SSL_CTX_set_ex_data(ctx_.get(), ssl_ctx_private_data_index, this); - - // Ask OpenSSL to validate the server host from the certificate to match - // the expected host name we are given: - X509_VERIFY_PARAM* param = SSL_CTX_get0_param(ctx_.get()); - X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size()); - - SSL_CTX_set_verify(ctx_.get(), SSL_VERIFY_PEER, - &TlsStreamImpl::OnCertVerifyResultsStatic); - - socket_ = std::move(socket); - ssl_.reset(SSL_new(ctx_.get())); - - // Enable TLS progress callback if VLOG level is >=3. - if (VLOG_IS_ON(3)) - SSL_set_info_callback(ssl_.get(), TlsInfoCallback); - - stream_bio_ = BIO_new_stream(socket_.get()); - SSL_set_bio(ssl_.get(), stream_bio_, stream_bio_); - SSL_set_connect_state(ssl_.get()); - - // We might have no message loop (e.g. we are in unit tests). - if (MessageLoop::ThreadHasCurrent()) { - MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(&TlsStreamImpl::DoHandshake, - weak_ptr_factory_.GetWeakPtr(), - success_callback, - error_callback)); - } else { - DoHandshake(success_callback, error_callback); - } - return true; -} - -void TlsStream::TlsStreamImpl::RetryHandshake( - const base::Closure& success_callback, - const Stream::ErrorCallback& error_callback, - Stream::AccessMode mode) { - VLOG(1) << "Retrying TLS handshake"; - DoHandshake(success_callback, error_callback); -} - -void TlsStream::TlsStreamImpl::DoHandshake( - const base::Closure& success_callback, - const Stream::ErrorCallback& error_callback) { - VLOG(1) << "Begin TLS handshake"; - int res = SSL_do_handshake(ssl_.get()); - if (res == 1) { - VLOG(1) << "Handshake successful"; - success_callback.Run(); - return; - } - ErrorPtr error; - int err = SSL_get_error(ssl_.get(), res); - if (err == SSL_ERROR_WANT_READ) { - VLOG(1) << "Waiting for read data..."; - bool ok = socket_->WaitForData( - Stream::AccessMode::READ, - base::Bind(&TlsStreamImpl::RetryHandshake, - weak_ptr_factory_.GetWeakPtr(), - success_callback, error_callback), - &error); - if (ok) - return; - } else if (err == SSL_ERROR_WANT_WRITE) { - VLOG(1) << "Waiting for write data..."; - bool ok = socket_->WaitForData( - Stream::AccessMode::WRITE, - base::Bind(&TlsStreamImpl::RetryHandshake, - weak_ptr_factory_.GetWeakPtr(), - success_callback, error_callback), - &error); - if (ok) - return; - } else { - ReportError(&error, FROM_HERE, "TLS handshake failed."); - } - error_callback.Run(error.get()); -} - -///////////////////////////////////////////////////////////////////////////// -TlsStream::TlsStream(std::unique_ptr impl) - : impl_{std::move(impl)} {} - -TlsStream::~TlsStream() { - if (impl_) { - impl_->Close(nullptr); - } -} - -void TlsStream::Connect(StreamPtr socket, - const std::string& host, - const base::Callback& success_callback, - const Stream::ErrorCallback& error_callback) { - std::unique_ptr impl{new TlsStreamImpl}; - std::unique_ptr stream{new TlsStream{std::move(impl)}}; - - TlsStreamImpl* pimpl = stream->impl_.get(); - ErrorPtr error; - bool success = pimpl->Init(std::move(socket), host, - base::Bind(success_callback, - base::Passed(std::move(stream))), - error_callback, &error); - - if (!success) - error_callback.Run(error.get()); -} - -bool TlsStream::IsOpen() const { - return impl_ ? true : false; -} - -bool TlsStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) { - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); -} - -bool TlsStream::Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) { - return stream_utils::ErrorOperationNotSupported(FROM_HERE, error); -} - -bool TlsStream::ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) { - if (!impl_) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - return impl_->ReadNonBlocking(buffer, size_to_read, size_read, end_of_stream, - error); -} - -bool TlsStream::WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) { - if (!impl_) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - return impl_->WriteNonBlocking(buffer, size_to_write, size_written, error); -} - -bool TlsStream::FlushBlocking(ErrorPtr* error) { - if (!impl_) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - return impl_->Flush(error); -} - -bool TlsStream::CloseBlocking(ErrorPtr* error) { - if (impl_ && !impl_->Close(error)) - return false; - impl_.reset(); - return true; -} - -bool TlsStream::WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) { - if (!impl_) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - return impl_->WaitForData(mode, callback, error); -} - -bool TlsStream::WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) { - if (!impl_) - return stream_utils::ErrorStreamClosed(FROM_HERE, error); - return impl_->WaitForDataBlocking(in_mode, timeout, out_mode, error); -} - -void TlsStream::CancelPendingAsyncOperations() { - if (impl_) - impl_->CancelPendingAsyncOperations(); - Stream::CancelPendingAsyncOperations(); -} - -} // namespace chromeos diff --git a/chromeos/streams/tls_stream.h b/chromeos/streams/tls_stream.h deleted file mode 100644 index fb5b323..0000000 --- a/chromeos/streams/tls_stream.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STREAMS_TLS_STREAM_H_ -#define LIBCHROMEOS_CHROMEOS_STREAMS_TLS_STREAM_H_ - -#include -#include - -#include -#include -#include -#include - -namespace chromeos { - -// This class provides client-side TLS stream that performs handshake with the -// server and established a secure communication channel which can be used -// by performing read/write operations on this stream. Both synchronous and -// asynchronous I/O is supported. -// The underlying socket stream must already be created and connected to the -// destination server and passed in TlsStream::Connect() method as |socket|. -class CHROMEOS_EXPORT TlsStream : public Stream { - public: - ~TlsStream() override; - - // Perform a TLS handshake and establish secure connection over |socket|. - // Calls |callback| when successful and passes the instance of TlsStream - // as an argument. In case of an error, |error_callback| is called. - // |host| must specify the expected remote host (server) name. - static void Connect( - StreamPtr socket, - const std::string& host, - const base::Callback& success_callback, - const Stream::ErrorCallback& error_callback); - - // Overrides from Stream: - bool IsOpen() const override; - bool CanRead() const override { return true; } - bool CanWrite() const override { return true; } - bool CanSeek() const override { return false; } - bool CanGetSize() const override { return false; } - uint64_t GetSize() const override { return 0; } - bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override; - uint64_t GetRemainingSize() const override { return 0; } - uint64_t GetPosition() const override { return 0; } - bool Seek(int64_t offset, - Whence whence, - uint64_t* new_position, - ErrorPtr* error) override; - bool ReadNonBlocking(void* buffer, - size_t size_to_read, - size_t* size_read, - bool* end_of_stream, - ErrorPtr* error) override; - bool WriteNonBlocking(const void* buffer, - size_t size_to_write, - size_t* size_written, - ErrorPtr* error) override; - bool FlushBlocking(ErrorPtr* error) override; - bool CloseBlocking(ErrorPtr* error) override; - bool WaitForData(AccessMode mode, - const base::Callback& callback, - ErrorPtr* error) override; - bool WaitForDataBlocking(AccessMode in_mode, - base::TimeDelta timeout, - AccessMode* out_mode, - ErrorPtr* error) override; - void CancelPendingAsyncOperations() override; - - private: - class TlsStreamImpl; - - // Private constructor called from TlsStream::Connect() factory method. - explicit TlsStream(std::unique_ptr impl); - - std::unique_ptr impl_; - DISALLOW_COPY_AND_ASSIGN(TlsStream); -}; - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STREAMS_TLS_STREAM_H_ diff --git a/chromeos/strings/string_utils.cc b/chromeos/strings/string_utils.cc deleted file mode 100644 index 503d8f8..0000000 --- a/chromeos/strings/string_utils.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -#include -#include - -namespace chromeos { -namespace string_utils { - -std::vector Split(const std::string& str, - const std::string& delimiter, - bool trim_whitespaces, - bool purge_empty_strings) { - std::vector tokens; - if (str.empty()) - return tokens; - - for (std::string::size_type i = 0;;) { - const std::string::size_type pos = - delimiter.empty() ? (i + 1) : str.find(delimiter, i); - std::string tmp_str{str.substr(i, pos - i)}; - if (trim_whitespaces) - base::TrimWhitespaceASCII(tmp_str, base::TRIM_ALL, &tmp_str); - if (!tmp_str.empty() || !purge_empty_strings) - tokens.emplace_back(std::move(tmp_str)); - if (pos >= str.size()) - break; - i = pos + delimiter.size(); - } - return tokens; -} - -bool SplitAtFirst(const std::string& str, - const std::string& delimiter, - std::string* left_part, - std::string* right_part, - bool trim_whitespaces) { - bool delimiter_found = false; - std::string::size_type pos = str.find(delimiter); - if (pos != std::string::npos) { - *left_part = str.substr(0, pos); - *right_part = str.substr(pos + delimiter.size()); - delimiter_found = true; - } else { - *left_part = str; - right_part->clear(); - } - - if (trim_whitespaces) { - base::TrimWhitespaceASCII(*left_part, base::TRIM_ALL, left_part); - base::TrimWhitespaceASCII(*right_part, base::TRIM_ALL, right_part); - } - - return delimiter_found; -} - -std::pair SplitAtFirst(const std::string& str, - const std::string& delimiter, - bool trim_whitespaces) { - std::pair pair; - SplitAtFirst(str, delimiter, &pair.first, &pair.second, trim_whitespaces); - return pair; -} - -std::string ToString(double value) { - return base::StringPrintf("%g", value); -} - -std::string ToString(bool value) { - return value ? "true" : "false"; -} - -std::string GetBytesAsString(const std::vector& buffer) { - return std::string(buffer.begin(), buffer.end()); -} - -std::vector GetStringAsBytes(const std::string& str) { - return std::vector(str.begin(), str.end()); -} - -} // namespace string_utils -} // namespace chromeos diff --git a/chromeos/strings/string_utils.h b/chromeos/strings/string_utils.h deleted file mode 100644 index 1f56395..0000000 --- a/chromeos/strings/string_utils.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_STRINGS_STRING_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_STRINGS_STRING_UTILS_H_ - -#include -#include -#include - -#include - -namespace chromeos { -namespace string_utils { - -// Treats the string as a delimited list of substrings and returns the array -// of original elements of the list. -// |trim_whitespaces| causes each element to have all whitespaces trimmed off. -// |purge_empty_strings| specifies whether empty elements from the original -// string should be omitted. -CHROMEOS_EXPORT std::vector Split(const std::string& str, - const std::string& delimiter, - bool trim_whitespaces, - bool purge_empty_strings); -// Splits the string, trims all whitespaces, omits empty string parts. -inline std::vector Split(const std::string& str, - const std::string& delimiter) { - return Split(str, delimiter, true, true); -} -// Splits the string, omits empty string parts. -inline std::vector Split(const std::string& str, - const std::string& delimiter, - bool trim_whitespaces) { - return Split(str, delimiter, trim_whitespaces, true); -} - -// Splits the string into two pieces at the first position of the specified -// delimiter. -CHROMEOS_EXPORT std::pair SplitAtFirst( - const std::string& str, - const std::string& delimiter, - bool trim_whitespaces); -// Splits the string into two pieces at the first position of the specified -// delimiter. Both parts have all whitespaces trimmed off. -inline std::pair SplitAtFirst( - const std::string& str, - const std::string& delimiter) { - return SplitAtFirst(str, delimiter, true); -} - -// The following overload returns false if the delimiter was not found in the -// source string. In this case, |left_part| will be set to |str| and -// |right_part| will be empty. -CHROMEOS_EXPORT bool SplitAtFirst(const std::string& str, - const std::string& delimiter, - std::string* left_part, - std::string* right_part, - bool trim_whitespaces); -// Always trims the white spaces in the split parts. -inline bool SplitAtFirst(const std::string& str, - const std::string& delimiter, - std::string* left_part, - std::string* right_part) { - return SplitAtFirst(str, delimiter, left_part, right_part, true); -} - -// Joins strings into a single string separated by |delimiter|. -template -std::string JoinRange(const std::string& delimiter, - InputIterator first, - InputIterator last) { - std::string result; - if (first == last) - return result; - result = *first; - for (++first; first != last; ++first) { - result += delimiter; - result += *first; - } - return result; -} - -template -std::string Join(const std::string& delimiter, const Container& strings) { - using std::begin; - using std::end; - return JoinRange(delimiter, begin(strings), end(strings)); -} - -inline std::string Join(const std::string& delimiter, - std::initializer_list strings) { - return JoinRange(delimiter, strings.begin(), strings.end()); -} - -inline std::string Join(const std::string& delimiter, - const std::string& str1, - const std::string& str2) { - return str1 + delimiter + str2; -} - -// string_utils::ToString() is a helper function to convert any scalar type -// to a string. In most cases, it redirects the call to std::to_string with -// two exceptions: for std::string itself and for double and bool. -template -inline std::string ToString(T value) { - return std::to_string(value); -} -// Having the following overload is handy for templates where the type -// of template parameter isn't known and could be a string itself. -inline std::string ToString(std::string value) { - return value; -} -// We overload this for double because std::to_string(double) uses %f to -// format the value and I would like to use a shorter %g format instead. -CHROMEOS_EXPORT std::string ToString(double value); -// And the bool to be converted as true/false instead of 1/0. -CHROMEOS_EXPORT std::string ToString(bool value); - -// Converts a byte-array into a string. This method doesn't perform any -// data re-encoding. It just takes every byte from the buffer and appends it -// to the string as a character. -CHROMEOS_EXPORT std::string GetBytesAsString(const std::vector& buf); - -// Converts a string into a byte-array. Opposite of GetBytesAsString(). -CHROMEOS_EXPORT std::vector GetStringAsBytes(const std::string& str); - -} // namespace string_utils -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_STRINGS_STRING_UTILS_H_ diff --git a/chromeos/strings/string_utils_unittest.cc b/chromeos/strings/string_utils_unittest.cc deleted file mode 100644 index 7b5909c..0000000 --- a/chromeos/strings/string_utils_unittest.cc +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include -#include - -#include - -namespace chromeos { - -TEST(StringUtils, Split) { - std::vector parts; - - parts = string_utils::Split("", ",", false, false); - EXPECT_EQ(0, parts.size()); - - parts = string_utils::Split("abc", ",", false, false); - EXPECT_EQ(1, parts.size()); - EXPECT_EQ("abc", parts[0]); - - parts = string_utils::Split(",a,bc , d, ,e, ", ",", true, true); - EXPECT_EQ(4, parts.size()); - EXPECT_EQ("a", parts[0]); - EXPECT_EQ("bc", parts[1]); - EXPECT_EQ("d", parts[2]); - EXPECT_EQ("e", parts[3]); - - parts = string_utils::Split(",a,bc , d, ,e, ", ",", false, true); - EXPECT_EQ(6, parts.size()); - EXPECT_EQ("a", parts[0]); - EXPECT_EQ("bc ", parts[1]); - EXPECT_EQ(" d", parts[2]); - EXPECT_EQ(" ", parts[3]); - EXPECT_EQ("e", parts[4]); - EXPECT_EQ(" ", parts[5]); - - parts = string_utils::Split(",a,bc , d, ,e, ", ",", true, false); - EXPECT_EQ(7, parts.size()); - EXPECT_EQ("", parts[0]); - EXPECT_EQ("a", parts[1]); - EXPECT_EQ("bc", parts[2]); - EXPECT_EQ("d", parts[3]); - EXPECT_EQ("", parts[4]); - EXPECT_EQ("e", parts[5]); - EXPECT_EQ("", parts[6]); - - parts = string_utils::Split(",a,bc , d, ,e, ", ",", false, false); - EXPECT_EQ(7, parts.size()); - EXPECT_EQ("", parts[0]); - EXPECT_EQ("a", parts[1]); - EXPECT_EQ("bc ", parts[2]); - EXPECT_EQ(" d", parts[3]); - EXPECT_EQ(" ", parts[4]); - EXPECT_EQ("e", parts[5]); - EXPECT_EQ(" ", parts[6]); - - parts = string_utils::Split("abc:=xyz", ":=", false, false); - EXPECT_EQ(2, parts.size()); - EXPECT_EQ("abc", parts[0]); - EXPECT_EQ("xyz", parts[1]); - - parts = string_utils::Split("abc", "", false, false); - EXPECT_EQ(3, parts.size()); - EXPECT_EQ("a", parts[0]); - EXPECT_EQ("b", parts[1]); - EXPECT_EQ("c", parts[2]); -} - -TEST(StringUtils, SplitAtFirst) { - std::pair pair; - - pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ":", true); - EXPECT_EQ("123", pair.first); - EXPECT_EQ("4 : 56 : 789", pair.second); - - pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ":", false); - EXPECT_EQ(" 123 ", pair.first); - EXPECT_EQ(" 4 : 56 : 789 ", pair.second); - - pair = string_utils::SplitAtFirst("", "="); - EXPECT_EQ("", pair.first); - EXPECT_EQ("", pair.second); - - pair = string_utils::SplitAtFirst("=", "="); - EXPECT_EQ("", pair.first); - EXPECT_EQ("", pair.second); - - pair = string_utils::SplitAtFirst("a=", "="); - EXPECT_EQ("a", pair.first); - EXPECT_EQ("", pair.second); - - pair = string_utils::SplitAtFirst("abc=", "="); - EXPECT_EQ("abc", pair.first); - EXPECT_EQ("", pair.second); - - pair = string_utils::SplitAtFirst("=a", "="); - EXPECT_EQ("", pair.first); - EXPECT_EQ("a", pair.second); - - pair = string_utils::SplitAtFirst("=abc=", "="); - EXPECT_EQ("", pair.first); - EXPECT_EQ("abc=", pair.second); - - pair = string_utils::SplitAtFirst("abc", "="); - EXPECT_EQ("abc", pair.first); - EXPECT_EQ("", pair.second); - - pair = string_utils::SplitAtFirst("abc:=xyz", ":="); - EXPECT_EQ("abc", pair.first); - EXPECT_EQ("xyz", pair.second); - - pair = string_utils::SplitAtFirst("abc", ""); - EXPECT_EQ("", pair.first); - EXPECT_EQ("abc", pair.second); -} - -TEST(StringUtils, Join_String) { - EXPECT_EQ("", string_utils::Join(",", {})); - EXPECT_EQ("abc", string_utils::Join(",", {"abc"})); - EXPECT_EQ("abc,,xyz", string_utils::Join(",", {"abc", "", "xyz"})); - EXPECT_EQ("abc,defg", string_utils::Join(",", {"abc", "defg"})); - EXPECT_EQ("1 : 2 : 3", string_utils::Join(" : ", {"1", "2", "3"})); - EXPECT_EQ("1:2", string_utils::Join(":", std::set{"1", "2"})); - EXPECT_EQ("1:2", string_utils::Join(":", std::vector{"1", "2"})); - EXPECT_EQ("1:2", string_utils::Join(":", std::list{"1", "2"})); - EXPECT_EQ("123", string_utils::Join("", {"1", "2", "3"})); -} - -TEST(StringUtils, Join_Pair) { - EXPECT_EQ("ab,cd", string_utils::Join(",", "ab", "cd")); - EXPECT_EQ("key = value", string_utils::Join(" = ", "key", "value")); -} - -TEST(StringUtils, GetBytesAsString) { - EXPECT_EQ("abc", string_utils::GetBytesAsString({'a', 'b', 'c'})); - EXPECT_TRUE(string_utils::GetBytesAsString({}).empty()); - auto str = string_utils::GetBytesAsString({0xFF, 0x00, 0x01, 0x7F, 0x80}); - ASSERT_EQ(5, str.size()); - EXPECT_EQ('\xFF', str[0]); - EXPECT_EQ('\x00', str[1]); - EXPECT_EQ('\x01', str[2]); - EXPECT_EQ('\x7F', str[3]); - EXPECT_EQ('\x80', str[4]); -} - -TEST(StringUtils, GetStringAsBytes) { - EXPECT_EQ((std::vector{'a', 'b', 'c'}), - string_utils::GetStringAsBytes("abc")); - EXPECT_TRUE(string_utils::GetStringAsBytes("").empty()); - auto buf = string_utils::GetStringAsBytes(std::string{"\x80\0\1\xFF", 4}); - ASSERT_EQ(4, buf.size()); - EXPECT_EQ(128, buf[0]); - EXPECT_EQ(0, buf[1]); - EXPECT_EQ(1, buf[2]); - EXPECT_EQ(255, buf[3]); -} - -} // namespace chromeos diff --git a/chromeos/syslog_logging.cc b/chromeos/syslog_logging.cc deleted file mode 100644 index 79d0840..0000000 --- a/chromeos/syslog_logging.cc +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/syslog_logging.h" - -#include - -#include - -// syslog.h and base/logging.h both try to #define LOG_INFO and LOG_WARNING. -// We need to #undef at least these two before including base/logging.h. The -// others are included to be consistent. -namespace { -const int kSyslogDebug = LOG_DEBUG; -const int kSyslogInfo = LOG_INFO; -const int kSyslogWarning = LOG_WARNING; -const int kSyslogError = LOG_ERR; -const int kSyslogCritical = LOG_CRIT; - -#undef LOG_INFO -#undef LOG_WARNING -#undef LOG_ERR -#undef LOG_CRIT -} // namespace - -#include - -static std::string s_ident; -static std::string s_accumulated; -static bool s_accumulate; -static bool s_log_to_syslog; -static bool s_log_to_stderr; -static bool s_log_header; - -static bool HandleMessage(int severity, - const char* file, - int line, - size_t message_start, - const std::string& message) { - switch (severity) { - case logging::LOG_INFO: - severity = kSyslogInfo; - break; - - case logging::LOG_WARNING: - severity = kSyslogWarning; - break; - - case logging::LOG_ERROR: - severity = kSyslogError; - break; - - case logging::LOG_FATAL: - severity = kSyslogCritical; - break; - - default: - severity = kSyslogDebug; - break; - } - - const char* str; - if (s_log_header) { - str = message.c_str(); - } else { - str = message.c_str() + message_start; - } - - if (s_log_to_syslog) - syslog(severity, "%s", str); - if (s_accumulate) - s_accumulated.append(str); - return !s_log_to_stderr && severity != kSyslogCritical; -} - -namespace chromeos { -void SetLogFlags(int log_flags) { - s_log_to_syslog = (log_flags & kLogToSyslog) != 0; - s_log_to_stderr = (log_flags & kLogToStderr) != 0; - s_log_header = (log_flags & kLogHeader) != 0; -} -int GetLogFlags() { - int flags = 0; - flags |= (s_log_to_syslog) ? kLogToSyslog : 0; - flags |= (s_log_to_stderr) ? kLogToStderr : 0; - flags |= (s_log_header) ? kLogHeader : 0; - return flags; -} -void InitLog(int init_flags) { - logging::LoggingSettings settings; - settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; - logging::InitLogging(settings); - - const bool kOptionPID = false; - const bool kOptionTID = false; - const bool kOptionTimestamp = false; - const bool kOptionTickcount = false; - logging::SetLogItems( - kOptionPID, kOptionTID, kOptionTimestamp, kOptionTickcount); - logging::SetLogMessageHandler(HandleMessage); - SetLogFlags(init_flags); -} -void OpenLog(const char* ident, bool log_pid) { - s_ident = ident; - openlog(s_ident.c_str(), log_pid ? LOG_PID : 0, LOG_USER); -} -void LogToString(bool enabled) { - s_accumulate = enabled; -} -std::string GetLog() { - return s_accumulated; -} -void ClearLog() { - s_accumulated.clear(); -} -bool FindLog(const char* string) { - return s_accumulated.find(string) != std::string::npos; -} -} // namespace chromeos diff --git a/chromeos/syslog_logging.h b/chromeos/syslog_logging.h deleted file mode 100644 index 134d3cc..0000000 --- a/chromeos/syslog_logging.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_SYSLOG_LOGGING_H_ -#define LIBCHROMEOS_CHROMEOS_SYSLOG_LOGGING_H_ - -#include - -#include - -namespace chromeos { - -enum InitFlags { - kLogToSyslog = 1, - kLogToStderr = 2, - kLogHeader = 4, -}; - -// Initialize logging subsystem. |init_flags| is a bitfield, with bits defined -// in InitFlags above. -CHROMEOS_EXPORT void InitLog(int init_flags); -// Gets the current logging flags. -CHROMEOS_EXPORT int GetLogFlags(); -// Sets the current logging flags. -CHROMEOS_EXPORT void SetLogFlags(int log_flags); -// Convenience function for configuring syslog via openlog. Users -// could call openlog directly except for naming collisions between -// base/logging.h and syslog.h. Similarly users cannot pass the -// normal parameters so we pick a representative set. |log_pid| -// causes pid to be logged with |ident|. -CHROMEOS_EXPORT void OpenLog(const char* ident, bool log_pid); -// Start accumulating the logs to a string. This is inefficient, so -// do not set to true if large numbers of log messages are coming. -// Accumulated logs are only ever cleared when the clear function ings -// called. -CHROMEOS_EXPORT void LogToString(bool enabled); -// Get the accumulated logs as a string. -CHROMEOS_EXPORT std::string GetLog(); -// Clear the accumulated logs. -CHROMEOS_EXPORT void ClearLog(); -// Returns true if the accumulated log contains the given string. Useful -// for testing. -CHROMEOS_EXPORT bool FindLog(const char* string); - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_SYSLOG_LOGGING_H_ diff --git a/chromeos/syslog_logging_unittest.cc b/chromeos/syslog_logging_unittest.cc deleted file mode 100644 index 982b83a..0000000 --- a/chromeos/syslog_logging_unittest.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include - -namespace chromeos { - -class SyslogLoggingDeathTest : public ::testing::Test { - public: - SyslogLoggingDeathTest() {} - virtual ~SyslogLoggingDeathTest() {} - - private: - DISALLOW_COPY_AND_ASSIGN(SyslogLoggingDeathTest); -}; - -TEST_F(SyslogLoggingDeathTest, FatalLoggingIsFatal) { - int old_flags = GetLogFlags(); - SetLogFlags(kLogToStderr); - EXPECT_DEATH({ LOG(FATAL) << "First Fatality!"; }, "First Fatality!"); - // No flags == don't log to syslog, stderr, or accumulated string. - SetLogFlags(0); - // Still a fatal log message - EXPECT_DEATH({ LOG(FATAL) << "Second Fatality!"; }, "Second Fatality!"); - SetLogFlags(old_flags); -} - -} // namespace chromeos diff --git a/chromeos/test_helpers.h b/chromeos/test_helpers.h deleted file mode 100644 index d43a9c8..0000000 --- a/chromeos/test_helpers.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_TEST_HELPERS_H_ -#define LIBCHROMEOS_CHROMEOS_TEST_HELPERS_H_ - -#include "gtest/gtest.h" - -#include - -#include -#include -#include -#include - -#include "chromeos/syslog_logging.h" - -inline void ExpectFileEquals(const char* golden, const char* file_path) { - std::string contents; - EXPECT_TRUE(base::ReadFileToString(base::FilePath(file_path), &contents)); - EXPECT_EQ(golden, contents); -} - -inline void SetUpTests(int* argc, char** argv, bool log_to_stderr) { - base::CommandLine::Init(*argc, argv); - ::chromeos::InitLog(log_to_stderr ? chromeos::kLogToStderr : 0); - ::chromeos::LogToString(true); - ::testing::InitGoogleTest(argc, argv); -} - -#endif // LIBCHROMEOS_CHROMEOS_TEST_HELPERS_H_ diff --git a/chromeos/type_name_undecorate.cc b/chromeos/type_name_undecorate.cc deleted file mode 100644 index 636c575..0000000 --- a/chromeos/type_name_undecorate.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#ifdef __GNUG__ -#include -#include -#include -#endif // __GNUG__ - -namespace chromeos { - -std::string UndecorateTypeName(const char* type_name) { -#ifdef __GNUG__ - // Under g++ use abi::__cxa_demangle() to undecorate the type name. - int status = 0; - - std::unique_ptr res{ - abi::__cxa_demangle(type_name, nullptr, nullptr, &status), - std::free - }; - - return (status == 0) ? res.get() : type_name; -#else - // If not compiled with g++, do nothing... - // E.g. MSVC's type_info::name() already contains undecorated name. - return type_name; -#endif -} - -} // namespace chromeos diff --git a/chromeos/type_name_undecorate.h b/chromeos/type_name_undecorate.h deleted file mode 100644 index ea3c14b..0000000 --- a/chromeos/type_name_undecorate.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_TYPE_NAME_UNDECORATE_H_ -#define LIBCHROMEOS_CHROMEOS_TYPE_NAME_UNDECORATE_H_ - -#include -#include - -#include - -namespace chromeos { - -// Use chromeos::UndecorateTypeName() to obtain human-readable type from -// the decorated/mangled type name returned by std::type_info::name(). -CHROMEOS_EXPORT std::string UndecorateTypeName(const char* type_name); - -// A template helper function that returns the undecorated type name for type T. -template -inline std::string GetUndecoratedTypeName() { - return UndecorateTypeName(typeid(T).name()); -} - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_TYPE_NAME_UNDECORATE_H_ diff --git a/chromeos/url_utils.cc b/chromeos/url_utils.cc deleted file mode 100644 index 3a2e166..0000000 --- a/chromeos/url_utils.cc +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace { -// Given a URL string, determine where the query string starts and ends. -// URLs have schema, domain and path (along with possible user name, password -// and port number which are of no interest for us here) which could optionally -// have a query string that is separated from the path by '?'. Finally, the URL -// could also have a '#'-separated URL fragment which is usually used by the -// browser as a bookmark element. So, for example: -// http://server.com/path/to/object?k=v&foo=bar#fragment -// Here: -// http://server.com/path/to/object - is the URL of the object, -// ?k=v&foo=bar - URL query string -// #fragment - URL fragment string -// If |exclude_fragment| is true, the function returns the start character and -// the length of the query string alone. If it is false, the query string length -// will include both the query string and the fragment. -bool GetQueryStringPos(const std::string& url, - bool exclude_fragment, - size_t* query_pos, - size_t* query_len) { - size_t query_start = url.find_first_of("?#"); - if (query_start == std::string::npos) { - *query_pos = url.size(); - if (query_len) - *query_len = 0; - return false; - } - - *query_pos = query_start; - if (query_len) { - size_t query_end = url.size(); - - if (exclude_fragment) { - if (url[query_start] == '?') { - size_t pos_fragment = url.find('#', query_start); - if (pos_fragment != std::string::npos) - query_end = pos_fragment; - } else { - query_end = query_start; - } - } - *query_len = query_end - query_start; - } - return true; -} -} // anonymous namespace - -namespace chromeos { - -std::string url::TrimOffQueryString(std::string* url) { - size_t query_pos; - if (!GetQueryStringPos(*url, false, &query_pos, nullptr)) - return std::string(); - std::string query_string = url->substr(query_pos); - url->resize(query_pos); - return query_string; -} - -std::string url::Combine(const std::string& url, const std::string& subpath) { - return CombineMultiple(url, {subpath}); -} - -std::string url::CombineMultiple(const std::string& url, - const std::vector& parts) { - std::string result = url; - if (!parts.empty()) { - std::string query_string = TrimOffQueryString(&result); - for (const auto& part : parts) { - if (!part.empty()) { - if (!result.empty() && result.back() != '/') - result += '/'; - size_t non_slash_pos = part.find_first_not_of('/'); - if (non_slash_pos != std::string::npos) - result += part.substr(non_slash_pos); - } - } - result += query_string; - } - return result; -} - -std::string url::GetQueryString(const std::string& url, bool remove_fragment) { - std::string query_string; - size_t query_pos, query_len; - if (GetQueryStringPos(url, remove_fragment, &query_pos, &query_len)) { - query_string = url.substr(query_pos, query_len); - } - return query_string; -} - -data_encoding::WebParamList url::GetQueryStringParameters( - const std::string& url) { - // Extract the query string and remove the leading '?'. - std::string query_string = GetQueryString(url, true); - if (!query_string.empty() && query_string.front() == '?') - query_string.erase(query_string.begin()); - return data_encoding::WebParamsDecode(query_string); -} - -std::string url::GetQueryStringValue(const std::string& url, - const std::string& name) { - return GetQueryStringValue(GetQueryStringParameters(url), name); -} - -std::string url::GetQueryStringValue(const data_encoding::WebParamList& params, - const std::string& name) { - for (const auto& pair : params) { - if (name.compare(pair.first) == 0) - return pair.second; - } - return std::string(); -} - -std::string url::RemoveQueryString(const std::string& url, - bool remove_fragment_too) { - size_t query_pos, query_len; - if (!GetQueryStringPos(url, !remove_fragment_too, &query_pos, &query_len)) - return url; - std::string result = url.substr(0, query_pos); - size_t fragment_pos = query_pos + query_len; - if (fragment_pos < url.size()) { - result += url.substr(fragment_pos); - } - return result; -} - -std::string url::AppendQueryParam(const std::string& url, - const std::string& name, - const std::string& value) { - return AppendQueryParams(url, {{name, value}}); -} - -std::string url::AppendQueryParams(const std::string& url, - const data_encoding::WebParamList& params) { - if (params.empty()) - return url; - size_t query_pos, query_len; - GetQueryStringPos(url, true, &query_pos, &query_len); - size_t fragment_pos = query_pos + query_len; - std::string result = url.substr(0, fragment_pos); - if (query_len == 0) { - result += '?'; - } else if (query_len > 1) { - result += '&'; - } - result += data_encoding::WebParamsEncode(params); - if (fragment_pos < url.size()) { - result += url.substr(fragment_pos); - } - return result; -} - -bool url::HasQueryString(const std::string& url) { - size_t query_pos, query_len; - GetQueryStringPos(url, true, &query_pos, &query_len); - return (query_len > 0); -} - -} // namespace chromeos diff --git a/chromeos/url_utils.h b/chromeos/url_utils.h deleted file mode 100644 index caa890b..0000000 --- a/chromeos/url_utils.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_URL_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_URL_UTILS_H_ - -#include -#include - -#include -#include -#include -#include - -namespace chromeos { - -namespace url { - -// Appends a subpath to url and delimiting then with '/' if the path doesn't -// end with it already. Also handles URLs with query parameters/fragment. -CHROMEOS_EXPORT std::string Combine( - const std::string& url, - const std::string& subpath) WARN_UNUSED_RESULT; -CHROMEOS_EXPORT std::string CombineMultiple( - const std::string& url, - const std::vector& parts) WARN_UNUSED_RESULT; - -// Removes the query string/fragment from |url| and returns the query string. -// This method actually modifies |url|. So, if you call it on this: -// http://www.test.org/?foo=bar -// it will modify |url| to "http://www.test.org/" and return "?foo=bar" -CHROMEOS_EXPORT std::string TrimOffQueryString(std::string* url); - -// Returns the query string, if available. -// For example, for the following URL: -// http://server.com/path/to/object?k=v&foo=bar#fragment -// Here: -// http://server.com/path/to/object - is the URL of the object, -// ?k=v&foo=bar - URL query string -// #fragment - URL fragment string -// If |remove_fragment| is true, the function returns the query string without -// the fragment. Otherwise the fragment is included as part of the result. -CHROMEOS_EXPORT std::string GetQueryString(const std::string& url, - bool remove_fragment); - -// Parses the query string into a set of key-value pairs. -CHROMEOS_EXPORT data_encoding::WebParamList GetQueryStringParameters( - const std::string& url); - -// Returns a value of the specified query parameter, or empty string if missing. -CHROMEOS_EXPORT std::string GetQueryStringValue( - const std::string& url, - const std::string& name); -CHROMEOS_EXPORT std::string GetQueryStringValue( - const data_encoding::WebParamList& params, - const std::string& name); - -// Removes the query string and/or a fragment part from URL. -// If |remove_fragment| is specified, the fragment is also removed. -// For example: -// http://server.com/path/to/object?k=v&foo=bar#fragment -// true -> http://server.com/path/to/object -// false -> http://server.com/path/to/object#fragment -CHROMEOS_EXPORT std::string RemoveQueryString( - const std::string& url, - bool remove_fragment) WARN_UNUSED_RESULT; - -// Appends a single query parameter to the URL. -CHROMEOS_EXPORT std::string AppendQueryParam( - const std::string& url, - const std::string& name, - const std::string& value) WARN_UNUSED_RESULT; -// Appends a list of query parameters to the URL. -CHROMEOS_EXPORT std::string AppendQueryParams( - const std::string& url, - const data_encoding::WebParamList& params) WARN_UNUSED_RESULT; - -// Checks if the URL has query parameters. -CHROMEOS_EXPORT bool HasQueryString(const std::string& url); - -} // namespace url -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_URL_UTILS_H_ diff --git a/chromeos/url_utils_unittest.cc b/chromeos/url_utils_unittest.cc deleted file mode 100644 index d52d720..0000000 --- a/chromeos/url_utils_unittest.cc +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include - -namespace chromeos { - -TEST(UrlUtils, Combine) { - EXPECT_EQ("http://sample.org/path", - url::Combine("http://sample.org", "path")); - EXPECT_EQ("http://sample.org/path", - url::Combine("http://sample.org/", "path")); - EXPECT_EQ("path1/path2", url::Combine("", "path1/path2")); - EXPECT_EQ("path1/path2", url::Combine("path1", "path2")); - EXPECT_EQ("http://sample.org", url::Combine("http://sample.org", "")); - EXPECT_EQ("http://sample.org/path", - url::Combine("http://sample.org/", "/path")); - EXPECT_EQ("http://sample.org/path", - url::Combine("http://sample.org", "//////path")); - EXPECT_EQ("http://sample.org/", url::Combine("http://sample.org", "///")); - EXPECT_EQ("http://sample.org/obj/path1/path2", - url::Combine("http://sample.org/obj", "path1/path2")); - EXPECT_EQ("http://sample.org/obj/path1/path2#tag", - url::Combine("http://sample.org/obj#tag", "path1/path2")); - EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1&k2=v2", - url::Combine("http://sample.org/obj?k1=v1&k2=v2", "path1/path2")); - EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1#k2=v2", - url::Combine("http://sample.org/obj/?k1=v1#k2=v2", "path1/path2")); - EXPECT_EQ("http://sample.org/obj/path1/path2#tag?", - url::Combine("http://sample.org/obj#tag?", "path1/path2")); - EXPECT_EQ("path1/path2", url::CombineMultiple("", {"path1", "path2"})); - EXPECT_EQ("http://sample.org/obj/part1/part2", - url::CombineMultiple("http://sample.org", - {"obj", "", "/part1/", "part2"})); -} - -TEST(UrlUtils, GetQueryString) { - EXPECT_EQ("", url::GetQueryString("http://sample.org", false)); - EXPECT_EQ("", url::GetQueryString("http://sample.org", true)); - EXPECT_EQ("", url::GetQueryString("", false)); - EXPECT_EQ("", url::GetQueryString("", true)); - - EXPECT_EQ("?q=v&b=2#tag?2", - url::GetQueryString("http://s.com/?q=v&b=2#tag?2", false)); - EXPECT_EQ("?q=v&b=2", - url::GetQueryString("http://s.com/?q=v&b=2#tag?2", true)); - - EXPECT_EQ("#tag?a=2", url::GetQueryString("http://s.com/#tag?a=2", false)); - EXPECT_EQ("", url::GetQueryString("http://s.com/#tag?a=2", true)); - - EXPECT_EQ("?a=2&b=2", url::GetQueryString("?a=2&b=2", false)); - EXPECT_EQ("?a=2&b=2", url::GetQueryString("?a=2&b=2", true)); - - EXPECT_EQ("#s#?d#?f?#s?#d", url::GetQueryString("#s#?d#?f?#s?#d", false)); - EXPECT_EQ("", url::GetQueryString("#s#?d#?f?#s?#d", true)); -} - -TEST(UrlUtils, GetQueryStringParameters) { - auto params = url::GetQueryStringParameters( - "http://sample.org/path?k=v&&%3Dkey%3D=val%26&r#blah"); - - EXPECT_EQ(3, params.size()); - EXPECT_EQ("k", params[0].first); - EXPECT_EQ("v", params[0].second); - EXPECT_EQ("=key=", params[1].first); - EXPECT_EQ("val&", params[1].second); - EXPECT_EQ("r", params[2].first); - EXPECT_EQ("", params[2].second); -} - -TEST(UrlUtils, GetQueryStringValue) { - std::string url = "http://url?key1=val1&&key2=val2"; - EXPECT_EQ("val1", url::GetQueryStringValue(url, "key1")); - EXPECT_EQ("val2", url::GetQueryStringValue(url, "key2")); - EXPECT_EQ("", url::GetQueryStringValue(url, "key3")); - - auto params = url::GetQueryStringParameters(url); - EXPECT_EQ("val1", url::GetQueryStringValue(params, "key1")); - EXPECT_EQ("val2", url::GetQueryStringValue(params, "key2")); - EXPECT_EQ("", url::GetQueryStringValue(params, "key3")); -} - -TEST(UrlUtils, TrimOffQueryString) { - std::string url = "http://url?key1=val1&key2=val2#fragment"; - std::string query = url::TrimOffQueryString(&url); - EXPECT_EQ("http://url", url); - EXPECT_EQ("?key1=val1&key2=val2#fragment", query); - - url = "http://url#fragment"; - query = url::TrimOffQueryString(&url); - EXPECT_EQ("http://url", url); - EXPECT_EQ("#fragment", query); - - url = "http://url"; - query = url::TrimOffQueryString(&url); - EXPECT_EQ("http://url", url); - EXPECT_EQ("", query); -} - -TEST(UrlUtils, RemoveQueryString) { - std::string url = "http://url?key1=val1&key2=val2#fragment"; - EXPECT_EQ("http://url", url::RemoveQueryString(url, true)); - EXPECT_EQ("http://url#fragment", url::RemoveQueryString(url, false)); -} - -TEST(UrlUtils, AppendQueryParam) { - std::string url = "http://server.com/path"; - url = url::AppendQueryParam(url, "param", "value"); - EXPECT_EQ("http://server.com/path?param=value", url); - url = url::AppendQueryParam(url, "param2", "v"); - EXPECT_EQ("http://server.com/path?param=value¶m2=v", url); - - url = "http://server.com/path#fragment"; - url = url::AppendQueryParam(url, "param", "value"); - EXPECT_EQ("http://server.com/path?param=value#fragment", url); - url = url::AppendQueryParam(url, "param2", "v"); - EXPECT_EQ("http://server.com/path?param=value¶m2=v#fragment", url); - - url = url::AppendQueryParam("http://server.com/path?", "param", "value"); - EXPECT_EQ("http://server.com/path?param=value", url); -} - -TEST(UrlUtils, AppendQueryParams) { - std::string url = "http://server.com/path"; - url = url::AppendQueryParams(url, {}); - EXPECT_EQ("http://server.com/path", url); - url = url::AppendQueryParams(url, {{"param", "value"}, {"q", "="}}); - EXPECT_EQ("http://server.com/path?param=value&q=%3D", url); - url += "#fr?"; - url = url::AppendQueryParams(url, {{"p", "1"}, {"s&", "\n"}}); - EXPECT_EQ("http://server.com/path?param=value&q=%3D&p=1&s%26=%0A#fr?", url); -} - -TEST(UrlUtils, HasQueryString) { - EXPECT_FALSE(url::HasQueryString("http://server.com/path")); - EXPECT_FALSE(url::HasQueryString("http://server.com/path#blah?v=1")); - EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1#blah")); - EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1")); - EXPECT_FALSE(url::HasQueryString("")); - EXPECT_TRUE(url::HasQueryString("?ss")); -} - -} // namespace chromeos diff --git a/chromeos/userdb_utils.cc b/chromeos/userdb_utils.cc deleted file mode 100644 index 096557a..0000000 --- a/chromeos/userdb_utils.cc +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chromeos/userdb_utils.h" - -#include -#include -#include -#include - -#include - -#include - -namespace chromeos { -namespace userdb { - -bool GetUserInfo(const std::string& user, uid_t* uid, gid_t* gid) { - ssize_t buf_len = sysconf(_SC_GETPW_R_SIZE_MAX); - if (buf_len < 0) - buf_len = 16384; // 16K should be enough?... - passwd pwd_buf; - passwd* pwd = nullptr; - std::vector buf(buf_len); - if (getpwnam_r(user.c_str(), &pwd_buf, buf.data(), buf_len, &pwd) || !pwd) { - PLOG(ERROR) << "Unable to find user " << user; - return false; - } - - if (uid) - *uid = pwd->pw_uid; - if (gid) - *gid = pwd->pw_gid; - return true; -} - -bool GetGroupInfo(const std::string& group, gid_t* gid) { - ssize_t buf_len = sysconf(_SC_GETGR_R_SIZE_MAX); - if (buf_len < 0) - buf_len = 16384; // 16K should be enough?... - struct group grp_buf; - struct group* grp = nullptr; - std::vector buf(buf_len); - if (getgrnam_r(group.c_str(), &grp_buf, buf.data(), buf_len, &grp) || !grp) { - PLOG(ERROR) << "Unable to find group " << group; - return false; - } - - if (gid) - *gid = grp->gr_gid; - return true; -} - -} // namespace userdb -} // namespace chromeos diff --git a/chromeos/userdb_utils.h b/chromeos/userdb_utils.h deleted file mode 100644 index 019336a..0000000 --- a/chromeos/userdb_utils.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_USERDB_UTILS_H_ -#define LIBCHROMEOS_CHROMEOS_USERDB_UTILS_H_ - -#include - -#include - -#include -#include -#include - -namespace chromeos { -namespace userdb { - -// Looks up the UID and GID corresponding to |user|. Returns true on success. -// Passing nullptr for |uid| or |gid| causes them to be ignored. -CHROMEOS_EXPORT bool GetUserInfo( - const std::string& user, uid_t* uid, gid_t* gid) WARN_UNUSED_RESULT; - -// Looks up the GID corresponding to |group|. Returns true on success. -// Passing nullptr for |gid| causes it to be ignored. -CHROMEOS_EXPORT bool GetGroupInfo( - const std::string& group, gid_t* gid) WARN_UNUSED_RESULT; - -} // namespace userdb -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_USERDB_UTILS_H_ diff --git a/chromeos/variant_dictionary.h b/chromeos/variant_dictionary.h deleted file mode 100644 index dbc8385..0000000 --- a/chromeos/variant_dictionary.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef LIBCHROMEOS_CHROMEOS_VARIANT_DICTIONARY_H_ -#define LIBCHROMEOS_CHROMEOS_VARIANT_DICTIONARY_H_ - -#include -#include - -#include -#include - -namespace chromeos { - -using VariantDictionary = std::map; - -// GetVariantValueOrDefault tries to retrieve the named key from the dictionary -// and convert it to the type T. If the value does not exist, or the type -// conversion fails, the default value of type T is returned. -template -const T GetVariantValueOrDefault(const VariantDictionary& dictionary, - const std::string& key) { - VariantDictionary::const_iterator it = dictionary.find(key); - if (it == dictionary.end()) { - return T(); - } - return it->second.TryGet(); -} - -} // namespace chromeos - -#endif // LIBCHROMEOS_CHROMEOS_VARIANT_DICTIONARY_H_ diff --git a/chromeos/variant_dictionary_unittest.cc b/chromeos/variant_dictionary_unittest.cc deleted file mode 100644 index d960dda..0000000 --- a/chromeos/variant_dictionary_unittest.cc +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include -#include -#include - -using chromeos::VariantDictionary; -using chromeos::GetVariantValueOrDefault; - -TEST(VariantDictionary, GetVariantValueOrDefault) { - VariantDictionary dictionary; - dictionary.emplace("a", 1); - dictionary.emplace("b", "string"); - - // Test values that are present in the VariantDictionary. - EXPECT_EQ(1, GetVariantValueOrDefault(dictionary, "a")); - EXPECT_EQ("string", GetVariantValueOrDefault(dictionary, "b")); - - // Test that missing keys result in defaults. - EXPECT_EQ("", GetVariantValueOrDefault(dictionary, "missing")); - EXPECT_EQ(0, GetVariantValueOrDefault(dictionary, "missing")); -} diff --git a/libchromeos.gypi b/libchromeos.gypi index 5dd5255..7e58063 100644 --- a/libchromeos.gypi +++ b/libchromeos.gypi @@ -46,41 +46,41 @@ }, 'libraries': ['-lmodp_b64'], 'sources': [ - 'chromeos/any.cc', - 'chromeos/asynchronous_signal_handler.cc', - 'chromeos/backoff_entry.cc', - 'chromeos/daemons/dbus_daemon.cc', - 'chromeos/daemons/daemon.cc', - 'chromeos/data_encoding.cc', - 'chromeos/dbus/async_event_sequencer.cc', - 'chromeos/dbus/data_serialization.cc', - 'chromeos/dbus/dbus_method_invoker.cc', - 'chromeos/dbus/dbus_method_response.cc', - 'chromeos/dbus/dbus_object.cc', - 'chromeos/dbus/dbus_service_watcher.cc', - 'chromeos/dbus/dbus_signal.cc', - 'chromeos/dbus/exported_object_manager.cc', - 'chromeos/dbus/exported_property_set.cc', - 'chromeos/dbus/utils.cc', - 'chromeos/errors/error.cc', - 'chromeos/errors/error_codes.cc', - 'chromeos/file_utils.cc', - 'chromeos/flag_helper.cc', - 'chromeos/key_value_store.cc', - 'chromeos/message_loops/base_message_loop.cc', - 'chromeos/message_loops/message_loop.cc', - 'chromeos/message_loops/message_loop_utils.cc', - 'chromeos/mime_utils.cc', - 'chromeos/osrelease_reader.cc', - 'chromeos/process.cc', - 'chromeos/process_reaper.cc', - 'chromeos/process_information.cc', - 'chromeos/secure_blob.cc', - 'chromeos/strings/string_utils.cc', - 'chromeos/syslog_logging.cc', - 'chromeos/type_name_undecorate.cc', - 'chromeos/url_utils.cc', - 'chromeos/userdb_utils.cc', + 'brillo/any.cc', + 'brillo/asynchronous_signal_handler.cc', + 'brillo/backoff_entry.cc', + 'brillo/daemons/dbus_daemon.cc', + 'brillo/daemons/daemon.cc', + 'brillo/data_encoding.cc', + 'brillo/dbus/async_event_sequencer.cc', + 'brillo/dbus/data_serialization.cc', + 'brillo/dbus/dbus_method_invoker.cc', + 'brillo/dbus/dbus_method_response.cc', + 'brillo/dbus/dbus_object.cc', + 'brillo/dbus/dbus_service_watcher.cc', + 'brillo/dbus/dbus_signal.cc', + 'brillo/dbus/exported_object_manager.cc', + 'brillo/dbus/exported_property_set.cc', + 'brillo/dbus/utils.cc', + 'brillo/errors/error.cc', + 'brillo/errors/error_codes.cc', + 'brillo/file_utils.cc', + 'brillo/flag_helper.cc', + 'brillo/key_value_store.cc', + 'brillo/message_loops/base_message_loop.cc', + 'brillo/message_loops/message_loop.cc', + 'brillo/message_loops/message_loop_utils.cc', + 'brillo/mime_utils.cc', + 'brillo/osrelease_reader.cc', + 'brillo/process.cc', + 'brillo/process_reaper.cc', + 'brillo/process_information.cc', + 'brillo/secure_blob.cc', + 'brillo/strings/string_utils.cc', + 'brillo/syslog_logging.cc', + 'brillo/type_name_undecorate.cc', + 'brillo/url_utils.cc', + 'brillo/userdb_utils.cc', ], }, { @@ -104,13 +104,13 @@ }, }, 'sources': [ - 'chromeos/http/curl_api.cc', - 'chromeos/http/http_connection_curl.cc', - 'chromeos/http/http_form_data.cc', - 'chromeos/http/http_request.cc', - 'chromeos/http/http_transport.cc', - 'chromeos/http/http_transport_curl.cc', - 'chromeos/http/http_utils.cc', + 'brillo/http/curl_api.cc', + 'brillo/http/http_connection_curl.cc', + 'brillo/http/http_form_data.cc', + 'brillo/http/http_request.cc', + 'brillo/http/http_transport.cc', + 'brillo/http/http_transport_curl.cc', + 'brillo/http/http_utils.cc', ], }, { @@ -133,15 +133,15 @@ }, }, 'sources': [ - 'chromeos/streams/file_stream.cc', - 'chromeos/streams/input_stream_set.cc', - 'chromeos/streams/memory_containers.cc', - 'chromeos/streams/memory_stream.cc', - 'chromeos/streams/openssl_stream_bio.cc', - 'chromeos/streams/stream.cc', - 'chromeos/streams/stream_errors.cc', - 'chromeos/streams/stream_utils.cc', - 'chromeos/streams/tls_stream.cc', + 'brillo/streams/file_stream.cc', + 'brillo/streams/input_stream_set.cc', + 'brillo/streams/memory_containers.cc', + 'brillo/streams/memory_stream.cc', + 'brillo/streams/openssl_stream_bio.cc', + 'brillo/streams/stream.cc', + 'brillo/streams/stream_errors.cc', + 'brillo/streams/stream_utils.cc', + 'brillo/streams/tls_stream.cc', ], }, { @@ -152,10 +152,10 @@ 'libchromeos-http-<(libbase_ver)', ], 'sources': [ - 'chromeos/http/http_connection_fake.cc', - 'chromeos/http/http_transport_fake.cc', - 'chromeos/message_loops/fake_message_loop.cc', - 'chromeos/streams/fake_stream.cc', + 'brillo/http/http_connection_fake.cc', + 'brillo/http/http_transport_fake.cc', + 'brillo/message_loops/fake_message_loop.cc', + 'brillo/streams/fake_stream.cc', ], 'includes': ['../common-mk/deps.gypi'], }, @@ -176,7 +176,7 @@ }, }, 'sources': [ - 'chromeos/cryptohome.cc', + 'brillo/cryptohome.cc', ], }, { @@ -199,7 +199,7 @@ '-fvisibility=default', ], 'sources': [ - 'chromeos/minijail/minijail.cc', + 'brillo/minijail/minijail.cc', ], }, { @@ -259,9 +259,9 @@ }, }, 'sources': [ - 'chromeos/glib/abstract_dbus_service.cc', - 'chromeos/glib/dbus.cc', - 'chromeos/message_loops/glib_message_loop.cc', + 'brillo/glib/abstract_dbus_service.cc', + 'brillo/glib/dbus.cc', + 'brillo/message_loops/glib_message_loop.cc', ], 'includes': ['../common-mk/deps.gypi'], }, @@ -304,51 +304,51 @@ }], ], 'sources': [ - 'chromeos/any_unittest.cc', - 'chromeos/any_internal_impl_unittest.cc', - 'chromeos/asynchronous_signal_handler_unittest.cc', - 'chromeos/backoff_entry_unittest.cc', - 'chromeos/data_encoding_unittest.cc', - 'chromeos/dbus/async_event_sequencer_unittest.cc', - 'chromeos/dbus/data_serialization_unittest.cc', - 'chromeos/dbus/dbus_method_invoker_unittest.cc', - 'chromeos/dbus/dbus_object_unittest.cc', - 'chromeos/dbus/dbus_param_reader_unittest.cc', - 'chromeos/dbus/dbus_param_writer_unittest.cc', - 'chromeos/dbus/dbus_signal_handler_unittest.cc', - 'chromeos/dbus/exported_object_manager_unittest.cc', - 'chromeos/dbus/exported_property_set_unittest.cc', - 'chromeos/errors/error_codes_unittest.cc', - 'chromeos/errors/error_unittest.cc', - 'chromeos/file_utils_unittest.cc', - 'chromeos/flag_helper_unittest.cc', - 'chromeos/glib/object_unittest.cc', - 'chromeos/http/http_connection_curl_unittest.cc', - 'chromeos/http/http_form_data_unittest.cc', - 'chromeos/http/http_request_unittest.cc', - 'chromeos/http/http_transport_curl_unittest.cc', - 'chromeos/http/http_utils_unittest.cc', - 'chromeos/key_value_store_unittest.cc', - 'chromeos/map_utils_unittest.cc', - 'chromeos/message_loops/fake_message_loop_unittest.cc', - 'chromeos/message_loops/glib_message_loop_unittest.cc', - 'chromeos/message_loops/message_loop_unittest.cc', - 'chromeos/mime_utils_unittest.cc', - 'chromeos/osrelease_reader_unittest.cc', - 'chromeos/process_reaper_unittest.cc', - 'chromeos/process_unittest.cc', - 'chromeos/secure_blob_unittest.cc', - 'chromeos/streams/fake_stream_unittest.cc', - 'chromeos/streams/file_stream_unittest.cc', - 'chromeos/streams/input_stream_set_unittest.cc', - 'chromeos/streams/memory_containers_unittest.cc', - 'chromeos/streams/memory_stream_unittest.cc', - 'chromeos/streams/openssl_stream_bio_unittests.cc', - 'chromeos/streams/stream_unittest.cc', - 'chromeos/streams/stream_utils_unittest.cc', - 'chromeos/strings/string_utils_unittest.cc', - 'chromeos/url_utils_unittest.cc', - 'chromeos/variant_dictionary_unittest.cc', + 'brillo/any_unittest.cc', + 'brillo/any_internal_impl_unittest.cc', + 'brillo/asynchronous_signal_handler_unittest.cc', + 'brillo/backoff_entry_unittest.cc', + 'brillo/data_encoding_unittest.cc', + 'brillo/dbus/async_event_sequencer_unittest.cc', + 'brillo/dbus/data_serialization_unittest.cc', + 'brillo/dbus/dbus_method_invoker_unittest.cc', + 'brillo/dbus/dbus_object_unittest.cc', + 'brillo/dbus/dbus_param_reader_unittest.cc', + 'brillo/dbus/dbus_param_writer_unittest.cc', + 'brillo/dbus/dbus_signal_handler_unittest.cc', + 'brillo/dbus/exported_object_manager_unittest.cc', + 'brillo/dbus/exported_property_set_unittest.cc', + 'brillo/errors/error_codes_unittest.cc', + 'brillo/errors/error_unittest.cc', + 'brillo/file_utils_unittest.cc', + 'brillo/flag_helper_unittest.cc', + 'brillo/glib/object_unittest.cc', + 'brillo/http/http_connection_curl_unittest.cc', + 'brillo/http/http_form_data_unittest.cc', + 'brillo/http/http_request_unittest.cc', + 'brillo/http/http_transport_curl_unittest.cc', + 'brillo/http/http_utils_unittest.cc', + 'brillo/key_value_store_unittest.cc', + 'brillo/map_utils_unittest.cc', + 'brillo/message_loops/fake_message_loop_unittest.cc', + 'brillo/message_loops/glib_message_loop_unittest.cc', + 'brillo/message_loops/message_loop_unittest.cc', + 'brillo/mime_utils_unittest.cc', + 'brillo/osrelease_reader_unittest.cc', + 'brillo/process_reaper_unittest.cc', + 'brillo/process_unittest.cc', + 'brillo/secure_blob_unittest.cc', + 'brillo/streams/fake_stream_unittest.cc', + 'brillo/streams/file_stream_unittest.cc', + 'brillo/streams/input_stream_set_unittest.cc', + 'brillo/streams/memory_containers_unittest.cc', + 'brillo/streams/memory_stream_unittest.cc', + 'brillo/streams/openssl_stream_bio_unittests.cc', + 'brillo/streams/stream_unittest.cc', + 'brillo/streams/stream_utils_unittest.cc', + 'brillo/strings/string_utils_unittest.cc', + 'brillo/url_utils_unittest.cc', + 'brillo/variant_dictionary_unittest.cc', 'testrunner.cc', '<(proto_in_dir)/test.proto', ] diff --git a/policy/device_policy.h b/policy/device_policy.h index 38bdbc3..12a21fd 100644 --- a/policy/device_policy.h +++ b/policy/device_policy.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef LIBCHROMEOS_CHROMEOS_POLICY_DEVICE_POLICY_H_ -#define LIBCHROMEOS_CHROMEOS_POLICY_DEVICE_POLICY_H_ +#ifndef LIBCHROMEOS_POLICY_DEVICE_POLICY_H_ +#define LIBCHROMEOS_POLICY_DEVICE_POLICY_H_ #include @@ -160,4 +160,4 @@ class DevicePolicy { #pragma GCC visibility pop -#endif // LIBCHROMEOS_CHROMEOS_POLICY_DEVICE_POLICY_H_ +#endif // LIBCHROMEOS_POLICY_DEVICE_POLICY_H_ diff --git a/policy/device_policy_impl.h b/policy/device_policy_impl.h index 484e78d..458cd35 100644 --- a/policy/device_policy_impl.h +++ b/policy/device_policy_impl.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef LIBCHROMEOS_CHROMEOS_POLICY_DEVICE_POLICY_IMPL_H_ -#define LIBCHROMEOS_CHROMEOS_POLICY_DEVICE_POLICY_IMPL_H_ +#ifndef LIBCHROMEOS_POLICY_DEVICE_POLICY_IMPL_H_ +#define LIBCHROMEOS_POLICY_DEVICE_POLICY_IMPL_H_ #include #include @@ -84,4 +84,4 @@ class DevicePolicyImpl : public DevicePolicy { #pragma GCC visibility pop -#endif // LIBCHROMEOS_CHROMEOS_POLICY_DEVICE_POLICY_IMPL_H_ +#endif // LIBCHROMEOS_POLICY_DEVICE_POLICY_IMPL_H_ diff --git a/policy/libpolicy.h b/policy/libpolicy.h index f05a37e..e2df0c3 100644 --- a/policy/libpolicy.h +++ b/policy/libpolicy.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef LIBCHROMEOS_CHROMEOS_POLICY_LIBPOLICY_H_ -#define LIBCHROMEOS_CHROMEOS_POLICY_LIBPOLICY_H_ +#ifndef LIBCHROMEOS_POLICY_LIBPOLICY_H_ +#define LIBCHROMEOS_POLICY_LIBPOLICY_H_ #include @@ -47,4 +47,4 @@ class PolicyProvider { #pragma GCC visibility pop -#endif // LIBCHROMEOS_CHROMEOS_POLICY_LIBPOLICY_H_ +#endif // LIBCHROMEOS_POLICY_LIBPOLICY_H_ diff --git a/policy/mock_device_policy.h b/policy/mock_device_policy.h index 0960664..aeb8dd8 100644 --- a/policy/mock_device_policy.h +++ b/policy/mock_device_policy.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef LIBCHROMEOS_CHROMEOS_POLICY_MOCK_DEVICE_POLICY_H_ -#define LIBCHROMEOS_CHROMEOS_POLICY_MOCK_DEVICE_POLICY_H_ +#ifndef LIBCHROMEOS_POLICY_MOCK_DEVICE_POLICY_H_ +#define LIBCHROMEOS_POLICY_MOCK_DEVICE_POLICY_H_ #include #include @@ -104,4 +104,4 @@ class MockDevicePolicy : public DevicePolicy { #pragma GCC visibility pop -#endif // LIBCHROMEOS_CHROMEOS_POLICY_MOCK_DEVICE_POLICY_H_ +#endif // LIBCHROMEOS_POLICY_MOCK_DEVICE_POLICY_H_ diff --git a/policy/mock_libpolicy.h b/policy/mock_libpolicy.h index c4315ea..04fe92d 100644 --- a/policy/mock_libpolicy.h +++ b/policy/mock_libpolicy.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef LIBCHROMEOS_CHROMEOS_POLICY_MOCK_LIBPOLICY_H_ -#define LIBCHROMEOS_CHROMEOS_POLICY_MOCK_LIBPOLICY_H_ +#ifndef LIBCHROMEOS_POLICY_MOCK_LIBPOLICY_H_ +#define LIBCHROMEOS_POLICY_MOCK_LIBPOLICY_H_ #include #include @@ -32,4 +32,4 @@ class MockPolicyProvider : public PolicyProvider { #pragma GCC visibility pop -#endif // LIBCHROMEOS_CHROMEOS_POLICY_MOCK_LIBPOLICY_H_ +#endif // LIBCHROMEOS_POLICY_MOCK_LIBPOLICY_H_ diff --git a/testrunner.cc b/testrunner.cc index 5e51343..7122b9c 100644 --- a/testrunner.cc +++ b/testrunner.cc @@ -8,7 +8,7 @@ #include #include -#include +#include int main(int argc, char** argv) { base::AtExitManager at_exit_manager; diff --git a/testrunner_android.cc b/testrunner_android.cc index e776bc1..93377bf 100644 --- a/testrunner_android.cc +++ b/testrunner_android.cc @@ -17,7 +17,7 @@ #include #include -#include +#include int main(int argc, char** argv) { base::AtExitManager at_exit_manager; -- cgit v1.2.3