diff --git a/tools/valgrind-webrtc/drmemory/suppressions.txt b/tools/valgrind-webrtc/drmemory/suppressions.txt index 14bfd7c83..32be45aef 100644 --- a/tools/valgrind-webrtc/drmemory/suppressions.txt +++ b/tools/valgrind-webrtc/drmemory/suppressions.txt @@ -180,6 +180,174 @@ name=https://code.google.com/p/webrtc/issues/detail?id=3158 (3) KERNEL32.dll!BaseThreadInitThunk # libjingle_unittest, fails on Win DrMemory Full +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (4) +*!_towlower_l +*!towlower +*!tolowercase +*!rtc::IsDefaultBrowserFirefox +*!rtc::GetProxySettingsForUrl +*!rtc::AutoDetectProxy::GetProxyForUrl +*!rtc::AutoDetectProxy::DoWork +*!rtc::SignalThread::Run +*!rtc::SignalThread::Worker::Run +*!rtc::Thread::PreRun +KERNEL32.dll!BaseThreadInitThunk + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (12) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Print +libjingle_peerconnection_unittes!webrtc::TraceImpl::WriteToFile +libjingle_peerconnection_unittes!webrtc::TraceImpl::Process +libjingle_peerconnection_unittes!webrtc::TraceImpl::Run +libjingle_peerconnection_unittes!webrtc::ThreadWindows::Run +libjingle_peerconnection_unittes!webrtc::ThreadWindows::StartThread +libjingle_peerconnection_unittes!_callthreadstartex +libjingle_peerconnection_unittes!_threadstartex +KERNEL32.dll!BaseThreadInitThunk + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (14) +libjingle_peerconnection_unittes!std::list<>::begin +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::WebRtcVideoEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (15) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!TestInvalidParameterHandler +libjingle_peerconnection_unittes!_invalid_parameter +... +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::WebRtcVideoEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (16) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::ConstructCodecs +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::WebRtcVoiceEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (32) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +... +libjingle_peerconnection_unittes!testing::internal::CountIf<> +libjingle_peerconnection_unittes!testing::TestResult::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (34) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +... +libjingle_peerconnection_unittes!TestPureCallHandler +libjingle_peerconnection_unittes!_purecall +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +HANDLE LEAK +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (35) +system call NtCreateEvent +KERNELBASE.dll!CreateEventExW +KERNELBASE.dll!CreateEventW +libjingle_peerconnection_unittes!webrtc::EventWindows::EventWindows +libjingle_peerconnection_unittes!webrtc::EventWrapper::Create +libjingle_peerconnection_unittes!webrtc::ProcessThreadImpl::ProcessThreadImpl +libjingle_peerconnection_unittes!webrtc::ProcessThread::CreateProcessThread +libjingle_peerconnection_unittes!webrtc::voe::SharedData::SharedData +libjingle_peerconnection_unittes!webrtc::VoiceEngineImpl::VoiceEngineImpl +libjingle_peerconnection_unittes!webrtc::GetVoiceEngine +libjingle_peerconnection_unittes!webrtc::VoiceEngine::Create +libjingle_peerconnection_unittes!cricket::VoEWrapper::VoEWrapper +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::WebRtcVoiceEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (36) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!webrtc::PeerConnection::CreateAnswer +libjingle_peerconnection_unittes!webrtc::ReturnType<>::Invoke<> +libjingle_peerconnection_unittes!webrtc::MethodCall2<>::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::MethodCall2<>::Marshal +libjingle_peerconnection_unittes!webrtc::PeerConnectionProxy::CreateAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::DoCreateOfferAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::DoCreateAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::CreateAnswerAsLocalDescription +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest_ReceiveOfferCreatePrAnswerAndAnswer_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (37) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::GetLogToStream +libjingle_peerconnection_unittes!rtc::LogMessage::ConfigureLogging +libjingle_peerconnection_unittes!main + UNINITIALIZED READ name=https://code.google.com/p/webrtc/issues/detail?id=3158 (4) *!_towlower_l diff --git a/tools/valgrind-webrtc/memcheck/suppressions.txt b/tools/valgrind-webrtc/memcheck/suppressions.txt index e95abb850..72650269c 100644 --- a/tools/valgrind-webrtc/memcheck/suppressions.txt +++ b/tools/valgrind-webrtc/memcheck/suppressions.txt @@ -9,6 +9,102 @@ #----------------------------------------------------------------------- # 1. webrtc stuff +{ + bug_1976_1 + Memcheck:Unaddressable + fun:pthread_mutex_unlock + fun:_ZN9rtc15CriticalSection5LeaveEv + fun:_ZN9rtc9CritScopeD1Ev + ... + fun:_ZN9rtc6Thread15ProcessMessagesEi + fun:_ZN9rtc6Thread3RunEv + fun:_ZN9rtc6Thread6PreRunEPv +} +{ + bug_1976_2 + Memcheck:Leak + fun:calloc + obj:/usr/lib/x86_64-linux-gnu/libnss3.so + ... + fun:NSS_NoDB_Init + fun:_ZN9rtc10NSSContext13InitializeSSLEPFbPvE + fun:_ZN9rtc13InitializeSSLEPFbPvE + fun:_ZN9rtc10RandomTest13SetUpTestCaseEv + fun:_ZN7testing8TestCase16RunSetUpTestCaseEv +} +{ + bug_2100_3 + Memcheck:Uninitialized + fun:tls1_enc + fun:ssl3_get_record + fun:ssl3_read_bytes + fun:ssl3_read_internal + fun:ssl3_read + fun:SSL_read + fun:_ZN9rtc20OpenSSLStreamAdapter4ReadEPvmPmPi + ... +} +{ + bug_2100_4 + Memcheck:Uninitialized + fun:_ZN7testing8internal11CmpHelperEQIjhEENS_15AssertionResultEPKcS4_RKT_RKT0_ + fun:_ZN7testing8internal8EqHelperILb0EE7CompareIjhEENS_15AssertionResultEPKcS6_RKT_RKT0_ + fun:_ZN24SSLStreamAdapterTestDTLS8ReadDataEPN9rtc15StreamInterfaceE + ... +} +{ + bug_2100_5 + Memcheck:Uninitialized + fun:dtls1_process_record + fun:dtls1_get_record + fun:dtls1_read_bytes + fun:ssl3_read_internal + fun:ssl3_read + fun:SSL_read + fun:_ZN9rtc20OpenSSLStreamAdapter4ReadEPvmPmPi + ... +} +{ + BIO_new_mem_buf_1 + Memcheck:Leak + fun:malloc + fun:default_malloc_ex + fun:CRYPTO_malloc + fun:BUF_MEM_new + fun:mem_new + fun:BIO_set + fun:BIO_new + fun:BIO_new_mem_buf + fun:_ZN9rtc18OpenSSLCertificate13FromPEMStringERKSs + ... +} +{ + BIO_new_mem_buf_2 + Memcheck:Leak + fun:malloc + fun:default_malloc_ex + fun:CRYPTO_malloc + fun:BUF_MEM_new + fun:mem_new + fun:BIO_set + fun:BIO_new + fun:BIO_new_mem_buf + fun:_ZN9rtc15OpenSSLIdentity14FromPEMStringsERKSsS2_ +} +{ + SignalsCloseAfterForcedCloseAll + Memcheck:Leak + fun:_Znw* + fun:_ZN9rtc10HttpServer10Connection12BeginProcessEPNS_15StreamInterfaceE + ... +} +{ + DoNotDeleteTask2 + Memcheck:Leak + fun:_Znw* + ... + fun:_ZN9rtc41unstarted_task_test_DoNotDeleteTask2_Test8TestBodyEv +} { bug_716 Memcheck:Leak diff --git a/tools/valgrind-webrtc/tsan/suppressions.txt b/tools/valgrind-webrtc/tsan/suppressions.txt index b9cac45ac..8257608cd 100644 --- a/tools/valgrind-webrtc/tsan/suppressions.txt +++ b/tools/valgrind-webrtc/tsan/suppressions.txt @@ -9,6 +9,473 @@ #----------------------------------------------------------------------- # 1. webrtc stuff +{ + bug_1205_33 + ThreadSanitizer:Race + fun:webrtc::PeerConnectionProxy::~PeerConnectionProxy + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::Release + fun:rtc::scoped_refptr::~scoped_refptr + ... +} +{ + bug_1205_34 + ThreadSanitizer:Race + fun:rtc::AtomicOps::Increment + fun:rtc::RefCountedObject::AddRef + fun:rtc::scoped_refptr::scoped_refptr + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + ... +} +{ + bug_1205_35 + ThreadSanitizer:Race + fun:rtc::scoped_refptr::scoped_refptr + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + ... +} +{ + bug_1205_36 + ThreadSanitizer:Race + fun:rtc::MessageHandler::~MessageHandler + fun:FakeAudioCaptureModule::~FakeAudioCaptureModule + ... +} +{ + bug_1205_39 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:FakeAudioCaptureModule::UpdateProcessing + fun:FakeAudioCaptureModule::StopRecording + fun:webrtc::VoEBaseImpl::StopSend + fun:webrtc::VoEBaseImpl::DeleteChannel + fun:cricket::WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel + ... +} +{ + bug_1205_41 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:FakeAudioCaptureModule::~FakeAudioCaptureModule + ... +} +{ + bug_2078_2 + ThreadSanitizer:Race + ... + fun:rtc::MemoryStreamBase::Read + ... + fun:cricket::RtpDumpReader::ReadPacket + ... +} +{ + bug_2078_3 + ThreadSanitizer:Race + fun:cricket::RtpSenderReceiver::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2078_4 + ThreadSanitizer:Race + fun:cricket::FileNetworkInterface::SendPacket + fun:cricket::RtpSenderReceiver::SendRtpPacket + fun:cricket::RtpSenderReceiver::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2078_7 + ThreadSanitizer:Race + fun:rtc::MemoryStreamBase::~MemoryStreamBase + fun:rtc::MemoryStream::~MemoryStream + ... +} +{ + bug_2078_8 + ThreadSanitizer:Race + fun:rtc::MemoryStreamBase::SetPosition + fun:rtc::StreamInterface::Rewind + fun:cricket::RtpTestUtility::VerifyTestPacketsFromStream + ... +} +{ + bug_2078_13 + ThreadSanitizer:Race + fun:std::_Vector_base::~_Vector_base + fun:std::vector::~vector + fun:cricket::RtpDumpPacket::~RtpDumpPacket + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_14 + ThreadSanitizer:Race + fun:cricket::RtpDumpReader::~RtpDumpReader + fun:cricket::RtpDumpLoopReader::~RtpDumpLoopReader + fun:cricket::RtpDumpLoopReader::~RtpDumpLoopReader + fun:rtc::scoped_ptr::~scoped_ptr + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_15 + ThreadSanitizer:Race + fun:rtc::FileStream::Close + fun:rtc::FileStream::~FileStream + fun:rtc::FileStream::~FileStream + fun:rtc::scoped_ptr::~scoped_ptr + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_16 + ThreadSanitizer:Race + fun:rtc::StreamInterface::~StreamInterface + fun:rtc::FileStream::~FileStream + fun:rtc::FileStream::~FileStream + fun:rtc::scoped_ptr::~scoped_ptr + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_18 + ThreadSanitizer:Race + fun:rtc::MemoryStream::~MemoryStream + ... +} +{ + bug_2078_22 + ThreadSanitizer:Race + fun:rtc::MessageQueue::Quit + fun:rtc::Thread::Stop + ... +} +{ + bug_2078_23 + ThreadSanitizer:Race + fun:::FileVideoCapturerTest::VideoCapturerListener::OnFrameCaptured + fun:sigslot::_connection2::emit + ... + fun:cricket::FileVideoCapturer::ReadFrame + fun:cricket::FileVideoCapturer::FileReadThread::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:cricket::FileVideoCapturer::FileReadThread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2078_24 + ThreadSanitizer:Race + fun:rtc::MemoryStreamBase::SetPosition + fun:rtc::StreamInterface::Rewind + ... +} +{ + bug_2078_25 + ThreadSanitizer:Race + fun:cricket::FileNetworkInterface::SendPacket + fun:cricket::MediaChannel::DoSendPacket + fun:cricket::MediaChannel::SendPacket + fun:cricket::RtpSenderReceiver::SendRtpPacket + fun:cricket::RtpSenderReceiver::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2079_1 + ThreadSanitizer:Race + fun:rtc::VirtualSocketServer::AddPacketToNetwork + fun:rtc::VirtualSocketServer::SendUdp + fun:rtc::VirtualSocket::SendUdp + fun:rtc::VirtualSocket::SendTo + fun:rtc::AsyncUDPSocket::SendTo + fun:cricket::StunServer::SendResponse + fun:cricket::StunServer::OnBindingRequest + fun:cricket::StunServer::OnPacket + fun:sigslot::_connection4::emit + ... +} +{ + bug_2079_5 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:cricket::Transport::SetRole + fun:cricket::BaseSession::GetOrCreateTransportProxy + fun:cricket::BaseSession::CreateChannel + fun:cricket::FakeSession::CreateChannel + fun:cricket::VoiceChannel::Init + fun:cricket::ChannelManager::CreateVoiceChannel_w + ... +} +{ + bug_2079_6 + ThreadSanitizer:Race + fun:rtc::Thread::ReceiveSends + fun:rtc::Thread::Send + fun:rtc::Thread::Invoke + ... +} +{ + bug_2079_8 + ThreadSanitizer:Race + fun:rtc::Thread::Invoke + fun:cricket::ChannelManager::SetCaptureDevice + ... +} +{ + bug_2079_9 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:cricket::Transport::SetIceRole + fun:cricket::BaseSession::GetOrCreateTransportProxy + fun:cricket::BaseSession::CreateChannel + fun:cricket::FakeSession::CreateChannel + fun:cricket::VoiceChannel::Init + fun:cricket::ChannelManager::CreateVoiceChannel_w + ... +} +{ + bug_2080_1 + ThreadSanitizer:Race + fun:rtc::MessageQueue::Quit + fun:rtc::SignalThread::Destroy + ... +} +{ + bug_2080_2 + ThreadSanitizer:Race + fun:rtc::MessageQueue::Quit + fun:rtc::AsyncHttpRequest::OnComplete + fun:sigslot::_connection2::emit + ... +} +{ + bug_2080_3 + ThreadSanitizer:Race + fun:rtc::LogMessage::UpdateMinLogSeverity + fun:rtc::LogMessage::AddLogToStream + ... +} +{ + bug_2080_8 + ThreadSanitizer:Race + ... + fun:rtc::AsyncUDPSocket::OnReadEvent + fun:sigslot::_connection1::emit + ... +} +{ + bug_2080_9 + ThreadSanitizer:Race + ... + fun:rtc::AsyncUDPSocket::OnWriteEvent + fun:sigslot::_connection1::emit + ... +} +{ + bug_2080_14 + ThreadSanitizer:Race + fun:rtc::IPAddress::IPAddress + fun:rtc::SocketAddress::ToSockAddrStorage + fun:rtc::PhysicalSocket::SendTo + fun:rtc::AsyncUDPSocket::SendTo + fun:rtc::NATServer::OnExternalPacket + ... +} +{ + bug_2080_16 + ThreadSanitizer:Race + ... + fun:rtc::IPAddress::operator< + ... +} +{ + bug_2080_18 + ThreadSanitizer:Race + fun:rtc::SocketAddress::port + ... +} +{ + bug_2080_19 + ThreadSanitizer:Race + fun:std::vector::_M_insert_aux + fun:std::vector::push_back + fun:rtc::TestClient::OnPacket + ... +} +{ + bug_2080_20 + ThreadSanitizer:Race + fun:rtc::VirtualSocketServer::AddPacketToNetwork + fun:rtc::VirtualSocketServer::SendUdp + fun:rtc::VirtualSocket::SendUdp + fun:rtc::VirtualSocket::SendTo + fun:rtc::AsyncUDPSocket::SendTo + fun:rtc::TestClient::SendTo + ... +} +{ + bug_2080_21 + ThreadSanitizer:Race + fun:rtc::SharedExclusiveTask::waiting_time_in_ms + ... +} +{ + bug_2080_22 + ThreadSanitizer:Race + fun:rtc::SharedExclusiveTask::~SharedExclusiveTask + fun:rtc::ReadTask::~ReadTask + ... +} +{ + bug_2080_24 + ThreadSanitizer:Race + ... + fun:OwnerThread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2080_25 + ThreadSanitizer:Race + fun:sigslot::has_slots::~has_slots + fun:OwnerThread::~OwnerThread + fun:OwnerThread::~OwnerThread + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2080_27 + ThreadSanitizer:Race + ... + fun:std::_Rb_tree::clear + fun:std::_Rb_tree::_M_erase_aux + fun:std::_Rb_tree::erase + fun:std::set::erase + fun:sigslot::has_slots::disconnect_all + fun:sigslot::has_slots::~has_slots + fun:OwnerThread::~OwnerThread + fun:OwnerThread::~OwnerThread + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2080_29 + ThreadSanitizer:Race + fun:rtc::Thread::Release + fun:ThreadTest_Release_Test::TestBody + ... +} +{ + bug_2080_30 + ThreadSanitizer:Race + fun:rtc::Thread::Invoke + fun:MessageQueueTest::IsLocked + fun:DeletedLockChecker::~DeletedLockChecker + ... +} +{ + bug_2080_31 + ThreadSanitizer:Race + fun:rtc::MessageHandler::~MessageHandler + fun:rtc::Thread::FunctorMessageHandler::~FunctorMessageHandler + ... +} +{ + bug_2080_32 + ThreadSanitizer:Race + fun:rtc::ReadTask::OnMessage + fun:rtc::MessageQueue::Dispatch + ... +} +{ + bug_2080_33 + ThreadSanitizer:Race + fun:rtc::SharedExclusiveLockTest_TestSharedShared_Test::TestBody + fun:testing::internal::HandleSehExceptionsInMethodIfSupported +} +{ + bug_2080_34 + ThreadSanitizer:Race + fun:rtc::WriteTask::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2080_35 + ThreadSanitizer:UnlockNonLocked + fun:pthread_mutex_unlock + fun:rtc::CriticalSection::Leave + fun:rtc::CritScope::~CritScope + ... + fun:rtc::AsyncFunctorMessageHandler::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2080_36 + ThreadSanitizer:Race + ... + fun:rtc::FunctorMessageHandler::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2931_1 + ThreadSanitizer:Race + ... + fun:rtc::FireAndForgetAsyncClosure::Execute + fun:rtc::AsyncInvoker::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2931_2 + ThreadSanitizer:Race + ... + fun:rtc::Callback0::HelperImpl::~HelperImpl + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::Release + fun:rtc::scoped_refptr::~scoped_refptr + fun:rtc::Callback0::~Callback0 + fun:rtc::FireAndForgetAsyncClosure::~FireAndForgetAsyncClosure + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::Release + fun:rtc::scoped_refptr::~scoped_refptr + fun:rtc::AsyncInvoker::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:AsyncInvokeTest_WithCallback_Test::TestBody + ... +} { bug_300 ThreadSanitizer:Race diff --git a/tools/valgrind-webrtc/tsan_v2/suppressions.txt b/tools/valgrind-webrtc/tsan_v2/suppressions.txt index aa28bfaff..5dca18475 100644 --- a/tools/valgrind-webrtc/tsan_v2/suppressions.txt +++ b/tools/valgrind-webrtc/tsan_v2/suppressions.txt @@ -13,6 +13,9 @@ race:webrtc/modules/audio_processing/aec/aec_rdft.c # libjingle_p2p_unittest # See https://code.google.com/p/webrtc/issues/detail?id=2079 +race:webrtc/base/messagequeue.cc +race:webrtc/base/testclient.cc +race:webrtc/base/virtualsocketserver.cc race:talk/base/messagequeue.cc race:talk/base/testclient.cc race:talk/base/virtualsocketserver.cc @@ -20,6 +23,10 @@ race:talk/p2p/base/stunserver_unittest.cc # libjingle_unittest # See https://code.google.com/p/webrtc/issues/detail?id=2080 +race:webrtc/base/logging.cc +race:webrtc/base/sharedexclusivelock_unittest.cc +race:webrtc/base/signalthread_unittest.cc +race:webrtc/base/thread.cc race:talk/base/logging.cc race:talk/base/sharedexclusivelock_unittest.cc race:talk/base/signalthread_unittest.cc diff --git a/webrtc/base/asyncfile.cc b/webrtc/base/asyncfile.cc new file mode 100644 index 000000000..ea904c554 --- /dev/null +++ b/webrtc/base/asyncfile.cc @@ -0,0 +1,21 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/asyncfile.h" + +namespace rtc { + +AsyncFile::AsyncFile() { +} + +AsyncFile::~AsyncFile() { +} + +} // namespace rtc diff --git a/webrtc/base/asyncfile.h b/webrtc/base/asyncfile.h new file mode 100644 index 000000000..dd4676688 --- /dev/null +++ b/webrtc/base/asyncfile.h @@ -0,0 +1,40 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCFILE_H__ +#define WEBRTC_BASE_ASYNCFILE_H__ + +#include "webrtc/base/sigslot.h" + +namespace rtc { + +// Provides the ability to perform file I/O asynchronously. +// TODO: Create a common base class with AsyncSocket. +class AsyncFile { + public: + AsyncFile(); + virtual ~AsyncFile(); + + // Determines whether the file will receive read events. + virtual bool readable() = 0; + virtual void set_readable(bool value) = 0; + + // Determines whether the file will receive write events. + virtual bool writable() = 0; + virtual void set_writable(bool value) = 0; + + sigslot::signal1 SignalReadEvent; + sigslot::signal1 SignalWriteEvent; + sigslot::signal2 SignalCloseEvent; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCFILE_H__ diff --git a/webrtc/base/asynchttprequest.cc b/webrtc/base/asynchttprequest.cc new file mode 100644 index 000000000..23042f17d --- /dev/null +++ b/webrtc/base/asynchttprequest.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/asynchttprequest.h" + +namespace rtc { + +enum { + MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE, + MSG_LAUNCH_REQUEST +}; +static const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec + +/////////////////////////////////////////////////////////////////////////////// +// AsyncHttpRequest +/////////////////////////////////////////////////////////////////////////////// + +AsyncHttpRequest::AsyncHttpRequest(const std::string &user_agent) + : start_delay_(0), + firewall_(NULL), + port_(80), + secure_(false), + timeout_(kDefaultHTTPTimeout), + fail_redirect_(false), + factory_(Thread::Current()->socketserver(), user_agent), + pool_(&factory_), + client_(user_agent.c_str(), &pool_), + error_(HE_NONE) { + client_.SignalHttpClientComplete.connect(this, + &AsyncHttpRequest::OnComplete); +} + +AsyncHttpRequest::~AsyncHttpRequest() { +} + +void AsyncHttpRequest::OnWorkStart() { + if (start_delay_ <= 0) { + LaunchRequest(); + } else { + Thread::Current()->PostDelayed(start_delay_, this, MSG_LAUNCH_REQUEST); + } +} + +void AsyncHttpRequest::OnWorkStop() { + // worker is already quitting, no need to explicitly quit + LOG(LS_INFO) << "HttpRequest cancelled"; +} + +void AsyncHttpRequest::OnComplete(HttpClient* client, HttpErrorType error) { + Thread::Current()->Clear(this, MSG_TIMEOUT); + + set_error(error); + if (!error) { + LOG(LS_INFO) << "HttpRequest completed successfully"; + + std::string value; + if (client_.response().hasHeader(HH_LOCATION, &value)) { + response_redirect_ = value.c_str(); + } + } else { + LOG(LS_INFO) << "HttpRequest completed with error: " << error; + } + + worker()->Quit(); +} + +void AsyncHttpRequest::OnMessage(Message* message) { + switch (message->message_id) { + case MSG_TIMEOUT: + LOG(LS_INFO) << "HttpRequest timed out"; + client_.reset(); + worker()->Quit(); + break; + case MSG_LAUNCH_REQUEST: + LaunchRequest(); + break; + default: + SignalThread::OnMessage(message); + break; + } +} + +void AsyncHttpRequest::DoWork() { + // Do nothing while we wait for the request to finish. We only do this so + // that we can be a SignalThread; in the future this class should not be + // a SignalThread, since it does not need to spawn a new thread. + Thread::Current()->ProcessMessages(kForever); +} + +void AsyncHttpRequest::LaunchRequest() { + factory_.SetProxy(proxy_); + if (secure_) + factory_.UseSSL(host_.c_str()); + + bool transparent_proxy = (port_ == 80) && + ((proxy_.type == PROXY_HTTPS) || (proxy_.type == PROXY_UNKNOWN)); + if (transparent_proxy) { + client_.set_proxy(proxy_); + } + client_.set_fail_redirect(fail_redirect_); + client_.set_server(SocketAddress(host_, port_)); + + LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path; + + Thread::Current()->PostDelayed(timeout_, this, MSG_TIMEOUT); + client_.start(); +} + +} // namespace rtc diff --git a/webrtc/base/asynchttprequest.h b/webrtc/base/asynchttprequest.h new file mode 100644 index 000000000..b8d13db8f --- /dev/null +++ b/webrtc/base/asynchttprequest.h @@ -0,0 +1,104 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCHTTPREQUEST_H_ +#define WEBRTC_BASE_ASYNCHTTPREQUEST_H_ + +#include +#include "webrtc/base/event.h" +#include "webrtc/base/httpclient.h" +#include "webrtc/base/signalthread.h" +#include "webrtc/base/socketpool.h" +#include "webrtc/base/sslsocketfactory.h" + +namespace rtc { + +class FirewallManager; + +/////////////////////////////////////////////////////////////////////////////// +// AsyncHttpRequest +// Performs an HTTP request on a background thread. Notifies on the foreground +// thread once the request is done (successfully or unsuccessfully). +/////////////////////////////////////////////////////////////////////////////// + +class AsyncHttpRequest : public SignalThread { + public: + explicit AsyncHttpRequest(const std::string &user_agent); + ~AsyncHttpRequest(); + + // If start_delay is less than or equal to zero, this starts immediately. + // Start_delay defaults to zero. + int start_delay() const { return start_delay_; } + void set_start_delay(int delay) { start_delay_ = delay; } + + const ProxyInfo& proxy() const { return proxy_; } + void set_proxy(const ProxyInfo& proxy) { + proxy_ = proxy; + } + void set_firewall(FirewallManager * firewall) { + firewall_ = firewall; + } + + // The DNS name of the host to connect to. + const std::string& host() { return host_; } + void set_host(const std::string& host) { host_ = host; } + + // The port to connect to on the target host. + int port() { return port_; } + void set_port(int port) { port_ = port; } + + // Whether the request should use SSL. + bool secure() { return secure_; } + void set_secure(bool secure) { secure_ = secure; } + + // Time to wait on the download, in ms. + int timeout() { return timeout_; } + void set_timeout(int timeout) { timeout_ = timeout; } + + // Fail redirects to allow analysis of redirect urls, etc. + bool fail_redirect() const { return fail_redirect_; } + void set_fail_redirect(bool redirect) { fail_redirect_ = redirect; } + + // Returns the redirect when redirection occurs + const std::string& response_redirect() { return response_redirect_; } + + HttpRequestData& request() { return client_.request(); } + HttpResponseData& response() { return client_.response(); } + HttpErrorType error() { return error_; } + + protected: + void set_error(HttpErrorType error) { error_ = error; } + virtual void OnWorkStart(); + virtual void OnWorkStop(); + void OnComplete(HttpClient* client, HttpErrorType error); + virtual void OnMessage(Message* message); + virtual void DoWork(); + + private: + void LaunchRequest(); + + int start_delay_; + ProxyInfo proxy_; + FirewallManager* firewall_; + std::string host_; + int port_; + bool secure_; + int timeout_; + bool fail_redirect_; + SslSocketFactory factory_; + ReuseSocketPool pool_; + HttpClient client_; + HttpErrorType error_; + std::string response_redirect_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCHTTPREQUEST_H_ diff --git a/webrtc/base/asynchttprequest_unittest.cc b/webrtc/base/asynchttprequest_unittest.cc new file mode 100644 index 000000000..0bfd795b1 --- /dev/null +++ b/webrtc/base/asynchttprequest_unittest.cc @@ -0,0 +1,233 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "webrtc/base/asynchttprequest.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +static const SocketAddress kServerAddr("127.0.0.1", 0); +static const SocketAddress kServerHostnameAddr("localhost", 0); +static const char kServerGetPath[] = "/get"; +static const char kServerPostPath[] = "/post"; +static const char kServerResponse[] = "This is a test"; + +class TestHttpServer : public HttpServer, public sigslot::has_slots<> { + public: + TestHttpServer(Thread* thread, const SocketAddress& addr) : + socket_(thread->socketserver()->CreateAsyncSocket(addr.family(), + SOCK_STREAM)) { + socket_->Bind(addr); + socket_->Listen(5); + socket_->SignalReadEvent.connect(this, &TestHttpServer::OnAccept); + } + + SocketAddress address() const { return socket_->GetLocalAddress(); } + void Close() const { socket_->Close(); } + + private: + void OnAccept(AsyncSocket* socket) { + AsyncSocket* new_socket = socket_->Accept(NULL); + if (new_socket) { + HandleConnection(new SocketStream(new_socket)); + } + } + rtc::scoped_ptr socket_; +}; + +class AsyncHttpRequestTest : public testing::Test, + public sigslot::has_slots<> { + public: + AsyncHttpRequestTest() + : started_(false), + done_(false), + server_(Thread::Current(), kServerAddr) { + server_.SignalHttpRequest.connect(this, &AsyncHttpRequestTest::OnRequest); + } + + bool started() const { return started_; } + bool done() const { return done_; } + + AsyncHttpRequest* CreateGetRequest(const std::string& host, int port, + const std::string& path) { + rtc::AsyncHttpRequest* request = + new rtc::AsyncHttpRequest("unittest"); + request->SignalWorkDone.connect(this, + &AsyncHttpRequestTest::OnRequestDone); + request->request().verb = rtc::HV_GET; + request->set_host(host); + request->set_port(port); + request->request().path = path; + request->response().document.reset(new MemoryStream()); + return request; + } + AsyncHttpRequest* CreatePostRequest(const std::string& host, int port, + const std::string& path, + const std::string content_type, + StreamInterface* content) { + rtc::AsyncHttpRequest* request = + new rtc::AsyncHttpRequest("unittest"); + request->SignalWorkDone.connect(this, + &AsyncHttpRequestTest::OnRequestDone); + request->request().verb = rtc::HV_POST; + request->set_host(host); + request->set_port(port); + request->request().path = path; + request->request().setContent(content_type, content); + request->response().document.reset(new MemoryStream()); + return request; + } + + const TestHttpServer& server() const { return server_; } + + protected: + void OnRequest(HttpServer* server, HttpServerTransaction* t) { + started_ = true; + + if (t->request.path == kServerGetPath) { + t->response.set_success("text/plain", new MemoryStream(kServerResponse)); + } else if (t->request.path == kServerPostPath) { + // reverse the data and reply + size_t size; + StreamInterface* in = t->request.document.get(); + StreamInterface* out = new MemoryStream(); + in->GetSize(&size); + for (size_t i = 0; i < size; ++i) { + char ch; + in->SetPosition(size - i - 1); + in->Read(&ch, 1, NULL, NULL); + out->Write(&ch, 1, NULL, NULL); + } + out->Rewind(); + t->response.set_success("text/plain", out); + } else { + t->response.set_error(404); + } + server_.Respond(t); + } + void OnRequestDone(SignalThread* thread) { + done_ = true; + } + + private: + bool started_; + bool done_; + TestHttpServer server_; +}; + +TEST_F(AsyncHttpRequestTest, TestGetSuccess) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerGetPath); + EXPECT_FALSE(started()); + req->Start(); + EXPECT_TRUE_WAIT(started(), 5000); // Should have started by now. + EXPECT_TRUE_WAIT(done(), 5000); + std::string response; + EXPECT_EQ(200U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->Rewind(); + req->response().document->ReadLine(&response); + EXPECT_EQ(kServerResponse, response); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, TestGetNotFound) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + "/bad"); + req->Start(); + EXPECT_TRUE_WAIT(done(), 5000); + size_t size; + EXPECT_EQ(404U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->GetSize(&size); + EXPECT_EQ(0U, size); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, TestGetToNonServer) { + AsyncHttpRequest* req = CreateGetRequest( + "127.0.0.1", server().address().port(), + kServerGetPath); + // Stop the server before we send the request. + server().Close(); + req->Start(); + EXPECT_TRUE_WAIT(done(), 10000); + size_t size; + EXPECT_EQ(500U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->GetSize(&size); + EXPECT_EQ(0U, size); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, DISABLED_TestGetToInvalidHostname) { + AsyncHttpRequest* req = CreateGetRequest( + "invalid", server().address().port(), + kServerGetPath); + req->Start(); + EXPECT_TRUE_WAIT(done(), 5000); + size_t size; + EXPECT_EQ(500U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->GetSize(&size); + EXPECT_EQ(0U, size); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, TestPostSuccess) { + AsyncHttpRequest* req = CreatePostRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerPostPath, "text/plain", new MemoryStream("abcd1234")); + req->Start(); + EXPECT_TRUE_WAIT(done(), 5000); + std::string response; + EXPECT_EQ(200U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->Rewind(); + req->response().document->ReadLine(&response); + EXPECT_EQ("4321dcba", response); + req->Release(); +} + +// Ensure that we shut down properly even if work is outstanding. +TEST_F(AsyncHttpRequestTest, TestCancel) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerGetPath); + req->Start(); + req->Destroy(true); +} + +TEST_F(AsyncHttpRequestTest, TestGetSuccessDelay) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerGetPath); + req->set_start_delay(10); // Delay 10ms. + req->Start(); + Thread::SleepMs(5); + EXPECT_FALSE(started()); // Should not have started immediately. + EXPECT_TRUE_WAIT(started(), 5000); // Should have started by now. + EXPECT_TRUE_WAIT(done(), 5000); + std::string response; + EXPECT_EQ(200U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->Rewind(); + req->response().document->ReadLine(&response); + EXPECT_EQ(kServerResponse, response); + req->Release(); +} + +} // namespace rtc diff --git a/webrtc/base/asyncinvoker-inl.h b/webrtc/base/asyncinvoker-inl.h new file mode 100644 index 000000000..733cb0e79 --- /dev/null +++ b/webrtc/base/asyncinvoker-inl.h @@ -0,0 +1,129 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCINVOKER_INL_H_ +#define WEBRTC_BASE_ASYNCINVOKER_INL_H_ + +#include "webrtc/base/bind.h" +#include "webrtc/base/callback.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +class AsyncInvoker; + +// Helper class for AsyncInvoker. Runs a task and triggers a callback +// on the calling thread if necessary. Instances are ref-counted so their +// lifetime can be independent of AsyncInvoker. +class AsyncClosure : public RefCountInterface { + public: + virtual ~AsyncClosure() {} + // Runs the asynchronous task, and triggers a callback to the calling + // thread if needed. Should be called from the target thread. + virtual void Execute() = 0; +}; + +// Simple closure that doesn't trigger a callback for the calling thread. +template +class FireAndForgetAsyncClosure : public AsyncClosure { + public: + explicit FireAndForgetAsyncClosure(const FunctorT& functor) + : functor_(functor) {} + virtual void Execute() { + functor_(); + } + private: + FunctorT functor_; +}; + +// Base class for closures that may trigger a callback for the calling thread. +// Listens for the "destroyed" signals from the calling thread and the invoker, +// and cancels the callback to the calling thread if either is destroyed. +class NotifyingAsyncClosureBase : public AsyncClosure, + public sigslot::has_slots<> { + public: + virtual ~NotifyingAsyncClosureBase() { disconnect_all(); } + + protected: + NotifyingAsyncClosureBase(AsyncInvoker* invoker, Thread* calling_thread); + void TriggerCallback(); + void SetCallback(const Callback0& callback) { + CritScope cs(&crit_); + callback_ = callback; + } + bool CallbackCanceled() const { return calling_thread_ == NULL; } + + private: + Callback0 callback_; + CriticalSection crit_; + AsyncInvoker* invoker_; + Thread* calling_thread_; + + void CancelCallback(); +}; + +// Closures that have a non-void return value and require a callback. +template +class NotifyingAsyncClosure : public NotifyingAsyncClosureBase { + public: + NotifyingAsyncClosure(AsyncInvoker* invoker, + Thread* calling_thread, + const FunctorT& functor, + void (HostT::*callback)(ReturnT), + HostT* callback_host) + : NotifyingAsyncClosureBase(invoker, calling_thread), + functor_(functor), + callback_(callback), + callback_host_(callback_host) {} + virtual void Execute() { + ReturnT result = functor_(); + if (!CallbackCanceled()) { + SetCallback(Callback0(Bind(callback_, callback_host_, result))); + TriggerCallback(); + } + } + + private: + FunctorT functor_; + void (HostT::*callback_)(ReturnT); + HostT* callback_host_; +}; + +// Closures that have a void return value and require a callback. +template +class NotifyingAsyncClosure + : public NotifyingAsyncClosureBase { + public: + NotifyingAsyncClosure(AsyncInvoker* invoker, + Thread* calling_thread, + const FunctorT& functor, + void (HostT::*callback)(), + HostT* callback_host) + : NotifyingAsyncClosureBase(invoker, calling_thread), + functor_(functor) { + SetCallback(Callback0(Bind(callback, callback_host))); + } + virtual void Execute() { + functor_(); + TriggerCallback(); + } + + private: + FunctorT functor_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCINVOKER_INL_H_ diff --git a/webrtc/base/asyncinvoker.cc b/webrtc/base/asyncinvoker.cc new file mode 100644 index 000000000..ee423f110 --- /dev/null +++ b/webrtc/base/asyncinvoker.cc @@ -0,0 +1,91 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/asyncinvoker.h" + +namespace rtc { + +AsyncInvoker::AsyncInvoker() : destroying_(false) {} + +AsyncInvoker::~AsyncInvoker() { + destroying_ = true; + SignalInvokerDestroyed(); + // Messages for this need to be cleared *before* our destructor is complete. + MessageQueueManager::Clear(this); +} + +void AsyncInvoker::OnMessage(Message* msg) { + // Get the AsyncClosure shared ptr from this message's data. + ScopedRefMessageData* data = + static_cast*>(msg->pdata); + scoped_refptr closure = data->data(); + delete msg->pdata; + msg->pdata = NULL; + + // Execute the closure and trigger the return message if needed. + closure->Execute(); +} + +void AsyncInvoker::Flush(Thread* thread, uint32 id /*= MQID_ANY*/) { + if (destroying_) return; + + // Run this on |thread| to reduce the number of context switches. + if (Thread::Current() != thread) { + thread->Invoke(Bind(&AsyncInvoker::Flush, this, thread, id)); + return; + } + + MessageList removed; + thread->Clear(this, id, &removed); + for (MessageList::iterator it = removed.begin(); it != removed.end(); ++it) { + // This message was pending on this thread, so run it now. + thread->Send(it->phandler, + it->message_id, + it->pdata); + } +} + +void AsyncInvoker::DoInvoke(Thread* thread, AsyncClosure* closure, + uint32 id) { + if (destroying_) { + LOG(LS_WARNING) << "Tried to invoke while destroying the invoker."; + // Since this call transwers ownership of |closure|, we clean it up here. + delete closure; + return; + } + thread->Post(this, id, new ScopedRefMessageData(closure)); +} + +NotifyingAsyncClosureBase::NotifyingAsyncClosureBase(AsyncInvoker* invoker, + Thread* calling_thread) + : invoker_(invoker), calling_thread_(calling_thread) { + calling_thread->SignalQueueDestroyed.connect( + this, &NotifyingAsyncClosureBase::CancelCallback); + invoker->SignalInvokerDestroyed.connect( + this, &NotifyingAsyncClosureBase::CancelCallback); +} + +void NotifyingAsyncClosureBase::TriggerCallback() { + CritScope cs(&crit_); + if (!CallbackCanceled() && !callback_.empty()) { + invoker_->AsyncInvoke(calling_thread_, callback_); + } +} + +void NotifyingAsyncClosureBase::CancelCallback() { + // If the callback is triggering when this is called, block the + // destructor of the dying object here by waiting until the callback + // is done triggering. + CritScope cs(&crit_); + // calling_thread_ == NULL means do not trigger the callback. + calling_thread_ = NULL; +} + +} // namespace rtc diff --git a/webrtc/base/asyncinvoker.h b/webrtc/base/asyncinvoker.h new file mode 100644 index 000000000..ee9a03c80 --- /dev/null +++ b/webrtc/base/asyncinvoker.h @@ -0,0 +1,134 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCINVOKER_H_ +#define WEBRTC_BASE_ASYNCINVOKER_H_ + +#include "webrtc/base/asyncinvoker-inl.h" +#include "webrtc/base/bind.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/scopedptrcollection.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Invokes function objects (aka functors) asynchronously on a Thread, and +// owns the lifetime of calls (ie, when this object is destroyed, calls in +// flight are cancelled). AsyncInvoker can optionally execute a user-specified +// function when the asynchronous call is complete, or operates in +// fire-and-forget mode otherwise. +// +// AsyncInvoker does not own the thread it calls functors on. +// +// A note about async calls and object lifetimes: users should +// be mindful of object lifetimes when calling functions asynchronously and +// ensure objects used by the function _cannot_ be deleted between the +// invocation and execution of the functor. AsyncInvoker is designed to +// help: any calls in flight will be cancelled when the AsyncInvoker used to +// make the call is destructed, and any calls executing will be allowed to +// complete before AsyncInvoker destructs. +// +// The easiest way to ensure lifetimes are handled correctly is to create a +// class that owns the Thread and AsyncInvoker objects, and then call its +// methods asynchronously as needed. +// +// Example: +// class MyClass { +// public: +// void FireAsyncTaskWithResult(Thread* thread, int x) { +// // Specify a callback to get the result upon completion. +// invoker_.AsyncInvoke( +// thread, Bind(&MyClass::AsyncTaskWithResult, this, x), +// &MyClass::OnTaskComplete, this); +// } +// void FireAnotherAsyncTask(Thread* thread) { +// // No callback specified means fire-and-forget. +// invoker_.AsyncInvoke( +// thread, Bind(&MyClass::AnotherAsyncTask, this)); +// +// private: +// int AsyncTaskWithResult(int x) { +// // Some long running process... +// return x * x; +// } +// void AnotherAsyncTask() { +// // Some other long running process... +// } +// void OnTaskComplete(int result) { result_ = result; } +// +// AsyncInvoker invoker_; +// int result_; +// }; +class AsyncInvoker : public MessageHandler { + public: + AsyncInvoker(); + virtual ~AsyncInvoker(); + + // Call |functor| asynchronously on |thread|, with no callback upon + // completion. Returns immediately. + template + void AsyncInvoke(Thread* thread, + const FunctorT& functor, + uint32 id = 0) { + AsyncClosure* closure = + new RefCountedObject >(functor); + DoInvoke(thread, closure, id); + } + + // Call |functor| asynchronously on |thread|, calling |callback| when done. + template + void AsyncInvoke(Thread* thread, + const FunctorT& functor, + void (HostT::*callback)(ReturnT), + HostT* callback_host, + uint32 id = 0) { + AsyncClosure* closure = + new RefCountedObject >( + this, Thread::Current(), functor, callback, callback_host); + DoInvoke(thread, closure, id); + } + + // Call |functor| asynchronously on |thread|, calling |callback| when done. + // Overloaded for void return. + template + void AsyncInvoke(Thread* thread, + const FunctorT& functor, + void (HostT::*callback)(), + HostT* callback_host, + uint32 id = 0) { + AsyncClosure* closure = + new RefCountedObject >( + this, Thread::Current(), functor, callback, callback_host); + DoInvoke(thread, closure, id); + } + + // Synchronously execute on |thread| all outstanding calls we own + // that are pending on |thread|, and wait for calls to complete + // before returning. Optionally filter by message id. + // The destructor will not wait for outstanding calls, so if that + // behavior is desired, call Flush() before destroying this object. + void Flush(Thread* thread, uint32 id = MQID_ANY); + + // Signaled when this object is destructed. + sigslot::signal0<> SignalInvokerDestroyed; + + private: + virtual void OnMessage(Message* msg); + void DoInvoke(Thread* thread, AsyncClosure* closure, uint32 id); + + bool destroying_; + + DISALLOW_COPY_AND_ASSIGN(AsyncInvoker); +}; + +} // namespace rtc + + +#endif // WEBRTC_BASE_ASYNCINVOKER_H_ diff --git a/webrtc/base/asyncpacketsocket.h b/webrtc/base/asyncpacketsocket.h new file mode 100644 index 000000000..dd91ea1f1 --- /dev/null +++ b/webrtc/base/asyncpacketsocket.h @@ -0,0 +1,140 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCPACKETSOCKET_H_ +#define WEBRTC_BASE_ASYNCPACKETSOCKET_H_ + +#include "webrtc/base/dscp.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +// This structure holds the info needed to update the packet send time header +// extension, including the information needed to update the authentication tag +// after changing the value. +struct PacketTimeUpdateParams { + PacketTimeUpdateParams() + : rtp_sendtime_extension_id(-1), srtp_auth_tag_len(-1), + srtp_packet_index(-1) { + } + + int rtp_sendtime_extension_id; // extension header id present in packet. + std::vector srtp_auth_key; // Authentication key. + int srtp_auth_tag_len; // Authentication tag length. + int64 srtp_packet_index; // Required for Rtp Packet authentication. +}; + +// This structure holds meta information for the packet which is about to send +// over network. +struct PacketOptions { + PacketOptions() : dscp(DSCP_NO_CHANGE) {} + explicit PacketOptions(DiffServCodePoint dscp) : dscp(dscp) {} + + DiffServCodePoint dscp; + PacketTimeUpdateParams packet_time_params; +}; + +// This structure will have the information about when packet is actually +// received by socket. +struct PacketTime { + PacketTime() : timestamp(-1), not_before(-1) {} + PacketTime(int64 timestamp, int64 not_before) + : timestamp(timestamp), not_before(not_before) { + } + + int64 timestamp; // Receive time after socket delivers the data. + int64 not_before; // Earliest possible time the data could have arrived, + // indicating the potential error in the |timestamp| value, + // in case the system, is busy. For example, the time of + // the last select() call. + // If unknown, this value will be set to zero. +}; + +inline PacketTime CreatePacketTime(int64 not_before) { + return PacketTime(TimeMicros(), not_before); +} + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncPacketSocket : public sigslot::has_slots<> { + public: + enum State { + STATE_CLOSED, + STATE_BINDING, + STATE_BOUND, + STATE_CONNECTING, + STATE_CONNECTED + }; + + AsyncPacketSocket() { } + virtual ~AsyncPacketSocket() { } + + // Returns current local address. Address may be set to NULL if the + // socket is not bound yet (GetState() returns STATE_BINDING). + virtual SocketAddress GetLocalAddress() const = 0; + + // Returns remote address. Returns zeroes if this is not a client TCP socket. + virtual SocketAddress GetRemoteAddress() const = 0; + + // Send a packet. + virtual int Send(const void *pv, size_t cb, const PacketOptions& options) = 0; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, + const PacketOptions& options) = 0; + + // Close the socket. + virtual int Close() = 0; + + // Returns current state of the socket. + virtual State GetState() const = 0; + + // Get/set options. + virtual int GetOption(Socket::Option opt, int* value) = 0; + virtual int SetOption(Socket::Option opt, int value) = 0; + + // Get/Set current error. + // TODO: Remove SetError(). + virtual int GetError() const = 0; + virtual void SetError(int error) = 0; + + // Emitted each time a packet is read. Used only for UDP and + // connected TCP sockets. + sigslot::signal5 SignalReadPacket; + + // Emitted when the socket is currently able to send. + sigslot::signal1 SignalReadyToSend; + + // Emitted after address for the socket is allocated, i.e. binding + // is finished. State of the socket is changed from BINDING to BOUND + // (for UDP and server TCP sockets) or CONNECTING (for client TCP + // sockets). + sigslot::signal2 SignalAddressReady; + + // Emitted for client TCP sockets when state is changed from + // CONNECTING to CONNECTED. + sigslot::signal1 SignalConnect; + + // Emitted for client TCP sockets when state is changed from + // CONNECTED to CLOSED. + sigslot::signal2 SignalClose; + + // Used only for listening TCP sockets. + sigslot::signal2 SignalNewConnection; + + private: + DISALLOW_EVIL_CONSTRUCTORS(AsyncPacketSocket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCPACKETSOCKET_H_ diff --git a/webrtc/base/asyncresolverinterface.h b/webrtc/base/asyncresolverinterface.h new file mode 100644 index 000000000..4b401bd97 --- /dev/null +++ b/webrtc/base/asyncresolverinterface.h @@ -0,0 +1,47 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCRESOLVERINTERFACE_H_ +#define WEBRTC_BASE_ASYNCRESOLVERINTERFACE_H_ + +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +// This interface defines the methods to resolve the address asynchronously. +class AsyncResolverInterface { + public: + AsyncResolverInterface() {} + virtual ~AsyncResolverInterface() {} + + // Start address resolve process. + virtual void Start(const SocketAddress& addr) = 0; + // Returns top most resolved address of |family| + virtual bool GetResolvedAddress(int family, SocketAddress* addr) const = 0; + // Returns error from resolver. + virtual int GetError() const = 0; + // Delete the resolver. + virtual void Destroy(bool wait) = 0; + // Returns top most resolved IPv4 address if address is resolved successfully. + // Otherwise returns address set in SetAddress. + SocketAddress address() const { + SocketAddress addr; + GetResolvedAddress(AF_INET, &addr); + return addr; + } + + // This signal is fired when address resolve process is completed. + sigslot::signal1 SignalDone; +}; + +} // namespace rtc + +#endif diff --git a/webrtc/base/asyncsocket.cc b/webrtc/base/asyncsocket.cc new file mode 100644 index 000000000..d565c6e99 --- /dev/null +++ b/webrtc/base/asyncsocket.cc @@ -0,0 +1,44 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/asyncsocket.h" + +namespace rtc { + +AsyncSocket::AsyncSocket() { +} + +AsyncSocket::~AsyncSocket() { +} + +AsyncSocketAdapter::AsyncSocketAdapter(AsyncSocket* socket) : socket_(NULL) { + Attach(socket); +} + +AsyncSocketAdapter::~AsyncSocketAdapter() { + delete socket_; +} + +void AsyncSocketAdapter::Attach(AsyncSocket* socket) { + ASSERT(!socket_); + socket_ = socket; + if (socket_) { + socket_->SignalConnectEvent.connect(this, + &AsyncSocketAdapter::OnConnectEvent); + socket_->SignalReadEvent.connect(this, + &AsyncSocketAdapter::OnReadEvent); + socket_->SignalWriteEvent.connect(this, + &AsyncSocketAdapter::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, + &AsyncSocketAdapter::OnCloseEvent); + } +} + +} // namespace rtc diff --git a/webrtc/base/asyncsocket.h b/webrtc/base/asyncsocket.h new file mode 100644 index 000000000..a6f3158e9 --- /dev/null +++ b/webrtc/base/asyncsocket.h @@ -0,0 +1,124 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCSOCKET_H_ +#define WEBRTC_BASE_ASYNCSOCKET_H_ + +#include "webrtc/base/common.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socket.h" + +namespace rtc { + +// TODO: Remove Socket and rename AsyncSocket to Socket. + +// Provides the ability to perform socket I/O asynchronously. +class AsyncSocket : public Socket { + public: + AsyncSocket(); + virtual ~AsyncSocket(); + + virtual AsyncSocket* Accept(SocketAddress* paddr) = 0; + + // SignalReadEvent and SignalWriteEvent use multi_threaded_local to allow + // access concurrently from different thread. + // For example SignalReadEvent::connect will be called in AsyncUDPSocket ctor + // but at the same time the SocketDispatcher maybe signaling the read event. + // ready to read + sigslot::signal1 SignalReadEvent; + // ready to write + sigslot::signal1 SignalWriteEvent; + sigslot::signal1 SignalConnectEvent; // connected + sigslot::signal2 SignalCloseEvent; // closed +}; + +class AsyncSocketAdapter : public AsyncSocket, public sigslot::has_slots<> { + public: + // The adapted socket may explicitly be NULL, and later assigned using Attach. + // However, subclasses which support detached mode must override any methods + // that will be called during the detached period (usually GetState()), to + // avoid dereferencing a null pointer. + explicit AsyncSocketAdapter(AsyncSocket* socket); + virtual ~AsyncSocketAdapter(); + void Attach(AsyncSocket* socket); + virtual SocketAddress GetLocalAddress() const { + return socket_->GetLocalAddress(); + } + virtual SocketAddress GetRemoteAddress() const { + return socket_->GetRemoteAddress(); + } + virtual int Bind(const SocketAddress& addr) { + return socket_->Bind(addr); + } + virtual int Connect(const SocketAddress& addr) { + return socket_->Connect(addr); + } + virtual int Send(const void* pv, size_t cb) { + return socket_->Send(pv, cb); + } + virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) { + return socket_->SendTo(pv, cb, addr); + } + virtual int Recv(void* pv, size_t cb) { + return socket_->Recv(pv, cb); + } + virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) { + return socket_->RecvFrom(pv, cb, paddr); + } + virtual int Listen(int backlog) { + return socket_->Listen(backlog); + } + virtual AsyncSocket* Accept(SocketAddress* paddr) { + return socket_->Accept(paddr); + } + virtual int Close() { + return socket_->Close(); + } + virtual int GetError() const { + return socket_->GetError(); + } + virtual void SetError(int error) { + return socket_->SetError(error); + } + virtual ConnState GetState() const { + return socket_->GetState(); + } + virtual int EstimateMTU(uint16* mtu) { + return socket_->EstimateMTU(mtu); + } + virtual int GetOption(Option opt, int* value) { + return socket_->GetOption(opt, value); + } + virtual int SetOption(Option opt, int value) { + return socket_->SetOption(opt, value); + } + + protected: + virtual void OnConnectEvent(AsyncSocket* socket) { + SignalConnectEvent(this); + } + virtual void OnReadEvent(AsyncSocket* socket) { + SignalReadEvent(this); + } + virtual void OnWriteEvent(AsyncSocket* socket) { + SignalWriteEvent(this); + } + virtual void OnCloseEvent(AsyncSocket* socket, int err) { + SignalCloseEvent(this, err); + } + + AsyncSocket* socket_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCSOCKET_H_ diff --git a/webrtc/base/asynctcpsocket.cc b/webrtc/base/asynctcpsocket.cc new file mode 100644 index 000000000..0f7abd5a6 --- /dev/null +++ b/webrtc/base/asynctcpsocket.cc @@ -0,0 +1,299 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/asynctcpsocket.h" + +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +#if defined(WEBRTC_POSIX) +#include +#endif // WEBRTC_POSIX + +namespace rtc { + +static const size_t kMaxPacketSize = 64 * 1024; + +typedef uint16 PacketLength; +static const size_t kPacketLenSize = sizeof(PacketLength); + +static const size_t kBufSize = kMaxPacketSize + kPacketLenSize; + +static const int kListenBacklog = 5; + +// Binds and connects |socket| +AsyncSocket* AsyncTCPSocketBase::ConnectSocket( + rtc::AsyncSocket* socket, + const rtc::SocketAddress& bind_address, + const rtc::SocketAddress& remote_address) { + rtc::scoped_ptr owned_socket(socket); + if (socket->Bind(bind_address) < 0) { + LOG(LS_ERROR) << "Bind() failed with error " << socket->GetError(); + return NULL; + } + if (socket->Connect(remote_address) < 0) { + LOG(LS_ERROR) << "Connect() failed with error " << socket->GetError(); + return NULL; + } + return owned_socket.release(); +} + +AsyncTCPSocketBase::AsyncTCPSocketBase(AsyncSocket* socket, bool listen, + size_t max_packet_size) + : socket_(socket), + listen_(listen), + insize_(max_packet_size), + inpos_(0), + outsize_(max_packet_size), + outpos_(0) { + inbuf_ = new char[insize_]; + outbuf_ = new char[outsize_]; + + ASSERT(socket_.get() != NULL); + socket_->SignalConnectEvent.connect( + this, &AsyncTCPSocketBase::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &AsyncTCPSocketBase::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &AsyncTCPSocketBase::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &AsyncTCPSocketBase::OnCloseEvent); + + if (listen_) { + if (socket_->Listen(kListenBacklog) < 0) { + LOG(LS_ERROR) << "Listen() failed with error " << socket_->GetError(); + } + } +} + +AsyncTCPSocketBase::~AsyncTCPSocketBase() { + delete [] inbuf_; + delete [] outbuf_; +} + +SocketAddress AsyncTCPSocketBase::GetLocalAddress() const { + return socket_->GetLocalAddress(); +} + +SocketAddress AsyncTCPSocketBase::GetRemoteAddress() const { + return socket_->GetRemoteAddress(); +} + +int AsyncTCPSocketBase::Close() { + return socket_->Close(); +} + +AsyncTCPSocket::State AsyncTCPSocketBase::GetState() const { + switch (socket_->GetState()) { + case Socket::CS_CLOSED: + return STATE_CLOSED; + case Socket::CS_CONNECTING: + if (listen_) { + return STATE_BOUND; + } else { + return STATE_CONNECTING; + } + case Socket::CS_CONNECTED: + return STATE_CONNECTED; + default: + ASSERT(false); + return STATE_CLOSED; + } +} + +int AsyncTCPSocketBase::GetOption(Socket::Option opt, int* value) { + return socket_->GetOption(opt, value); +} + +int AsyncTCPSocketBase::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int AsyncTCPSocketBase::GetError() const { + return socket_->GetError(); +} + +void AsyncTCPSocketBase::SetError(int error) { + return socket_->SetError(error); +} + +int AsyncTCPSocketBase::SendTo(const void *pv, size_t cb, + const SocketAddress& addr, + const rtc::PacketOptions& options) { + if (addr == GetRemoteAddress()) + return Send(pv, cb, options); + + ASSERT(false); + socket_->SetError(ENOTCONN); + return -1; +} + +int AsyncTCPSocketBase::SendRaw(const void * pv, size_t cb) { + if (outpos_ + cb > outsize_) { + socket_->SetError(EMSGSIZE); + return -1; + } + + memcpy(outbuf_ + outpos_, pv, cb); + outpos_ += cb; + + return FlushOutBuffer(); +} + +int AsyncTCPSocketBase::FlushOutBuffer() { + int res = socket_->Send(outbuf_, outpos_); + if (res <= 0) { + return res; + } + if (static_cast(res) <= outpos_) { + outpos_ -= res; + } else { + ASSERT(false); + return -1; + } + if (outpos_ > 0) { + memmove(outbuf_, outbuf_ + res, outpos_); + } + return res; +} + +void AsyncTCPSocketBase::AppendToOutBuffer(const void* pv, size_t cb) { + ASSERT(outpos_ + cb < outsize_); + memcpy(outbuf_ + outpos_, pv, cb); + outpos_ += cb; +} + +void AsyncTCPSocketBase::OnConnectEvent(AsyncSocket* socket) { + SignalConnect(this); +} + +void AsyncTCPSocketBase::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket_.get() == socket); + + if (listen_) { + rtc::SocketAddress address; + rtc::AsyncSocket* new_socket = socket->Accept(&address); + if (!new_socket) { + // TODO: Do something better like forwarding the error + // to the user. + LOG(LS_ERROR) << "TCP accept failed with error " << socket_->GetError(); + return; + } + + HandleIncomingConnection(new_socket); + + // Prime a read event in case data is waiting. + new_socket->SignalReadEvent(new_socket); + } else { + int len = socket_->Recv(inbuf_ + inpos_, insize_ - inpos_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + if (!socket_->IsBlocking()) { + LOG(LS_ERROR) << "Recv() returned error: " << socket_->GetError(); + } + return; + } + + inpos_ += len; + + ProcessInput(inbuf_, &inpos_); + + if (inpos_ >= insize_) { + LOG(LS_ERROR) << "input buffer overflow"; + ASSERT(false); + inpos_ = 0; + } + } +} + +void AsyncTCPSocketBase::OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket_.get() == socket); + + if (outpos_ > 0) { + FlushOutBuffer(); + } + + if (outpos_ == 0) { + SignalReadyToSend(this); + } +} + +void AsyncTCPSocketBase::OnCloseEvent(AsyncSocket* socket, int error) { + SignalClose(this, error); +} + +// AsyncTCPSocket +// Binds and connects |socket| and creates AsyncTCPSocket for +// it. Takes ownership of |socket|. Returns NULL if bind() or +// connect() fail (|socket| is destroyed in that case). +AsyncTCPSocket* AsyncTCPSocket::Create( + AsyncSocket* socket, + const SocketAddress& bind_address, + const SocketAddress& remote_address) { + return new AsyncTCPSocket(AsyncTCPSocketBase::ConnectSocket( + socket, bind_address, remote_address), false); +} + +AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket, bool listen) + : AsyncTCPSocketBase(socket, listen, kBufSize) { +} + +int AsyncTCPSocket::Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) { + if (cb > kBufSize) { + SetError(EMSGSIZE); + return -1; + } + + // If we are blocking on send, then silently drop this packet + if (!IsOutBufferEmpty()) + return static_cast(cb); + + PacketLength pkt_len = HostToNetwork16(static_cast(cb)); + AppendToOutBuffer(&pkt_len, kPacketLenSize); + AppendToOutBuffer(pv, cb); + + int res = FlushOutBuffer(); + if (res <= 0) { + // drop packet if we made no progress + ClearOutBuffer(); + return res; + } + + // We claim to have sent the whole thing, even if we only sent partial + return static_cast(cb); +} + +void AsyncTCPSocket::ProcessInput(char * data, size_t* len) { + SocketAddress remote_addr(GetRemoteAddress()); + + while (true) { + if (*len < kPacketLenSize) + return; + + PacketLength pkt_len = rtc::GetBE16(data); + if (*len < kPacketLenSize + pkt_len) + return; + + SignalReadPacket(this, data + kPacketLenSize, pkt_len, remote_addr, + CreatePacketTime(0)); + + *len -= kPacketLenSize + pkt_len; + if (*len > 0) { + memmove(data, data + kPacketLenSize + pkt_len, *len); + } + } +} + +void AsyncTCPSocket::HandleIncomingConnection(AsyncSocket* socket) { + SignalNewConnection(this, new AsyncTCPSocket(socket, false)); +} + +} // namespace rtc diff --git a/webrtc/base/asynctcpsocket.h b/webrtc/base/asynctcpsocket.h new file mode 100644 index 000000000..ddee2615a --- /dev/null +++ b/webrtc/base/asynctcpsocket.h @@ -0,0 +1,100 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCTCPSOCKET_H_ +#define WEBRTC_BASE_ASYNCTCPSOCKET_H_ + +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +// Simulates UDP semantics over TCP. Send and Recv packet sizes +// are preserved, and drops packets silently on Send, rather than +// buffer them in user space. +class AsyncTCPSocketBase : public AsyncPacketSocket { + public: + AsyncTCPSocketBase(AsyncSocket* socket, bool listen, size_t max_packet_size); + virtual ~AsyncTCPSocketBase(); + + // Pure virtual methods to send and recv data. + virtual int Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) = 0; + virtual void ProcessInput(char* data, size_t* len) = 0; + // Signals incoming connection. + virtual void HandleIncomingConnection(AsyncSocket* socket) = 0; + + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, + const rtc::PacketOptions& options); + virtual int Close(); + + virtual State GetState() const; + virtual int GetOption(Socket::Option opt, int* value); + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + protected: + // Binds and connects |socket| and creates AsyncTCPSocket for + // it. Takes ownership of |socket|. Returns NULL if bind() or + // connect() fail (|socket| is destroyed in that case). + static AsyncSocket* ConnectSocket(AsyncSocket* socket, + const SocketAddress& bind_address, + const SocketAddress& remote_address); + virtual int SendRaw(const void* pv, size_t cb); + int FlushOutBuffer(); + // Add data to |outbuf_|. + void AppendToOutBuffer(const void* pv, size_t cb); + + // Helper methods for |outpos_|. + bool IsOutBufferEmpty() const { return outpos_ == 0; } + void ClearOutBuffer() { outpos_ = 0; } + + private: + // Called by the underlying socket + void OnConnectEvent(AsyncSocket* socket); + void OnReadEvent(AsyncSocket* socket); + void OnWriteEvent(AsyncSocket* socket); + void OnCloseEvent(AsyncSocket* socket, int error); + + scoped_ptr socket_; + bool listen_; + char* inbuf_, * outbuf_; + size_t insize_, inpos_, outsize_, outpos_; + + DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocketBase); +}; + +class AsyncTCPSocket : public AsyncTCPSocketBase { + public: + // Binds and connects |socket| and creates AsyncTCPSocket for + // it. Takes ownership of |socket|. Returns NULL if bind() or + // connect() fail (|socket| is destroyed in that case). + static AsyncTCPSocket* Create(AsyncSocket* socket, + const SocketAddress& bind_address, + const SocketAddress& remote_address); + AsyncTCPSocket(AsyncSocket* socket, bool listen); + virtual ~AsyncTCPSocket() {} + + virtual int Send(const void* pv, size_t cb, + const rtc::PacketOptions& options); + virtual void ProcessInput(char* data, size_t* len); + virtual void HandleIncomingConnection(AsyncSocket* socket); + + private: + DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCTCPSOCKET_H_ diff --git a/webrtc/base/asynctcpsocket_unittest.cc b/webrtc/base/asynctcpsocket_unittest.cc new file mode 100644 index 000000000..b93175864 --- /dev/null +++ b/webrtc/base/asynctcpsocket_unittest.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/virtualsocketserver.h" + +namespace rtc { + +class AsyncTCPSocketTest + : public testing::Test, + public sigslot::has_slots<> { + public: + AsyncTCPSocketTest() + : pss_(new rtc::PhysicalSocketServer), + vss_(new rtc::VirtualSocketServer(pss_.get())), + socket_(vss_->CreateAsyncSocket(SOCK_STREAM)), + tcp_socket_(new AsyncTCPSocket(socket_, true)), + ready_to_send_(false) { + tcp_socket_->SignalReadyToSend.connect(this, + &AsyncTCPSocketTest::OnReadyToSend); + } + + void OnReadyToSend(rtc::AsyncPacketSocket* socket) { + ready_to_send_ = true; + } + + protected: + scoped_ptr pss_; + scoped_ptr vss_; + AsyncSocket* socket_; + scoped_ptr tcp_socket_; + bool ready_to_send_; +}; + +TEST_F(AsyncTCPSocketTest, OnWriteEvent) { + EXPECT_FALSE(ready_to_send_); + socket_->SignalWriteEvent(socket_); + EXPECT_TRUE(ready_to_send_); +} + +} // namespace rtc diff --git a/webrtc/base/asyncudpsocket.cc b/webrtc/base/asyncudpsocket.cc new file mode 100644 index 000000000..3e2ecc4cd --- /dev/null +++ b/webrtc/base/asyncudpsocket.cc @@ -0,0 +1,122 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +static const int BUF_SIZE = 64 * 1024; + +AsyncUDPSocket* AsyncUDPSocket::Create( + AsyncSocket* socket, + const SocketAddress& bind_address) { + scoped_ptr owned_socket(socket); + if (socket->Bind(bind_address) < 0) { + LOG(LS_ERROR) << "Bind() failed with error " << socket->GetError(); + return NULL; + } + return new AsyncUDPSocket(owned_socket.release()); +} + +AsyncUDPSocket* AsyncUDPSocket::Create(SocketFactory* factory, + const SocketAddress& bind_address) { + AsyncSocket* socket = + factory->CreateAsyncSocket(bind_address.family(), SOCK_DGRAM); + if (!socket) + return NULL; + return Create(socket, bind_address); +} + +AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket) + : socket_(socket) { + ASSERT(socket_); + size_ = BUF_SIZE; + buf_ = new char[size_]; + + // The socket should start out readable but not writable. + socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &AsyncUDPSocket::OnWriteEvent); +} + +AsyncUDPSocket::~AsyncUDPSocket() { + delete [] buf_; +} + +SocketAddress AsyncUDPSocket::GetLocalAddress() const { + return socket_->GetLocalAddress(); +} + +SocketAddress AsyncUDPSocket::GetRemoteAddress() const { + return socket_->GetRemoteAddress(); +} + +int AsyncUDPSocket::Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) { + return socket_->Send(pv, cb); +} + +int AsyncUDPSocket::SendTo(const void *pv, size_t cb, + const SocketAddress& addr, + const rtc::PacketOptions& options) { + return socket_->SendTo(pv, cb, addr); +} + +int AsyncUDPSocket::Close() { + return socket_->Close(); +} + +AsyncUDPSocket::State AsyncUDPSocket::GetState() const { + return STATE_BOUND; +} + +int AsyncUDPSocket::GetOption(Socket::Option opt, int* value) { + return socket_->GetOption(opt, value); +} + +int AsyncUDPSocket::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int AsyncUDPSocket::GetError() const { + return socket_->GetError(); +} + +void AsyncUDPSocket::SetError(int error) { + return socket_->SetError(error); +} + +void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket_.get() == socket); + + SocketAddress remote_addr; + int len = socket_->RecvFrom(buf_, size_, &remote_addr); + if (len < 0) { + // An error here typically means we got an ICMP error in response to our + // send datagram, indicating the remote address was unreachable. + // When doing ICE, this kind of thing will often happen. + // TODO: Do something better like forwarding the error to the user. + SocketAddress local_addr = socket_->GetLocalAddress(); + LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToSensitiveString() << "] " + << "receive failed with error " << socket_->GetError(); + return; + } + + // TODO: Make sure that we got all of the packet. + // If we did not, then we should resize our buffer to be large enough. + SignalReadPacket(this, buf_, static_cast(len), remote_addr, + CreatePacketTime(0)); +} + +void AsyncUDPSocket::OnWriteEvent(AsyncSocket* socket) { + SignalReadyToSend(this); +} + +} // namespace rtc diff --git a/webrtc/base/asyncudpsocket.h b/webrtc/base/asyncudpsocket.h new file mode 100644 index 000000000..ac64dca68 --- /dev/null +++ b/webrtc/base/asyncudpsocket.h @@ -0,0 +1,63 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ASYNCUDPSOCKET_H_ +#define WEBRTC_BASE_ASYNCUDPSOCKET_H_ + +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncUDPSocket : public AsyncPacketSocket { + public: + // Binds |socket| and creates AsyncUDPSocket for it. Takes ownership + // of |socket|. Returns NULL if bind() fails (|socket| is destroyed + // in that case). + static AsyncUDPSocket* Create(AsyncSocket* socket, + const SocketAddress& bind_address); + // Creates a new socket for sending asynchronous UDP packets using an + // asynchronous socket from the given factory. + static AsyncUDPSocket* Create(SocketFactory* factory, + const SocketAddress& bind_address); + explicit AsyncUDPSocket(AsyncSocket* socket); + virtual ~AsyncUDPSocket(); + + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Send(const void *pv, size_t cb, + const rtc::PacketOptions& options); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, + const rtc::PacketOptions& options); + virtual int Close(); + + virtual State GetState() const; + virtual int GetOption(Socket::Option opt, int* value); + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + private: + // Called when the underlying socket is ready to be read from. + void OnReadEvent(AsyncSocket* socket); + // Called when the underlying socket is ready to send. + void OnWriteEvent(AsyncSocket* socket); + + scoped_ptr socket_; + char* buf_; + size_t size_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCUDPSOCKET_H_ diff --git a/webrtc/base/asyncudpsocket_unittest.cc b/webrtc/base/asyncudpsocket_unittest.cc new file mode 100644 index 000000000..bd65940fc --- /dev/null +++ b/webrtc/base/asyncudpsocket_unittest.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/virtualsocketserver.h" + +namespace rtc { + +class AsyncUdpSocketTest + : public testing::Test, + public sigslot::has_slots<> { + public: + AsyncUdpSocketTest() + : pss_(new rtc::PhysicalSocketServer), + vss_(new rtc::VirtualSocketServer(pss_.get())), + socket_(vss_->CreateAsyncSocket(SOCK_DGRAM)), + udp_socket_(new AsyncUDPSocket(socket_)), + ready_to_send_(false) { + udp_socket_->SignalReadyToSend.connect(this, + &AsyncUdpSocketTest::OnReadyToSend); + } + + void OnReadyToSend(rtc::AsyncPacketSocket* socket) { + ready_to_send_ = true; + } + + protected: + scoped_ptr pss_; + scoped_ptr vss_; + AsyncSocket* socket_; + scoped_ptr udp_socket_; + bool ready_to_send_; +}; + +TEST_F(AsyncUdpSocketTest, OnWriteEvent) { + EXPECT_FALSE(ready_to_send_); + socket_->SignalWriteEvent(socket_); + EXPECT_TRUE(ready_to_send_); +} + +} // namespace rtc diff --git a/webrtc/base/atomicops.h b/webrtc/base/atomicops.h new file mode 100644 index 000000000..6096e8c08 --- /dev/null +++ b/webrtc/base/atomicops.h @@ -0,0 +1,149 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ATOMICOPS_H_ +#define WEBRTC_BASE_ATOMICOPS_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +// A single-producer, single-consumer, fixed-size queue. +// All methods not ending in Unsafe can be safely called without locking, +// provided that calls to consumer methods (Peek/Pop) or producer methods (Push) +// only happen on a single thread per method type. If multiple threads need to +// read simultaneously or write simultaneously, other synchronization is +// necessary. Synchronization is also required if a call into any Unsafe method +// could happen at the same time as a call to any other method. +template +class FixedSizeLockFreeQueue { + private: +// Atomic primitives and memory barrier +#if defined(__arm__) + typedef uint32 Atomic32; + + // Copied from google3/base/atomicops-internals-arm-v6plus.h + static inline void MemoryBarrier() { + asm volatile("dmb":::"memory"); + } + + // Adapted from google3/base/atomicops-internals-arm-v6plus.h + static inline void AtomicIncrement(volatile Atomic32* ptr) { + Atomic32 str_success, value; + asm volatile ( + "1:\n" + "ldrex %1, [%2]\n" + "add %1, %1, #1\n" + "strex %0, %1, [%2]\n" + "teq %0, #0\n" + "bne 1b" + : "=&r"(str_success), "=&r"(value) + : "r" (ptr) + : "cc", "memory"); + } +#elif !defined(SKIP_ATOMIC_CHECK) +#error "No atomic operations defined for the given architecture." +#endif + + public: + // Constructs an empty queue, with capacity 0. + FixedSizeLockFreeQueue() : pushed_count_(0), + popped_count_(0), + capacity_(0), + data_() {} + // Constructs an empty queue with the given capacity. + FixedSizeLockFreeQueue(size_t capacity) : pushed_count_(0), + popped_count_(0), + capacity_(capacity), + data_(new T[capacity]) {} + + // Pushes a value onto the queue. Returns true if the value was successfully + // pushed (there was space in the queue). This method can be safely called at + // the same time as PeekFront/PopFront. + bool PushBack(T value) { + if (capacity_ == 0) { + LOG(LS_WARNING) << "Queue capacity is 0."; + return false; + } + if (IsFull()) { + return false; + } + + data_[pushed_count_ % capacity_] = value; + // Make sure the data is written before the count is incremented, so other + // threads can't see the value exists before being able to read it. + MemoryBarrier(); + AtomicIncrement(&pushed_count_); + return true; + } + + // Retrieves the oldest value pushed onto the queue. Returns true if there was + // an item to peek (the queue was non-empty). This method can be safely called + // at the same time as PushBack. + bool PeekFront(T* value_out) { + if (capacity_ == 0) { + LOG(LS_WARNING) << "Queue capacity is 0."; + return false; + } + if (IsEmpty()) { + return false; + } + + *value_out = data_[popped_count_ % capacity_]; + return true; + } + + // Retrieves the oldest value pushed onto the queue and removes it from the + // queue. Returns true if there was an item to pop (the queue was non-empty). + // This method can be safely called at the same time as PushBack. + bool PopFront(T* value_out) { + if (PeekFront(value_out)) { + AtomicIncrement(&popped_count_); + return true; + } + return false; + } + + // Clears the current items in the queue and sets the new (fixed) size. This + // method cannot be called at the same time as any other method. + void ClearAndResizeUnsafe(int new_capacity) { + capacity_ = new_capacity; + data_.reset(new T[new_capacity]); + pushed_count_ = 0; + popped_count_ = 0; + } + + // Returns true if there is no space left in the queue for new elements. + int IsFull() const { return pushed_count_ == popped_count_ + capacity_; } + // Returns true if there are no elements in the queue. + int IsEmpty() const { return pushed_count_ == popped_count_; } + // Returns the current number of elements in the queue. This is always in the + // range [0, capacity] + size_t Size() const { return pushed_count_ - popped_count_; } + + // Returns the capacity of the queue (max size). + size_t capacity() const { return capacity_; } + + private: + volatile Atomic32 pushed_count_; + volatile Atomic32 popped_count_; + size_t capacity_; + rtc::scoped_ptr data_; + DISALLOW_COPY_AND_ASSIGN(FixedSizeLockFreeQueue); +}; + +} + +#endif // WEBRTC_BASE_ATOMICOPS_H_ diff --git a/webrtc/base/atomicops_unittest.cc b/webrtc/base/atomicops_unittest.cc new file mode 100644 index 000000000..5152c4de6 --- /dev/null +++ b/webrtc/base/atomicops_unittest.cc @@ -0,0 +1,79 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if !defined(__arm__) +// For testing purposes, define faked versions of the atomic operations +#include "webrtc/base/basictypes.h" +namespace rtc { +typedef uint32 Atomic32; +static inline void MemoryBarrier() { } +static inline void AtomicIncrement(volatile Atomic32* ptr) { + *ptr = *ptr + 1; +} +} +#define SKIP_ATOMIC_CHECK +#endif + +#include "webrtc/base/atomicops.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" + +TEST(FixedSizeLockFreeQueueTest, TestDefaultConstruct) { + rtc::FixedSizeLockFreeQueue queue; + EXPECT_EQ(0u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + EXPECT_FALSE(queue.PushBack(1)); + int val; + EXPECT_FALSE(queue.PopFront(&val)); +} + +TEST(FixedSizeLockFreeQueueTest, TestConstruct) { + rtc::FixedSizeLockFreeQueue queue(5); + EXPECT_EQ(5u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + int val; + EXPECT_FALSE(queue.PopFront(&val)); +} + +TEST(FixedSizeLockFreeQueueTest, TestPushPop) { + rtc::FixedSizeLockFreeQueue queue(2); + EXPECT_EQ(2u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + EXPECT_TRUE(queue.PushBack(1)); + EXPECT_EQ(1u, queue.Size()); + EXPECT_TRUE(queue.PushBack(2)); + EXPECT_EQ(2u, queue.Size()); + EXPECT_FALSE(queue.PushBack(3)); + EXPECT_EQ(2u, queue.Size()); + int val; + EXPECT_TRUE(queue.PopFront(&val)); + EXPECT_EQ(1, val); + EXPECT_EQ(1u, queue.Size()); + EXPECT_TRUE(queue.PopFront(&val)); + EXPECT_EQ(2, val); + EXPECT_EQ(0u, queue.Size()); + EXPECT_FALSE(queue.PopFront(&val)); + EXPECT_EQ(0u, queue.Size()); +} + +TEST(FixedSizeLockFreeQueueTest, TestResize) { + rtc::FixedSizeLockFreeQueue queue(2); + EXPECT_EQ(2u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + EXPECT_TRUE(queue.PushBack(1)); + EXPECT_EQ(1u, queue.Size()); + + queue.ClearAndResizeUnsafe(5); + EXPECT_EQ(5u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + int val; + EXPECT_FALSE(queue.PopFront(&val)); +} diff --git a/webrtc/base/autodetectproxy.cc b/webrtc/base/autodetectproxy.cc new file mode 100644 index 000000000..bc54b9638 --- /dev/null +++ b/webrtc/base/autodetectproxy.cc @@ -0,0 +1,282 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/autodetectproxy.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/nethelpers.h" + +namespace rtc { + +static const ProxyType TEST_ORDER[] = { + PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN +}; + +static const int kSavedStringLimit = 128; + +static void SaveStringToStack(char *dst, + const std::string &src, + size_t dst_size) { + strncpy(dst, src.c_str(), dst_size - 1); + dst[dst_size - 1] = '\0'; +} + +AutoDetectProxy::AutoDetectProxy(const std::string& user_agent) + : agent_(user_agent), resolver_(NULL), socket_(NULL), next_(0) { +} + +AutoDetectProxy::~AutoDetectProxy() { + if (resolver_) { + resolver_->Destroy(false); + } +} + +void AutoDetectProxy::DoWork() { + // TODO: Try connecting to server_url without proxy first here? + if (!server_url_.empty()) { + LOG(LS_INFO) << "GetProxySettingsForUrl(" << server_url_ << ") - start"; + GetProxyForUrl(agent_.c_str(), server_url_.c_str(), &proxy_); + LOG(LS_INFO) << "GetProxySettingsForUrl - stop"; + } + Url url(proxy_.address.HostAsURIString()); + if (url.valid()) { + LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host"; + proxy_.address.SetIP(url.host()); + } + LOG(LS_INFO) << "AutoDetectProxy found proxy at " << proxy_.address; + if (proxy_.type == PROXY_UNKNOWN) { + LOG(LS_INFO) << "AutoDetectProxy initiating proxy classification"; + Next(); + // Process I/O until Stop() + Thread::Current()->ProcessMessages(kForever); + // Clean up the autodetect socket, from the thread that created it + delete socket_; + } + // TODO: If we found a proxy, try to use it to verify that it + // works by sending a request to server_url. This could either be + // done here or by the HttpPortAllocator. +} + +void AutoDetectProxy::OnMessage(Message *msg) { + if (MSG_UNRESOLVABLE == msg->message_id) { + // If we can't resolve the proxy, skip straight to failure. + Complete(PROXY_UNKNOWN); + } else if (MSG_TIMEOUT == msg->message_id) { + OnCloseEvent(socket_, ETIMEDOUT); + } else { + // This must be the ST_MSG_WORKER_DONE message that deletes the + // AutoDetectProxy object. We have observed crashes within this stack that + // seem to be highly reproducible for a small subset of users and thus are + // probably correlated with a specific proxy setting, so copy potentially + // relevant information onto the stack to make it available in Windows + // minidumps. + + // Save the user agent and the number of auto-detection passes that we + // needed. + char agent[kSavedStringLimit]; + SaveStringToStack(agent, agent_, sizeof agent); + + int next = next_; + + // Now the detected proxy config (minus the password field, which could be + // sensitive). + ProxyType type = proxy().type; + + char address_hostname[kSavedStringLimit]; + SaveStringToStack(address_hostname, + proxy().address.hostname(), + sizeof address_hostname); + + IPAddress address_ip = proxy().address.ipaddr(); + + uint16 address_port = proxy().address.port(); + + char autoconfig_url[kSavedStringLimit]; + SaveStringToStack(autoconfig_url, + proxy().autoconfig_url, + sizeof autoconfig_url); + + bool autodetect = proxy().autodetect; + + char bypass_list[kSavedStringLimit]; + SaveStringToStack(bypass_list, proxy().bypass_list, sizeof bypass_list); + + char username[kSavedStringLimit]; + SaveStringToStack(username, proxy().username, sizeof username); + + SignalThread::OnMessage(msg); + + // Log the gathered data at a log level that will never actually be enabled + // so that the compiler is forced to retain the data on the stack. + LOG(LS_SENSITIVE) << agent << " " << next << " " << type << " " + << address_hostname << " " << address_ip << " " + << address_port << " " << autoconfig_url << " " + << autodetect << " " << bypass_list << " " << username; + } +} + +void AutoDetectProxy::OnResolveResult(AsyncResolverInterface* resolver) { + if (resolver != resolver_) { + return; + } + int error = resolver_->GetError(); + if (error == 0) { + LOG(LS_VERBOSE) << "Resolved " << proxy_.address << " to " + << resolver_->address(); + proxy_.address = resolver_->address(); + if (!DoConnect()) { + Thread::Current()->Post(this, MSG_TIMEOUT); + } + } else { + LOG(LS_INFO) << "Failed to resolve " << resolver_->address(); + resolver_->Destroy(false); + resolver_ = NULL; + proxy_.address = SocketAddress(); + Thread::Current()->Post(this, MSG_UNRESOLVABLE); + } +} + +void AutoDetectProxy::Next() { + if (TEST_ORDER[next_] >= PROXY_UNKNOWN) { + Complete(PROXY_UNKNOWN); + return; + } + + LOG(LS_VERBOSE) << "AutoDetectProxy connecting to " + << proxy_.address.ToSensitiveString(); + + if (socket_) { + Thread::Current()->Clear(this, MSG_TIMEOUT); + Thread::Current()->Clear(this, MSG_UNRESOLVABLE); + socket_->Close(); + Thread::Current()->Dispose(socket_); + socket_ = NULL; + } + int timeout = 2000; + if (proxy_.address.IsUnresolvedIP()) { + // Launch an asyncresolver. This thread will spin waiting for it. + timeout += 2000; + if (!resolver_) { + resolver_ = new AsyncResolver(); + } + resolver_->SignalDone.connect(this, &AutoDetectProxy::OnResolveResult); + resolver_->Start(proxy_.address); + } else { + if (!DoConnect()) { + Thread::Current()->Post(this, MSG_TIMEOUT); + return; + } + } + Thread::Current()->PostDelayed(timeout, this, MSG_TIMEOUT); +} + +bool AutoDetectProxy::DoConnect() { + if (resolver_) { + resolver_->Destroy(false); + resolver_ = NULL; + } + socket_ = + Thread::Current()->socketserver()->CreateAsyncSocket( + proxy_.address.family(), SOCK_STREAM); + if (!socket_) { + LOG(LS_VERBOSE) << "Unable to create socket for " << proxy_.address; + return false; + } + socket_->SignalConnectEvent.connect(this, &AutoDetectProxy::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &AutoDetectProxy::OnReadEvent); + socket_->SignalCloseEvent.connect(this, &AutoDetectProxy::OnCloseEvent); + socket_->Connect(proxy_.address); + return true; +} + +void AutoDetectProxy::Complete(ProxyType type) { + Thread::Current()->Clear(this, MSG_TIMEOUT); + Thread::Current()->Clear(this, MSG_UNRESOLVABLE); + if (socket_) { + socket_->Close(); + } + + proxy_.type = type; + LoggingSeverity sev = (proxy_.type == PROXY_UNKNOWN) ? LS_ERROR : LS_INFO; + LOG_V(sev) << "AutoDetectProxy detected " + << proxy_.address.ToSensitiveString() + << " as type " << proxy_.type; + + Thread::Current()->Quit(); +} + +void AutoDetectProxy::OnConnectEvent(AsyncSocket * socket) { + std::string probe; + + switch (TEST_ORDER[next_]) { + case PROXY_HTTPS: + probe.assign("CONNECT www.google.com:443 HTTP/1.0\r\n" + "User-Agent: "); + probe.append(agent_); + probe.append("\r\n" + "Host: www.google.com\r\n" + "Content-Length: 0\r\n" + "Proxy-Connection: Keep-Alive\r\n" + "\r\n"); + break; + case PROXY_SOCKS5: + probe.assign("\005\001\000", 3); + break; + default: + ASSERT(false); + return; + } + + LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_] + << " sending " << probe.size() << " bytes"; + socket_->Send(probe.data(), probe.size()); +} + +void AutoDetectProxy::OnReadEvent(AsyncSocket * socket) { + char data[257]; + int len = socket_->Recv(data, 256); + if (len > 0) { + data[len] = 0; + LOG(LS_VERBOSE) << "AutoDetectProxy read " << len << " bytes"; + } + + switch (TEST_ORDER[next_]) { + case PROXY_HTTPS: + if ((len >= 2) && (data[0] == '\x05')) { + Complete(PROXY_SOCKS5); + return; + } + if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) { + Complete(PROXY_HTTPS); + return; + } + break; + case PROXY_SOCKS5: + if ((len >= 2) && (data[0] == '\x05')) { + Complete(PROXY_SOCKS5); + return; + } + break; + default: + ASSERT(false); + return; + } + + ++next_; + Next(); +} + +void AutoDetectProxy::OnCloseEvent(AsyncSocket * socket, int error) { + LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error; + ++next_; + Next(); +} + +} // namespace rtc diff --git a/webrtc/base/autodetectproxy.h b/webrtc/base/autodetectproxy.h new file mode 100644 index 000000000..45e9c40be --- /dev/null +++ b/webrtc/base/autodetectproxy.h @@ -0,0 +1,90 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_AUTODETECTPROXY_H_ +#define WEBRTC_BASE_AUTODETECTPROXY_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/proxydetect.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/signalthread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// AutoDetectProxy +/////////////////////////////////////////////////////////////////////////////// + +class AsyncResolverInterface; +class AsyncSocket; + +class AutoDetectProxy : public SignalThread { + public: + explicit AutoDetectProxy(const std::string& user_agent); + + const ProxyInfo& proxy() const { return proxy_; } + + void set_server_url(const std::string& url) { + server_url_ = url; + } + void set_proxy(const SocketAddress& proxy) { + proxy_.type = PROXY_UNKNOWN; + proxy_.address = proxy; + } + void set_auth_info(bool use_auth, const std::string& username, + const CryptString& password) { + if (use_auth) { + proxy_.username = username; + proxy_.password = password; + } + } + // Default implementation of GetProxySettingsForUrl. Override for special + // implementation. + virtual bool GetProxyForUrl(const char* agent, const char* url, + rtc::ProxyInfo* proxy) { + return GetProxySettingsForUrl(agent, url, proxy, true); + } + enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE, + MSG_UNRESOLVABLE, + ADP_MSG_FIRST_AVAILABLE}; + + protected: + virtual ~AutoDetectProxy(); + + // SignalThread Interface + virtual void DoWork(); + virtual void OnMessage(Message *msg); + + void Next(); + void Complete(ProxyType type); + + void OnConnectEvent(AsyncSocket * socket); + void OnReadEvent(AsyncSocket * socket); + void OnCloseEvent(AsyncSocket * socket, int error); + void OnResolveResult(AsyncResolverInterface* resolver); + bool DoConnect(); + + private: + std::string agent_; + std::string server_url_; + ProxyInfo proxy_; + AsyncResolverInterface* resolver_; + AsyncSocket* socket_; + int next_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(AutoDetectProxy); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_AUTODETECTPROXY_H_ diff --git a/webrtc/base/autodetectproxy_unittest.cc b/webrtc/base/autodetectproxy_unittest.cc new file mode 100644 index 000000000..80f220f2f --- /dev/null +++ b/webrtc/base/autodetectproxy_unittest.cc @@ -0,0 +1,131 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/autodetectproxy.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" + +namespace rtc { + +static const char kUserAgent[] = ""; +static const char kPath[] = "/"; +static const char kHost[] = "relay.google.com"; +static const uint16 kPort = 443; +static const bool kSecure = true; +// At most, AutoDetectProxy should take ~6 seconds. Each connect step is +// allotted 2 seconds, with the initial resolution + connect given an +// extra 2 seconds. The slowest case is: +// 1) Resolution + HTTPS takes full 4 seconds and fails (but resolution +// succeeds). +// 2) SOCKS5 takes the full 2 seconds. +// Socket creation time seems unbounded, and has been observed to take >1 second +// on a linux machine under load. As such, we allow for 10 seconds for timeout, +// though could still end up with some flakiness. +static const int kTimeoutMs = 10000; + +class AutoDetectProxyTest : public testing::Test, public sigslot::has_slots<> { + public: + AutoDetectProxyTest() : auto_detect_proxy_(NULL), done_(false) {} + + protected: + bool Create(const std::string &user_agent, + const std::string &path, + const std::string &host, + uint16 port, + bool secure, + bool startnow) { + auto_detect_proxy_ = new AutoDetectProxy(user_agent); + EXPECT_TRUE(auto_detect_proxy_ != NULL); + if (!auto_detect_proxy_) { + return false; + } + Url host_url(path, host, port); + host_url.set_secure(secure); + auto_detect_proxy_->set_server_url(host_url.url()); + auto_detect_proxy_->SignalWorkDone.connect( + this, + &AutoDetectProxyTest::OnWorkDone); + if (startnow) { + auto_detect_proxy_->Start(); + } + return true; + } + + bool Run(int timeout_ms) { + EXPECT_TRUE_WAIT(done_, timeout_ms); + return done_; + } + + void SetProxy(const SocketAddress& proxy) { + auto_detect_proxy_->set_proxy(proxy); + } + + void Start() { + auto_detect_proxy_->Start(); + } + + void TestCopesWithProxy(const SocketAddress& proxy) { + // Tests that at least autodetect doesn't crash for a given proxy address. + ASSERT_TRUE(Create(kUserAgent, + kPath, + kHost, + kPort, + kSecure, + false)); + SetProxy(proxy); + Start(); + ASSERT_TRUE(Run(kTimeoutMs)); + } + + private: + void OnWorkDone(rtc::SignalThread *thread) { + AutoDetectProxy *auto_detect_proxy = + static_cast(thread); + EXPECT_TRUE(auto_detect_proxy == auto_detect_proxy_); + auto_detect_proxy_ = NULL; + auto_detect_proxy->Release(); + done_ = true; + } + + AutoDetectProxy *auto_detect_proxy_; + bool done_; +}; + +TEST_F(AutoDetectProxyTest, TestDetectUnresolvedProxy) { + TestCopesWithProxy(rtc::SocketAddress("localhost", 9999)); +} + +TEST_F(AutoDetectProxyTest, TestDetectUnresolvableProxy) { + TestCopesWithProxy(rtc::SocketAddress("invalid", 9999)); +} + +TEST_F(AutoDetectProxyTest, TestDetectIPv6Proxy) { + TestCopesWithProxy(rtc::SocketAddress("::1", 9999)); +} + +TEST_F(AutoDetectProxyTest, TestDetectIPv4Proxy) { + TestCopesWithProxy(rtc::SocketAddress("127.0.0.1", 9999)); +} + +// Test that proxy detection completes successfully. (Does not actually verify +// the correct detection result since we don't know what proxy to expect on an +// arbitrary machine.) +TEST_F(AutoDetectProxyTest, TestProxyDetection) { + ASSERT_TRUE(Create(kUserAgent, + kPath, + kHost, + kPort, + kSecure, + true)); + ASSERT_TRUE(Run(kTimeoutMs)); +} + +} // namespace rtc diff --git a/webrtc/base/bandwidthsmoother.cc b/webrtc/base/bandwidthsmoother.cc new file mode 100644 index 000000000..0cbf3f3d1 --- /dev/null +++ b/webrtc/base/bandwidthsmoother.cc @@ -0,0 +1,84 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/bandwidthsmoother.h" + +#include + +namespace rtc { + +BandwidthSmoother::BandwidthSmoother(int initial_bandwidth_guess, + uint32 time_between_increase, + double percent_increase, + size_t samples_count_to_average, + double min_sample_count_percent) + : time_between_increase_(time_between_increase), + percent_increase_(rtc::_max(1.0, percent_increase)), + time_at_last_change_(0), + bandwidth_estimation_(initial_bandwidth_guess), + accumulator_(samples_count_to_average), + min_sample_count_percent_( + rtc::_min(1.0, + rtc::_max(0.0, min_sample_count_percent))) { +} + +// Samples a new bandwidth measurement +// returns true if the bandwidth estimation changed +bool BandwidthSmoother::Sample(uint32 sample_time, int bandwidth) { + if (bandwidth < 0) { + return false; + } + + accumulator_.AddSample(bandwidth); + + if (accumulator_.count() < static_cast( + accumulator_.max_count() * min_sample_count_percent_)) { + // We have not collected enough samples yet. + return false; + } + + // Replace bandwidth with the mean of sampled bandwidths. + const int mean_bandwidth = static_cast(accumulator_.ComputeMean()); + + if (mean_bandwidth < bandwidth_estimation_) { + time_at_last_change_ = sample_time; + bandwidth_estimation_ = mean_bandwidth; + return true; + } + + const int old_bandwidth_estimation = bandwidth_estimation_; + const double increase_threshold_d = percent_increase_ * bandwidth_estimation_; + if (increase_threshold_d > INT_MAX) { + // If bandwidth goes any higher we would overflow. + return false; + } + + const int increase_threshold = static_cast(increase_threshold_d); + if (mean_bandwidth < increase_threshold) { + time_at_last_change_ = sample_time; + // The value of bandwidth_estimation remains the same if we don't exceed + // percent_increase_ * bandwidth_estimation_ for at least + // time_between_increase_ time. + } else if (sample_time >= time_at_last_change_ + time_between_increase_) { + time_at_last_change_ = sample_time; + if (increase_threshold == 0) { + // Bandwidth_estimation_ must be zero. Assume a jump from zero to a + // positive bandwidth means we have regained connectivity. + bandwidth_estimation_ = mean_bandwidth; + } else { + bandwidth_estimation_ = increase_threshold; + } + } + // Else don't make a change. + + return old_bandwidth_estimation != bandwidth_estimation_; +} + +} // namespace rtc diff --git a/webrtc/base/bandwidthsmoother.h b/webrtc/base/bandwidthsmoother.h new file mode 100644 index 000000000..cf5a25dfa --- /dev/null +++ b/webrtc/base/bandwidthsmoother.h @@ -0,0 +1,59 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_BANDWIDTHSMOOTHER_H_ +#define WEBRTC_BASE_BANDWIDTHSMOOTHER_H_ + +#include "webrtc/base/rollingaccumulator.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +// The purpose of BandwidthSmoother is to smooth out bandwidth +// estimations so that 'trstate' messages can be triggered when we +// are "sure" there is sufficient bandwidth. To avoid frequent fluctuations, +// we take a slightly pessimistic view of our bandwidth. We only increase +// our estimation when we have sampled bandwidth measurements of values +// at least as large as the current estimation * percent_increase +// for at least time_between_increase time. If a sampled bandwidth +// is less than our current estimation we immediately decrease our estimation +// to that sampled value. +// We retain the initial bandwidth guess as our current bandwidth estimation +// until we have received (min_sample_count_percent * samples_count_to_average) +// number of samples. Min_sample_count_percent must be in range [0, 1]. +class BandwidthSmoother { + public: + BandwidthSmoother(int initial_bandwidth_guess, + uint32 time_between_increase, + double percent_increase, + size_t samples_count_to_average, + double min_sample_count_percent); + + // Samples a new bandwidth measurement. + // bandwidth is expected to be non-negative. + // returns true if the bandwidth estimation changed + bool Sample(uint32 sample_time, int bandwidth); + + int get_bandwidth_estimation() const { + return bandwidth_estimation_; + } + + private: + uint32 time_between_increase_; + double percent_increase_; + uint32 time_at_last_change_; + int bandwidth_estimation_; + RollingAccumulator accumulator_; + double min_sample_count_percent_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BANDWIDTHSMOOTHER_H_ diff --git a/webrtc/base/bandwidthsmoother_unittest.cc b/webrtc/base/bandwidthsmoother_unittest.cc new file mode 100644 index 000000000..132c0b13a --- /dev/null +++ b/webrtc/base/bandwidthsmoother_unittest.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/bandwidthsmoother.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +static const int kTimeBetweenIncrease = 10; +static const double kPercentIncrease = 1.1; +static const size_t kSamplesCountToAverage = 2; +static const double kMinSampleCountPercent = 1.0; + +TEST(BandwidthSmootherTest, TestSampleIncrease) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + int bandwidth_sample = 1000; + EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation()); + bandwidth_sample = + static_cast(bandwidth_sample * kPercentIncrease); + EXPECT_FALSE(mon.Sample(9, bandwidth_sample)); + EXPECT_TRUE(mon.Sample(10, bandwidth_sample)); + EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation()); + int next_expected_est = + static_cast(bandwidth_sample * kPercentIncrease); + bandwidth_sample *= 2; + EXPECT_TRUE(mon.Sample(20, bandwidth_sample)); + EXPECT_EQ(next_expected_est, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleIncreaseFromZero) { + BandwidthSmoother mon(0, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + const int kBandwidthSample = 1000; + EXPECT_EQ(0, mon.get_bandwidth_estimation()); + EXPECT_FALSE(mon.Sample(9, kBandwidthSample)); + EXPECT_TRUE(mon.Sample(10, kBandwidthSample)); + EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleDecrease) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + const int kBandwidthSample = 999; + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_FALSE(mon.Sample(1, kBandwidthSample)); + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_TRUE(mon.Sample(2, kBandwidthSample)); + EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleTooFewSamples) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + 10, // 10 samples. + 0.5); // 5 min samples. + + const int kBandwidthSample = 500; + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_FALSE(mon.Sample(1, kBandwidthSample)); + EXPECT_FALSE(mon.Sample(2, kBandwidthSample)); + EXPECT_FALSE(mon.Sample(3, kBandwidthSample)); + EXPECT_FALSE(mon.Sample(4, kBandwidthSample)); + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_TRUE(mon.Sample(5, kBandwidthSample)); + EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleRollover) { + const int kHugeBandwidth = 2000000000; // > INT_MAX/1.1 + BandwidthSmoother mon(kHugeBandwidth, + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + EXPECT_FALSE(mon.Sample(10, INT_MAX)); + EXPECT_FALSE(mon.Sample(11, INT_MAX)); + EXPECT_EQ(kHugeBandwidth, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleNegative) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + EXPECT_FALSE(mon.Sample(10, -1)); + EXPECT_FALSE(mon.Sample(11, -1)); + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); +} + +} // namespace rtc diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp new file mode 100644 index 000000000..a3cf7706e --- /dev/null +++ b/webrtc/base/base.gyp @@ -0,0 +1,708 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +{ + 'includes': [ '../build/common.gypi', ], + 'conditions': [ + ['os_posix == 1 and OS != "mac" and OS != "ios"', { + 'conditions': [ + ['sysroot!=""', { + 'variables': { + 'pkg-config': '../../../build/linux/pkg-config-wrapper "<(sysroot)" "<(target_arch)"', + }, + }, { + 'variables': { + 'pkg-config': 'pkg-config' + }, + }], + ], + }], + ], + 'targets': [ + { + 'target_name': 'webrtc_base', + 'type': 'static_library', + 'defines': [ + 'FEATURE_ENABLE_SSL', + 'GTEST_RELATIVE_PATH', + 'LOGGING=1', + 'USE_WEBRTC_DEV_BRANCH', + ], + 'sources': [ + 'asyncfile.cc', + 'asyncfile.h', + 'asynchttprequest.cc', + 'asynchttprequest.h', + 'asyncinvoker.cc', + 'asyncinvoker.h', + 'asyncpacketsocket.h', + 'asyncresolverinterface.h', + 'asyncsocket.cc', + 'asyncsocket.h', + 'asynctcpsocket.cc', + 'asynctcpsocket.h', + 'asyncudpsocket.cc', + 'asyncudpsocket.h', + 'atomicops.h', + 'autodetectproxy.cc', + 'autodetectproxy.h', + 'bandwidthsmoother.cc', + 'bandwidthsmoother.h', + 'base64.cc', + 'base64.h', + 'basicdefs.h', + 'basictypes.h', + 'bind.h', + 'buffer.h', + 'bytebuffer.cc', + 'bytebuffer.h', + 'byteorder.h', + 'callback.h', + 'checks.cc', + 'checks.h', + 'common.cc', + 'common.h', + 'constructormagic.h', + 'cpumonitor.cc', + 'cpumonitor.h', + 'crc32.cc', + 'crc32.h', + 'criticalsection.h', + 'cryptstring.h', + 'dbus.cc', + 'dbus.h', + 'diskcache.cc', + 'diskcache.h', + 'diskcache_win32.cc', + 'diskcache_win32.h', + 'event.cc', + 'event.h', + 'filelock.cc', + 'filelock.h', + 'fileutils.cc', + 'fileutils.h', + 'fileutils_mock.h', + 'firewallsocketserver.cc', + 'firewallsocketserver.h', + 'flags.cc', + 'flags.h', + 'gunit_prod.h', + 'helpers.cc', + 'helpers.h', + 'httpbase.cc', + 'httpbase.h', + 'httpclient.cc', + 'httpclient.h', + 'httpcommon-inl.h', + 'httpcommon.cc', + 'httpcommon.h', + 'httprequest.cc', + 'httprequest.h', + 'httpserver.cc', + 'httpserver.h', + 'ifaddrs-android.cc', + 'ifaddrs-android.h', + 'iosfilesystem.mm', + 'ipaddress.cc', + 'ipaddress.h', + 'json.cc', + 'json.h', + 'latebindingsymboltable.cc', + 'latebindingsymboltable.h', + 'libdbusglibsymboltable.cc', + 'libdbusglibsymboltable.h', + 'linux.cc', + 'linux.h', + 'linuxfdwalk.c', + 'linuxwindowpicker.cc', + 'linuxwindowpicker.h', + 'linked_ptr.h', + 'logging.cc', + 'logging.h', + 'macasyncsocket.cc', + 'macasyncsocket.h', + 'maccocoasocketserver.h', + 'maccocoasocketserver.mm', + 'maccocoathreadhelper.h', + 'maccocoathreadhelper.mm', + 'macconversion.cc', + 'macconversion.h', + 'macsocketserver.cc', + 'macsocketserver.h', + 'macutils.cc', + 'macutils.h', + 'macwindowpicker.cc', + 'macwindowpicker.h', + 'mathutils.h', + 'md5.cc', + 'md5.h', + 'md5digest.h', + 'messagedigest.cc', + 'messagedigest.h', + 'messagehandler.cc', + 'messagehandler.h', + 'messagequeue.cc', + 'messagequeue.h', + 'multipart.cc', + 'multipart.h', + 'natserver.cc', + 'natserver.h', + 'natsocketfactory.cc', + 'natsocketfactory.h', + 'nattypes.cc', + 'nattypes.h', + 'nethelpers.cc', + 'nethelpers.h', + 'network.cc', + 'network.h', + 'nssidentity.cc', + 'nssidentity.h', + 'nssstreamadapter.cc', + 'nssstreamadapter.h', + 'nullsocketserver.h', + 'openssl.h', + 'openssladapter.cc', + 'openssladapter.h', + 'openssldigest.cc', + 'openssldigest.h', + 'opensslidentity.cc', + 'opensslidentity.h', + 'opensslstreamadapter.cc', + 'opensslstreamadapter.h', + 'optionsfile.cc', + 'optionsfile.h', + 'pathutils.cc', + 'pathutils.h', + 'physicalsocketserver.cc', + 'physicalsocketserver.h', + 'posix.cc', + 'posix.h', + 'profiler.cc', + 'profiler.h', + 'proxydetect.cc', + 'proxydetect.h', + 'proxyinfo.cc', + 'proxyinfo.h', + 'proxyserver.cc', + 'proxyserver.h', + 'ratelimiter.cc', + 'ratelimiter.h', + 'ratetracker.cc', + 'ratetracker.h', + 'refcount.h', + 'referencecountedsingletonfactory.h', + 'rollingaccumulator.h', + 'schanneladapter.cc', + 'schanneladapter.h', + 'scoped_autorelease_pool.h', + 'scoped_autorelease_pool.mm', + 'scoped_ptr.h', + 'scoped_ref_ptr.h', + 'scopedptrcollection.h', + 'sec_buffer.h', + 'sha1.cc', + 'sha1.h', + 'sha1digest.h', + 'sharedexclusivelock.cc', + 'sharedexclusivelock.h', + 'signalthread.cc', + 'signalthread.h', + 'sigslot.h', + 'sigslotrepeater.h', + 'socket.h', + 'socketadapters.cc', + 'socketadapters.h', + 'socketaddress.cc', + 'socketaddress.h', + 'socketaddresspair.cc', + 'socketaddresspair.h', + 'socketfactory.h', + 'socketpool.cc', + 'socketpool.h', + 'socketserver.h', + 'socketstream.cc', + 'socketstream.h', + 'ssladapter.cc', + 'ssladapter.h', + 'sslconfig.h', + 'sslfingerprint.cc', + 'sslfingerprint.h', + 'sslidentity.cc', + 'sslidentity.h', + 'sslroots.h', + 'sslsocketfactory.cc', + 'sslsocketfactory.h', + 'sslstreamadapter.cc', + 'sslstreamadapter.h', + 'sslstreamadapterhelper.cc', + 'sslstreamadapterhelper.h', + 'stream.cc', + 'stream.h', + 'stringdigest.h', + 'stringencode.cc', + 'stringencode.h', + 'stringutils.cc', + 'stringutils.h', + 'systeminfo.cc', + 'systeminfo.h', + 'task.cc', + 'task.h', + 'taskparent.cc', + 'taskparent.h', + 'taskrunner.cc', + 'taskrunner.h', + 'testclient.cc', + 'testclient.h', + 'thread.cc', + 'thread.h', + 'timeutils.cc', + 'timeutils.h', + 'timing.cc', + 'timing.h', + 'transformadapter.cc', + 'transformadapter.h', + 'unixfilesystem.cc', + 'unixfilesystem.h', + 'urlencode.cc', + 'urlencode.h', + 'versionparsing.cc', + 'versionparsing.h', + 'virtualsocketserver.cc', + 'virtualsocketserver.h', + 'win32.cc', + 'win32.h', + 'win32filesystem.cc', + 'win32filesystem.h', + 'win32regkey.cc', + 'win32regkey.h', + 'win32securityerrors.cc', + 'win32socketinit.cc', + 'win32socketinit.h', + 'win32socketserver.cc', + 'win32socketserver.h', + 'win32window.cc', + 'win32window.h', + 'win32windowpicker.cc', + 'win32windowpicker.h', + 'window.h', + 'windowpicker.h', + 'windowpickerfactory.h', + 'winfirewall.cc', + 'winfirewall.h', + 'winping.cc', + 'winping.h', + 'worker.cc', + 'worker.h', + '../overrides/webrtc/base/basictypes.h', + '../overrides/webrtc/base/constructormagic.h', + '../overrides/webrtc/base/logging.cc', + '../overrides/webrtc/base/logging.h', + '../overrides/webrtc/base/win32socketinit.cc', + ], + # TODO(henrike): issue 3307, make webrtc_base build without disabling + # these flags. + 'cflags!': [ + '-Wextra', + '-Wall', + ], + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'direct_dependent_settings': { + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'defines': [ + 'FEATURE_ENABLE_SSL', + 'GTEST_RELATIVE_PATH', + ], + }, + 'include_dirs': [ + '../../third_party/jsoncpp/overrides/include', + '../../third_party/jsoncpp/source/include', + ], + 'conditions': [ + ['build_with_chromium==1', { + 'include_dirs': [ + '../overrides', + '../../openssl/openssl/include', + ], + 'sources!': [ + 'asyncinvoker.cc', + 'asyncinvoker.h', + 'asyncinvoker-inl.h', + 'asyncresolverinterface.h', + 'atomicops.h', + 'bandwidthsmoother.cc', + 'bandwidthsmoother.h', + 'basictypes.h', + 'bind.h', + 'bind.h.pump', + 'buffer.h', + 'callback.h', + 'callback.h.pump', + 'constructormagic.h', + 'dbus.cc', + 'dbus.h', + 'diskcache_win32.cc', + 'diskcache_win32.h', + 'fakecpumonitor.h', + 'fakenetwork.h', + 'fakesslidentity.h', + 'faketaskrunner.h', + 'filelock.cc', + 'filelock.h', + 'fileutils_mock.h', + 'genericslot.h', + 'genericslot.h.pump', + 'httpserver.cc', + 'httpserver.h', + 'json.cc', + 'json.h', + 'latebindingsymboltable.cc', + 'latebindingsymboltable.cc.def', + 'latebindingsymboltable.h', + 'latebindingsymboltable.h.def', + 'libdbusglibsymboltable.cc', + 'libdbusglibsymboltable.h', + 'linuxfdwalk.c', + 'linuxfdwalk.h', + 'linuxwindowpicker.cc', + 'linuxwindowpicker.h', + 'logging.cc', + 'logging.h', + 'macasyncsocket.cc', + 'macasyncsocket.h', + 'maccocoasocketserver.h', + 'maccocoasocketserver.mm', + 'macsocketserver.cc', + 'macsocketserver.h', + 'macwindowpicker.cc', + 'macwindowpicker.h', + 'mathutils.h', + 'multipart.cc', + 'multipart.h', + 'natserver.cc', + 'natserver.h', + 'natserver_main.cc', + 'natsocketfactory.cc', + 'natsocketfactory.h', + 'nattypes.cc', + 'nattypes.h', + 'openssl.h', + 'optionsfile.cc', + 'optionsfile.h', + 'posix.cc', + 'posix.h', + 'profiler.cc', + 'profiler.h', + 'proxyserver.cc', + 'proxyserver.h', + 'refcount.h', + 'referencecountedsingletonfactory.h', + 'rollingaccumulator.h', + 'safe_conversions.h', + 'safe_conversions_impl.h', + 'scopedptrcollection.h', + 'scoped_ref_ptr.h', + 'sec_buffer.h', + 'sharedexclusivelock.cc', + 'sharedexclusivelock.h', + 'sslconfig.h', + 'sslroots.h', + 'stringdigest.h', + 'testbase64.h', + 'testclient.cc', + 'testclient.h', + 'testechoserver.h', + 'testutils.h', + 'transformadapter.cc', + 'transformadapter.h', + 'versionparsing.cc', + 'versionparsing.h', + 'virtualsocketserver.cc', + 'virtualsocketserver.h', + 'win32regkey.cc', + 'win32regkey.h', + 'win32socketinit.cc', + 'win32socketinit.h', + 'win32socketserver.cc', + 'win32socketserver.h', + 'win32toolhelp.h', + 'window.h', + 'windowpickerfactory.h', + 'windowpicker.h', + ], + 'defines': [ + 'NO_MAIN_THREAD_WRAPPING', + 'SSL_USE_NSS', + ], + 'direct_dependent_settings': { + 'defines': [ + 'NO_MAIN_THREAD_WRAPPING', + 'SSL_USE_NSS', + ], + }, + }, { + 'dependencies': [ + '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp', + ], + 'sources!': [ + '../overrides/webrtc/base/basictypes.h', + '../overrides/webrtc/base/constructormagic.h', + '../overrides/webrtc/base/win32socketinit.cc', + '../overrides/webrtc/base/logging.cc', + '../overrides/webrtc/base/logging.h', + ], + }], + ['use_openssl==1', { + 'defines': [ + 'SSL_USE_OPENSSL', + 'HAVE_OPENSSL_SSL_H', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SSL_USE_OPENSSL', + 'HAVE_OPENSSL_SSL_H', + ], + }, + 'dependencies': [ + '<(DEPTH)/third_party/openssl/openssl.gyp:openssl', + ], + }, { + 'defines': [ + 'SSL_USE_NSS', + 'HAVE_NSS_SSL_H', + 'SSL_USE_NSS_RNG', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SSL_USE_NSS', + 'HAVE_NSS_SSL_H', + 'SSL_USE_NSS_RNG', + ], + }, + }], + ['OS == "android"', { + 'defines': [ + 'HAVE_OPENSSL_SSL_H' + ], + 'direct_dependent_settings': { + 'defines': [ + 'HAVE_OPENSSL_SSL_H' + ], + }, + 'link_settings': { + 'libraries': [ + '-llog', + '-lGLESv2', + ], + }, + }, { + 'defines': [ + 'HAVE_NSS_SSL_H' + 'SSL_USE_NSS_RNG', + ], + 'direct_dependent_settings': { + 'defines': [ + 'HAVE_NSS_SSL_H' + 'SSL_USE_NSS_RNG', + ], + }, + 'sources!': [ + 'ifaddrs-android.cc', + 'ifaddrs-android.h', + ], + }], + ['OS=="ios"', { + 'all_dependent_settings': { + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '-framework Foundation', + '-framework IOKit', + '-framework Security', + '-framework SystemConfiguration', + '-framework UIKit', + ], + }, + }, + 'dependencies': [ + '<(DEPTH)/net/third_party/nss/ssl.gyp:libssl', + ], + }], + ['OS=="linux"', { + 'link_settings': { + 'libraries': [ + '-lcrypto', + '-ldl', + '-lrt', + '-lXext', + '-lX11', + '-lXcomposite', + '-lXrender', + ' + +#include "webrtc/base/common.h" + +using std::vector; + +namespace rtc { + +static const char kPad = '='; +static const unsigned char pd = 0xFD; // Padding +static const unsigned char sp = 0xFE; // Whitespace +static const unsigned char il = 0xFF; // Illegal base64 character + +const char Base64::Base64Table[] = +// 0000000000111111111122222222223333333333444444444455555555556666 +// 0123456789012345678901234567890123456789012345678901234567890123 + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Decode Table gives the index of any valid base64 character in the +// Base64 table +// 65 == A, 97 == a, 48 == 0, 43 == +, 47 == / + +const unsigned char Base64::DecodeTable[] = { +// 0 1 2 3 4 5 6 7 8 9 + il,il,il,il,il,il,il,il,il,sp, // 0 - 9 + sp,sp,sp,sp,il,il,il,il,il,il, // 10 - 19 + il,il,il,il,il,il,il,il,il,il, // 20 - 29 + il,il,sp,il,il,il,il,il,il,il, // 30 - 39 + il,il,il,62,il,il,il,63,52,53, // 40 - 49 + 54,55,56,57,58,59,60,61,il,il, // 50 - 59 + il,pd,il,il,il, 0, 1, 2, 3, 4, // 60 - 69 + 5, 6, 7, 8, 9,10,11,12,13,14, // 70 - 79 + 15,16,17,18,19,20,21,22,23,24, // 80 - 89 + 25,il,il,il,il,il,il,26,27,28, // 90 - 99 + 29,30,31,32,33,34,35,36,37,38, // 100 - 109 + 39,40,41,42,43,44,45,46,47,48, // 110 - 119 + 49,50,51,il,il,il,il,il,il,il, // 120 - 129 + il,il,il,il,il,il,il,il,il,il, // 130 - 139 + il,il,il,il,il,il,il,il,il,il, // 140 - 149 + il,il,il,il,il,il,il,il,il,il, // 150 - 159 + il,il,il,il,il,il,il,il,il,il, // 160 - 169 + il,il,il,il,il,il,il,il,il,il, // 170 - 179 + il,il,il,il,il,il,il,il,il,il, // 180 - 189 + il,il,il,il,il,il,il,il,il,il, // 190 - 199 + il,il,il,il,il,il,il,il,il,il, // 200 - 209 + il,il,il,il,il,il,il,il,il,il, // 210 - 219 + il,il,il,il,il,il,il,il,il,il, // 220 - 229 + il,il,il,il,il,il,il,il,il,il, // 230 - 239 + il,il,il,il,il,il,il,il,il,il, // 240 - 249 + il,il,il,il,il,il // 250 - 255 +}; + +bool Base64::IsBase64Char(char ch) { + return (('A' <= ch) && (ch <= 'Z')) || + (('a' <= ch) && (ch <= 'z')) || + (('0' <= ch) && (ch <= '9')) || + (ch == '+') || (ch == '/'); +} + +bool Base64::GetNextBase64Char(char ch, char* next_ch) { + if (next_ch == NULL) { + return false; + } + const char* p = strchr(Base64Table, ch); + if (!p) + return false; + ++p; + *next_ch = (*p) ? *p : Base64Table[0]; + return true; +} + +bool Base64::IsBase64Encoded(const std::string& str) { + for (size_t i = 0; i < str.size(); ++i) { + if (!IsBase64Char(str.at(i))) + return false; + } + return true; +} + +void Base64::EncodeFromArray(const void* data, size_t len, + std::string* result) { + ASSERT(NULL != result); + result->clear(); + result->resize(((len + 2) / 3) * 4); + const unsigned char* byte_data = static_cast(data); + + unsigned char c; + size_t i = 0; + size_t dest_ix = 0; + while (i < len) { + c = (byte_data[i] >> 2) & 0x3f; + (*result)[dest_ix++] = Base64Table[c]; + + c = (byte_data[i] << 4) & 0x3f; + if (++i < len) { + c |= (byte_data[i] >> 4) & 0x0f; + } + (*result)[dest_ix++] = Base64Table[c]; + + if (i < len) { + c = (byte_data[i] << 2) & 0x3f; + if (++i < len) { + c |= (byte_data[i] >> 6) & 0x03; + } + (*result)[dest_ix++] = Base64Table[c]; + } else { + (*result)[dest_ix++] = kPad; + } + + if (i < len) { + c = byte_data[i] & 0x3f; + (*result)[dest_ix++] = Base64Table[c]; + ++i; + } else { + (*result)[dest_ix++] = kPad; + } + } +} + +size_t Base64::GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads, + const char* data, size_t len, size_t* dpos, + unsigned char qbuf[4], bool* padded) +{ + size_t byte_len = 0, pad_len = 0, pad_start = 0; + for (; (byte_len < 4) && (*dpos < len); ++*dpos) { + qbuf[byte_len] = DecodeTable[static_cast(data[*dpos])]; + if ((il == qbuf[byte_len]) || (illegal_pads && (pd == qbuf[byte_len]))) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore illegal characters + } else if (sp == qbuf[byte_len]) { + if (parse_flags == DO_PARSE_STRICT) + break; + // Ignore spaces + } else if (pd == qbuf[byte_len]) { + if (byte_len < 2) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore unexpected padding + } else if (byte_len + pad_len >= 4) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore extra pads + } else { + if (1 == ++pad_len) { + pad_start = *dpos; + } + } + } else { + if (pad_len > 0) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore pads which are followed by data + pad_len = 0; + } + ++byte_len; + } + } + for (size_t i = byte_len; i < 4; ++i) { + qbuf[i] = 0; + } + if (4 == byte_len + pad_len) { + *padded = true; + } else { + *padded = false; + if (pad_len) { + // Roll back illegal padding + *dpos = pad_start; + } + } + return byte_len; +} + +bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + std::string* result, size_t* data_used) { + return DecodeFromArrayTemplate( + data, len, flags, result, data_used); +} + +bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + vector* result, size_t* data_used) { + return DecodeFromArrayTemplate >(data, len, flags, result, + data_used); +} + +template +bool Base64::DecodeFromArrayTemplate(const char* data, size_t len, + DecodeFlags flags, T* result, + size_t* data_used) +{ + ASSERT(NULL != result); + ASSERT(flags <= (DO_PARSE_MASK | DO_PAD_MASK | DO_TERM_MASK)); + + const DecodeFlags parse_flags = flags & DO_PARSE_MASK; + const DecodeFlags pad_flags = flags & DO_PAD_MASK; + const DecodeFlags term_flags = flags & DO_TERM_MASK; + ASSERT(0 != parse_flags); + ASSERT(0 != pad_flags); + ASSERT(0 != term_flags); + + result->clear(); + result->reserve(len); + + size_t dpos = 0; + bool success = true, padded; + unsigned char c, qbuf[4]; + while (dpos < len) { + size_t qlen = GetNextQuantum(parse_flags, (DO_PAD_NO == pad_flags), + data, len, &dpos, qbuf, &padded); + c = (qbuf[0] << 2) | ((qbuf[1] >> 4) & 0x3); + if (qlen >= 2) { + result->push_back(c); + c = ((qbuf[1] << 4) & 0xf0) | ((qbuf[2] >> 2) & 0xf); + if (qlen >= 3) { + result->push_back(c); + c = ((qbuf[2] << 6) & 0xc0) | qbuf[3]; + if (qlen >= 4) { + result->push_back(c); + c = 0; + } + } + } + if (qlen < 4) { + if ((DO_TERM_ANY != term_flags) && (0 != c)) { + success = false; // unused bits + } + if ((DO_PAD_YES == pad_flags) && !padded) { + success = false; // expected padding + } + break; + } + } + if ((DO_TERM_BUFFER == term_flags) && (dpos != len)) { + success = false; // unused chars + } + if (data_used) { + *data_used = dpos; + } + return success; +} + +} // namespace rtc diff --git a/webrtc/base/base64.h b/webrtc/base/base64.h new file mode 100644 index 000000000..d5a7dd84c --- /dev/null +++ b/webrtc/base/base64.h @@ -0,0 +1,104 @@ + +//********************************************************************* +//* C_Base64 - a simple base64 encoder and decoder. +//* +//* Copyright (c) 1999, Bob Withers - bwit@pobox.com +//* +//* This code may be freely used for any purpose, either personal +//* or commercial, provided the authors copyright notice remains +//* intact. +//********************************************************************* + +#ifndef WEBRTC_BASE_BASE64_H__ +#define WEBRTC_BASE_BASE64_H__ + +#include +#include + +namespace rtc { + +class Base64 +{ +public: + enum DecodeOption { + DO_PARSE_STRICT = 1, // Parse only base64 characters + DO_PARSE_WHITE = 2, // Parse only base64 and whitespace characters + DO_PARSE_ANY = 3, // Parse all characters + DO_PARSE_MASK = 3, + + DO_PAD_YES = 4, // Padding is required + DO_PAD_ANY = 8, // Padding is optional + DO_PAD_NO = 12, // Padding is disallowed + DO_PAD_MASK = 12, + + DO_TERM_BUFFER = 16, // Must termiante at end of buffer + DO_TERM_CHAR = 32, // May terminate at any character boundary + DO_TERM_ANY = 48, // May terminate at a sub-character bit offset + DO_TERM_MASK = 48, + + // Strictest interpretation + DO_STRICT = DO_PARSE_STRICT | DO_PAD_YES | DO_TERM_BUFFER, + + DO_LAX = DO_PARSE_ANY | DO_PAD_ANY | DO_TERM_CHAR, + }; + typedef int DecodeFlags; + + static bool IsBase64Char(char ch); + + // Get the char next to the |ch| from the Base64Table. + // If the |ch| is the last one in the Base64Table then returns + // the first one from the table. + // Expects the |ch| be a base64 char. + // The result will be saved in |next_ch|. + // Returns true on success. + static bool GetNextBase64Char(char ch, char* next_ch); + + // Determines whether the given string consists entirely of valid base64 + // encoded characters. + static bool IsBase64Encoded(const std::string& str); + + static void EncodeFromArray(const void* data, size_t len, + std::string* result); + static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + std::string* result, size_t* data_used); + static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + std::vector* result, size_t* data_used); + + // Convenience Methods + static inline std::string Encode(const std::string& data) { + std::string result; + EncodeFromArray(data.data(), data.size(), &result); + return result; + } + static inline std::string Decode(const std::string& data, DecodeFlags flags) { + std::string result; + DecodeFromArray(data.data(), data.size(), flags, &result, NULL); + return result; + } + static inline bool Decode(const std::string& data, DecodeFlags flags, + std::string* result, size_t* data_used) + { + return DecodeFromArray(data.data(), data.size(), flags, result, data_used); + } + static inline bool Decode(const std::string& data, DecodeFlags flags, + std::vector* result, size_t* data_used) + { + return DecodeFromArray(data.data(), data.size(), flags, result, data_used); + } + +private: + static const char Base64Table[]; + static const unsigned char DecodeTable[]; + + static size_t GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads, + const char* data, size_t len, size_t* dpos, + unsigned char qbuf[4], bool* padded); + template + static bool DecodeFromArrayTemplate(const char* data, size_t len, + DecodeFlags flags, T* result, + size_t* data_used); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BASE64_H__ diff --git a/webrtc/base/base64_unittest.cc b/webrtc/base/base64_unittest.cc new file mode 100644 index 000000000..c4d407244 --- /dev/null +++ b/webrtc/base/base64_unittest.cc @@ -0,0 +1,1001 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/common.h" +#include "webrtc/base/base64.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/stream.h" + +#include "webrtc/base/testbase64.h" + +using namespace std; +using namespace rtc; + +static struct { + size_t plain_length; + const char* plaintext; + const char* cyphertext; +} base64_tests[] = { + + // Basic bit patterns; + // values obtained with "echo -n '...' | uuencode -m test" + + { 1, "\000", "AA==" }, + { 1, "\001", "AQ==" }, + { 1, "\002", "Ag==" }, + { 1, "\004", "BA==" }, + { 1, "\010", "CA==" }, + { 1, "\020", "EA==" }, + { 1, "\040", "IA==" }, + { 1, "\100", "QA==" }, + { 1, "\200", "gA==" }, + + { 1, "\377", "/w==" }, + { 1, "\376", "/g==" }, + { 1, "\375", "/Q==" }, + { 1, "\373", "+w==" }, + { 1, "\367", "9w==" }, + { 1, "\357", "7w==" }, + { 1, "\337", "3w==" }, + { 1, "\277", "vw==" }, + { 1, "\177", "fw==" }, + { 2, "\000\000", "AAA=" }, + { 2, "\000\001", "AAE=" }, + { 2, "\000\002", "AAI=" }, + { 2, "\000\004", "AAQ=" }, + { 2, "\000\010", "AAg=" }, + { 2, "\000\020", "ABA=" }, + { 2, "\000\040", "ACA=" }, + { 2, "\000\100", "AEA=" }, + { 2, "\000\200", "AIA=" }, + { 2, "\001\000", "AQA=" }, + { 2, "\002\000", "AgA=" }, + { 2, "\004\000", "BAA=" }, + { 2, "\010\000", "CAA=" }, + { 2, "\020\000", "EAA=" }, + { 2, "\040\000", "IAA=" }, + { 2, "\100\000", "QAA=" }, + { 2, "\200\000", "gAA=" }, + + { 2, "\377\377", "//8=" }, + { 2, "\377\376", "//4=" }, + { 2, "\377\375", "//0=" }, + { 2, "\377\373", "//s=" }, + { 2, "\377\367", "//c=" }, + { 2, "\377\357", "/+8=" }, + { 2, "\377\337", "/98=" }, + { 2, "\377\277", "/78=" }, + { 2, "\377\177", "/38=" }, + { 2, "\376\377", "/v8=" }, + { 2, "\375\377", "/f8=" }, + { 2, "\373\377", "+/8=" }, + { 2, "\367\377", "9/8=" }, + { 2, "\357\377", "7/8=" }, + { 2, "\337\377", "3/8=" }, + { 2, "\277\377", "v/8=" }, + { 2, "\177\377", "f/8=" }, + + { 3, "\000\000\000", "AAAA" }, + { 3, "\000\000\001", "AAAB" }, + { 3, "\000\000\002", "AAAC" }, + { 3, "\000\000\004", "AAAE" }, + { 3, "\000\000\010", "AAAI" }, + { 3, "\000\000\020", "AAAQ" }, + { 3, "\000\000\040", "AAAg" }, + { 3, "\000\000\100", "AABA" }, + { 3, "\000\000\200", "AACA" }, + { 3, "\000\001\000", "AAEA" }, + { 3, "\000\002\000", "AAIA" }, + { 3, "\000\004\000", "AAQA" }, + { 3, "\000\010\000", "AAgA" }, + { 3, "\000\020\000", "ABAA" }, + { 3, "\000\040\000", "ACAA" }, + { 3, "\000\100\000", "AEAA" }, + { 3, "\000\200\000", "AIAA" }, + { 3, "\001\000\000", "AQAA" }, + { 3, "\002\000\000", "AgAA" }, + { 3, "\004\000\000", "BAAA" }, + { 3, "\010\000\000", "CAAA" }, + { 3, "\020\000\000", "EAAA" }, + { 3, "\040\000\000", "IAAA" }, + { 3, "\100\000\000", "QAAA" }, + { 3, "\200\000\000", "gAAA" }, + + { 3, "\377\377\377", "////" }, + { 3, "\377\377\376", "///+" }, + { 3, "\377\377\375", "///9" }, + { 3, "\377\377\373", "///7" }, + { 3, "\377\377\367", "///3" }, + { 3, "\377\377\357", "///v" }, + { 3, "\377\377\337", "///f" }, + { 3, "\377\377\277", "//+/" }, + { 3, "\377\377\177", "//9/" }, + { 3, "\377\376\377", "//7/" }, + { 3, "\377\375\377", "//3/" }, + { 3, "\377\373\377", "//v/" }, + { 3, "\377\367\377", "//f/" }, + { 3, "\377\357\377", "/+//" }, + { 3, "\377\337\377", "/9//" }, + { 3, "\377\277\377", "/7//" }, + { 3, "\377\177\377", "/3//" }, + { 3, "\376\377\377", "/v//" }, + { 3, "\375\377\377", "/f//" }, + { 3, "\373\377\377", "+///" }, + { 3, "\367\377\377", "9///" }, + { 3, "\357\377\377", "7///" }, + { 3, "\337\377\377", "3///" }, + { 3, "\277\377\377", "v///" }, + { 3, "\177\377\377", "f///" }, + + // Random numbers: values obtained with + // + // #! /bin/bash + // dd bs=$1 count=1 if=/dev/random of=/tmp/bar.random + // od -N $1 -t o1 /tmp/bar.random + // uuencode -m test < /tmp/bar.random + // + // where $1 is the number of bytes (2, 3) + + { 2, "\243\361", "o/E=" }, + { 2, "\024\167", "FHc=" }, + { 2, "\313\252", "y6o=" }, + { 2, "\046\041", "JiE=" }, + { 2, "\145\236", "ZZ4=" }, + { 2, "\254\325", "rNU=" }, + { 2, "\061\330", "Mdg=" }, + { 2, "\245\032", "pRo=" }, + { 2, "\006\000", "BgA=" }, + { 2, "\375\131", "/Vk=" }, + { 2, "\303\210", "w4g=" }, + { 2, "\040\037", "IB8=" }, + { 2, "\261\372", "sfo=" }, + { 2, "\335\014", "3Qw=" }, + { 2, "\233\217", "m48=" }, + { 2, "\373\056", "+y4=" }, + { 2, "\247\232", "p5o=" }, + { 2, "\107\053", "Rys=" }, + { 2, "\204\077", "hD8=" }, + { 2, "\276\211", "vok=" }, + { 2, "\313\110", "y0g=" }, + { 2, "\363\376", "8/4=" }, + { 2, "\251\234", "qZw=" }, + { 2, "\103\262", "Q7I=" }, + { 2, "\142\312", "Yso=" }, + { 2, "\067\211", "N4k=" }, + { 2, "\220\001", "kAE=" }, + { 2, "\152\240", "aqA=" }, + { 2, "\367\061", "9zE=" }, + { 2, "\133\255", "W60=" }, + { 2, "\176\035", "fh0=" }, + { 2, "\032\231", "Gpk=" }, + + { 3, "\013\007\144", "Cwdk" }, + { 3, "\030\112\106", "GEpG" }, + { 3, "\047\325\046", "J9Um" }, + { 3, "\310\160\022", "yHAS" }, + { 3, "\131\100\237", "WUCf" }, + { 3, "\064\342\134", "NOJc" }, + { 3, "\010\177\004", "CH8E" }, + { 3, "\345\147\205", "5WeF" }, + { 3, "\300\343\360", "wOPw" }, + { 3, "\061\240\201", "MaCB" }, + { 3, "\225\333\044", "ldsk" }, + { 3, "\215\137\352", "jV/q" }, + { 3, "\371\147\160", "+Wdw" }, + { 3, "\030\320\051", "GNAp" }, + { 3, "\044\174\241", "JHyh" }, + { 3, "\260\127\037", "sFcf" }, + { 3, "\111\045\033", "SSUb" }, + { 3, "\202\114\107", "gkxH" }, + { 3, "\057\371\042", "L/ki" }, + { 3, "\223\247\244", "k6ek" }, + { 3, "\047\216\144", "J45k" }, + { 3, "\203\070\327", "gzjX" }, + { 3, "\247\140\072", "p2A6" }, + { 3, "\124\115\116", "VE1O" }, + { 3, "\157\162\050", "b3Io" }, + { 3, "\357\223\004", "75ME" }, + { 3, "\052\117\156", "Kk9u" }, + { 3, "\347\154\000", "52wA" }, + { 3, "\303\012\142", "wwpi" }, + { 3, "\060\035\362", "MB3y" }, + { 3, "\130\226\361", "WJbx" }, + { 3, "\173\013\071", "ews5" }, + { 3, "\336\004\027", "3gQX" }, + { 3, "\357\366\234", "7/ac" }, + { 3, "\353\304\111", "68RJ" }, + { 3, "\024\264\131", "FLRZ" }, + { 3, "\075\114\251", "PUyp" }, + { 3, "\315\031\225", "zRmV" }, + { 3, "\154\201\276", "bIG+" }, + { 3, "\200\066\072", "gDY6" }, + { 3, "\142\350\267", "Yui3" }, + { 3, "\033\000\166", "GwB2" }, + { 3, "\210\055\077", "iC0/" }, + { 3, "\341\037\124", "4R9U" }, + { 3, "\161\103\152", "cUNq" }, + { 3, "\270\142\131", "uGJZ" }, + { 3, "\337\076\074", "3z48" }, + { 3, "\375\106\362", "/Uby" }, + { 3, "\227\301\127", "l8FX" }, + { 3, "\340\002\234", "4AKc" }, + { 3, "\121\064\033", "UTQb" }, + { 3, "\157\134\143", "b1xj" }, + { 3, "\247\055\327", "py3X" }, + { 3, "\340\142\005", "4GIF" }, + { 3, "\060\260\143", "MLBj" }, + { 3, "\075\203\170", "PYN4" }, + { 3, "\143\160\016", "Y3AO" }, + { 3, "\313\013\063", "ywsz" }, + { 3, "\174\236\135", "fJ5d" }, + { 3, "\103\047\026", "QycW" }, + { 3, "\365\005\343", "9QXj" }, + { 3, "\271\160\223", "uXCT" }, + { 3, "\362\255\172", "8q16" }, + { 3, "\113\012\015", "SwoN" }, + + // various lengths, generated by this python script: + // + // from string import lowercase as lc + // for i in range(27): + // print '{ %2d, "%s",%s "%s" },' % (i, lc[:i], ' ' * (26-i), + // lc[:i].encode('base64').strip()) + + { 0, "abcdefghijklmnopqrstuvwxyz", "" }, + { 1, "abcdefghijklmnopqrstuvwxyz", "YQ==" }, + { 2, "abcdefghijklmnopqrstuvwxyz", "YWI=" }, + { 3, "abcdefghijklmnopqrstuvwxyz", "YWJj" }, + { 4, "abcdefghijklmnopqrstuvwxyz", "YWJjZA==" }, + { 5, "abcdefghijklmnopqrstuvwxyz", "YWJjZGU=" }, + { 6, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVm" }, + { 7, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZw==" }, + { 8, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2g=" }, + { 9, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hp" }, + { 10, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpag==" }, + { 11, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpams=" }, + { 12, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamts" }, + { 13, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbQ==" }, + { 14, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW4=" }, + { 15, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5v" }, + { 16, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcA==" }, + { 17, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHE=" }, + { 18, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFy" }, + { 19, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFycw==" }, + { 20, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3Q=" }, + { 21, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1" }, + { 22, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dg==" }, + { 23, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnc=" }, + { 24, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4" }, + { 25, "abcdefghijklmnopqrstuvwxy", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eQ==" }, + { 26, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=" }, +}; +#if 0 +static struct { + const char* plaintext; + const char* cyphertext; +} base64_strings[] = { + + // The first few Google quotes + // Cyphertext created with "uuencode - GNU sharutils 4.2.1" + { + "Everyone! We're teetering on the brink of disaster." + " - Sergey Brin, 6/24/99, regarding the company's state " + "after the unleashing of Netscape/Google search", + + "RXZlcnlvbmUhICBXZSdyZSB0ZWV0ZXJpbmcgb24gdGhlIGJyaW5rIG9mIGRp" + "c2FzdGVyLiAtIFNlcmdleSBCcmluLCA2LzI0Lzk5LCByZWdhcmRpbmcgdGhl" + "IGNvbXBhbnkncyBzdGF0ZSBhZnRlciB0aGUgdW5sZWFzaGluZyBvZiBOZXRz" + "Y2FwZS9Hb29nbGUgc2VhcmNo" }, + + { + "I'm not sure why we're still alive, but we seem to be." + " - Larry Page, 6/24/99, while hiding in the kitchenette " + "during the Netscape traffic overflow", + + "SSdtIG5vdCBzdXJlIHdoeSB3ZSdyZSBzdGlsbCBhbGl2ZSwgYnV0IHdlIHNl" + "ZW0gdG8gYmUuIC0gTGFycnkgUGFnZSwgNi8yNC85OSwgd2hpbGUgaGlkaW5n" + "IGluIHRoZSBraXRjaGVuZXR0ZSBkdXJpbmcgdGhlIE5ldHNjYXBlIHRyYWZm" + "aWMgb3ZlcmZsb3c" }, + + { + "I think kids want porn." + " - Sergey Brin, 6/99, on why Google shouldn't prioritize a " + "filtered search for children and families", + + "SSB0aGluayBraWRzIHdhbnQgcG9ybi4gLSBTZXJnZXkgQnJpbiwgNi85OSwg" + "b24gd2h5IEdvb2dsZSBzaG91bGRuJ3QgcHJpb3JpdGl6ZSBhIGZpbHRlcmVk" + "IHNlYXJjaCBmb3IgY2hpbGRyZW4gYW5kIGZhbWlsaWVz" }, +}; +#endif +// Compare bytes 0..len-1 of x and y. If not equal, abort with verbose error +// message showing position and numeric value that differed. +// Handles embedded nulls just like any other byte. +// Only added because string.compare() in gcc-3.3.3 seems to misbehave with +// embedded nulls. +// TODO: switch back to string.compare() if/when gcc is fixed +#define EXPECT_EQ_ARRAY(len, x, y, msg) \ + for (size_t j = 0; j < len; ++j) { \ + if (x[j] != y[j]) { \ + LOG(LS_ERROR) << "" # x << " != " # y \ + << " byte " << j << " msg: " << msg; \ + } \ + } + +size_t Base64Escape(const unsigned char *src, size_t szsrc, char *dest, + size_t szdest) { + std::string escaped; + Base64::EncodeFromArray((const char *)src, szsrc, &escaped); + memcpy(dest, escaped.data(), min(escaped.size(), szdest)); + return escaped.size(); +} + +size_t Base64Unescape(const char *src, size_t szsrc, char *dest, + size_t szdest) { + std::string unescaped; + EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, &unescaped, + NULL)); + memcpy(dest, unescaped.data(), min(unescaped.size(), szdest)); + return unescaped.size(); +} + +size_t Base64Unescape(const char *src, size_t szsrc, string *s) { + EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, s, NULL)); + return s->size(); +} + +TEST(Base64, EncodeDecodeBattery) { + LOG(LS_VERBOSE) << "Testing base-64"; + + size_t i; + + // Check the short strings; this tests the math (and boundaries) + for( i = 0; i < sizeof(base64_tests) / sizeof(base64_tests[0]); ++i ) { + char encode_buffer[100]; + size_t encode_length; + char decode_buffer[100]; + size_t decode_length; + size_t cypher_length; + + LOG(LS_VERBOSE) << "B64: " << base64_tests[i].cyphertext; + + const unsigned char* unsigned_plaintext = + reinterpret_cast(base64_tests[i].plaintext); + + cypher_length = strlen(base64_tests[i].cyphertext); + + // The basic escape function: + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = Base64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + sizeof(encode_buffer)); + // Is it of the expected length? + EXPECT_EQ(encode_length, cypher_length); + + // Is it the expected encoded value? + EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext); + + // If we encode it into a buffer of exactly the right length... + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = Base64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + cypher_length); + // Is it still of the expected length? + EXPECT_EQ(encode_length, cypher_length); + + // And is the value still correct? (i.e., not losing the last byte) + EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext); + + // If we decode it back: + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = Base64Unescape(encode_buffer, + cypher_length, + decode_buffer, + sizeof(decode_buffer)); + + // Is it of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // Is it the expected decoded value? + EXPECT_EQ(0, memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + // Our decoder treats the padding '=' characters at the end as + // optional. If encode_buffer has any, run some additional + // tests that fiddle with them. + char* first_equals = strchr(encode_buffer, '='); + if (first_equals) { + // How many equals signs does the string start with? + int equals = (*(first_equals+1) == '=') ? 2 : 1; + + // Try chopping off the equals sign(s) entirely. The decoder + // should still be okay with this. + string decoded2("this junk should also be ignored"); + *first_equals = '\0'; + EXPECT_NE(0U, Base64Unescape(encode_buffer, first_equals-encode_buffer, + &decoded2)); + EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded2.size(), decoded2.data(), base64_tests[i].plaintext, i); + + size_t len; + + // try putting some extra stuff after the equals signs, or in between them + if (equals == 2) { + sprintfn(first_equals, 6, " = = "); + len = first_equals - encode_buffer + 5; + } else { + sprintfn(first_equals, 6, " = "); + len = first_equals - encode_buffer + 3; + } + decoded2.assign("this junk should be ignored"); + EXPECT_NE(0U, Base64Unescape(encode_buffer, len, &decoded2)); + EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded2.size(), decoded2, base64_tests[i].plaintext, i); + } + } +} + +// here's a weird case: a giant base64 encoded stream which broke our base64 +// decoding. Let's test it explicitly. +const char SpecificTest[] = + "/9j/4AAQSkZJRgABAgEASABIAAD/4Q0HRXhpZgAATU0AKgAAAAgADAEOAAIAAAAgAAAAngEPAAI\n" + "AAAAFAAAAvgEQAAIAAAAJAAAAwwESAAMAAAABAAEAAAEaAAUAAAABAAAAzAEbAAUAAAABAAAA1A\n" + "EoAAMAAAABAAIAAAExAAIAAAAUAAAA3AEyAAIAAAAUAAAA8AE8AAIAAAAQAAABBAITAAMAAAABA\n" + "AIAAIdpAAQAAAABAAABFAAAAsQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAFNPTlkA\n" + "RFNDLVAyMDAAAAAASAAAAAEAAABIAAAAAUFkb2JlIFBob3Rvc2hvcCA3LjAAMjAwNzowMTozMCA\n" + "yMzoxMDowNABNYWMgT1MgWCAxMC40LjgAAByCmgAFAAAAAQAAAmqCnQAFAAAAAQAAAnKIIgADAA\n" + "AAAQACAACIJwADAAAAAQBkAACQAAAHAAAABDAyMjCQAwACAAAAFAAAAnqQBAACAAAAFAAAAo6RA\n" + "QAHAAAABAECAwCRAgAFAAAAAQAAAqKSBAAKAAAAAQAAAqqSBQAFAAAAAQAAArKSBwADAAAAAQAF\n" + "AACSCAADAAAAAQAAAACSCQADAAAAAQAPAACSCgAFAAAAAQAAArqgAAAHAAAABDAxMDCgAQADAAA\n" + "AAf//AACgAgAEAAAAAQAAAGSgAwAEAAAAAQAAAGSjAAAHAAAAAQMAAACjAQAHAAAAAQEAAACkAQ\n" + "ADAAAAAQAAAACkAgADAAAAAQAAAACkAwADAAAAAQAAAACkBgADAAAAAQAAAACkCAADAAAAAQAAA\n" + "ACkCQADAAAAAQAAAACkCgADAAAAAQAAAAAAAAAAAAAACgAAAZAAAAAcAAAACjIwMDc6MDE6MjAg\n" + "MjM6MDU6NTIAMjAwNzowMToyMCAyMzowNTo1MgAAAAAIAAAAAQAAAAAAAAAKAAAAMAAAABAAAAB\n" + "PAAAACgAAAAYBAwADAAAAAQAGAAABGgAFAAAAAQAAAxIBGwAFAAAAAQAAAxoBKAADAAAAAQACAA\n" + "ACAQAEAAAAAQAAAyICAgAEAAAAAQAACd0AAAAAAAAASAAAAAEAAABIAAAAAf/Y/+AAEEpGSUYAA\n" + "QIBAEgASAAA/+0ADEFkb2JlX0NNAAL/7gAOQWRvYmUAZIAAAAAB/9sAhAAMCAgICQgMCQkMEQsK\n" + "CxEVDwwMDxUYExMVExMYEQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQ0LCw0\n" + "ODRAODhAUDg4OFBQODg4OFBEMDAwMDBERDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA\n" + "wMDAz/wAARCABkAGQDASIAAhEBAxEB/90ABAAH/8QBPwAAAQUBAQEBAQEAAAAAAAAAAwABAgQFB\n" + "gcICQoLAQABBQEBAQEBAQAAAAAAAAABAAIDBAUGBwgJCgsQAAEEAQMCBAIFBwYIBQMMMwEAAhED\n" + "BCESMQVBUWETInGBMgYUkaGxQiMkFVLBYjM0coLRQwclklPw4fFjczUWorKDJkSTVGRFwqN0Nhf\n" + "SVeJl8rOEw9N14/NGJ5SkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9xEAAg\n" + "IBAgQEAwQFBgcHBgU1AQACEQMhMRIEQVFhcSITBTKBkRShsUIjwVLR8DMkYuFygpJDUxVjczTxJ\n" + "QYWorKDByY1wtJEk1SjF2RFVTZ0ZeLys4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm\n" + "9ic3R1dnd4eXp7fH/9oADAMBAAIRAxEAPwDy7bKNTUXNLz9EaJPDWMjxH4ozhtpYwaACT8ShaaW\n" + "bW0uEc9/JFfjj0Q4Hk/PRDxwX7y47W9z/AN9Cv4+O3ILK2DcRqT2CaSvEbcl1Jbz37KG1dBldLo\n" + "qaS4l9xGjG9v6yoDAdYIaIjUk+AREgo4y5sapirb8Yl0NHHdKvBNm4yA1o5Pc+SPEFvCWqB3HZF\n" + "Hj2SbWQ/afGFP0bHP8ATY0uc4w1o1JPkkimGiS2KvqlnmBkOZQTyydzgPMM9v8A0lp4v1Nx9gF1\n" + "tpdqJaGtH/S3I0i3lISXW/8AMqnd/O2bfg2eUkqVYf/Q8zuncO4Bj7lZ+n7f5Mj5KsJcY8NUZ4d\n" + "uEDVo1HkeU0rg3Om4H2rabCWUN7DQuK1n5FWKW4uCwG92gDRJBS6exhxmMboQI+Cv4WFTQ42Bs2\n" + "fvnkkqEmy2YxoMMbpVzaz6jt+RbpHZs8lzkHqrasKkYOKP0jgDfZ4N/wDM1tNrcWfSPmRyq9uNV\n" + "DnFg2s97i7UkjxKVrq0eVz3spZsja+ASDzwsh9jnOk/JFzb3XZD3v1c4yT8UACTCniKDUnKz5Nj\n" + "G33XV1DV73BrT8dF23SejV4zg9g33cOsPb+SxVvqv9ViwNy8vS0iWs/daf8A0Y5dpTi1sADGxCR\n" + "K1o0YBEmInlXWYbDBcDLdPJXa8f71Yrx2jnUoAqLnfZK5hJaW2vdwEk5a/wD/0fN6Ia/e76IiVf\n" + "xavUL7CPpnT4LNbYXAVjuQt/AqDmNYO/Kjnoy4hr5J8SwMhrRMaeSvbsxrfUazcOw4UX0Cisem2\n" + "SBoD4+Kz8nC6llbSLCRrubJA8kwUWbUDa29X1PMa7aQWjuDC0MXMdbDbhI7eazBiUfZ6GOYRe1s\n" + "WvGgJ8Vbw2+m4Bx9s6JpNHuuGo1FF53r/SHYua61gLse0lzXeBP5rkvqx0o5vVWz7WY49QkiQSP\n" + "oN/tLoevW/ogxv0HA7tJ0AnhT+pdDGYVl/wCdcTPkGn2NU0JWNWvlgAbHV6fEqdu2gR/r2WlWwt\n" + "AA5VXAEsLXTqJafArQY5rRr9LiPBJiZsZCI1pJjxCi0j4oncSICSkWwzwkjeaSch//0vO7sP7Lm\n" + "enO9ogtd5FbPT3Q5pCpZVc4ld3Lmn3O8j9EI2BYdunKjOobMQIyI+rusc2wx4d0eutwGnHh/uQc\n" + "Ha7ladj6mVANGvcqOgz0Go7HJ12/GEHcwvB/dPY6ImbbaMaASGuIBjkN7qofs9Ubg9g7OI9p/t/\n" + "RTSmhTHr0v6eSz6UgCPP2/wAVu9Ex2V49dVY2iACB4BZeVXQ/AJ3gzGnnOi2+kACpru8flUsNmt\n" + "zHRf6xfWCnoeAfTh2ZaQKazx/Ke7+QxcKz61fWA2uuObaC4zGhaPJrXBL64ZFmR124O09ENraPK\n" + "N3/AH5GqxIrZVUyp2K2vfdkENsDnxuex9m4Ox9n82xSgNd9D+p/XR1npgseR9ppOy4Dx/NfH/CL\n" + "oQJGunmvMv8AFq3KHVcq3HkYQbD2nuSf0I/rMavSg6TLjLigQhJ7Z58v9QkmlsTOqSCn/9PzL7R\n" + "d6Qq3n0wZ2zotXpT9xLfFYvkr/S7jXeB8E0jRkhKpC3q8LcJ/kmCrTnkuAPCq4do9Q/ytVbuAeY\n" + "Gg5lQybQK+82GBqEQUA1kOHPYf3LLsoyN36G5w8iUfHxepbXE2l0cApALgLHzBq9UxhTXU5hMC1\n" + "ktnSCup6S4Ctk+C5XqVGcaHPfuiuHkeTTuWz0+9zaKiH6CC0/yXBSQ2a/MxojV57634rq+v2PLY\n" + "be1r2nsYG13/AFKxbfCBMcr0brGAzrGEwCG31ncx0SfBzf7S4+zoHUWWsJq3hz9oLfcBH77R9H+\n" + "0pA13u/qPgDp/Q6ri39JlfpXkDx+h/msWn1L6wdO6bSbcrIbU2Q0xLnSe21kuVejJspbVS5+4bd\n" + "ocBAkD/orG+tP1ar67Wy7GtZTm1SCXfRsb+a18fRe38x6SG3/44H1Z3f0y2I+l6DoSXD/8xPrDs\n" + "3enVu3bdnqN3R+//USSVo//1PLohhce+gRWS0Nsby3lRgFkKxQyW7SgUh3em5Tbq2uB9wWw1wey\n" + "J1XGV2XYdm5k7e4WzidXY9oMwo5RZ4T6Hd1ixwfp96PWbAJBVTHzK7O6Ky5oJB1HZMqmUEFlkGy\n" + "xpa4zI1Hkq31dy7bMN9BAc3HeWAnnbyxEycmuup1jiAGglZ31PyrmZ9tQg1WtNj54EHR3/S2qTH\n" + "1Yc5GgD1FFtzPdWGkd2AyflogZmRmsz6PSrbXbdo+txOrP337f3fzVo15DK2uyrTtqpBOnBKx6b\n" + "7MjJsz7tHWOAYP3WD6LU6cqGjFCNl1MmvLcxv6YtDTLSAqP27LrdtYHXFnJZI+Tp3MWg68OpDPv\n" + "UMUM2lkQBoouKQ6swjE9Nml+1sz1PW+z6xt27zuj+skrX2ZvqR5z8kkuOfdPt43/1fMm/grFG6f\n" + "Lss9JA7JG7tnZs/SfJUrfS3foJ9TvHCopJsV8nWx/t24bJn8Fo/5TjWJXMJIS+i+G36TsZ/7Q9P\n" + "8ATfzfeOFofVSZv2/zvt+O3X/v65dJPjt/BiyfN1/wn0zre79nVej/ADG8ep4x2/6Srjd6TdviF\n" + "52ko8m6/Ht9X1KnftEo+POwxzK8mSTF46vrH6T1/OEl5Okkl//Z/+0uHFBob3Rvc2hvcCAzLjAA\n" + "OEJJTQQEAAAAAAArHAIAAAIAAhwCeAAfICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAA\n" + "4QklNBCUAAAAAABD7Caa9B0wqNp2P4sxXqayFOEJJTQPqAAAAAB2wPD94bWwgdmVyc2lvbj0iMS\n" + "4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUgQ\n" + "29tcHV0ZXIvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Q\n" + "cm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk\n" + "+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1Ib3Jpem9udGFsUmVzPC9rZXk+Cgk8ZGljdD\n" + "4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCTxzdHJpbmc+Y\n" + "29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCTxrZXk+Y29tLmFwcGxlLnByaW50\n" + "LnRpY2tldC5pdGVtQXJyYXk8L2tleT4KCQk8YXJyYXk+CgkJCTxkaWN0PgoJCQkJPGtleT5jb20\n" + "uYXBwbGUucHJpbnQuUGFnZUZvcm1hdC5QTUhvcml6b250YWxSZXM8L2tleT4KCQkJCTxyZWFsPj\n" + "cyPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJC\n" + "QkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJpbmc+CgkJCQk8a2V5PmNv\n" + "bS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGRhdGU+MjAwNy0wMS0zMFQ\n" + "yMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuc3RhdGVGbG\n" + "FnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY3Q+CgkJPC9hcnJheT4KC\n" + "TwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwv\n" + "a2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4\n" + "KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS\n" + "5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KC\n" + "QkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwva2V5PgoJ\n" + "CQkJPGludGVnZXI+MTwvaW50ZWdlcj4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5\n" + "jbGllbnQ8L2tleT4KCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW\n" + "5nPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lm1vZERhdGU8L2tleT4KCQkJCTxkY\n" + "XRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQu\n" + "dGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJPGludGVnZXI+MDwvaW50ZWdlcj4KCQkJPC9kaWN\n" + "0PgoJCTwvYXJyYXk+Cgk8L2RpY3Q+Cgk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0Ll\n" + "BNU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZ\n" + "WF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4K\n" + "CQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5Pgo\n" + "JCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1TY2FsaW5nPC\n" + "9rZXk+CgkJCQk8cmVhbD4xPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0L\n" + "mNsaWVudDwva2V5PgoJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJp\n" + "bmc+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGR\n" + "hdGU+MjAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC\n" + "50aWNrZXQuc3RhdGVGbGFnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY\n" + "3Q+CgkJPC9hcnJheT4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQu\n" + "UE1WZXJ0aWNhbFJlczwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n" + "0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cm\n" + "luZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFyc\n" + "mF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1WZXJ0\n" + "aWNhbFJlczwva2V5PgoJCQkJPHJlYWw+NzI8L3JlYWw+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcml\n" + "udC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbm\n" + "FnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZ\n" + "Xk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT4KCQkJCTxrZXk+Y29tLmFw\n" + "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI\n" + "+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5jb20uYXBwbGUucHJpbnQuUG\n" + "FnZUZvcm1hdC5QTVZlcnRpY2FsU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwb\n" + "GUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGlu\n" + "Z21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF\n" + "5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2\n" + "VGb3JtYXQuUE1WZXJ0aWNhbFNjYWxpbmc8L2tleT4KCQkJCTxyZWFsPjE8L3JlYWw+CgkJCQk8a\n" + "2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5h\n" + "cHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n" + "pY2tldC5tb2REYXRlPC9rZXk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT\n" + "4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpb\n" + "nRlZ2VyPjA8L2ludGVnZXI+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5j\n" + "b20uYXBwbGUucHJpbnQuc3ViVGlja2V0LnBhcGVyX2luZm9fdGlja2V0PC9rZXk+Cgk8ZGljdD4\n" + "KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUmVjdDwva2\n" + "V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5P\n" + "goJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJPGtleT5j\n" + "b20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGl\n" + "jdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUm\n" + "VjdDwva2V5PgoJCQkJCTxhcnJheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhb\n" + "D4wLjA8L3JlYWw+CgkJCQkJCTxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFs\n" + "PgoJCQkJCTwvYXJyYXk+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDw\n" + "va2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQ\n" + "kJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+M\n" + "jAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlj\n" + "a2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q\n" + "+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYX\n" + "QuUE1BZGp1c3RlZFBhcGVyUmVjdDwva2V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wc\n" + "mludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21h\n" + "bmFnZXI8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTw\n" + "va2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYW\n" + "dlRm9ybWF0LlBNQWRqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZ\n" + "WFsPi0xODwvcmVhbD4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3Jl\n" + "YWw+CgkJCQkJCTxyZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmF\n" + "wcGxlLnByaW50LnRpY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcm\n" + "ludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQub\n" + "W9kRGF0ZTwva2V5PgoJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJ\n" + "CTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWd\n" + "lcj4wPC9pbnRlZ2VyPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5Pm\n" + "NvbS5hcHBsZS5wcmludC5QYXBlckluZm8uUE1QYXBlck5hbWU8L2tleT4KCQk8ZGljdD4KCQkJP\n" + "GtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20u\n" + "YXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcml\n" + "udC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZX\n" + "k+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVBhcGVyTmFtZTwva2V5PgoJCQkJCTxzdHJpb\n" + "mc+bmEtbGV0dGVyPC9zdHJpbmc+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNs\n" + "aWVudDwva2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50LnBtLlBvc3RTY3JpcHQ8L3N\n" + "0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQ\n" + "kJCTxkYXRlPjIwMDMtMDctMDFUMTc6NDk6MzZaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFwcGxlL\n" + "nByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4xPC9pbnRlZ2VyPgoJ\n" + "CQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5\n" + "QYXBlckluZm8uUE1VbmFkanVzdGVkUGFnZVJlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb2\n" + "0uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUuc\n" + "HJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNr\n" + "ZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmF\n" + "wcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYWdlUmVjdDwva2V5PgoJCQkJCTxhcn\n" + "JheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhbD4wLjA8L3JlYWw+CgkJCQkJC\n" + "TxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFsPgoJCQkJCTwvYXJyYXk+CgkJ\n" + "CQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJCQkJCTxzdHJpbmc\n" + "+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLn\n" + "ByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+MjAwNy0wMS0zMFQyMjowODo0M\n" + "Vo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LnN0YXRlRmxhZzwva2V5\n" + "PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9\n" + "kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYXBlcl\n" + "JlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b\n" + "3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5n\n" + "PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJ\n" + "heT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYW\n" + "RqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZWFsPi0xODwvcmVhb\n" + "D4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3JlYWw+CgkJCQkJCTxy\n" + "ZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n" + "pY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZX\n" + "I8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5P\n" + "goJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFw\n" + "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4wPC9pbnRlZ2V\n" + "yPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcm\n" + "ludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJPGRpY3Q+CgkJCTxrZXk+Y29tL\n" + "mFwcGxlLnByaW50LnRpY2tldC5jcmVhdG9yPC9rZXk+CgkJCTxzdHJpbmc+Y29tLmFwcGxlLnBy\n" + "aW50LnBtLlBvc3RTY3JpcHQ8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n" + "0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcH\n" + "BsZS5wcmludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJCQkJPHN0cmluZz5VU\n" + "yBMZXR0ZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50\n" + "PC9rZXk+CgkJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5\n" + "nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPG\n" + "RhdGU+MjAwMy0wNy0wMVQxNzo0OTozNlo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpb\n" + "nQudGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8\n" + "L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2t\n" + "ldC5BUElWZXJzaW9uPC9rZXk+CgkJPHN0cmluZz4wMC4yMDwvc3RyaW5nPgoJCTxrZXk+Y29tLm\n" + "FwcGxlLnByaW50LnRpY2tldC5wcml2YXRlTG9jazwva2V5PgoJCTxmYWxzZS8+CgkJPGtleT5jb\n" + "20uYXBwbGUucHJpbnQudGlja2V0LnR5cGU8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmlu\n" + "dC5QYXBlckluZm9UaWNrZXQ8L3N0cmluZz4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW5\n" + "0LnRpY2tldC5BUElWZXJzaW9uPC9rZXk+Cgk8c3RyaW5nPjAwLjIwPC9zdHJpbmc+Cgk8a2V5Pm\n" + "NvbS5hcHBsZS5wcmludC50aWNrZXQucHJpdmF0ZUxvY2s8L2tleT4KCTxmYWxzZS8+Cgk8a2V5P\n" + "mNvbS5hcHBsZS5wcmludC50aWNrZXQudHlwZTwva2V5PgoJPHN0cmluZz5jb20uYXBwbGUucHJp\n" + "bnQuUGFnZUZvcm1hdFRpY2tldDwvc3RyaW5nPgo8L2RpY3Q+CjwvcGxpc3Q+CjhCSU0D6QAAAAA\n" + "AeAADAAAASABIAAAAAALeAkD/7v/uAwYCUgNnBSgD/AACAAAASABIAAAAAALYAigAAQAAAGQAAA\n" + "ABAAMDAwAAAAF//wABAAEAAAAAAAAAAAAAAABoCAAZAZAAAAAAACAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAADhCSU0D7QAAAAAAEABIAAAAAQABAEgAAAABAAE4QklNBCYAAAAAAA4AAAAAAAAA\n" + "AAAAP4AAADhCSU0EDQAAAAAABAAAAB44QklNBBkAAAAAAAQAAAAeOEJJTQPzAAAAAAAJAAAAAAA\n" + "AAAABADhCSU0ECgAAAAAAAQAAOEJJTScQAAAAAAAKAAEAAAAAAAAAAThCSU0D9QAAAAAASAAvZm\n" + "YAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAA\n" + "QAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP//\n" + "//////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA///\n" + "//////////////////////////wPoAAA4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQ\n" + "QeAAAAAAAEAAAAADhCSU0EGgAAAAADRQAAAAYAAAAAAAAAAAAAAGQAAABkAAAACABEAFMAQwAwA\n" + "DIAMwAyADUAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAGQAAABkAAAAAAAAAAAA\n" + "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHN\n" + "PYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAA\n" + "AAQnRvbWxvbmcAAABkAAAAAFJnaHRsb25nAAAAZAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABA\n" + "AAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAG\n" + "b3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQA\n" + "AAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAA\n" + "BUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAZAAAAABSZ2h0bG9uZ\n" + "wAAAGQAAAADdXJsVEVYVAAAAAEAAAAAAABudWxsVEVYVAAAAAEAAAAAAABNc2dlVEVYVAAAAAEA\n" + "AAAAAAZhbHRUYWdURVhUAAAAAQAAAAAADmNlbGxUZXh0SXNIVE1MYm9vbAEAAAAIY2VsbFRleHR\n" + "URVhUAAAAAQAAAAAACWhvcnpBbGlnbmVudW0AAAAPRVNsaWNlSG9yekFsaWduAAAAB2RlZmF1bH\n" + "QAAAAJdmVydEFsaWduZW51bQAAAA9FU2xpY2VWZXJ0QWxpZ24AAAAHZGVmYXVsdAAAAAtiZ0Nvb\n" + "G9yVHlwZWVudW0AAAARRVNsaWNlQkdDb2xvclR5cGUAAAAATm9uZQAAAAl0b3BPdXRzZXRsb25n\n" + "AAAAAAAAAApsZWZ0T3V0c2V0bG9uZwAAAAAAAAAMYm90dG9tT3V0c2V0bG9uZwAAAAAAAAALcml\n" + "naHRPdXRzZXRsb25nAAAAAAA4QklNBBEAAAAAAAEBADhCSU0EFAAAAAAABAAAAAE4QklNBAwAAA\n" + "AACfkAAAABAAAAZAAAAGQAAAEsAAB1MAAACd0AGAAB/9j/4AAQSkZJRgABAgEASABIAAD/7QAMQ\n" + "WRvYmVfQ00AAv/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUT\n" + "ExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4\n" + "ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZA\n" + "MBIgACEQEDEQH/3QAEAAf/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBA\n" + "QEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEy\n" + "BhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80Y\n" + "nlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBT\n" + "UBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTV\n" + "KMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/\n" + "2gAMAwEAAhEDEQA/APLtso1NRc0vP0Rok8NYyPEfijOG2ljBoAJPxKFppZtbS4Rz38kV+OPRDge\n" + "T89EPHBfvLjtb3P8A30K/j47cgsrYNxGpPYJpK8RtyXUlvPfsobV0GV0uippLiX3EaMb2/rKgMB\n" + "1ghoiNST4BESCjjLmxqmKtvxiXQ0cd0q8E2bjIDWjk9z5I8QW8JaoHcdkUePZJtZD9p8YU/Rsc/\n" + "wBNjS5zjDWjUk+SSKYaJLYq+qWeYGQ5lBPLJ3OA8wz2/wDSWni/U3H2AXW2l2oloa0f9LcjSLeU\n" + "hJdb/wAyqd387Zt+DZ5SSpVh/9DzO6dw7gGPuVn6ft/kyPkqwlxjw1Rnh24QNWjUeR5TSuDc6bg\n" + "fatpsJZQ3sNC4rWfkVYpbi4LAb3aANEkFLp7GHGYxuhAj4K/hYVNDjYGzZ++eSSoSbLZjGgwxul\n" + "XNrPqO35FukdmzyXOQeqtqwqRg4o/SOAN9ng3/AMzW02txZ9I+ZHKr241UOcWDaz3uLtSSPEpWu\n" + "rR5XPeylmyNr4BIPPCyH2Oc6T8kXNvddkPe/VzjJPxQAJMKeIoNScrPk2MbfddXUNXvcGtPx0Xb\n" + "dJ6NXjOD2Dfdw6w9v5LFW+q/1WLA3Ly9LSJaz91p/wDRjl2lOLWwAMbEJErWjRgESYieVdZhsMF\n" + "wMt08ldrx/vVivHaOdSgCoud9krmElpba93ASTlr/AP/R83ohr97voiJV/Fq9QvsI+mdPgs1thc\n" + "BWO5C38CoOY1g78qOejLiGvknxLAyGtExp5K9uzGt9RrNw7DhRfQKKx6bZIGgPj4rPycLqWVtIs\n" + "JGu5skDyTBRZtQNrb1fU8xrtpBaO4MLQxcx1sNuEjt5rMGJR9noY5hF7Wxa8aAnxVvDb6bgHH2z\n" + "omk0e64ajUUXnev9Idi5rrWAux7SXNd4E/muS+rHSjm9VbPtZjj1CSJBI+g3+0uh69b+iDG/QcD\n" + "u0nQCeFP6l0MZhWX/AJ1xM+QafY1TQlY1a+WABsdXp8Sp27aBH+vZaVbC0ADlVcASwtdOolp8Ct\n" + "BjmtGv0uI8EmJmxkIjWkmPEKLSPiidxIgJKRbDPCSN5pJyH//S87uw/suZ6c72iC13kVs9PdDmk\n" + "KllVziV3cuafc7yP0QjYFh26cqM6hsxAjIj6u6xzbDHh3R663AaceH+5BwdruVp2PqZUA0a9yo6\n" + "DPQajscnXb8YQdzC8H909joiZttoxoBIa4gGOQ3uqh+z1RuD2Ds4j2n+39FNKaFMevS/p5LPpSA\n" + "I8/b/ABW70THZXj11VjaIAIHgFl5VdD8AneDMaec6Lb6QAKmu7x+VSw2a3MdF/rF9YKeh4B9OHZ\n" + "lpAprPH8p7v5DFwrPrV9YDa645toLjMaFo8mtcEvrhkWZHXbg7T0Q2to8o3f8AfkarEitlVTKnY\n" + "ra992QQ2wOfG57H2bg7H2fzbFKA130P6n9dHWemCx5H2mk7LgPH818f8IuhAka6ea8y/wAWrcod\n" + "VyrceRhBsPae5J/Qj+sxq9KDpMuMuKBCEntnny/1CSaWxM6pIKf/0/MvtF3pCrefTBnbOi1elP3\n" + "Et8Vi+Sv9LuNd4HwTSNGSEqkLerwtwn+SYKtOeS4A8Krh2j1D/K1Vu4B5gaDmVDJtAr7zYYGoRB\n" + "QDWQ4c9h/csuyjI3fobnDyJR8fF6ltcTaXRwCkAuAsfMGr1TGFNdTmEwLWS2dIK6npLgK2T4Lle\n" + "pUZxoc9+6K4eR5NO5bPT73NoqIfoILT/JcFJDZr8zGiNXnvrfiur6/Y8tht7WvaexgbXf8AUrFt\n" + "8IExyvRusYDOsYTAIbfWdzHRJ8HN/tLj7OgdRZawmreHP2gt9wEfvtH0f7SkDXe7+o+AOn9DquL\n" + "f0mV+leQPH6H+axafUvrB07ptJtyshtTZDTEudJ7bWS5V6MmyltVLn7ht2hwECQP+isb60/Vqvr\n" + "tbLsa1lObVIJd9Gxv5rXx9F7fzHpIbf/jgfVnd/TLYj6XoOhJcP/zE+sOzd6dW7dt2eo3dH7/9R\n" + "JJWj//U8uiGFx76BFZLQ2xvLeVGAWQrFDJbtKBSHd6blNura4H3BbDXB7InVcZXZdh2bmTt7hbO\n" + "J1dj2gzCjlFnhPod3WLHB+n3o9ZsAkFVMfMrs7orLmgkHUdkyqZQQWWQbLGlrjMjUeSrfV3Ltsw\n" + "30EBzcd5YCedvLETJya66nWOIAaCVnfU/KuZn21CDVa02PngQdHf9LapMfVhzkaAPUUW3M91YaR\n" + "3YDJ+WiBmZGazPo9Kttdt2j63E6s/fft/d/NWjXkMra7KtO2qkE6cErHpvsyMmzPu0dY4Bg/dYP\n" + "otTpyoaMUI2XUya8tzG/pi0NMtICo/bsut21gdcWclkj5OncxaDrw6kM+9QxQzaWRAGii4pDqzC\n" + "MT02aX7WzPU9b7PrG3bvO6P6yStfZm+pHnPySS4590+3jf/V8yb+CsUbp8uyz0kDskbu2dmz9J8\n" + "lSt9Ld+gn1O8cKikmxXydbH+3bhsmfwWj/lONYlcwkhL6L4bfpOxn/tD0/wBN/N944Wh9VJm/b/\n" + "O+347df+/rl0k+O38GLJ83X/CfTOt7v2dV6P8AMbx6njHb/pKuN3pN2+IXnaSjybr8e31fUqd+0\n" + "Sj487DHMryZJMXjq+sfpPX84SXk6SSX/9kAOEJJTQQhAAAAAABVAAAAAQEAAAAPAEEAZABvAGIA\n" + "ZQAgAFAAaABvAHQAbwBzAGgAbwBwAAAAEwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAA\n" + "gADcALgAwAAAAAQA4QklNBAYAAAAAAAcABQAAAAEBAP/hFWdodHRwOi8vbnMuYWRvYmUuY29tL3\n" + "hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prY\n" + "zlkJz8+Cjw/YWRvYmUteGFwLWZpbHRlcnMgZXNjPSJDUiI/Pgo8eDp4YXBtZXRhIHhtbG5zOng9\n" + "J2Fkb2JlOm5zOm1ldGEvJyB4OnhhcHRrPSdYTVAgdG9vbGtpdCAyLjguMi0zMywgZnJhbWV3b3J\n" + "rIDEuNSc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi\n" + "1yZGYtc3ludGF4LW5zIycgeG1sbnM6aVg9J2h0dHA6Ly9ucy5hZG9iZS5jb20vaVgvMS4wLyc+C\n" + "gogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXVpZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05\n" + "MWQ1NDAzZjkyZjknCiAgeG1sbnM6cGRmPSdodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvJz4\n" + "KICA8IS0tIHBkZjpTdWJqZWN0IGlzIGFsaWFzZWQgLS0+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCi\n" + "A8cmRmOkRlc2NyaXB0aW9uIGFib3V0PSd1dWlkOjIyZDAyYjBhLWIyNDktMTFkYi04YWY4LTkxZ\n" + "DU0MDNmOTJmOScKICB4bWxuczpwaG90b3Nob3A9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9z\n" + "aG9wLzEuMC8nPgogIDwhLS0gcGhvdG9zaG9wOkNhcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwvcmR\n" + "mOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGEtYj\n" + "I0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcD0naHR0cDovL25zLmFkb2JlL\n" + "mNvbS94YXAvMS4wLyc+CiAgPCEtLSB4YXA6RGVzY3JpcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwv\n" + "cmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGE\n" + "tYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcE1NPSdodHRwOi8vbnMuYW\n" + "RvYmUuY29tL3hhcC8xLjAvbW0vJz4KICA8eGFwTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwa\n" + "G90b3Nob3A6MjJkMDJiMDYtYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5PC94YXBNTTpEb2N1\n" + "bWVudElEPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXV\n" + "pZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05MWQ1NDAzZjkyZjknCiAgeG1sbnM6ZGM9J2h0dH\n" + "A6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvJz4KICA8ZGM6ZGVzY3JpcHRpb24+CiAgIDxyZ\n" + "GY6QWx0PgogICAgPHJkZjpsaSB4bWw6bGFuZz0neC1kZWZhdWx0Jz4gICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgIDwvcmRmOkFsdD4KICA8L2RjOmRlc2NyaXB0aW9\n" + "uPgogPC9yZGY6RGVzY3JpcHRpb24+Cgo8L3JkZjpSREY+CjwveDp4YXBtZXRhPgogICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA\n" + "ogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0ndyc/P\n" + "v/uAA5BZG9iZQBkQAAAAAH/2wCEAAQDAwMDAwQDAwQGBAMEBgcFBAQFBwgGBgcGBggKCAkJCQkI\n" + "CgoMDAwMDAoMDAwMDAwMDAwMDAwMDAwMDAwMDAwBBAUFCAcIDwoKDxQODg4UFA4ODg4UEQwMDAw\n" + "MEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZAMBEQACEQEDEQ\n" + "H/3QAEAA3/xAGiAAAABwEBAQEBAAAAAAAAAAAEBQMCBgEABwgJCgsBAAICAwEBAQEBAAAAAAAAA\n" + "AEAAgMEBQYHCAkKCxAAAgEDAwIEAgYHAwQCBgJzAQIDEQQABSESMUFRBhNhInGBFDKRoQcVsUIj\n" + "wVLR4TMWYvAkcoLxJUM0U5KismNzwjVEJ5OjszYXVGR0w9LiCCaDCQoYGYSURUaktFbTVSga8uP\n" + "zxNTk9GV1hZWltcXV5fVmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9zhIWGh4iJiouMjY6PgpOUlZaX\n" + "mJmam5ydnp+So6SlpqeoqaqrrK2ur6EQACAgECAwUFBAUGBAgDA20BAAIRAwQhEjFBBVETYSIGc\n" + "YGRMqGx8BTB0eEjQhVSYnLxMyQ0Q4IWklMlomOywgdz0jXiRIMXVJMICQoYGSY2RRonZHRVN/Kj\n" + "s8MoKdPj84SUpLTE1OT0ZXWFlaW1xdXl9UZWZnaGlqa2xtbm9kdXZ3eHl6e3x9fn9zhIWGh4iJi\n" + "ouMjY6Pg5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6vr/2gAMAwEAAhEDEQA/APBnplwPAdR+GB\n" + "KY6dYtNG1w39yh4+xb+zIksgEfFaRSSoIx8f7RPRRkSWQimM+lRmwWVXFWYigHxUUVoMiJM+Fj0\n" + "tg0RBegLE0Wu+3c+GTBazFCGI7HtSp9slbFYYzyoBsegw2hY1Afl3wqqRqahk+0tDgKpgu4DAUU\n" + "+HY+GRS2ePiMKtUB3G+KGuONq//Q8OzpFbW5WnxMop4k9crG5ZnZNJkEOn21utVRYw7HxZtz+OR\n" + "vdsrZ2lRtci4aVxFEQA0neg/ZXxJpTITNNuOFss0vSotYNvZ2qGRkPKSTqiU8Sdqk5SZU5Ix8XJ\n" + "NNZ8k6bp8TtM73OputUtYq0Unux/hkRkJOzZLCAN2KR+VpbtSkCBaDnIzdlWu59u+XeJTjeASk8\n" + "+juZOESEAVqx8BvU/PJibScTrTy09560hkWOGFd2YgFnPQKD19zhOSkxw2l8Vm6XAiYb8gg+k5O\n" + "9mnhoon9H3cs5s7WF5pp29OGGMFndyaAKBuTiEEPQLD8h/NDmNdYlttNkYjlbFjcXCr3LLH8II8\n" + "C2WUGviZvon/OPWkm3RNSv72SYllMkKxQRV67CQMSKYQAxMkR/wBC56d61P0heel4cYuVOXWvTp\n" + "h4Qjjf/9Hw5qBYyISaqjBV+QpvkAzKcki4HomnIxck/wBhtlR2bhunvlDywddMUl4zW+kQ9FQ8X\n" + "nfuSewrtmPkycPvc/DhMhvyegXOrWWhmLQPKlsj6xIAiLCoZkY96nv7npmJvI2XOjQFMl0fyRqM\n" + "NoxvZvrGt33wlATwiMnVnY1LEdSfuyXF3KIDmUu88w2XlnTl8raAlb2ZFfVL0jdYRtQnxc7BfDC\n" + "OaJR7nm3me5tdOtjbMvp3ZRXkV6chVQRX79hmVjgZG+jgZ5jHGhzecXF5LPL6jEjstSSaDM51Ka\n" + "6MZ9S1C0sEBe8uZo4YCBXdjxGw60wEWyEqfUHkT8vLXRJFuLdTcaqfhlvWUErtukZ3ABPUjIXTE\n" + "m3rGmeV2Tk5UKz/AG/E/wAcgZKya20C3b02kjYtH8AqCygbkUH0nLYlgUb+gbWtPbpXt/n2ybB/\n" + "/9Lw4oaVxGd+PxH3qBkGaY3KyiSP01IkiUclH8sg+LKydm6INvZvKsFu+kWtvD8LRoFNRup6moO\n" + "aqd277HsGW+XPLmn6XM17FF6l7vW4fd2Zuu+RFls2tmUNrLJb7TSBertGQGqetDkxE0na0pvtHs\n" + "QkszWyiGAG5laYlnkeMVHJj8sA5rPk+SvMepTalqlxd3B5zTOXdj/MxqafLpm5xioh5nPK5kpRG\n" + "pkcKAST0A6k5NpfUP5K/ki1ssHmHzF+71KRQ8Nud/Qibb/kYw6/yjbrXISlSH07YaHbWyxx2kXE\n" + "KACB2zHJtLI7XSelBRvH2xCpvaaTDHXkOTVBPcUG2479RlsdmJVPRtvV+ylenQ0y62FP/9PxRpo\n" + "WG5FxKKxKFDA+GVS5NsebLdFsRePc3siVW4f4QR0QVAGYeSXR2unhtZ6s60K6jt+MMSFwtF2+xX\n" + "wr7eGUGLlRPQMsE2vxQm7itxKg3VCfT2+nb8cDYaCDtfOXmCCcROrQrUhkkCHYn6emRMqZxjbLd\n" + "F1+W/4xajHzjNCtQKMffETWUdngX5p+QZ9A8xS6hbo0ui37NNDPT7DOalHpsCD08Rmyw5ARTpdV\n" + "gIPEF35MeRn80ed4S5EdrpKm9kZ15K0iH92hB7Me/tmS60vt/QrCYyekiBdgSTXcjqV9q9MokFD\n" + "N7S3aFVVR8RoK9zldqndvAY6nffr/AGYQqLhjdpCoIAZW22HavU/LJBUP9WblX0xTw7fOmWsX/9\n" + "Tw7FdvMqWkQ3Z1qfED+mQIbI77PX/LFis9vBajZm2Y+x65rMh3t30Bsze400aVaIbSLk6r8CMRT\n" + "l/NmOcllnGDD9Y8uecNfEEiXrMgDGWAyGOOu5WlB+vMrHODTlxZCdjsyFdB006VpVtLasurQxBL\n" + "64WiLI4/aFT1ANOXemV5piR2b9NiljB4yyHy9CLOVI5GJhB+CvXY9R8xmINzs5HNZ+Z96BZpbxA\n" + "fVJo39UFefwopYgL4nMiMd2qZoIn/AJx00u3t/Lt7qpp9Yv5GLf5MUTERqfbvmzBeezjd9H+VlL\n" + "wSQzBqsvOGQD7L12rXsemPNxmXQSxxIPU2nFV4HYqR1xEUWj4ZAxBryr2G+J2VGDZlLrxUH6KZA\n" + "Fkqb15VFelfwy+2FP8A/9Xxlf6AdA182Yk9eFeLxSjoVfcfSMo4uIOfkweFOnpvlWYrLEwNFAA+\n" + "nMOYdrhFvQLeSO7coBXiK8iKiv07Zj8Ac4QtNrW1njUcKcT+yAR/xGmR4WcsStLpTuPU9IFaEsV\n" + "BP3k4m2AgBzSwyQNcIwNTE1aI3wnam9O2Ug7s5Ckk/NDndeVXa2H78MqqV6jmeBp9+ZWKXqDjZ4\n" + "+gvVvy30qCy0qzsLRBCnBI2VdgUTqPvOZ7y+Q7pz+bn5q6d+VflZxZlJ/NN4ypptk5qtB9qRwDX\n" + "gn/AAx2y2ItpfKFv+eH5qNeTajJ5ovVaVywSqvEtTUKqupAA6D2y0BNPtv/AJx//M5PzL8mJeXT\n" + "L+ndPf6rqarSpkAqsnEAAeoN6DpkJRYci9lROSgSUUH9o9K5Tw0ztfSHnXkOtK9q+PHwydq//9b\n" + "yxrVoZNBtNSA5zRMPXmH8j0CLXuBmHE+qneamHpEuqYeV7pzFVTRgQK5XMNmnlb1vyyY5QA1OwJ\n" + "+eUF2seTOLu5s7azVIVAkpVn/hhnIALG73Yz5jvb1dICqzpDNIqyFD8SxH7R28cxibZCiWOsdJs\n" + "PTM6XNstPhnkjIhcHuJBVfvOCiUSn0TfWrTTLjyw8guA/PifTO3xcxxA8a5ZAbimvJP0m3p/kFF\n" + "WxhmpWQJ9NW3zZPHz5vlb/nIDVbrWfzO1RJhxGnpDaRL/khA1T7ktmSOTAJhZaAUtLawsbayl8v\n" + "xWi3Gpay0cF3HPcFRJJHJMXVrcJ8UaAFG5LWjF8tAYW9H/wCcOo9bTzxrt/owkTyksZW5gkIKvI\n" + "7k26nvyReRJHyyBWT7dWQyOWlbnK2526e1O1MqIUFE84uPLkOdK9RXI0E2/wD/1/DA1bURZLY/W\n" + "ZDZqwb0eXw7dMgIi7bjllVXsz7yNcfWC0Vd3Ip92Y2UOz0cnsPlwyx8xQ/u24sMxCadoJp9LOXk\n" + "VX/uwRUE0BI8cokbLMyoKouHu2MaKGXw7fLDwgoGSkbHpaNZyLLHRSKcFFQQRvUdMlwUFOQyLzr\n" + "ztpCaba6fPau4ijv4OURY8AjVFKV7ZZiO+7Vnh6XvXkSWNbW2WTb92KDxIFMzwHlZc3zX+fuizW\n" + "f5p3ty8XGDU4YLmCQiisyII3+4rvl8UB5ffEghRGvOm7AbnvWvjk1fen/ONPldPKP5aWOpPCfr2\n" + "uE31y6q2wbaMEn+VAMDSdyzrzj+avlHyTp0l/r2rxWFuHWJuIeacu4qFCRgsajfBwsty89/6Gr/\n" + "ACa9an+JL/hSnrfoubhXwpXpjwhaL//Q8E1AqtcAZMs8l6i1nqMa1oSVP0VynKLDmaWdSfQXl69\n" + "jF1Jv8MhDb5rpB3AO7INRRLhhGp4R05FgaGvTMU8200xS70zVDMRp2pTIOvBmB3PgQP15kxIcnD\n" + "LH/EEz0rRvOJhldr9pQtCqyd6VrShGTqw5d4ARv9jHfOGl+ZJNMluLkyenaFbiRdqFYW5nrWuwO\n" + "MKB5MdSMRxnhlu9N8p6lLFpti63FUjCtFJTrDKvse2bEDZ4XJ9RZB+YPli2/Mjy5bxoUi1a0YS2\n" + "85UOwIXiy9jRu+TBppfOF1+V3m22vrdpNPM8cs/oo0VJlUqQPjValR3+IZNNvtLS9Yu9Mi0/TJr\n" + "kyp6QhWVVCIWRATsKBemwwFrDzT87fybs/wA1bW21PRb+DTvNlgGSRp6iC8i3KJJx+y6n7D0Pwm\n" + "hxBZXT55/6Fi/Nf0PW+qWXq+t6X1X67F6vD/ftK04V/wBl344U8b//0fBapxheVh9ocV+nviqY2\n" + "/qQJDew/bioWHiuQ8m0bbvaPKGtQ6jaxSo9JloCK75gZI0Xb4sgkHo8MouoAvP94BsRmGY7uWJU\n" + "gzbypOQpNOvIdK4Nw2WCE2tXulTkjEEbdafgclxMhFBas93dwyQzsWDghlJFONKHJCZtjOFBJfy\n" + "j1y9vPL9zpbIs0WkXL2sUjA8hDXlGCRXtt07ZuYvL5KJeo6bfajbkzWkcToR8dqshZ6in2fhNK/\n" + "PDTUlXmHVvMdr5o0v9H2kdrqGpfu7m0nkY87Uf7tkKAU4/s03ynLkEBbfihx7dGT6va67LbRMNR\n" + "aKOBuUTKgIBXoK1BOYR1M3aQ0mOt9yxUeZNdtJhFapLqMluSXkg5oxJrUMW5KevQ9MmNXXNqOiH\n" + "Rr/Hmv8A1r9I/oj95w+r+j9Yf1+NP5+nXtTD+dF8tkfkOlv/0vC3ph7f0/alcVTbS4A8QibuKb5\n" + "RI05EBYRFpdX3ly79a2qYCavH/EY7TCYyMD5PSdD8+wXUSn1ArDqOhBzFlipz4ZwWbaV5htbsgF\n" + "qg9crMXKErGyYwajFGzxyHlGSePbbwyqg5UZlCaxrFpaWU95LIqrEjMAT4Dp9OShGy1ZslBhv/A\n" + "Dj9rd/a+aL+xUK+m38L3d0HrxRo2HFtu5D8c27y8t30raarbWkU+u6g4gsNORn+EcUaSh2Pc0/4\n" + "lgtAjezzbT9SutY1i782al8Nxdyotqh6xWybIg+jc5q8s+I27bFDgFPQp9RE+nrag70+L6crrZu\n" + "4jajokdv6LW/Dii1Wo61PXKQN3KPK0L+h4/rnD/K5V78a5LhXxd3/0/DMXXtwxVNtL9Xkaf3f7N\n" + "etfbKMjdjtkZ9D6ufrlK0+HpX8coF9HJ26sXvfqXrf7i/U+uften/d/wCyrmQL6uOav0pvpP8Ai\n" + "b1F+rV59+vH6a5XLhcjH4nRmY/xpxHP0/UptWvT6Mx/RbmjxWK+aP8AFf1M/pCv1Kvxen9inavf\n" + "MrFwXtzcLUeLXq5Mv/I3nz1b0v8AjofuKVry9KrUpTanOlf9jmQ68va/zH9b/COn/o7/AI431mP\n" + "65SvLh+zWvbl9rMfNfC34K4kmj9T6lD6FKclp/DNYXZx5srsPrHor6nXvkgxTPS/U+rv6dPU5mt\n" + "fngFN5ulv+l/pL/Lp/scerHo//2Q==\n"; + +static std::string gCommandLine; + +TEST(Base64, LargeSample) { + LOG(LS_VERBOSE) << "Testing specific base64 file"; + + char unescaped[64 * 1024]; + + // unescape that massive blob above + size_t size = Base64Unescape(SpecificTest, + sizeof(SpecificTest), + unescaped, + sizeof(unescaped)); + + EXPECT_EQ(size, sizeof(testbase64)); + EXPECT_EQ(0, memcmp(testbase64, unescaped, sizeof(testbase64))); +} + +bool DecodeTest(const char* encoded, size_t expect_unparsed, + const char* decoded, Base64::DecodeFlags flags) +{ + std::string result; + size_t consumed = 0, encoded_len = strlen(encoded); + bool success = Base64::DecodeFromArray(encoded, encoded_len, flags, + &result, &consumed); + size_t unparsed = encoded_len - consumed; + EXPECT_EQ(expect_unparsed, unparsed) << "\"" << encoded + << "\" -> \"" << decoded + << "\""; + EXPECT_STREQ(decoded, result.c_str()); + return success; +} + +#define Flags(x,y,z) \ + Base64::DO_PARSE_##x | Base64::DO_PAD_##y | Base64::DO_TERM_##z + +TEST(Base64, DecodeParseOptions) { + // Trailing whitespace + EXPECT_TRUE (DecodeTest("YWJjZA== ", 1, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(ANY, YES, CHAR))); + + // Embedded whitespace + EXPECT_FALSE(DecodeTest("YWJjZA= =", 3, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(ANY, YES, CHAR))); + + // Embedded non-base64 characters + EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA=*=", 0, "abcd", Flags(ANY, YES, CHAR))); + + // Unexpected padding characters + EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a", Flags(STRICT, YES, CHAR))); + EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YW=JjZA==", 0, "abcd", Flags(ANY, YES, CHAR))); +} + +TEST(Base64, DecodePadOptions) { + // Padding + EXPECT_TRUE (DecodeTest("YWJjZA==", 0, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA==", 0, "abcd", Flags(STRICT, ANY, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA==", 2, "abcd", Flags(STRICT, NO, CHAR))); + + // Incomplete padding + EXPECT_FALSE(DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, ANY, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, NO, CHAR))); + + // No padding + EXPECT_FALSE(DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, ANY, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, NO, CHAR))); +} + +TEST(Base64, DecodeTerminateOptions) { + // Complete quantum + EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, BUFFER))); + EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, CHAR))); + EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, ANY))); + + // Complete quantum with trailing data + EXPECT_FALSE(DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, BUFFER))); + EXPECT_TRUE (DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, CHAR))); + EXPECT_TRUE (DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, ANY))); + + // Incomplete quantum + EXPECT_FALSE(DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, BUFFER))); + EXPECT_FALSE(DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, CHAR))); + EXPECT_TRUE (DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, ANY))); +} + +TEST(Base64, GetNextBase64Char) { + // The table looks like this: + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + char next_char; + EXPECT_TRUE(Base64::GetNextBase64Char('A', &next_char)); + EXPECT_EQ('B', next_char); + EXPECT_TRUE(Base64::GetNextBase64Char('Z', &next_char)); + EXPECT_EQ('a', next_char); + EXPECT_TRUE(Base64::GetNextBase64Char('/', &next_char)); + EXPECT_EQ('A', next_char); + EXPECT_FALSE(Base64::GetNextBase64Char('&', &next_char)); + EXPECT_FALSE(Base64::GetNextBase64Char('Z', NULL)); +} diff --git a/webrtc/base/base_tests.gyp b/webrtc/base/base_tests.gyp new file mode 100644 index 000000000..406c73e1e --- /dev/null +++ b/webrtc/base/base_tests.gyp @@ -0,0 +1,152 @@ +# Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. +{ + 'includes': [ '../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'webrtc_base_tests_utils', + 'type': 'static_library', + 'sources': [ + 'unittest_main.cc', + # Also use this as a convenient dumping ground for misc files that are + # included by multiple targets below. + 'fakecpumonitor.h', + 'fakenetwork.h', + 'fakesslidentity.h', + 'faketaskrunner.h', + 'gunit.h', + 'testbase64.h', + 'testechoserver.h', + 'win32toolhelp.h', + ], + 'dependencies': [ + 'base.gyp:webrtc_base', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + }, + { + 'target_name': 'webrtc_base_tests', + 'type': 'executable', + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + 'base.gyp:webrtc_base', + 'webrtc_base_tests_utils', + ], + 'sources': [ + 'asynchttprequest_unittest.cc', + 'atomicops_unittest.cc', + 'autodetectproxy_unittest.cc', + 'bandwidthsmoother_unittest.cc', + 'base64_unittest.cc', + 'basictypes_unittest.cc', + 'bind_unittest.cc', + 'buffer_unittest.cc', + 'bytebuffer_unittest.cc', + 'byteorder_unittest.cc', + 'callback_unittest.cc', + 'cpumonitor_unittest.cc', + 'crc32_unittest.cc', + 'criticalsection_unittest.cc', + 'event_unittest.cc', + 'filelock_unittest.cc', + 'fileutils_unittest.cc', + 'helpers_unittest.cc', + 'httpbase_unittest.cc', + 'httpcommon_unittest.cc', + 'httpserver_unittest.cc', + 'ipaddress_unittest.cc', + 'logging_unittest.cc', + 'md5digest_unittest.cc', + 'messagedigest_unittest.cc', + 'messagequeue_unittest.cc', + 'multipart_unittest.cc', + 'nat_unittest.cc', + 'network_unittest.cc', + 'nullsocketserver_unittest.cc', + 'optionsfile_unittest.cc', + 'pathutils_unittest.cc', + 'physicalsocketserver_unittest.cc', + 'profiler_unittest.cc', + 'proxy_unittest.cc', + 'proxydetect_unittest.cc', + 'ratelimiter_unittest.cc', + 'ratetracker_unittest.cc', + 'referencecountedsingletonfactory_unittest.cc', + 'rollingaccumulator_unittest.cc', + 'scopedptrcollection_unittest.cc', + 'sha1digest_unittest.cc', + 'sharedexclusivelock_unittest.cc', + 'signalthread_unittest.cc', + 'sigslot_unittest.cc', + 'socket_unittest.cc', + 'socket_unittest.h', + 'socketaddress_unittest.cc', + 'stream_unittest.cc', + 'stringencode_unittest.cc', + 'stringutils_unittest.cc', + # TODO(ronghuawu): Reenable this test. + # 'systeminfo_unittest.cc', + 'task_unittest.cc', + 'testclient_unittest.cc', + 'thread_unittest.cc', + 'timeutils_unittest.cc', + 'urlencode_unittest.cc', + 'versionparsing_unittest.cc', + 'virtualsocket_unittest.cc', + # TODO(ronghuawu): Reenable this test. + # 'windowpicker_unittest.cc', + ], + 'conditions': [ + ['OS=="linux"', { + 'sources': [ + 'latebindingsymboltable_unittest.cc', + # TODO(ronghuawu): Reenable this test. + # 'linux_unittest.cc', + 'linuxfdwalk_unittest.cc', + ], + }], + ['OS=="win"', { + 'sources': [ + 'win32_unittest.cc', + 'win32regkey_unittest.cc', + 'win32socketserver_unittest.cc', + 'win32toolhelp_unittest.cc', + 'win32window_unittest.cc', + 'win32windowpicker_unittest.cc', + 'winfirewall_unittest.cc', + ], + 'sources!': [ + # TODO(ronghuawu): Fix TestUdpReadyToSendIPv6 on windows bot + # then reenable these tests. + 'physicalsocketserver_unittest.cc', + 'socket_unittest.cc', + 'win32socketserver_unittest.cc', + 'win32windowpicker_unittest.cc', + ], + }], + ['OS=="mac"', { + 'sources': [ + 'macsocketserver_unittest.cc', + 'macutils_unittest.cc', + ], + }], + ['os_posix==1', { + 'sources': [ + 'sslidentity_unittest.cc', + 'sslstreamadapter_unittest.cc', + ], + }], + ['OS=="ios" or (OS=="mac" and target_arch!="ia32")', { + 'defines': [ + 'CARBON_DEPRECATED=YES', + ], + }], + ], # conditions + }, + ], +} diff --git a/webrtc/base/basicdefs.h b/webrtc/base/basicdefs.h new file mode 100644 index 000000000..1dee2ae65 --- /dev/null +++ b/webrtc/base/basicdefs.h @@ -0,0 +1,20 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_BASICDEFS_H_ +#define WEBRTC_BASE_BASICDEFS_H_ + +#if HAVE_CONFIG_H +#include "config.h" // NOLINT +#endif + +#define ARRAY_SIZE(x) (static_cast(sizeof(x) / sizeof(x[0]))) + +#endif // WEBRTC_BASE_BASICDEFS_H_ diff --git a/webrtc/base/basictypes.h b/webrtc/base/basictypes.h new file mode 100644 index 000000000..8fed1bad9 --- /dev/null +++ b/webrtc/base/basictypes.h @@ -0,0 +1,154 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_BASICTYPES_H_ +#define WEBRTC_BASE_BASICTYPES_H_ + +#include // for NULL, size_t + +#if !(defined(_MSC_VER) && (_MSC_VER < 1600)) +#include // for uintptr_t +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" // NOLINT +#endif + +#include "webrtc/base/constructormagic.h" + +#if !defined(INT_TYPES_DEFINED) +#define INT_TYPES_DEFINED +#ifdef COMPILER_MSVC +typedef unsigned __int64 uint64; +typedef __int64 int64; +#ifndef INT64_C +#define INT64_C(x) x ## I64 +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## UI64 +#endif +#define INT64_F "I64" +#else // COMPILER_MSVC +// On Mac OS X, cssmconfig.h defines uint64 as uint64_t +// TODO(fbarchard): Use long long for compatibility with chromium on BSD/OSX. +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +typedef uint64_t uint64; +typedef int64_t int64; +#ifndef INT64_C +#define INT64_C(x) x ## LL +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## ULL +#endif +#define INT64_F "l" +#elif defined(__LP64__) +typedef unsigned long uint64; // NOLINT +typedef long int64; // NOLINT +#ifndef INT64_C +#define INT64_C(x) x ## L +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## UL +#endif +#define INT64_F "l" +#else // __LP64__ +typedef unsigned long long uint64; // NOLINT +typedef long long int64; // NOLINT +#ifndef INT64_C +#define INT64_C(x) x ## LL +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## ULL +#endif +#define INT64_F "ll" +#endif // __LP64__ +#endif // COMPILER_MSVC +typedef unsigned int uint32; +typedef int int32; +typedef unsigned short uint16; // NOLINT +typedef short int16; // NOLINT +typedef unsigned char uint8; +typedef signed char int8; +#endif // INT_TYPES_DEFINED + +// Detect compiler is for x86 or x64. +#if defined(__x86_64__) || defined(_M_X64) || \ + defined(__i386__) || defined(_M_IX86) +#define CPU_X86 1 +#endif +// Detect compiler is for arm. +#if defined(__arm__) || defined(_M_ARM) +#define CPU_ARM 1 +#endif +#if defined(CPU_X86) && defined(CPU_ARM) +#error CPU_X86 and CPU_ARM both defined. +#endif +#if !defined(ARCH_CPU_BIG_ENDIAN) && !defined(ARCH_CPU_LITTLE_ENDIAN) +// x86, arm or GCC provided __BYTE_ORDER__ macros +#if CPU_X86 || CPU_ARM || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define ARCH_CPU_LITTLE_ENDIAN +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define ARCH_CPU_BIG_ENDIAN +#else +#error ARCH_CPU_BIG_ENDIAN or ARCH_CPU_LITTLE_ENDIAN should be defined. +#endif +#endif +#if defined(ARCH_CPU_BIG_ENDIAN) && defined(ARCH_CPU_LITTLE_ENDIAN) +#error ARCH_CPU_BIG_ENDIAN and ARCH_CPU_LITTLE_ENDIAN both defined. +#endif + +#if defined(WEBRTC_WIN) +typedef int socklen_t; +#endif + +// The following only works for C++ +#ifdef __cplusplus +namespace rtc { + template inline T _min(T a, T b) { return (a > b) ? b : a; } + template inline T _max(T a, T b) { return (a < b) ? b : a; } + + // For wait functions that take a number of milliseconds, kForever indicates + // unlimited time. + const int kForever = -1; +} + +#define ALIGNP(p, t) \ + (reinterpret_cast(((reinterpret_cast(p) + \ + ((t) - 1)) & ~((t) - 1)))) +#define IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a) - 1))) + +// Note: UNUSED is also defined in common.h +#ifndef UNUSED +#define UNUSED(x) Unused(static_cast(&x)) +#define UNUSED2(x, y) Unused(static_cast(&x)); \ + Unused(static_cast(&y)) +#define UNUSED3(x, y, z) Unused(static_cast(&x)); \ + Unused(static_cast(&y)); \ + Unused(static_cast(&z)) +#define UNUSED4(x, y, z, a) Unused(static_cast(&x)); \ + Unused(static_cast(&y)); \ + Unused(static_cast(&z)); \ + Unused(static_cast(&a)) +#define UNUSED5(x, y, z, a, b) Unused(static_cast(&x)); \ + Unused(static_cast(&y)); \ + Unused(static_cast(&z)); \ + Unused(static_cast(&a)); \ + Unused(static_cast(&b)) +inline void Unused(const void*) {} +#endif // UNUSED + +// Use these to declare and define a static local variable (static T;) so that +// it is leaked so that its destructors are not called at exit. +#define LIBJINGLE_DEFINE_STATIC_LOCAL(type, name, arguments) \ + static type& name = *new type arguments + +#endif // __cplusplus +#endif // WEBRTC_BASE_BASICTYPES_H_ diff --git a/webrtc/base/basictypes_unittest.cc b/webrtc/base/basictypes_unittest.cc new file mode 100644 index 000000000..20515ecf9 --- /dev/null +++ b/webrtc/base/basictypes_unittest.cc @@ -0,0 +1,75 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/basictypes.h" + +#include "webrtc/base/gunit.h" + +namespace rtc { + +TEST(BasicTypesTest, Endian) { + uint16 v16 = 0x1234u; + uint8 first_byte = *reinterpret_cast(&v16); +#if defined(ARCH_CPU_LITTLE_ENDIAN) + EXPECT_EQ(0x34u, first_byte); +#elif defined(ARCH_CPU_BIG_ENDIAN) + EXPECT_EQ(0x12u, first_byte); +#endif +} + +TEST(BasicTypesTest, SizeOfTypes) { + int8 i8 = -1; + uint8 u8 = 1u; + int16 i16 = -1; + uint16 u16 = 1u; + int32 i32 = -1; + uint32 u32 = 1u; + int64 i64 = -1; + uint64 u64 = 1u; + EXPECT_EQ(1u, sizeof(i8)); + EXPECT_EQ(1u, sizeof(u8)); + EXPECT_EQ(2u, sizeof(i16)); + EXPECT_EQ(2u, sizeof(u16)); + EXPECT_EQ(4u, sizeof(i32)); + EXPECT_EQ(4u, sizeof(u32)); + EXPECT_EQ(8u, sizeof(i64)); + EXPECT_EQ(8u, sizeof(u64)); + EXPECT_GT(0, i8); + EXPECT_LT(0u, u8); + EXPECT_GT(0, i16); + EXPECT_LT(0u, u16); + EXPECT_GT(0, i32); + EXPECT_LT(0u, u32); + EXPECT_GT(0, i64); + EXPECT_LT(0u, u64); +} + +TEST(BasicTypesTest, SizeOfConstants) { + EXPECT_EQ(8u, sizeof(INT64_C(0))); + EXPECT_EQ(8u, sizeof(UINT64_C(0))); + EXPECT_EQ(8u, sizeof(INT64_C(0x1234567887654321))); + EXPECT_EQ(8u, sizeof(UINT64_C(0x8765432112345678))); +} + +// Test CPU_ macros +#if !defined(CPU_ARM) && defined(__arm__) +#error expected CPU_ARM to be defined. +#endif +#if !defined(CPU_X86) && (defined(WEBRTC_WIN) || defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)) +#error expected CPU_X86 to be defined. +#endif +#if !defined(ARCH_CPU_LITTLE_ENDIAN) && \ + (defined(WEBRTC_WIN) || defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(CPU_X86)) +#error expected ARCH_CPU_LITTLE_ENDIAN to be defined. +#endif + +// TODO(fbarchard): Test all macros in basictypes.h + +} // namespace rtc diff --git a/webrtc/base/bind.h b/webrtc/base/bind.h new file mode 100644 index 000000000..2e3104edf --- /dev/null +++ b/webrtc/base/bind.h @@ -0,0 +1,587 @@ +// This file was GENERATED by command: +// pump.py bind.h.pump +// DO NOT EDIT BY HAND!!! + +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// To generate bind.h from bind.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump + +// Bind() is an overloaded function that converts method calls into function +// objects (aka functors). It captures any arguments to the method by value +// when Bind is called, producing a stateful, nullary function object. Care +// should be taken about the lifetime of objects captured by Bind(); the +// returned functor knows nothing about the lifetime of the method's object or +// any arguments passed by pointer, and calling the functor with a destroyed +// object will surely do bad things. +// +// Example usage: +// struct Foo { +// int Test1() { return 42; } +// int Test2() const { return 52; } +// int Test3(int x) { return x*x; } +// float Test4(int x, float y) { return x + y; } +// }; +// +// int main() { +// Foo foo; +// cout << rtc::Bind(&Foo::Test1, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test2, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test3, &foo, 3)() << endl; +// cout << rtc::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl; +// } + +#ifndef WEBRTC_BASE_BIND_H_ +#define WEBRTC_BASE_BIND_H_ + +#define NONAME + +namespace rtc { +namespace detail { +// This is needed because the template parameters in Bind can't be resolved +// if they're used both as parameters of the function pointer type and as +// parameters to Bind itself: the function pointer parameters are exact +// matches to the function prototype, but the parameters to bind have +// references stripped. This trick allows the compiler to dictate the Bind +// parameter types rather than deduce them. +template struct identity { typedef T type; }; +} // namespace detail + +template +class MethodFunctor0 { + public: + MethodFunctor0(MethodT method, ObjectT* object) + : method_(method), object_(object) {} + R operator()() const { + return (object_->*method_)(); } + private: + MethodT method_; + ObjectT* object_; +}; + +template +class Functor0 { + public: + explicit Functor0(const FunctorT& functor) + : functor_(functor) {} + R operator()() const { + return functor_(); } + private: + FunctorT functor_; +}; + + +#define FP_T(x) R (ObjectT::*x)() + +template +MethodFunctor0 +Bind(FP_T(method), ObjectT* object) { + return MethodFunctor0( + method, object); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)() const + +template +MethodFunctor0 +Bind(FP_T(method), const ObjectT* object) { + return MethodFunctor0( + method, object); +} + +#undef FP_T +#define FP_T(x) R (*x)() + +template +Functor0 +Bind(FP_T(function)) { + return Functor0( + function); +} + +#undef FP_T + +template +class MethodFunctor1 { + public: + MethodFunctor1(MethodT method, ObjectT* object, + P1 p1) + : method_(method), object_(object), + p1_(p1) {} + R operator()() const { + return (object_->*method_)(p1_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; +}; + +template +class Functor1 { + public: + Functor1(const FunctorT& functor, P1 p1) + : functor_(functor), + p1_(p1) {} + R operator()() const { + return functor_(p1_); } + private: + FunctorT functor_; + P1 p1_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1) + +template +MethodFunctor1 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1) { + return MethodFunctor1( + method, object, p1); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1) const + +template +MethodFunctor1 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1) { + return MethodFunctor1( + method, object, p1); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1) + +template +Functor1 +Bind(FP_T(function), + typename detail::identity::type p1) { + return Functor1( + function, p1); +} + +#undef FP_T + +template +class MethodFunctor2 { + public: + MethodFunctor2(MethodT method, ObjectT* object, + P1 p1, + P2 p2) + : method_(method), object_(object), + p1_(p1), + p2_(p2) {} + R operator()() const { + return (object_->*method_)(p1_, p2_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; +}; + +template +class Functor2 { + public: + Functor2(const FunctorT& functor, P1 p1, P2 p2) + : functor_(functor), + p1_(p1), + p2_(p2) {} + R operator()() const { + return functor_(p1_, p2_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2) + +template +MethodFunctor2 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2) { + return MethodFunctor2( + method, object, p1, p2); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2) const + +template +MethodFunctor2 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2) { + return MethodFunctor2( + method, object, p1, p2); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2) + +template +Functor2 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2) { + return Functor2( + function, p1, p2); +} + +#undef FP_T + +template +class MethodFunctor3 { + public: + MethodFunctor3(MethodT method, ObjectT* object, + P1 p1, + P2 p2, + P3 p3) + : method_(method), object_(object), + p1_(p1), + p2_(p2), + p3_(p3) {} + R operator()() const { + return (object_->*method_)(p1_, p2_, p3_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; + P3 p3_; +}; + +template +class Functor3 { + public: + Functor3(const FunctorT& functor, P1 p1, P2 p2, P3 p3) + : functor_(functor), + p1_(p1), + p2_(p2), + p3_(p3) {} + R operator()() const { + return functor_(p1_, p2_, p3_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; + P3 p3_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3) + +template +MethodFunctor3 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3) { + return MethodFunctor3( + method, object, p1, p2, p3); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3) const + +template +MethodFunctor3 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3) { + return MethodFunctor3( + method, object, p1, p2, p3); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2, P3) + +template +Functor3 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3) { + return Functor3( + function, p1, p2, p3); +} + +#undef FP_T + +template +class MethodFunctor4 { + public: + MethodFunctor4(MethodT method, ObjectT* object, + P1 p1, + P2 p2, + P3 p3, + P4 p4) + : method_(method), object_(object), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4) {} + R operator()() const { + return (object_->*method_)(p1_, p2_, p3_, p4_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; +}; + +template +class Functor4 { + public: + Functor4(const FunctorT& functor, P1 p1, P2 p2, P3 p3, P4 p4) + : functor_(functor), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4) {} + R operator()() const { + return functor_(p1_, p2_, p3_, p4_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4) + +template +MethodFunctor4 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4) { + return MethodFunctor4( + method, object, p1, p2, p3, p4); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4) const + +template +MethodFunctor4 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4) { + return MethodFunctor4( + method, object, p1, p2, p3, p4); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2, P3, P4) + +template +Functor4 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4) { + return Functor4( + function, p1, p2, p3, p4); +} + +#undef FP_T + +template +class MethodFunctor5 { + public: + MethodFunctor5(MethodT method, ObjectT* object, + P1 p1, + P2 p2, + P3 p3, + P4 p4, + P5 p5) + : method_(method), object_(object), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5) {} + R operator()() const { + return (object_->*method_)(p1_, p2_, p3_, p4_, p5_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; +}; + +template +class Functor5 { + public: + Functor5(const FunctorT& functor, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) + : functor_(functor), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5) {} + R operator()() const { + return functor_(p1_, p2_, p3_, p4_, p5_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5) + +template +MethodFunctor5 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4, + typename detail::identity::type p5) { + return MethodFunctor5( + method, object, p1, p2, p3, p4, p5); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5) const + +template +MethodFunctor5 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4, + typename detail::identity::type p5) { + return MethodFunctor5( + method, object, p1, p2, p3, p4, p5); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2, P3, P4, P5) + +template +Functor5 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4, + typename detail::identity::type p5) { + return Functor5( + function, p1, p2, p3, p4, p5); +} + +#undef FP_T + +} // namespace rtc + +#undef NONAME + +#endif // WEBRTC_BASE_BIND_H_ diff --git a/webrtc/base/bind.h.pump b/webrtc/base/bind.h.pump new file mode 100644 index 000000000..b5663c45d --- /dev/null +++ b/webrtc/base/bind.h.pump @@ -0,0 +1,138 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// To generate bind.h from bind.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump + +// Bind() is an overloaded function that converts method calls into function +// objects (aka functors). It captures any arguments to the method by value +// when Bind is called, producing a stateful, nullary function object. Care +// should be taken about the lifetime of objects captured by Bind(); the +// returned functor knows nothing about the lifetime of the method's object or +// any arguments passed by pointer, and calling the functor with a destroyed +// object will surely do bad things. +// +// Example usage: +// struct Foo { +// int Test1() { return 42; } +// int Test2() const { return 52; } +// int Test3(int x) { return x*x; } +// float Test4(int x, float y) { return x + y; } +// }; +// +// int main() { +// Foo foo; +// cout << rtc::Bind(&Foo::Test1, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test2, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test3, &foo, 3)() << endl; +// cout << rtc::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl; +// } + +#ifndef WEBRTC_BASE_BIND_H_ +#define WEBRTC_BASE_BIND_H_ + +#define NONAME + +namespace rtc { +namespace detail { +// This is needed because the template parameters in Bind can't be resolved +// if they're used both as parameters of the function pointer type and as +// parameters to Bind itself: the function pointer parameters are exact +// matches to the function prototype, but the parameters to bind have +// references stripped. This trick allows the compiler to dictate the Bind +// parameter types rather than deduce them. +template struct identity { typedef T type; }; +} // namespace detail + +$var n = 5 +$range i 0..n +$for i [[ +$range j 1..i + +template +class MethodFunctor$i { + public: + MethodFunctor$i(MethodT method, ObjectT* object$for j [[, + P$j p$j]]) + : method_(method), object_(object)$for j [[, + p$(j)_(p$j)]] {} + R operator()() const { + return (object_->*method_)($for j , [[p$(j)_]]); } + private: + MethodT method_; + ObjectT* object_;$for j [[ + + P$j p$(j)_;]] + +}; + +template +class Functor$i { + public: + $if i == 0 [[explicit ]] +Functor$i(const FunctorT& functor$for j [[, P$j p$j]]) + : functor_(functor)$for j [[, + p$(j)_(p$j)]] {} + R operator()() const { + return functor_($for j , [[p$(j)_]]); } + private: + FunctorT functor_;$for j [[ + + P$j p$(j)_;]] + +}; + + +#define FP_T(x) R (ObjectT::*x)($for j , [[P$j]]) + +template +MethodFunctor$i +Bind(FP_T(method), ObjectT* object$for j [[, + typename detail::identity::type p$j]]) { + return MethodFunctor$i( + method, object$for j [[, p$j]]); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)($for j , [[P$j]]) const + +template +MethodFunctor$i +Bind(FP_T(method), const ObjectT* object$for j [[, + typename detail::identity::type p$j]]) { + return MethodFunctor$i( + method, object$for j [[, p$j]]); +} + +#undef FP_T +#define FP_T(x) R (*x)($for j , [[P$j]]) + +template +Functor$i +Bind(FP_T(function)$for j [[, + typename detail::identity::type p$j]]) { + return Functor$i( + function$for j [[, p$j]]); +} + +#undef FP_T + +]] + +} // namespace rtc + +#undef NONAME + +#endif // WEBRTC_BASE_BIND_H_ diff --git a/webrtc/base/bind_unittest.cc b/webrtc/base/bind_unittest.cc new file mode 100644 index 000000000..ed8dd5cf2 --- /dev/null +++ b/webrtc/base/bind_unittest.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/bind.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +namespace { + +struct MethodBindTester { + void NullaryVoid() { ++call_count; } + int NullaryInt() { ++call_count; return 1; } + int NullaryConst() const { ++call_count; return 2; } + void UnaryVoid(int dummy) { ++call_count; } + template T Identity(T value) { ++call_count; return value; } + int UnaryByRef(int& value) const { ++call_count; return ++value; } // NOLINT + int Multiply(int a, int b) const { ++call_count; return a * b; } + mutable int call_count; +}; + +int Return42() { return 42; } +int Negate(int a) { return -a; } +int Multiply(int a, int b) { return a * b; } + +} // namespace + +TEST(BindTest, BindToMethod) { + MethodBindTester object = {0}; + EXPECT_EQ(0, object.call_count); + Bind(&MethodBindTester::NullaryVoid, &object)(); + EXPECT_EQ(1, object.call_count); + EXPECT_EQ(1, Bind(&MethodBindTester::NullaryInt, &object)()); + EXPECT_EQ(2, object.call_count); + EXPECT_EQ(2, Bind(&MethodBindTester::NullaryConst, + static_cast(&object))()); + EXPECT_EQ(3, object.call_count); + Bind(&MethodBindTester::UnaryVoid, &object, 5)(); + EXPECT_EQ(4, object.call_count); + EXPECT_EQ(100, Bind(&MethodBindTester::Identity, &object, 100)()); + EXPECT_EQ(5, object.call_count); + const std::string string_value("test string"); + EXPECT_EQ(string_value, Bind(&MethodBindTester::Identity, + &object, string_value)()); + EXPECT_EQ(6, object.call_count); + int value = 11; + EXPECT_EQ(12, Bind(&MethodBindTester::UnaryByRef, &object, value)()); + EXPECT_EQ(12, value); + EXPECT_EQ(7, object.call_count); + EXPECT_EQ(56, Bind(&MethodBindTester::Multiply, &object, 7, 8)()); + EXPECT_EQ(8, object.call_count); +} + +TEST(BindTest, BindToFunction) { + EXPECT_EQ(42, Bind(&Return42)()); + EXPECT_EQ(3, Bind(&Negate, -3)()); + EXPECT_EQ(56, Bind(&Multiply, 8, 7)()); +} + +} // namespace rtc diff --git a/webrtc/base/buffer.h b/webrtc/base/buffer.h new file mode 100644 index 000000000..dbe7b1aa7 --- /dev/null +++ b/webrtc/base/buffer.h @@ -0,0 +1,102 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_BUFFER_H_ +#define WEBRTC_BASE_BUFFER_H_ + +#include + +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +// Basic buffer class, can be grown and shrunk dynamically. +// Unlike std::string/vector, does not initialize data when expanding capacity. +class Buffer { + public: + Buffer() { + Construct(NULL, 0, 0); + } + Buffer(const void* data, size_t length) { + Construct(data, length, length); + } + Buffer(const void* data, size_t length, size_t capacity) { + Construct(data, length, capacity); + } + Buffer(const Buffer& buf) { + Construct(buf.data(), buf.length(), buf.length()); + } + + const char* data() const { return data_.get(); } + char* data() { return data_.get(); } + // TODO: should this be size(), like STL? + size_t length() const { return length_; } + size_t capacity() const { return capacity_; } + + Buffer& operator=(const Buffer& buf) { + if (&buf != this) { + Construct(buf.data(), buf.length(), buf.length()); + } + return *this; + } + bool operator==(const Buffer& buf) const { + return (length_ == buf.length() && + memcmp(data_.get(), buf.data(), length_) == 0); + } + bool operator!=(const Buffer& buf) const { + return !operator==(buf); + } + + void SetData(const void* data, size_t length) { + ASSERT(data != NULL || length == 0); + SetLength(length); + memcpy(data_.get(), data, length); + } + void AppendData(const void* data, size_t length) { + ASSERT(data != NULL || length == 0); + size_t old_length = length_; + SetLength(length_ + length); + memcpy(data_.get() + old_length, data, length); + } + void SetLength(size_t length) { + SetCapacity(length); + length_ = length; + } + void SetCapacity(size_t capacity) { + if (capacity > capacity_) { + rtc::scoped_ptr data(new char[capacity]); + memcpy(data.get(), data_.get(), length_); + data_.swap(data); + capacity_ = capacity; + } + } + + void TransferTo(Buffer* buf) { + ASSERT(buf != NULL); + buf->data_.reset(data_.release()); + buf->length_ = length_; + buf->capacity_ = capacity_; + Construct(NULL, 0, 0); + } + + protected: + void Construct(const void* data, size_t length, size_t capacity) { + data_.reset(new char[capacity_ = capacity]); + SetData(data, length); + } + + scoped_ptr data_; + size_t length_; + size_t capacity_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BUFFER_H_ diff --git a/webrtc/base/buffer_unittest.cc b/webrtc/base/buffer_unittest.cc new file mode 100644 index 000000000..71b3f89e3 --- /dev/null +++ b/webrtc/base/buffer_unittest.cc @@ -0,0 +1,143 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/buffer.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +static const char kTestData[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF +}; + +TEST(BufferTest, TestConstructDefault) { + Buffer buf; + EXPECT_EQ(0U, buf.length()); + EXPECT_EQ(0U, buf.capacity()); + EXPECT_EQ(Buffer(), buf); +} + +TEST(BufferTest, TestConstructEmptyWithCapacity) { + Buffer buf(NULL, 0, 256U); + EXPECT_EQ(0U, buf.length()); + EXPECT_EQ(256U, buf.capacity()); + EXPECT_EQ(Buffer(), buf); +} + +TEST(BufferTest, TestConstructData) { + Buffer buf(kTestData, sizeof(kTestData)); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf); +} + +TEST(BufferTest, TestConstructDataWithCapacity) { + Buffer buf(kTestData, sizeof(kTestData), 256U); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(256U, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf); +} + +TEST(BufferTest, TestConstructCopy) { + Buffer buf1(kTestData, sizeof(kTestData), 256), buf2(buf1); + EXPECT_EQ(sizeof(kTestData), buf2.length()); + EXPECT_EQ(sizeof(kTestData), buf2.capacity()); // capacity isn't copied + EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(buf1, buf2); +} + +TEST(BufferTest, TestAssign) { + Buffer buf1, buf2(kTestData, sizeof(kTestData), 256); + EXPECT_NE(buf1, buf2); + buf1 = buf2; + EXPECT_EQ(sizeof(kTestData), buf1.length()); + EXPECT_EQ(sizeof(kTestData), buf1.capacity()); // capacity isn't copied + EXPECT_EQ(0, memcmp(buf1.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(buf1, buf2); +} + +TEST(BufferTest, TestSetData) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestAppendData) { + Buffer buf(kTestData, sizeof(kTestData)); + buf.AppendData(kTestData, sizeof(kTestData)); + EXPECT_EQ(2 * sizeof(kTestData), buf.length()); + EXPECT_EQ(2 * sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData), + kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetLengthSmaller) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + buf.SetLength(sizeof(kTestData) / 2); + EXPECT_EQ(sizeof(kTestData) / 2, buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData) / 2)); +} + +TEST(BufferTest, TestSetLengthLarger) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + buf.SetLength(sizeof(kTestData) * 2); + EXPECT_EQ(sizeof(kTestData) * 2, buf.length()); + EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetCapacitySmaller) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + buf.SetCapacity(sizeof(kTestData) / 2); // should be ignored + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetCapacityLarger) { + Buffer buf(kTestData, sizeof(kTestData)); + buf.SetCapacity(sizeof(kTestData) * 2); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetCapacityThenSetLength) { + Buffer buf(kTestData, sizeof(kTestData)); + buf.SetCapacity(sizeof(kTestData) * 4); + memcpy(buf.data() + sizeof(kTestData), kTestData, sizeof(kTestData)); + buf.SetLength(sizeof(kTestData) * 2); + EXPECT_EQ(sizeof(kTestData) * 2, buf.length()); + EXPECT_EQ(sizeof(kTestData) * 4, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData), + kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestTransfer) { + Buffer buf1(kTestData, sizeof(kTestData), 256U), buf2; + buf1.TransferTo(&buf2); + EXPECT_EQ(0U, buf1.length()); + EXPECT_EQ(0U, buf1.capacity()); + EXPECT_EQ(sizeof(kTestData), buf2.length()); + EXPECT_EQ(256U, buf2.capacity()); // capacity does transfer + EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData))); +} + +} // namespace rtc diff --git a/webrtc/base/bytebuffer.cc b/webrtc/base/bytebuffer.cc new file mode 100644 index 000000000..6133759e5 --- /dev/null +++ b/webrtc/base/bytebuffer.cc @@ -0,0 +1,234 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/bytebuffer.h" + +#include +#include + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" + +namespace rtc { + +static const int DEFAULT_SIZE = 4096; + +ByteBuffer::ByteBuffer() { + Construct(NULL, DEFAULT_SIZE, ORDER_NETWORK); +} + +ByteBuffer::ByteBuffer(ByteOrder byte_order) { + Construct(NULL, DEFAULT_SIZE, byte_order); +} + +ByteBuffer::ByteBuffer(const char* bytes, size_t len) { + Construct(bytes, len, ORDER_NETWORK); +} + +ByteBuffer::ByteBuffer(const char* bytes, size_t len, ByteOrder byte_order) { + Construct(bytes, len, byte_order); +} + +ByteBuffer::ByteBuffer(const char* bytes) { + Construct(bytes, strlen(bytes), ORDER_NETWORK); +} + +void ByteBuffer::Construct(const char* bytes, size_t len, + ByteOrder byte_order) { + version_ = 0; + start_ = 0; + size_ = len; + byte_order_ = byte_order; + bytes_ = new char[size_]; + + if (bytes) { + end_ = len; + memcpy(bytes_, bytes, end_); + } else { + end_ = 0; + } +} + +ByteBuffer::~ByteBuffer() { + delete[] bytes_; +} + +bool ByteBuffer::ReadUInt8(uint8* val) { + if (!val) return false; + + return ReadBytes(reinterpret_cast(val), 1); +} + +bool ByteBuffer::ReadUInt16(uint16* val) { + if (!val) return false; + + uint16 v; + if (!ReadBytes(reinterpret_cast(&v), 2)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost16(v) : v; + return true; + } +} + +bool ByteBuffer::ReadUInt24(uint32* val) { + if (!val) return false; + + uint32 v = 0; + char* read_into = reinterpret_cast(&v); + if (byte_order_ == ORDER_NETWORK || IsHostBigEndian()) { + ++read_into; + } + + if (!ReadBytes(read_into, 3)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost32(v) : v; + return true; + } +} + +bool ByteBuffer::ReadUInt32(uint32* val) { + if (!val) return false; + + uint32 v; + if (!ReadBytes(reinterpret_cast(&v), 4)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost32(v) : v; + return true; + } +} + +bool ByteBuffer::ReadUInt64(uint64* val) { + if (!val) return false; + + uint64 v; + if (!ReadBytes(reinterpret_cast(&v), 8)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost64(v) : v; + return true; + } +} + +bool ByteBuffer::ReadString(std::string* val, size_t len) { + if (!val) return false; + + if (len > Length()) { + return false; + } else { + val->append(bytes_ + start_, len); + start_ += len; + return true; + } +} + +bool ByteBuffer::ReadBytes(char* val, size_t len) { + if (len > Length()) { + return false; + } else { + memcpy(val, bytes_ + start_, len); + start_ += len; + return true; + } +} + +void ByteBuffer::WriteUInt8(uint8 val) { + WriteBytes(reinterpret_cast(&val), 1); +} + +void ByteBuffer::WriteUInt16(uint16 val) { + uint16 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork16(val) : val; + WriteBytes(reinterpret_cast(&v), 2); +} + +void ByteBuffer::WriteUInt24(uint32 val) { + uint32 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork32(val) : val; + char* start = reinterpret_cast(&v); + if (byte_order_ == ORDER_NETWORK || IsHostBigEndian()) { + ++start; + } + WriteBytes(start, 3); +} + +void ByteBuffer::WriteUInt32(uint32 val) { + uint32 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork32(val) : val; + WriteBytes(reinterpret_cast(&v), 4); +} + +void ByteBuffer::WriteUInt64(uint64 val) { + uint64 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork64(val) : val; + WriteBytes(reinterpret_cast(&v), 8); +} + +void ByteBuffer::WriteString(const std::string& val) { + WriteBytes(val.c_str(), val.size()); +} + +void ByteBuffer::WriteBytes(const char* val, size_t len) { + memcpy(ReserveWriteBuffer(len), val, len); +} + +char* ByteBuffer::ReserveWriteBuffer(size_t len) { + if (Length() + len > Capacity()) + Resize(Length() + len); + + char* start = bytes_ + end_; + end_ += len; + return start; +} + +void ByteBuffer::Resize(size_t size) { + size_t len = _min(end_ - start_, size); + if (size <= size_) { + // Don't reallocate, just move data backwards + memmove(bytes_, bytes_ + start_, len); + } else { + // Reallocate a larger buffer. + size_ = _max(size, 3 * size_ / 2); + char* new_bytes = new char[size_]; + memcpy(new_bytes, bytes_ + start_, len); + delete [] bytes_; + bytes_ = new_bytes; + } + start_ = 0; + end_ = len; + ++version_; +} + +bool ByteBuffer::Consume(size_t size) { + if (size > Length()) + return false; + start_ += size; + return true; +} + +ByteBuffer::ReadPosition ByteBuffer::GetReadPosition() const { + return ReadPosition(start_, version_); +} + +bool ByteBuffer::SetReadPosition(const ReadPosition &position) { + if (position.version_ != version_) { + return false; + } + start_ = position.start_; + return true; +} + +void ByteBuffer::Clear() { + memset(bytes_, 0, size_); + start_ = end_ = 0; + ++version_; +} + +} // namespace rtc diff --git a/webrtc/base/bytebuffer.h b/webrtc/base/bytebuffer.h new file mode 100644 index 000000000..1934f418e --- /dev/null +++ b/webrtc/base/bytebuffer.h @@ -0,0 +1,119 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_BYTEBUFFER_H_ +#define WEBRTC_BASE_BYTEBUFFER_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +class ByteBuffer { + public: + + enum ByteOrder { + ORDER_NETWORK = 0, // Default, use network byte order (big endian). + ORDER_HOST, // Use the native order of the host. + }; + + // |byte_order| defines order of bytes in the buffer. + ByteBuffer(); + explicit ByteBuffer(ByteOrder byte_order); + ByteBuffer(const char* bytes, size_t len); + ByteBuffer(const char* bytes, size_t len, ByteOrder byte_order); + + // Initializes buffer from a zero-terminated string. + explicit ByteBuffer(const char* bytes); + + ~ByteBuffer(); + + const char* Data() const { return bytes_ + start_; } + size_t Length() const { return end_ - start_; } + size_t Capacity() const { return size_ - start_; } + ByteOrder Order() const { return byte_order_; } + + // Read a next value from the buffer. Return false if there isn't + // enough data left for the specified type. + bool ReadUInt8(uint8* val); + bool ReadUInt16(uint16* val); + bool ReadUInt24(uint32* val); + bool ReadUInt32(uint32* val); + bool ReadUInt64(uint64* val); + bool ReadBytes(char* val, size_t len); + + // Appends next |len| bytes from the buffer to |val|. Returns false + // if there is less than |len| bytes left. + bool ReadString(std::string* val, size_t len); + + // Write value to the buffer. Resizes the buffer when it is + // neccessary. + void WriteUInt8(uint8 val); + void WriteUInt16(uint16 val); + void WriteUInt24(uint32 val); + void WriteUInt32(uint32 val); + void WriteUInt64(uint64 val); + void WriteString(const std::string& val); + void WriteBytes(const char* val, size_t len); + + // Reserves the given number of bytes and returns a char* that can be written + // into. Useful for functions that require a char* buffer and not a + // ByteBuffer. + char* ReserveWriteBuffer(size_t len); + + // Resize the buffer to the specified |size|. This invalidates any remembered + // seek positions. + void Resize(size_t size); + + // Moves current position |size| bytes forward. Returns false if + // there is less than |size| bytes left in the buffer. Consume doesn't + // permanently remove data, so remembered read positions are still valid + // after this call. + bool Consume(size_t size); + + // Clears the contents of the buffer. After this, Length() will be 0. + void Clear(); + + // Used with GetReadPosition/SetReadPosition. + class ReadPosition { + friend class ByteBuffer; + ReadPosition(size_t start, int version) + : start_(start), version_(version) { } + size_t start_; + int version_; + }; + + // Remembers the current read position for a future SetReadPosition. Any + // calls to Shift or Resize in the interim will invalidate the position. + ReadPosition GetReadPosition() const; + + // If the given position is still valid, restores that read position. + bool SetReadPosition(const ReadPosition &position); + + private: + void Construct(const char* bytes, size_t size, ByteOrder byte_order); + + char* bytes_; + size_t size_; + size_t start_; + size_t end_; + int version_; + ByteOrder byte_order_; + + // There are sensible ways to define these, but they aren't needed in our code + // base. + DISALLOW_COPY_AND_ASSIGN(ByteBuffer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BYTEBUFFER_H_ diff --git a/webrtc/base/bytebuffer_unittest.cc b/webrtc/base/bytebuffer_unittest.cc new file mode 100644 index 000000000..f4b0504ef --- /dev/null +++ b/webrtc/base/bytebuffer_unittest.cc @@ -0,0 +1,211 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +TEST(ByteBufferTest, TestByteOrder) { + uint16 n16 = 1; + uint32 n32 = 1; + uint64 n64 = 1; + + EXPECT_EQ(n16, NetworkToHost16(HostToNetwork16(n16))); + EXPECT_EQ(n32, NetworkToHost32(HostToNetwork32(n32))); + EXPECT_EQ(n64, NetworkToHost64(HostToNetwork64(n64))); + + if (IsHostBigEndian()) { + // The host is the network (big) endian. + EXPECT_EQ(n16, HostToNetwork16(n16)); + EXPECT_EQ(n32, HostToNetwork32(n32)); + EXPECT_EQ(n64, HostToNetwork64(n64)); + + // GetBE converts big endian to little endian here. + EXPECT_EQ(n16 >> 8, GetBE16(&n16)); + EXPECT_EQ(n32 >> 24, GetBE32(&n32)); + EXPECT_EQ(n64 >> 56, GetBE64(&n64)); + } else { + // The host is little endian. + EXPECT_NE(n16, HostToNetwork16(n16)); + EXPECT_NE(n32, HostToNetwork32(n32)); + EXPECT_NE(n64, HostToNetwork64(n64)); + + // GetBE converts little endian to big endian here. + EXPECT_EQ(GetBE16(&n16), HostToNetwork16(n16)); + EXPECT_EQ(GetBE32(&n32), HostToNetwork32(n32)); + EXPECT_EQ(GetBE64(&n64), HostToNetwork64(n64)); + + // GetBE converts little endian to big endian here. + EXPECT_EQ(n16 << 8, GetBE16(&n16)); + EXPECT_EQ(n32 << 24, GetBE32(&n32)); + EXPECT_EQ(n64 << 56, GetBE64(&n64)); + } +} + +TEST(ByteBufferTest, TestBufferLength) { + ByteBuffer buffer; + size_t size = 0; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt8(1); + ++size; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt16(1); + size += 2; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt24(1); + size += 3; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt32(1); + size += 4; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt64(1); + size += 8; + EXPECT_EQ(size, buffer.Length()); + + EXPECT_TRUE(buffer.Consume(0)); + EXPECT_EQ(size, buffer.Length()); + + EXPECT_TRUE(buffer.Consume(4)); + size -= 4; + EXPECT_EQ(size, buffer.Length()); +} + +TEST(ByteBufferTest, TestGetSetReadPosition) { + ByteBuffer buffer("ABCDEF", 6); + EXPECT_EQ(6U, buffer.Length()); + ByteBuffer::ReadPosition pos(buffer.GetReadPosition()); + EXPECT_TRUE(buffer.SetReadPosition(pos)); + EXPECT_EQ(6U, buffer.Length()); + std::string read; + EXPECT_TRUE(buffer.ReadString(&read, 3)); + EXPECT_EQ("ABC", read); + EXPECT_EQ(3U, buffer.Length()); + EXPECT_TRUE(buffer.SetReadPosition(pos)); + EXPECT_EQ(6U, buffer.Length()); + read.clear(); + EXPECT_TRUE(buffer.ReadString(&read, 3)); + EXPECT_EQ("ABC", read); + EXPECT_EQ(3U, buffer.Length()); + // For a resize by writing Capacity() number of bytes. + size_t capacity = buffer.Capacity(); + buffer.ReserveWriteBuffer(buffer.Capacity()); + EXPECT_EQ(capacity + 3U, buffer.Length()); + EXPECT_FALSE(buffer.SetReadPosition(pos)); + read.clear(); + EXPECT_TRUE(buffer.ReadString(&read, 3)); + EXPECT_EQ("DEF", read); +} + +TEST(ByteBufferTest, TestReadWriteBuffer) { + ByteBuffer::ByteOrder orders[2] = { ByteBuffer::ORDER_HOST, + ByteBuffer::ORDER_NETWORK }; + for (size_t i = 0; i < ARRAY_SIZE(orders); i++) { + ByteBuffer buffer(orders[i]); + EXPECT_EQ(orders[i], buffer.Order()); + uint8 ru8; + EXPECT_FALSE(buffer.ReadUInt8(&ru8)); + + // Write and read uint8. + uint8 wu8 = 1; + buffer.WriteUInt8(wu8); + EXPECT_TRUE(buffer.ReadUInt8(&ru8)); + EXPECT_EQ(wu8, ru8); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint16. + uint16 wu16 = (1 << 8) + 1; + buffer.WriteUInt16(wu16); + uint16 ru16; + EXPECT_TRUE(buffer.ReadUInt16(&ru16)); + EXPECT_EQ(wu16, ru16); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint24. + uint32 wu24 = (3 << 16) + (2 << 8) + 1; + buffer.WriteUInt24(wu24); + uint32 ru24; + EXPECT_TRUE(buffer.ReadUInt24(&ru24)); + EXPECT_EQ(wu24, ru24); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint32. + uint32 wu32 = (4 << 24) + (3 << 16) + (2 << 8) + 1; + buffer.WriteUInt32(wu32); + uint32 ru32; + EXPECT_TRUE(buffer.ReadUInt32(&ru32)); + EXPECT_EQ(wu32, ru32); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint64. + uint32 another32 = (8 << 24) + (7 << 16) + (6 << 8) + 5; + uint64 wu64 = (static_cast(another32) << 32) + wu32; + buffer.WriteUInt64(wu64); + uint64 ru64; + EXPECT_TRUE(buffer.ReadUInt64(&ru64)); + EXPECT_EQ(wu64, ru64); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read string. + std::string write_string("hello"); + buffer.WriteString(write_string); + std::string read_string; + EXPECT_TRUE(buffer.ReadString(&read_string, write_string.size())); + EXPECT_EQ(write_string, read_string); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read bytes + char write_bytes[] = "foo"; + buffer.WriteBytes(write_bytes, 3); + char read_bytes[3]; + EXPECT_TRUE(buffer.ReadBytes(read_bytes, 3)); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(write_bytes[i], read_bytes[i]); + } + EXPECT_EQ(0U, buffer.Length()); + + // Write and read reserved buffer space + char* write_dst = buffer.ReserveWriteBuffer(3); + memcpy(write_dst, write_bytes, 3); + memset(read_bytes, 0, 3); + EXPECT_TRUE(buffer.ReadBytes(read_bytes, 3)); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(write_bytes[i], read_bytes[i]); + } + EXPECT_EQ(0U, buffer.Length()); + + // Write and read in order. + buffer.WriteUInt8(wu8); + buffer.WriteUInt16(wu16); + buffer.WriteUInt24(wu24); + buffer.WriteUInt32(wu32); + buffer.WriteUInt64(wu64); + EXPECT_TRUE(buffer.ReadUInt8(&ru8)); + EXPECT_EQ(wu8, ru8); + EXPECT_TRUE(buffer.ReadUInt16(&ru16)); + EXPECT_EQ(wu16, ru16); + EXPECT_TRUE(buffer.ReadUInt24(&ru24)); + EXPECT_EQ(wu24, ru24); + EXPECT_TRUE(buffer.ReadUInt32(&ru32)); + EXPECT_EQ(wu32, ru32); + EXPECT_TRUE(buffer.ReadUInt64(&ru64)); + EXPECT_EQ(wu64, ru64); + EXPECT_EQ(0U, buffer.Length()); + } +} + +} // namespace rtc diff --git a/webrtc/base/byteorder.h b/webrtc/base/byteorder.h new file mode 100644 index 000000000..d907d9e41 --- /dev/null +++ b/webrtc/base/byteorder.h @@ -0,0 +1,168 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_BYTEORDER_H_ +#define WEBRTC_BASE_BYTEORDER_H_ + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) +#include +#endif + +#if defined(WEBRTC_WIN) +#include +#endif + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Reading and writing of little and big-endian numbers from memory +// TODO: Optimized versions, with direct read/writes of +// integers in host-endian format, when the platform supports it. + +inline void Set8(void* memory, size_t offset, uint8 v) { + static_cast(memory)[offset] = v; +} + +inline uint8 Get8(const void* memory, size_t offset) { + return static_cast(memory)[offset]; +} + +inline void SetBE16(void* memory, uint16 v) { + Set8(memory, 0, static_cast(v >> 8)); + Set8(memory, 1, static_cast(v >> 0)); +} + +inline void SetBE32(void* memory, uint32 v) { + Set8(memory, 0, static_cast(v >> 24)); + Set8(memory, 1, static_cast(v >> 16)); + Set8(memory, 2, static_cast(v >> 8)); + Set8(memory, 3, static_cast(v >> 0)); +} + +inline void SetBE64(void* memory, uint64 v) { + Set8(memory, 0, static_cast(v >> 56)); + Set8(memory, 1, static_cast(v >> 48)); + Set8(memory, 2, static_cast(v >> 40)); + Set8(memory, 3, static_cast(v >> 32)); + Set8(memory, 4, static_cast(v >> 24)); + Set8(memory, 5, static_cast(v >> 16)); + Set8(memory, 6, static_cast(v >> 8)); + Set8(memory, 7, static_cast(v >> 0)); +} + +inline uint16 GetBE16(const void* memory) { + return static_cast((Get8(memory, 0) << 8) | + (Get8(memory, 1) << 0)); +} + +inline uint32 GetBE32(const void* memory) { + return (static_cast(Get8(memory, 0)) << 24) | + (static_cast(Get8(memory, 1)) << 16) | + (static_cast(Get8(memory, 2)) << 8) | + (static_cast(Get8(memory, 3)) << 0); +} + +inline uint64 GetBE64(const void* memory) { + return (static_cast(Get8(memory, 0)) << 56) | + (static_cast(Get8(memory, 1)) << 48) | + (static_cast(Get8(memory, 2)) << 40) | + (static_cast(Get8(memory, 3)) << 32) | + (static_cast(Get8(memory, 4)) << 24) | + (static_cast(Get8(memory, 5)) << 16) | + (static_cast(Get8(memory, 6)) << 8) | + (static_cast(Get8(memory, 7)) << 0); +} + +inline void SetLE16(void* memory, uint16 v) { + Set8(memory, 0, static_cast(v >> 0)); + Set8(memory, 1, static_cast(v >> 8)); +} + +inline void SetLE32(void* memory, uint32 v) { + Set8(memory, 0, static_cast(v >> 0)); + Set8(memory, 1, static_cast(v >> 8)); + Set8(memory, 2, static_cast(v >> 16)); + Set8(memory, 3, static_cast(v >> 24)); +} + +inline void SetLE64(void* memory, uint64 v) { + Set8(memory, 0, static_cast(v >> 0)); + Set8(memory, 1, static_cast(v >> 8)); + Set8(memory, 2, static_cast(v >> 16)); + Set8(memory, 3, static_cast(v >> 24)); + Set8(memory, 4, static_cast(v >> 32)); + Set8(memory, 5, static_cast(v >> 40)); + Set8(memory, 6, static_cast(v >> 48)); + Set8(memory, 7, static_cast(v >> 56)); +} + +inline uint16 GetLE16(const void* memory) { + return static_cast((Get8(memory, 0) << 0) | + (Get8(memory, 1) << 8)); +} + +inline uint32 GetLE32(const void* memory) { + return (static_cast(Get8(memory, 0)) << 0) | + (static_cast(Get8(memory, 1)) << 8) | + (static_cast(Get8(memory, 2)) << 16) | + (static_cast(Get8(memory, 3)) << 24); +} + +inline uint64 GetLE64(const void* memory) { + return (static_cast(Get8(memory, 0)) << 0) | + (static_cast(Get8(memory, 1)) << 8) | + (static_cast(Get8(memory, 2)) << 16) | + (static_cast(Get8(memory, 3)) << 24) | + (static_cast(Get8(memory, 4)) << 32) | + (static_cast(Get8(memory, 5)) << 40) | + (static_cast(Get8(memory, 6)) << 48) | + (static_cast(Get8(memory, 7)) << 56); +} + +// Check if the current host is big endian. +inline bool IsHostBigEndian() { + static const int number = 1; + return 0 == *reinterpret_cast(&number); +} + +inline uint16 HostToNetwork16(uint16 n) { + uint16 result; + SetBE16(&result, n); + return result; +} + +inline uint32 HostToNetwork32(uint32 n) { + uint32 result; + SetBE32(&result, n); + return result; +} + +inline uint64 HostToNetwork64(uint64 n) { + uint64 result; + SetBE64(&result, n); + return result; +} + +inline uint16 NetworkToHost16(uint16 n) { + return GetBE16(&n); +} + +inline uint32 NetworkToHost32(uint32 n) { + return GetBE32(&n); +} + +inline uint64 NetworkToHost64(uint64 n) { + return GetBE64(&n); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_BYTEORDER_H_ diff --git a/webrtc/base/byteorder_unittest.cc b/webrtc/base/byteorder_unittest.cc new file mode 100644 index 000000000..f4e7df3b7 --- /dev/null +++ b/webrtc/base/byteorder_unittest.cc @@ -0,0 +1,83 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/byteorder.h" + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +// Test memory set functions put values into memory in expected order. +TEST(ByteOrderTest, TestSet) { + uint8 buf[8] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + Set8(buf, 0, 0xfb); + Set8(buf, 1, 0x12); + EXPECT_EQ(0xfb, buf[0]); + EXPECT_EQ(0x12, buf[1]); + SetBE16(buf, 0x1234); + EXPECT_EQ(0x12, buf[0]); + EXPECT_EQ(0x34, buf[1]); + SetLE16(buf, 0x1234); + EXPECT_EQ(0x34, buf[0]); + EXPECT_EQ(0x12, buf[1]); + SetBE32(buf, 0x12345678); + EXPECT_EQ(0x12, buf[0]); + EXPECT_EQ(0x34, buf[1]); + EXPECT_EQ(0x56, buf[2]); + EXPECT_EQ(0x78, buf[3]); + SetLE32(buf, 0x12345678); + EXPECT_EQ(0x78, buf[0]); + EXPECT_EQ(0x56, buf[1]); + EXPECT_EQ(0x34, buf[2]); + EXPECT_EQ(0x12, buf[3]); + SetBE64(buf, UINT64_C(0x0123456789abcdef)); + EXPECT_EQ(0x01, buf[0]); + EXPECT_EQ(0x23, buf[1]); + EXPECT_EQ(0x45, buf[2]); + EXPECT_EQ(0x67, buf[3]); + EXPECT_EQ(0x89, buf[4]); + EXPECT_EQ(0xab, buf[5]); + EXPECT_EQ(0xcd, buf[6]); + EXPECT_EQ(0xef, buf[7]); + SetLE64(buf, UINT64_C(0x0123456789abcdef)); + EXPECT_EQ(0xef, buf[0]); + EXPECT_EQ(0xcd, buf[1]); + EXPECT_EQ(0xab, buf[2]); + EXPECT_EQ(0x89, buf[3]); + EXPECT_EQ(0x67, buf[4]); + EXPECT_EQ(0x45, buf[5]); + EXPECT_EQ(0x23, buf[6]); + EXPECT_EQ(0x01, buf[7]); +} + +// Test memory get functions get values from memory in expected order. +TEST(ByteOrderTest, TestGet) { + uint8 buf[8]; + buf[0] = 0x01u; + buf[1] = 0x23u; + buf[2] = 0x45u; + buf[3] = 0x67u; + buf[4] = 0x89u; + buf[5] = 0xabu; + buf[6] = 0xcdu; + buf[7] = 0xefu; + EXPECT_EQ(0x01u, Get8(buf, 0)); + EXPECT_EQ(0x23u, Get8(buf, 1)); + EXPECT_EQ(0x0123u, GetBE16(buf)); + EXPECT_EQ(0x2301u, GetLE16(buf)); + EXPECT_EQ(0x01234567u, GetBE32(buf)); + EXPECT_EQ(0x67452301u, GetLE32(buf)); + EXPECT_EQ(UINT64_C(0x0123456789abcdef), GetBE64(buf)); + EXPECT_EQ(UINT64_C(0xefcdab8967452301), GetLE64(buf)); +} + +} // namespace rtc + diff --git a/webrtc/base/callback.h b/webrtc/base/callback.h new file mode 100644 index 000000000..949510e95 --- /dev/null +++ b/webrtc/base/callback.h @@ -0,0 +1,261 @@ +// This file was GENERATED by command: +// pump.py callback.h.pump +// DO NOT EDIT BY HAND!!! + +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// To generate callback.h from callback.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py callback.h.pump + +// Callbacks are callable object containers. They can hold a function pointer +// or a function object and behave like a value type. Internally, data is +// reference-counted, making copies and pass-by-value inexpensive. +// +// Callbacks are typed using template arguments. The format is: +// CallbackN +// where N is the number of arguments supplied to the callable object. +// Callbacks are invoked using operator(), just like a function or a function +// object. Default-constructed callbacks are "empty," and executing an empty +// callback does nothing. A callback can be made empty by assigning it from +// a default-constructed callback. +// +// Callbacks are similar in purpose to std::function (which isn't available on +// all platforms we support) and a lightweight alternative to sigslots. Since +// they effectively hide the type of the object they call, they're useful in +// breaking dependencies between objects that need to interact with one another. +// Notably, they can hold the results of Bind(), std::bind*, etc, without +// needing +// to know the resulting object type of those calls. +// +// Sigslots, on the other hand, provide a fuller feature set, such as multiple +// subscriptions to a signal, optional thread-safety, and lifetime tracking of +// slots. When these features are needed, choose sigslots. +// +// Example: +// int sqr(int x) { return x * x; } +// struct AddK { +// int k; +// int operator()(int x) const { return x + k; } +// } add_k = {5}; +// +// Callback1 my_callback; +// cout << my_callback.empty() << endl; // true +// +// my_callback = Callback1(&sqr); +// cout << my_callback.empty() << endl; // false +// cout << my_callback(3) << endl; // 9 +// +// my_callback = Callback1(add_k); +// cout << my_callback(10) << endl; // 15 +// +// my_callback = Callback1(); +// cout << my_callback.empty() << endl; // true + +#ifndef WEBRTC_BASE_CALLBACK_H_ +#define WEBRTC_BASE_CALLBACK_H_ + +#include "webrtc/base/logging.h" +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ref_ptr.h" + +namespace rtc { + +template +class Callback0 { + public: + // Default copy operations are appropriate for this class. + Callback0() {} + template Callback0(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()() { + if (empty()) + return R(); + return helper_->Run(); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run() = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run() { + return functor_(); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback1 { + public: + // Default copy operations are appropriate for this class. + Callback1() {} + template Callback1(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1) { + if (empty()) + return R(); + return helper_->Run(p1); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1) { + return functor_(p1); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback2 { + public: + // Default copy operations are appropriate for this class. + Callback2() {} + template Callback2(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2) { + if (empty()) + return R(); + return helper_->Run(p1, p2); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2) { + return functor_(p1, p2); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback3 { + public: + // Default copy operations are appropriate for this class. + Callback3() {} + template Callback3(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2, P3 p3) { + if (empty()) + return R(); + return helper_->Run(p1, p2, p3); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2, P3 p3) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2, P3 p3) { + return functor_(p1, p2, p3); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback4 { + public: + // Default copy operations are appropriate for this class. + Callback4() {} + template Callback4(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2, P3 p3, P4 p4) { + if (empty()) + return R(); + return helper_->Run(p1, p2, p3, p4); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4) { + return functor_(p1, p2, p3, p4); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback5 { + public: + // Default copy operations are appropriate for this class. + Callback5() {} + template Callback5(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { + if (empty()) + return R(); + return helper_->Run(p1, p2, p3, p4, p5); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { + return functor_(p1, p2, p3, p4, p5); + } + T functor_; + }; + scoped_refptr helper_; +}; +} // namespace rtc + +#endif // WEBRTC_BASE_CALLBACK_H_ diff --git a/webrtc/base/callback.h.pump b/webrtc/base/callback.h.pump new file mode 100644 index 000000000..86957df52 --- /dev/null +++ b/webrtc/base/callback.h.pump @@ -0,0 +1,103 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// To generate callback.h from callback.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py callback.h.pump + +// Callbacks are callable object containers. They can hold a function pointer +// or a function object and behave like a value type. Internally, data is +// reference-counted, making copies and pass-by-value inexpensive. +// +// Callbacks are typed using template arguments. The format is: +// CallbackN +// where N is the number of arguments supplied to the callable object. +// Callbacks are invoked using operator(), just like a function or a function +// object. Default-constructed callbacks are "empty," and executing an empty +// callback does nothing. A callback can be made empty by assigning it from +// a default-constructed callback. +// +// Callbacks are similar in purpose to std::function (which isn't available on +// all platforms we support) and a lightweight alternative to sigslots. Since +// they effectively hide the type of the object they call, they're useful in +// breaking dependencies between objects that need to interact with one another. +// Notably, they can hold the results of Bind(), std::bind*, etc, without needing +// to know the resulting object type of those calls. +// +// Sigslots, on the other hand, provide a fuller feature set, such as multiple +// subscriptions to a signal, optional thread-safety, and lifetime tracking of +// slots. When these features are needed, choose sigslots. +// +// Example: +// int sqr(int x) { return x * x; } +// struct AddK { +// int k; +// int operator()(int x) const { return x + k; } +// } add_k = {5}; +// +// Callback1 my_callback; +// cout << my_callback.empty() << endl; // true +// +// my_callback = Callback1(&sqr); +// cout << my_callback.empty() << endl; // false +// cout << my_callback(3) << endl; // 9 +// +// my_callback = Callback1(add_k); +// cout << my_callback(10) << endl; // 15 +// +// my_callback = Callback1(); +// cout << my_callback.empty() << endl; // true + +#ifndef WEBRTC_BASE_CALLBACK_H_ +#define WEBRTC_BASE_CALLBACK_H_ + +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ref_ptr.h" + +namespace rtc { + +$var n = 5 +$range i 0..n +$for i [[ +$range j 1..i + +template +class Callback$i { + public: + // Default copy operations are appropriate for this class. + Callback$i() {} + template Callback$i(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()($for j , [[P$j p$j]]) { + if (empty()) + return R(); + return helper_->Run($for j , [[p$j]]); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run($for j , [[P$j p$j]]) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run($for j , [[P$j p$j]]) { + return functor_($for j , [[p$j]]); + } + T functor_; + }; + scoped_refptr helper_; +}; + +]] +} // namespace rtc + +#endif // WEBRTC_BASE_CALLBACK_H_ diff --git a/webrtc/base/callback_unittest.cc b/webrtc/base/callback_unittest.cc new file mode 100644 index 000000000..66c939140 --- /dev/null +++ b/webrtc/base/callback_unittest.cc @@ -0,0 +1,81 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/bind.h" +#include "webrtc/base/callback.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +namespace { + +void f() {} +int g() { return 42; } +int h(int x) { return x * x; } +void i(int& x) { x *= x; } // NOLINT: Testing refs + +struct BindTester { + int a() { return 24; } + int b(int x) const { return x * x; } +}; + +} // namespace + +TEST(CallbackTest, VoidReturn) { + Callback0 cb; + EXPECT_TRUE(cb.empty()); + cb(); // Executing an empty callback should not crash. + cb = Callback0(&f); + EXPECT_FALSE(cb.empty()); + cb(); +} + +TEST(CallbackTest, IntReturn) { + Callback0 cb; + EXPECT_TRUE(cb.empty()); + cb = Callback0(&g); + EXPECT_FALSE(cb.empty()); + EXPECT_EQ(42, cb()); + EXPECT_EQ(42, cb()); +} + +TEST(CallbackTest, OneParam) { + Callback1 cb1(&h); + EXPECT_FALSE(cb1.empty()); + EXPECT_EQ(9, cb1(-3)); + EXPECT_EQ(100, cb1(10)); + + // Try clearing a callback. + cb1 = Callback1(); + EXPECT_TRUE(cb1.empty()); + + // Try a callback with a ref parameter. + Callback1 cb2(&i); + int x = 3; + cb2(x); + EXPECT_EQ(9, x); + cb2(x); + EXPECT_EQ(81, x); +} + +TEST(CallbackTest, WithBind) { + BindTester t; + Callback0 cb1 = Bind(&BindTester::a, &t); + EXPECT_EQ(24, cb1()); + EXPECT_EQ(24, cb1()); + cb1 = Bind(&BindTester::b, &t, 10); + EXPECT_EQ(100, cb1()); + EXPECT_EQ(100, cb1()); + cb1 = Bind(&BindTester::b, &t, 5); + EXPECT_EQ(25, cb1()); + EXPECT_EQ(25, cb1()); +} + +} // namespace rtc diff --git a/webrtc/base/checks.cc b/webrtc/base/checks.cc new file mode 100644 index 000000000..dd7183362 --- /dev/null +++ b/webrtc/base/checks.cc @@ -0,0 +1,30 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" + +void Fatal(const char* file, int line, const char* format, ...) { + char msg[256]; + + va_list arguments; + va_start(arguments, format); + vsnprintf(msg, sizeof(msg), format, arguments); + va_end(arguments); + + LOG(LS_ERROR) << "\n\n#\n# Fatal error in " << file + << ", line " << line << "\n#" << msg + << "\n#\n"; + abort(); +} diff --git a/webrtc/base/checks.h b/webrtc/base/checks.h new file mode 100644 index 000000000..93f7580ed --- /dev/null +++ b/webrtc/base/checks.h @@ -0,0 +1,27 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This module contains some basic debugging facilities. +// Originally comes from shared/commandlineflags/checks.h + +#ifndef WEBRTC_BASE_CHECKS_H_ +#define WEBRTC_BASE_CHECKS_H_ + +#include + +// Prints an error message to stderr and aborts execution. +void Fatal(const char* file, int line, const char* format, ...); + + +// The UNREACHABLE macro is very useful during development. +#define UNREACHABLE() \ + Fatal(__FILE__, __LINE__, "unreachable code") + +#endif // WEBRTC_BASE_CHECKS_H_ diff --git a/webrtc/base/common.cc b/webrtc/base/common.cc new file mode 100644 index 000000000..8ea475b0e --- /dev/null +++ b/webrtc/base/common.cc @@ -0,0 +1,64 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include + +#if WEBRTC_WIN +#define WIN32_LEAN_AND_MEAN +#include +#endif // WEBRTC_WIN + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +#include +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +////////////////////////////////////////////////////////////////////// +// Assertions +////////////////////////////////////////////////////////////////////// + +namespace rtc { + +void Break() { +#if WEBRTC_WIN + ::DebugBreak(); +#else // !WEBRTC_WIN + // On POSIX systems, SIGTRAP signals debuggers to break without killing the + // process. If a debugger isn't attached, the uncaught SIGTRAP will crash the + // app. + raise(SIGTRAP); +#endif + // If a debugger wasn't attached, we will have crashed by this point. If a + // debugger is attached, we'll continue from here. +} + +static AssertLogger custom_assert_logger_ = NULL; + +void SetCustomAssertLogger(AssertLogger logger) { + custom_assert_logger_ = logger; +} + +void LogAssert(const char* function, const char* file, int line, + const char* expression) { + if (custom_assert_logger_) { + custom_assert_logger_(function, file, line, expression); + } else { + LOG(LS_ERROR) << file << "(" << line << ")" << ": ASSERT FAILED: " + << expression << " @ " << function; + } +} + +} // namespace rtc diff --git a/webrtc/base/common.h b/webrtc/base/common.h new file mode 100644 index 000000000..51a5d1e2d --- /dev/null +++ b/webrtc/base/common.h @@ -0,0 +1,201 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_COMMON_H_ // NOLINT +#define WEBRTC_BASE_COMMON_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" + +#if defined(_MSC_VER) +// warning C4355: 'this' : used in base member initializer list +#pragma warning(disable:4355) +#endif + +////////////////////////////////////////////////////////////////////// +// General Utilities +////////////////////////////////////////////////////////////////////// + +// Note: UNUSED is also defined in basictypes.h +#ifndef UNUSED +#define UNUSED(x) Unused(static_cast(&x)) +#define UNUSED2(x, y) Unused(static_cast(&x)); \ + Unused(static_cast(&y)) +#define UNUSED3(x, y, z) Unused(static_cast(&x)); \ + Unused(static_cast(&y)); \ + Unused(static_cast(&z)) +#define UNUSED4(x, y, z, a) Unused(static_cast(&x)); \ + Unused(static_cast(&y)); \ + Unused(static_cast(&z)); \ + Unused(static_cast(&a)) +#define UNUSED5(x, y, z, a, b) Unused(static_cast(&x)); \ + Unused(static_cast(&y)); \ + Unused(static_cast(&z)); \ + Unused(static_cast(&a)); \ + Unused(static_cast(&b)) +inline void Unused(const void*) {} +#endif // UNUSED + +#if !defined(WEBRTC_WIN) + +#ifndef strnicmp +#define strnicmp(x, y, n) strncasecmp(x, y, n) +#endif + +#ifndef stricmp +#define stricmp(x, y) strcasecmp(x, y) +#endif + +// TODO(fbarchard): Remove this. std::max should be used everywhere in the code. +// NOMINMAX must be defined where we include . +#define stdmax(x, y) std::max(x, y) +#else +#define stdmax(x, y) rtc::_max(x, y) +#endif + +#define ARRAY_SIZE(x) (static_cast(sizeof(x) / sizeof(x[0]))) + +///////////////////////////////////////////////////////////////////////////// +// Assertions +///////////////////////////////////////////////////////////////////////////// + +#ifndef ENABLE_DEBUG +#define ENABLE_DEBUG _DEBUG +#endif // !defined(ENABLE_DEBUG) + +// Even for release builds, allow for the override of LogAssert. Though no +// macro is provided, this can still be used for explicit runtime asserts +// and allow applications to override the assert behavior. + +namespace rtc { + + +// If a debugger is attached, triggers a debugger breakpoint. If a debugger is +// not attached, forces program termination. +void Break(); + +// LogAssert writes information about an assertion to the log. It's called by +// Assert (and from the ASSERT macro in debug mode) before any other action +// is taken (e.g. breaking the debugger, abort()ing, etc.). +void LogAssert(const char* function, const char* file, int line, + const char* expression); + +typedef void (*AssertLogger)(const char* function, + const char* file, + int line, + const char* expression); + +// Sets a custom assert logger to be used instead of the default LogAssert +// behavior. To clear the custom assert logger, pass NULL for |logger| and the +// default behavior will be restored. Only one custom assert logger can be set +// at a time, so this should generally be set during application startup and +// only by one component. +void SetCustomAssertLogger(AssertLogger logger); + +} // namespace rtc + +#if ENABLE_DEBUG + +namespace rtc { + +inline bool Assert(bool result, const char* function, const char* file, + int line, const char* expression) { + if (!result) { + LogAssert(function, file, line, expression); + Break(); + return false; + } + return true; +} + +} // namespace rtc + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#define __FUNCTION__ "" +#endif + +#ifndef ASSERT +#define ASSERT(x) \ + (void)rtc::Assert((x), __FUNCTION__, __FILE__, __LINE__, #x) +#endif + +#ifndef VERIFY +#define VERIFY(x) rtc::Assert((x), __FUNCTION__, __FILE__, __LINE__, #x) +#endif + +#else // !ENABLE_DEBUG + +namespace rtc { + +inline bool ImplicitCastToBool(bool result) { return result; } + +} // namespace rtc + +#ifndef ASSERT +#define ASSERT(x) (void)0 +#endif + +#ifndef VERIFY +#define VERIFY(x) rtc::ImplicitCastToBool(x) +#endif + +#endif // !ENABLE_DEBUG + +#define COMPILE_TIME_ASSERT(expr) char CTA_UNIQUE_NAME[expr] +#define CTA_UNIQUE_NAME CTA_MAKE_NAME(__LINE__) +#define CTA_MAKE_NAME(line) CTA_MAKE_NAME2(line) +#define CTA_MAKE_NAME2(line) constraint_ ## line + +// Forces compiler to inline, even against its better judgement. Use wisely. +#if defined(__GNUC__) +#define FORCE_INLINE __attribute__((always_inline)) +#elif defined(WEBRTC_WIN) +#define FORCE_INLINE __forceinline +#else +#define FORCE_INLINE +#endif + +// Borrowed from Chromium's base/compiler_specific.h. +// Annotate a virtual method indicating it must be overriding a virtual +// method in the parent class. +// Use like: +// virtual void foo() OVERRIDE; +#if defined(WEBRTC_WIN) +#define OVERRIDE override +#elif defined(__clang__) +// Clang defaults to C++03 and warns about using override. Squelch that. +// Intentionally no push/pop here so all users of OVERRIDE ignore the warning +// too. This is like passing -Wno-c++11-extensions, except that GCC won't die +// (because it won't see this pragma). +#pragma clang diagnostic ignored "-Wc++11-extensions" +#define OVERRIDE override +#elif defined(__GNUC__) && __cplusplus >= 201103 && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 +// GCC 4.7 supports explicit virtual overrides when C++11 support is enabled. +#define OVERRIDE override +#else +#define OVERRIDE +#endif + +// Annotate a function indicating the caller must examine the return value. +// Use like: +// int foo() WARN_UNUSED_RESULT; +// To explicitly ignore a result, see |ignore_result()| in . +// TODO(ajm): Hack to avoid multiple definitions until the base/ of webrtc and +// libjingle are merged. +#if !defined(WARN_UNUSED_RESULT) +#if defined(__GNUC__) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#endif // WARN_UNUSED_RESULT + +#endif // WEBRTC_BASE_COMMON_H_ // NOLINT diff --git a/webrtc/base/compile_assert.h b/webrtc/base/compile_assert.h new file mode 100644 index 000000000..6d4249c8f --- /dev/null +++ b/webrtc/base/compile_assert.h @@ -0,0 +1,82 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// COMPILE_ASSERT macro, borrowed from google3/base/macros.h. +#ifndef WEBRTC_BASE_COMPILE_ASSERT_H_ +#define WEBRTC_BASE_COMPILE_ASSERT_H_ + +// The COMPILE_ASSERT macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// COMPILE_ASSERT(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +// TODO(ajm): Hack to avoid multiple definitions until the base/ of webrtc and +// libjingle are merged. +#if !defined(COMPILE_ASSERT) +template +struct CompileAssert { +}; + +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] // NOLINT +#endif // COMPILE_ASSERT + +// Implementation details of COMPILE_ASSERT: +// +// - COMPILE_ASSERT works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outer parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert +// +// instead, these compilers will refuse to compile +// +// COMPILE_ASSERT(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + +#endif // WEBRTC_BASE_COMPILE_ASSERT_H_ diff --git a/webrtc/base/constructormagic.h b/webrtc/base/constructormagic.h new file mode 100644 index 000000000..c20be2b32 --- /dev/null +++ b/webrtc/base/constructormagic.h @@ -0,0 +1,41 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_CONSTRUCTORMAGIC_H_ +#define WEBRTC_BASE_CONSTRUCTORMAGIC_H_ + +#define DISALLOW_ASSIGN(TypeName) \ + void operator=(const TypeName&) + +// A macro to disallow the evil copy constructor and operator= functions +// This should be used in the private: declarations for a class. +// Undefine this, just in case. Some third-party includes have their own +// version. +#undef DISALLOW_COPY_AND_ASSIGN +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + DISALLOW_ASSIGN(TypeName) + +// Alternative, less-accurate legacy name. +#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_EVIL_CONSTRUCTORS(TypeName) + + +#endif // WEBRTC_BASE_CONSTRUCTORMAGIC_H_ diff --git a/webrtc/base/cpumonitor.cc b/webrtc/base/cpumonitor.cc new file mode 100644 index 000000000..f5c0d063a --- /dev/null +++ b/webrtc/base/cpumonitor.cc @@ -0,0 +1,419 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/cpumonitor.h" + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/systeminfo.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif + +#if defined(WEBRTC_POSIX) +#include +#endif + +#if defined(WEBRTC_MAC) +#include +#include +#include +#include +#endif // defined(WEBRTC_MAC) + +#if defined(WEBRTC_LINUX) +#include +#include +#include +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#endif // defined(WEBRTC_LINUX) + +#if defined(WEBRTC_MAC) +static uint64 TimeValueTToInt64(const time_value_t &time_value) { + return rtc::kNumMicrosecsPerSec * time_value.seconds + + time_value.microseconds; +} +#endif // defined(WEBRTC_MAC) + +// How CpuSampler works +// When threads switch, the time they spent is accumulated to system counters. +// The time can be treated as user, kernel or idle. +// user time is applications. +// kernel time is the OS, including the thread switching code itself. +// typically kernel time indicates IO. +// idle time is a process that wastes time when nothing is ready to run. +// +// User time is broken down by process (application). One of the applications +// is the current process. When you add up all application times, this is +// system time. If only your application is running, system time should be the +// same as process time. +// +// All cores contribute to these accumulators. A dual core process is able to +// process twice as many cycles as a single core. The actual code efficiency +// may be worse, due to contention, but the available cycles is exactly twice +// as many, and the cpu load will reflect the efficiency. Hyperthreads behave +// the same way. The load will reflect 200%, but the actual amount of work +// completed will be much less than a true dual core. +// +// Total available performance is the sum of all accumulators. +// If you tracked this for 1 second, it would essentially give you the clock +// rate - number of cycles per second. +// Speed step / Turbo Boost is not considered, so infact more processing time +// may be available. + +namespace rtc { + +// Note Tests on Windows show 600 ms is minimum stable interval for Windows 7. +static const int32 kDefaultInterval = 950; // Slightly under 1 second. + +CpuSampler::CpuSampler() + : min_load_interval_(kDefaultInterval) +#if defined(WEBRTC_WIN) + , get_system_times_(NULL), + nt_query_system_information_(NULL), + force_fallback_(false) +#endif + { +} + +CpuSampler::~CpuSampler() { +} + +// Set minimum interval in ms between computing new load values. Default 950. +void CpuSampler::set_load_interval(int min_load_interval) { + min_load_interval_ = min_load_interval; +} + +bool CpuSampler::Init() { + sysinfo_.reset(new SystemInfo); + cpus_ = sysinfo_->GetMaxCpus(); + if (cpus_ == 0) { + return false; + } +#if defined(WEBRTC_WIN) + // Note that GetSystemTimes is available in Windows XP SP1 or later. + // http://msdn.microsoft.com/en-us/library/ms724400.aspx + // NtQuerySystemInformation is used as a fallback. + if (!force_fallback_) { + get_system_times_ = GetProcAddress(GetModuleHandle(L"kernel32.dll"), + "GetSystemTimes"); + } + nt_query_system_information_ = GetProcAddress(GetModuleHandle(L"ntdll.dll"), + "NtQuerySystemInformation"); + if ((get_system_times_ == NULL) && (nt_query_system_information_ == NULL)) { + return false; + } +#endif +#if defined(WEBRTC_LINUX) + Pathname sname("/proc/stat"); + sfile_.reset(Filesystem::OpenFile(sname, "rb")); + if (!sfile_) { + LOG_ERR(LS_ERROR) << "open proc/stat failed:"; + return false; + } + if (!sfile_->DisableBuffering()) { + LOG_ERR(LS_ERROR) << "could not disable buffering for proc/stat"; + return false; + } +#endif // defined(WEBRTC_LINUX) + GetProcessLoad(); // Initialize values. + GetSystemLoad(); + // Help next user call return valid data by recomputing load. + process_.prev_load_time_ = 0u; + system_.prev_load_time_ = 0u; + return true; +} + +float CpuSampler::UpdateCpuLoad(uint64 current_total_times, + uint64 current_cpu_times, + uint64 *prev_total_times, + uint64 *prev_cpu_times) { + float result = 0.f; + if (current_total_times < *prev_total_times || + current_cpu_times < *prev_cpu_times) { + LOG(LS_ERROR) << "Inconsistent time values are passed. ignored"; + } else { + const uint64 cpu_diff = current_cpu_times - *prev_cpu_times; + const uint64 total_diff = current_total_times - *prev_total_times; + result = (total_diff == 0ULL ? 0.f : + static_cast(1.0f * cpu_diff / total_diff)); + if (result > static_cast(cpus_)) { + result = static_cast(cpus_); + } + *prev_total_times = current_total_times; + *prev_cpu_times = current_cpu_times; + } + return result; +} + +float CpuSampler::GetSystemLoad() { + uint32 timenow = Time(); + int elapsed = static_cast(TimeDiff(timenow, system_.prev_load_time_)); + if (min_load_interval_ != 0 && system_.prev_load_time_ != 0u && + elapsed < min_load_interval_) { + return system_.prev_load_; + } +#if defined(WEBRTC_WIN) + uint64 total_times, cpu_times; + + typedef BOOL (_stdcall *GST_PROC)(LPFILETIME, LPFILETIME, LPFILETIME); + typedef NTSTATUS (WINAPI *QSI_PROC)(SYSTEM_INFORMATION_CLASS, + PVOID, ULONG, PULONG); + + GST_PROC get_system_times = reinterpret_cast(get_system_times_); + QSI_PROC nt_query_system_information = reinterpret_cast( + nt_query_system_information_); + + if (get_system_times) { + FILETIME idle_time, kernel_time, user_time; + if (!get_system_times(&idle_time, &kernel_time, &user_time)) { + LOG(LS_ERROR) << "::GetSystemTimes() failed: " << ::GetLastError(); + return 0.f; + } + // kernel_time includes Kernel idle time, so no need to + // include cpu_time as total_times + total_times = ToUInt64(kernel_time) + ToUInt64(user_time); + cpu_times = total_times - ToUInt64(idle_time); + + } else { + if (nt_query_system_information) { + ULONG returned_length = 0; + scoped_ptr processor_info( + new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[cpus_]); + nt_query_system_information( + ::SystemProcessorPerformanceInformation, + reinterpret_cast(processor_info.get()), + cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), + &returned_length); + + if (returned_length != + (cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION))) { + LOG(LS_ERROR) << "NtQuerySystemInformation has unexpected size"; + return 0.f; + } + + uint64 current_idle = 0; + uint64 current_kernel = 0; + uint64 current_user = 0; + for (int ix = 0; ix < cpus_; ++ix) { + current_idle += processor_info[ix].IdleTime.QuadPart; + current_kernel += processor_info[ix].UserTime.QuadPart; + current_user += processor_info[ix].KernelTime.QuadPart; + } + total_times = current_kernel + current_user; + cpu_times = total_times - current_idle; + } else { + return 0.f; + } + } +#endif // WEBRTC_WIN + +#if defined(WEBRTC_MAC) + host_cpu_load_info_data_t cpu_info; + mach_msg_type_number_t info_count = HOST_CPU_LOAD_INFO_COUNT; + if (KERN_SUCCESS != host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, + reinterpret_cast(&cpu_info), + &info_count)) { + LOG(LS_ERROR) << "::host_statistics() failed"; + return 0.f; + } + + const uint64 cpu_times = cpu_info.cpu_ticks[CPU_STATE_NICE] + + cpu_info.cpu_ticks[CPU_STATE_SYSTEM] + + cpu_info.cpu_ticks[CPU_STATE_USER]; + const uint64 total_times = cpu_times + cpu_info.cpu_ticks[CPU_STATE_IDLE]; +#endif // defined(WEBRTC_MAC) + +#if defined(WEBRTC_LINUX) + if (!sfile_) { + LOG(LS_ERROR) << "Invalid handle for proc/stat"; + return 0.f; + } + std::string statbuf; + sfile_->SetPosition(0); + if (!sfile_->ReadLine(&statbuf)) { + LOG_ERR(LS_ERROR) << "Could not read proc/stat file"; + return 0.f; + } + + unsigned long long user; + unsigned long long nice; + unsigned long long system; + unsigned long long idle; + if (sscanf(statbuf.c_str(), "cpu %Lu %Lu %Lu %Lu", + &user, &nice, + &system, &idle) != 4) { + LOG_ERR(LS_ERROR) << "Could not parse cpu info"; + return 0.f; + } + const uint64 cpu_times = nice + system + user; + const uint64 total_times = cpu_times + idle; +#endif // defined(WEBRTC_LINUX) + +#if defined(__native_client__) + // TODO(ryanpetrie): Implement this via PPAPI when it's available. + const uint64 cpu_times = 0; + const uint64 total_times = 0; +#endif // defined(__native_client__) + + system_.prev_load_time_ = timenow; + system_.prev_load_ = UpdateCpuLoad(total_times, + cpu_times * cpus_, + &system_.prev_total_times_, + &system_.prev_cpu_times_); + return system_.prev_load_; +} + +float CpuSampler::GetProcessLoad() { + uint32 timenow = Time(); + int elapsed = static_cast(TimeDiff(timenow, process_.prev_load_time_)); + if (min_load_interval_ != 0 && process_.prev_load_time_ != 0u && + elapsed < min_load_interval_) { + return process_.prev_load_; + } +#if defined(WEBRTC_WIN) + FILETIME current_file_time; + ::GetSystemTimeAsFileTime(¤t_file_time); + + FILETIME create_time, exit_time, kernel_time, user_time; + if (!::GetProcessTimes(::GetCurrentProcess(), + &create_time, &exit_time, &kernel_time, &user_time)) { + LOG(LS_ERROR) << "::GetProcessTimes() failed: " << ::GetLastError(); + return 0.f; + } + + const uint64 total_times = + ToUInt64(current_file_time) - ToUInt64(create_time); + const uint64 cpu_times = + (ToUInt64(kernel_time) + ToUInt64(user_time)); +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) + // Common to both OSX and Linux. + struct timeval tv; + gettimeofday(&tv, NULL); + const uint64 total_times = tv.tv_sec * kNumMicrosecsPerSec + tv.tv_usec; +#endif + +#if defined(WEBRTC_MAC) + // Get live thread usage. + task_thread_times_info task_times_info; + mach_msg_type_number_t info_count = TASK_THREAD_TIMES_INFO_COUNT; + + if (KERN_SUCCESS != task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, + reinterpret_cast(&task_times_info), + &info_count)) { + LOG(LS_ERROR) << "::task_info(TASK_THREAD_TIMES_INFO) failed"; + return 0.f; + } + + // Get terminated thread usage. + task_basic_info task_term_info; + info_count = TASK_BASIC_INFO_COUNT; + if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, + reinterpret_cast(&task_term_info), + &info_count)) { + LOG(LS_ERROR) << "::task_info(TASK_BASIC_INFO) failed"; + return 0.f; + } + + const uint64 cpu_times = (TimeValueTToInt64(task_times_info.user_time) + + TimeValueTToInt64(task_times_info.system_time) + + TimeValueTToInt64(task_term_info.user_time) + + TimeValueTToInt64(task_term_info.system_time)); +#endif // defined(WEBRTC_MAC) + +#if defined(WEBRTC_LINUX) + rusage usage; + if (getrusage(RUSAGE_SELF, &usage) < 0) { + LOG_ERR(LS_ERROR) << "getrusage failed"; + return 0.f; + } + + const uint64 cpu_times = + (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * kNumMicrosecsPerSec + + usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; +#endif // defined(WEBRTC_LINUX) + +#if defined(__native_client__) + // TODO(ryanpetrie): Implement this via PPAPI when it's available. + const uint64 cpu_times = 0; +#endif // defined(__native_client__) + + process_.prev_load_time_ = timenow; + process_.prev_load_ = UpdateCpuLoad(total_times, + cpu_times, + &process_.prev_total_times_, + &process_.prev_cpu_times_); + return process_.prev_load_; +} + +int CpuSampler::GetMaxCpus() const { + return cpus_; +} + +int CpuSampler::GetCurrentCpus() { + return sysinfo_->GetCurCpus(); +} + +/////////////////////////////////////////////////////////////////// +// Implementation of class CpuMonitor. +CpuMonitor::CpuMonitor(Thread* thread) + : monitor_thread_(thread) { +} + +CpuMonitor::~CpuMonitor() { + Stop(); +} + +void CpuMonitor::set_thread(Thread* thread) { + ASSERT(monitor_thread_ == NULL || monitor_thread_ == thread); + monitor_thread_ = thread; +} + +bool CpuMonitor::Start(int period_ms) { + if (!monitor_thread_ || !sampler_.Init()) return false; + + monitor_thread_->SignalQueueDestroyed.connect( + this, &CpuMonitor::OnMessageQueueDestroyed); + + period_ms_ = period_ms; + monitor_thread_->PostDelayed(period_ms_, this); + + return true; +} + +void CpuMonitor::Stop() { + if (monitor_thread_) { + monitor_thread_->Clear(this); + } +} + +void CpuMonitor::OnMessage(Message* msg) { + int max_cpus = sampler_.GetMaxCpus(); + int current_cpus = sampler_.GetCurrentCpus(); + float process_load = sampler_.GetProcessLoad(); + float system_load = sampler_.GetSystemLoad(); + SignalUpdate(current_cpus, max_cpus, process_load, system_load); + + if (monitor_thread_) { + monitor_thread_->PostDelayed(period_ms_, this); + } +} + +} // namespace rtc diff --git a/webrtc/base/cpumonitor.h b/webrtc/base/cpumonitor.h new file mode 100644 index 000000000..39b09b380 --- /dev/null +++ b/webrtc/base/cpumonitor.h @@ -0,0 +1,123 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_CPUMONITOR_H_ +#define WEBRTC_BASE_CPUMONITOR_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#if defined(WEBRTC_LINUX) +#include "webrtc/base/stream.h" +#endif // defined(WEBRTC_LINUX) + +namespace rtc { +class Thread; +class SystemInfo; + +struct CpuStats { + CpuStats() + : prev_total_times_(0), + prev_cpu_times_(0), + prev_load_(0.f), + prev_load_time_(0u) { + } + + uint64 prev_total_times_; + uint64 prev_cpu_times_; + float prev_load_; // Previous load value. + uint32 prev_load_time_; // Time previous load value was taken. +}; + +// CpuSampler samples the process and system load. +class CpuSampler { + public: + CpuSampler(); + ~CpuSampler(); + + // Initialize CpuSampler. Returns true if successful. + bool Init(); + + // Set minimum interval in ms between computing new load values. + // Default 950 ms. Set to 0 to disable interval. + void set_load_interval(int min_load_interval); + + // Return CPU load of current process as a float from 0 to 1. + float GetProcessLoad(); + + // Return CPU load of current process as a float from 0 to 1. + float GetSystemLoad(); + + // Return number of cpus. Includes hyperthreads. + int GetMaxCpus() const; + + // Return current number of cpus available to this process. + int GetCurrentCpus(); + + // For testing. Allows forcing of fallback to using NTDLL functions. + void set_force_fallback(bool fallback) { +#if defined(WEBRTC_WIN) + force_fallback_ = fallback; +#endif + } + + private: + float UpdateCpuLoad(uint64 current_total_times, + uint64 current_cpu_times, + uint64 *prev_total_times, + uint64 *prev_cpu_times); + CpuStats process_; + CpuStats system_; + int cpus_; + int min_load_interval_; // Minimum time between computing new load. + scoped_ptr sysinfo_; +#if defined(WEBRTC_WIN) + void* get_system_times_; + void* nt_query_system_information_; + bool force_fallback_; +#endif +#if defined(WEBRTC_LINUX) + // File for reading /proc/stat + scoped_ptr sfile_; +#endif // defined(WEBRTC_LINUX) +}; + +// CpuMonitor samples and signals the CPU load periodically. +class CpuMonitor + : public rtc::MessageHandler, public sigslot::has_slots<> { + public: + explicit CpuMonitor(Thread* thread); + virtual ~CpuMonitor(); + void set_thread(Thread* thread); + + bool Start(int period_ms); + void Stop(); + // Signal parameters are current cpus, max cpus, process load and system load. + sigslot::signal4 SignalUpdate; + + protected: + // Override virtual method of parent MessageHandler. + virtual void OnMessage(rtc::Message* msg); + // Clear the monitor thread and stop sending it messages if the thread goes + // away before our lifetime. + void OnMessageQueueDestroyed() { monitor_thread_ = NULL; } + + private: + Thread* monitor_thread_; + CpuSampler sampler_; + int period_ms_; + + DISALLOW_COPY_AND_ASSIGN(CpuMonitor); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_CPUMONITOR_H_ diff --git a/webrtc/base/cpumonitor_unittest.cc b/webrtc/base/cpumonitor_unittest.cc new file mode 100644 index 000000000..6d9af5aec --- /dev/null +++ b/webrtc/base/cpumonitor_unittest.cc @@ -0,0 +1,388 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/cpumonitor.h" +#include "webrtc/base/flags.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/timing.h" + +namespace rtc { + +static const int kMaxCpus = 1024; +static const int kSettleTime = 100; // Amount of time to between tests. +static const int kIdleTime = 500; // Amount of time to be idle in ms. +static const int kBusyTime = 1000; // Amount of time to be busy in ms. +static const int kLongInterval = 2000; // Interval longer than busy times + +class BusyThread : public rtc::Thread { + public: + BusyThread(double load, double duration, double interval) : + load_(load), duration_(duration), interval_(interval) { + } + virtual ~BusyThread() { + Stop(); + } + void Run() { + Timing time; + double busy_time = interval_ * load_ / 100.0; + for (;;) { + time.BusyWait(busy_time); + time.IdleWait(interval_ - busy_time); + if (duration_) { + duration_ -= interval_; + if (duration_ <= 0) { + break; + } + } + } + } + private: + double load_; + double duration_; + double interval_; +}; + +class CpuLoadListener : public sigslot::has_slots<> { + public: + CpuLoadListener() + : current_cpus_(0), + cpus_(0), + process_load_(.0f), + system_load_(.0f), + count_(0) { + } + + void OnCpuLoad(int current_cpus, int cpus, float proc_load, float sys_load) { + current_cpus_ = current_cpus; + cpus_ = cpus; + process_load_ = proc_load; + system_load_ = sys_load; + ++count_; + } + + int current_cpus() const { return current_cpus_; } + int cpus() const { return cpus_; } + float process_load() const { return process_load_; } + float system_load() const { return system_load_; } + int count() const { return count_; } + + private: + int current_cpus_; + int cpus_; + float process_load_; + float system_load_; + int count_; +}; + +// Set affinity (which cpu to run on), but respecting FLAG_affinity: +// -1 means no affinity - run on whatever cpu is available. +// 0 .. N means run on specific cpu. The tool will create N threads and call +// SetThreadAffinity on 0 to N - 1 as cpu. FLAG_affinity sets the first cpu +// so the range becomes affinity to affinity + N - 1 +// Note that this function affects Windows scheduling, effectively giving +// the thread with affinity for a specified CPU more priority on that CPU. +bool SetThreadAffinity(BusyThread* t, int cpu, int affinity) { +#if defined(WEBRTC_WIN) + if (affinity >= 0) { + return ::SetThreadAffinityMask(t->GetHandle(), + 1 << (cpu + affinity)) != FALSE; + } +#endif + return true; +} + +bool SetThreadPriority(BusyThread* t, int prio) { + if (!prio) { + return true; + } + bool ok = t->SetPriority(static_cast(prio)); + if (!ok) { + std::cout << "Error setting thread priority." << std::endl; + } + return ok; +} + +int CpuLoad(double cpuload, double duration, int numthreads, + int priority, double interval, int affinity) { + int ret = 0; + std::vector threads; + for (int i = 0; i < numthreads; ++i) { + threads.push_back(new BusyThread(cpuload, duration, interval)); + // NOTE(fbarchard): Priority must be done before Start. + if (!SetThreadPriority(threads[i], priority) || + !threads[i]->Start() || + !SetThreadAffinity(threads[i], i, affinity)) { + ret = 1; + break; + } + } + // Wait on each thread + if (ret == 0) { + for (int i = 0; i < numthreads; ++i) { + threads[i]->Stop(); + } + } + + for (int i = 0; i < numthreads; ++i) { + delete threads[i]; + } + return ret; +} + +// Make 2 CPUs busy +static void CpuTwoBusyLoop(int busytime) { + CpuLoad(100.0, busytime / 1000.0, 2, 1, 0.050, -1); +} + +// Make 1 CPUs busy +static void CpuBusyLoop(int busytime) { + CpuLoad(100.0, busytime / 1000.0, 1, 1, 0.050, -1); +} + +// Make 1 use half CPU time. +static void CpuHalfBusyLoop(int busytime) { + CpuLoad(50.0, busytime / 1000.0, 1, 1, 0.050, -1); +} + +void TestCpuSampler(bool test_proc, bool test_sys, bool force_fallback) { + CpuSampler sampler; + sampler.set_force_fallback(force_fallback); + EXPECT_TRUE(sampler.Init()); + sampler.set_load_interval(100); + int cpus = sampler.GetMaxCpus(); + + // Test1: CpuSampler under idle situation. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + Thread::SleepMs(kIdleTime); + + float proc_idle = 0.f, sys_idle = 0.f; + if (test_proc) { + proc_idle = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_idle = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_idle; + EXPECT_GE(proc_idle, 0.f); + EXPECT_LE(proc_idle, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_idle; + EXPECT_GE(sys_idle, 0.f); + EXPECT_LE(sys_idle, static_cast(cpus)); + } + + // Test2: CpuSampler with main process at 50% busy. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + CpuHalfBusyLoop(kBusyTime); + + float proc_halfbusy = 0.f, sys_halfbusy = 0.f; + if (test_proc) { + proc_halfbusy = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_halfbusy = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Halfbusy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_halfbusy; + EXPECT_GE(proc_halfbusy, 0.f); + EXPECT_LE(proc_halfbusy, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Halfbusy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_halfbusy; + EXPECT_GE(sys_halfbusy, 0.f); + EXPECT_LE(sys_halfbusy, static_cast(cpus)); + } + + // Test3: CpuSampler with main process busy. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + CpuBusyLoop(kBusyTime); + + float proc_busy = 0.f, sys_busy = 0.f; + if (test_proc) { + proc_busy = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_busy = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Busy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_busy; + EXPECT_GE(proc_busy, 0.f); + EXPECT_LE(proc_busy, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Busy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_busy; + EXPECT_GE(sys_busy, 0.f); + EXPECT_LE(sys_busy, static_cast(cpus)); + } + + // Test4: CpuSampler with 2 cpus process busy. + if (cpus >= 2) { + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + CpuTwoBusyLoop(kBusyTime); + + float proc_twobusy = 0.f, sys_twobusy = 0.f; + if (test_proc) { + proc_twobusy = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_twobusy = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad 2 CPU Busy:" + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_twobusy; + EXPECT_GE(proc_twobusy, 0.f); + EXPECT_LE(proc_twobusy, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad 2 CPU Busy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_twobusy; + EXPECT_GE(sys_twobusy, 0.f); + EXPECT_LE(sys_twobusy, static_cast(cpus)); + } + } + + // Test5: CpuSampler with idle process after being busy. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + Thread::SleepMs(kIdleTime); + + if (test_proc) { + proc_idle = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_idle = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_idle; + EXPECT_GE(proc_idle, 0.f); + EXPECT_LE(proc_idle, proc_busy); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_idle; + EXPECT_GE(sys_idle, 0.f); + EXPECT_LE(sys_idle, static_cast(cpus)); + } +} + +TEST(CpuMonitorTest, TestCpus) { + CpuSampler sampler; + EXPECT_TRUE(sampler.Init()); + int current_cpus = sampler.GetCurrentCpus(); + int cpus = sampler.GetMaxCpus(); + LOG(LS_INFO) << "Current Cpus: " << std::setw(9) << current_cpus; + LOG(LS_INFO) << "Maximum Cpus: " << std::setw(9) << cpus; + EXPECT_GT(cpus, 0); + EXPECT_LE(cpus, kMaxCpus); + EXPECT_GT(current_cpus, 0); + EXPECT_LE(current_cpus, cpus); +} + +#if defined(WEBRTC_WIN) +// Tests overall system CpuSampler using legacy OS fallback code if applicable. +TEST(CpuMonitorTest, TestGetSystemLoadForceFallback) { + TestCpuSampler(false, true, true); +} +#endif + +// Tests both process and system functions in use at same time. +TEST(CpuMonitorTest, TestGetBothLoad) { + TestCpuSampler(true, true, false); +} + +// Tests a query less than the interval produces the same value. +TEST(CpuMonitorTest, TestInterval) { + CpuSampler sampler; + EXPECT_TRUE(sampler.Init()); + + // Test1: Set interval to large value so sampler will not update. + sampler.set_load_interval(kLongInterval); + + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + float proc_orig = sampler.GetProcessLoad(); + float sys_orig = sampler.GetSystemLoad(); + + Thread::SleepMs(kIdleTime); + + float proc_halftime = sampler.GetProcessLoad(); + float sys_halftime = sampler.GetSystemLoad(); + + EXPECT_EQ(proc_orig, proc_halftime); + EXPECT_EQ(sys_orig, sys_halftime); +} + +TEST(CpuMonitorTest, TestCpuMonitor) { + CpuMonitor monitor(Thread::Current()); + CpuLoadListener listener; + monitor.SignalUpdate.connect(&listener, &CpuLoadListener::OnCpuLoad); + EXPECT_TRUE(monitor.Start(10)); + // We have checked cpu load more than twice. + EXPECT_TRUE_WAIT(listener.count() > 2, 1000); + EXPECT_GT(listener.current_cpus(), 0); + EXPECT_GT(listener.cpus(), 0); + EXPECT_GE(listener.process_load(), .0f); + EXPECT_GE(listener.system_load(), .0f); + + monitor.Stop(); + // Wait 20 ms to ake sure all signals are delivered. + Thread::Current()->ProcessMessages(20); + int old_count = listener.count(); + Thread::Current()->ProcessMessages(20); + // Verfy no more siganls. + EXPECT_EQ(old_count, listener.count()); +} + +} // namespace rtc diff --git a/webrtc/base/crc32.cc b/webrtc/base/crc32.cc new file mode 100644 index 000000000..d643a25a4 --- /dev/null +++ b/webrtc/base/crc32.cc @@ -0,0 +1,52 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/crc32.h" + +#include "webrtc/base/basicdefs.h" + +namespace rtc { + +// This implementation is based on the sample implementation in RFC 1952. + +// CRC32 polynomial, in reversed form. +// See RFC 1952, or http://en.wikipedia.org/wiki/Cyclic_redundancy_check +static const uint32 kCrc32Polynomial = 0xEDB88320; +static uint32 kCrc32Table[256] = { 0 }; + +static void EnsureCrc32TableInited() { + if (kCrc32Table[ARRAY_SIZE(kCrc32Table) - 1]) + return; // already inited + for (uint32 i = 0; i < ARRAY_SIZE(kCrc32Table); ++i) { + uint32 c = i; + for (size_t j = 0; j < 8; ++j) { + if (c & 1) { + c = kCrc32Polynomial ^ (c >> 1); + } else { + c >>= 1; + } + } + kCrc32Table[i] = c; + } +} + +uint32 UpdateCrc32(uint32 start, const void* buf, size_t len) { + EnsureCrc32TableInited(); + + uint32 c = start ^ 0xFFFFFFFF; + const uint8* u = static_cast(buf); + for (size_t i = 0; i < len; ++i) { + c = kCrc32Table[(c ^ u[i]) & 0xFF] ^ (c >> 8); + } + return c ^ 0xFFFFFFFF; +} + +} // namespace rtc + diff --git a/webrtc/base/crc32.h b/webrtc/base/crc32.h new file mode 100644 index 000000000..99b4cac89 --- /dev/null +++ b/webrtc/base/crc32.h @@ -0,0 +1,34 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_CRC32_H_ +#define WEBRTC_BASE_CRC32_H_ + +#include + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Updates a CRC32 checksum with |len| bytes from |buf|. |initial| holds the +// checksum result from the previous update; for the first call, it should be 0. +uint32 UpdateCrc32(uint32 initial, const void* buf, size_t len); + +// Computes a CRC32 checksum using |len| bytes from |buf|. +inline uint32 ComputeCrc32(const void* buf, size_t len) { + return UpdateCrc32(0, buf, len); +} +inline uint32 ComputeCrc32(const std::string& str) { + return ComputeCrc32(str.c_str(), str.size()); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_CRC32_H_ diff --git a/webrtc/base/crc32_unittest.cc b/webrtc/base/crc32_unittest.cc new file mode 100644 index 000000000..0bfdeeea0 --- /dev/null +++ b/webrtc/base/crc32_unittest.cc @@ -0,0 +1,35 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/crc32.h" +#include "webrtc/base/gunit.h" + +#include + +namespace rtc { + +TEST(Crc32Test, TestBasic) { + EXPECT_EQ(0U, ComputeCrc32("")); + EXPECT_EQ(0x352441C2U, ComputeCrc32("abc")); + EXPECT_EQ(0x171A3F5FU, + ComputeCrc32("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")); +} + +TEST(Crc32Test, TestMultipleUpdates) { + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + uint32 c = 0; + for (size_t i = 0; i < input.size(); ++i) { + c = UpdateCrc32(c, &input[i], 1); + } + EXPECT_EQ(0x171A3F5FU, c); +} + +} // namespace rtc diff --git a/webrtc/base/criticalsection.h b/webrtc/base/criticalsection.h new file mode 100644 index 000000000..a950a47f5 --- /dev/null +++ b/webrtc/base/criticalsection.h @@ -0,0 +1,179 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_CRITICALSECTION_H__ +#define WEBRTC_BASE_CRITICALSECTION_H__ + +#include "webrtc/base/constructormagic.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#if defined(WEBRTC_POSIX) +#include +#endif + +#ifdef _DEBUG +#define CS_TRACK_OWNER 1 +#endif // _DEBUG + +#if CS_TRACK_OWNER +#define TRACK_OWNER(x) x +#else // !CS_TRACK_OWNER +#define TRACK_OWNER(x) +#endif // !CS_TRACK_OWNER + +namespace rtc { + +#if defined(WEBRTC_WIN) +class CriticalSection { + public: + CriticalSection() { + InitializeCriticalSection(&crit_); + // Windows docs say 0 is not a valid thread id + TRACK_OWNER(thread_ = 0); + } + ~CriticalSection() { + DeleteCriticalSection(&crit_); + } + void Enter() { + EnterCriticalSection(&crit_); + TRACK_OWNER(thread_ = GetCurrentThreadId()); + } + bool TryEnter() { + if (TryEnterCriticalSection(&crit_) != FALSE) { + TRACK_OWNER(thread_ = GetCurrentThreadId()); + return true; + } + return false; + } + void Leave() { + TRACK_OWNER(thread_ = 0); + LeaveCriticalSection(&crit_); + } + +#if CS_TRACK_OWNER + bool CurrentThreadIsOwner() const { return thread_ == GetCurrentThreadId(); } +#endif // CS_TRACK_OWNER + + private: + CRITICAL_SECTION crit_; + TRACK_OWNER(DWORD thread_); // The section's owning thread id +}; +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) +class CriticalSection { + public: + CriticalSection() { + pthread_mutexattr_t mutex_attribute; + pthread_mutexattr_init(&mutex_attribute); + pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex_, &mutex_attribute); + pthread_mutexattr_destroy(&mutex_attribute); + TRACK_OWNER(thread_ = 0); + } + ~CriticalSection() { + pthread_mutex_destroy(&mutex_); + } + void Enter() { + pthread_mutex_lock(&mutex_); + TRACK_OWNER(thread_ = pthread_self()); + } + bool TryEnter() { + if (pthread_mutex_trylock(&mutex_) == 0) { + TRACK_OWNER(thread_ = pthread_self()); + return true; + } + return false; + } + void Leave() { + TRACK_OWNER(thread_ = 0); + pthread_mutex_unlock(&mutex_); + } + +#if CS_TRACK_OWNER + bool CurrentThreadIsOwner() const { return pthread_equal(thread_, pthread_self()); } +#endif // CS_TRACK_OWNER + + private: + pthread_mutex_t mutex_; + TRACK_OWNER(pthread_t thread_); +}; +#endif // WEBRTC_POSIX + +// CritScope, for serializing execution through a scope. +class CritScope { + public: + explicit CritScope(CriticalSection *pcrit) { + pcrit_ = pcrit; + pcrit_->Enter(); + } + ~CritScope() { + pcrit_->Leave(); + } + private: + CriticalSection *pcrit_; + DISALLOW_COPY_AND_ASSIGN(CritScope); +}; + +// Tries to lock a critical section on construction via +// CriticalSection::TryEnter, and unlocks on destruction if the +// lock was taken. Never blocks. +// +// IMPORTANT: Unlike CritScope, the lock may not be owned by this thread in +// subsequent code. Users *must* check locked() to determine if the +// lock was taken. If you're not calling locked(), you're doing it wrong! +class TryCritScope { + public: + explicit TryCritScope(CriticalSection *pcrit) { + pcrit_ = pcrit; + locked_ = pcrit_->TryEnter(); + } + ~TryCritScope() { + if (locked_) { + pcrit_->Leave(); + } + } + bool locked() const { + return locked_; + } + private: + CriticalSection *pcrit_; + bool locked_; + DISALLOW_COPY_AND_ASSIGN(TryCritScope); +}; + +// TODO: Move this to atomicops.h, which can't be done easily because of +// complex compile rules. +class AtomicOps { + public: +#if defined(WEBRTC_WIN) + // Assumes sizeof(int) == sizeof(LONG), which it is on Win32 and Win64. + static int Increment(int* i) { + return ::InterlockedIncrement(reinterpret_cast(i)); + } + static int Decrement(int* i) { + return ::InterlockedDecrement(reinterpret_cast(i)); + } +#else + static int Increment(int* i) { + return __sync_add_and_fetch(i, 1); + } + static int Decrement(int* i) { + return __sync_sub_and_fetch(i, 1); + } +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_CRITICALSECTION_H__ diff --git a/webrtc/base/criticalsection_unittest.cc b/webrtc/base/criticalsection_unittest.cc new file mode 100644 index 000000000..e1b05cb01 --- /dev/null +++ b/webrtc/base/criticalsection_unittest.cc @@ -0,0 +1,146 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/event.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/scopedptrcollection.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +namespace { + +const int kLongTime = 10000; // 10 seconds +const int kNumThreads = 16; +const int kOperationsToRun = 1000; + +template +class AtomicOpRunner : public MessageHandler { + public: + explicit AtomicOpRunner(int initial_value) + : value_(initial_value), + threads_active_(0), + start_event_(true, false), + done_event_(true, false) {} + + int value() const { return value_; } + + bool Run() { + // Signal all threads to start. + start_event_.Set(); + + // Wait for all threads to finish. + return done_event_.Wait(kLongTime); + } + + void SetExpectedThreadCount(int count) { + threads_active_ = count; + } + + virtual void OnMessage(Message* msg) { + std::vector values; + values.reserve(kOperationsToRun); + + // Wait to start. + ASSERT_TRUE(start_event_.Wait(kLongTime)); + + // Generate a bunch of values by updating value_ atomically. + for (int i = 0; i < kOperationsToRun; ++i) { + values.push_back(T::AtomicOp(&value_)); + } + + { // Add them all to the set. + CritScope cs(&all_values_crit_); + for (size_t i = 0; i < values.size(); ++i) { + std::pair::iterator, bool> result = + all_values_.insert(values[i]); + // Each value should only be taken by one thread, so if this value + // has already been added, something went wrong. + EXPECT_TRUE(result.second) + << "Thread=" << Thread::Current() << " value=" << values[i]; + } + } + + // Signal that we're done. + if (AtomicOps::Decrement(&threads_active_) == 0) { + done_event_.Set(); + } + } + + private: + int value_; + int threads_active_; + CriticalSection all_values_crit_; + std::set all_values_; + Event start_event_; + Event done_event_; +}; + +struct IncrementOp { + static int AtomicOp(int* i) { return AtomicOps::Increment(i); } +}; + +struct DecrementOp { + static int AtomicOp(int* i) { return AtomicOps::Decrement(i); } +}; + +void StartThreads(ScopedPtrCollection* threads, + MessageHandler* handler) { + for (int i = 0; i < kNumThreads; ++i) { + Thread* thread = new Thread(); + thread->Start(); + thread->Post(handler); + threads->PushBack(thread); + } +} + +} // namespace + +TEST(AtomicOpsTest, Simple) { + int value = 0; + EXPECT_EQ(1, AtomicOps::Increment(&value)); + EXPECT_EQ(1, value); + EXPECT_EQ(2, AtomicOps::Increment(&value)); + EXPECT_EQ(2, value); + EXPECT_EQ(1, AtomicOps::Decrement(&value)); + EXPECT_EQ(1, value); + EXPECT_EQ(0, AtomicOps::Decrement(&value)); + EXPECT_EQ(0, value); +} + +TEST(AtomicOpsTest, Increment) { + // Create and start lots of threads. + AtomicOpRunner runner(0); + ScopedPtrCollection threads; + StartThreads(&threads, &runner); + runner.SetExpectedThreadCount(kNumThreads); + + // Release the hounds! + EXPECT_TRUE(runner.Run()); + EXPECT_EQ(kOperationsToRun * kNumThreads, runner.value()); +} + +TEST(AtomicOpsTest, Decrement) { + // Create and start lots of threads. + AtomicOpRunner runner(kOperationsToRun * kNumThreads); + ScopedPtrCollection threads; + StartThreads(&threads, &runner); + runner.SetExpectedThreadCount(kNumThreads); + + // Release the hounds! + EXPECT_TRUE(runner.Run()); + EXPECT_EQ(0, runner.value()); +} + +} // namespace rtc diff --git a/webrtc/base/cryptstring.h b/webrtc/base/cryptstring.h new file mode 100644 index 000000000..f43057d8f --- /dev/null +++ b/webrtc/base/cryptstring.h @@ -0,0 +1,183 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef _WEBRTC_BASE_CRYPTSTRING_H_ +#define _WEBRTC_BASE_CRYPTSTRING_H_ + +#include + +#include +#include + +#include "webrtc/base/linked_ptr.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class CryptStringImpl { +public: + virtual ~CryptStringImpl() {} + virtual size_t GetLength() const = 0; + virtual void CopyTo(char * dest, bool nullterminate) const = 0; + virtual std::string UrlEncode() const = 0; + virtual CryptStringImpl * Copy() const = 0; + virtual void CopyRawTo(std::vector * dest) const = 0; +}; + +class EmptyCryptStringImpl : public CryptStringImpl { +public: + virtual ~EmptyCryptStringImpl() {} + virtual size_t GetLength() const { return 0; } + virtual void CopyTo(char * dest, bool nullterminate) const { + if (nullterminate) { + *dest = '\0'; + } + } + virtual std::string UrlEncode() const { return ""; } + virtual CryptStringImpl * Copy() const { return new EmptyCryptStringImpl(); } + virtual void CopyRawTo(std::vector * dest) const { + dest->clear(); + } +}; + +class CryptString { +public: + CryptString() : impl_(new EmptyCryptStringImpl()) {} + size_t GetLength() const { return impl_->GetLength(); } + void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); } + CryptString(const CryptString & other) : impl_(other.impl_->Copy()) {} + explicit CryptString(const CryptStringImpl & impl) : impl_(impl.Copy()) {} + CryptString & operator=(const CryptString & other) { + if (this != &other) { + impl_.reset(other.impl_->Copy()); + } + return *this; + } + void Clear() { impl_.reset(new EmptyCryptStringImpl()); } + std::string UrlEncode() const { return impl_->UrlEncode(); } + void CopyRawTo(std::vector * dest) const { + return impl_->CopyRawTo(dest); + } + +private: + scoped_ptr impl_; +}; + + +// Used for constructing strings where a password is involved and we +// need to ensure that we zero memory afterwards +class FormatCryptString { +public: + FormatCryptString() { + storage_ = new char[32]; + capacity_ = 32; + length_ = 0; + storage_[0] = 0; + } + + void Append(const std::string & text) { + Append(text.data(), text.length()); + } + + void Append(const char * data, size_t length) { + EnsureStorage(length_ + length + 1); + memcpy(storage_ + length_, data, length); + length_ += length; + storage_[length_] = '\0'; + } + + void Append(const CryptString * password) { + size_t len = password->GetLength(); + EnsureStorage(length_ + len + 1); + password->CopyTo(storage_ + length_, true); + length_ += len; + } + + size_t GetLength() { + return length_; + } + + const char * GetData() { + return storage_; + } + + + // Ensures storage of at least n bytes + void EnsureStorage(size_t n) { + if (capacity_ >= n) { + return; + } + + size_t old_capacity = capacity_; + char * old_storage = storage_; + + for (;;) { + capacity_ *= 2; + if (capacity_ >= n) + break; + } + + storage_ = new char[capacity_]; + + if (old_capacity) { + memcpy(storage_, old_storage, length_); + + // zero memory in a way that an optimizer won't optimize it out + old_storage[0] = 0; + for (size_t i = 1; i < old_capacity; i++) { + old_storage[i] = old_storage[i - 1]; + } + delete[] old_storage; + } + } + + ~FormatCryptString() { + if (capacity_) { + storage_[0] = 0; + for (size_t i = 1; i < capacity_; i++) { + storage_[i] = storage_[i - 1]; + } + } + delete[] storage_; + } +private: + char * storage_; + size_t capacity_; + size_t length_; +}; + +class InsecureCryptStringImpl : public CryptStringImpl { + public: + std::string& password() { return password_; } + const std::string& password() const { return password_; } + + virtual ~InsecureCryptStringImpl() {} + virtual size_t GetLength() const { return password_.size(); } + virtual void CopyTo(char * dest, bool nullterminate) const { + memcpy(dest, password_.data(), password_.size()); + if (nullterminate) dest[password_.size()] = 0; + } + virtual std::string UrlEncode() const { return password_; } + virtual CryptStringImpl * Copy() const { + InsecureCryptStringImpl * copy = new InsecureCryptStringImpl; + copy->password() = password_; + return copy; + } + virtual void CopyRawTo(std::vector * dest) const { + dest->resize(password_.size()); + memcpy(&dest->front(), password_.data(), password_.size()); + } + private: + std::string password_; +}; + +} + +#endif // _WEBRTC_BASE_CRYPTSTRING_H_ diff --git a/webrtc/base/dbus.cc b/webrtc/base/dbus.cc new file mode 100644 index 000000000..b8392f9a2 --- /dev/null +++ b/webrtc/base/dbus.cc @@ -0,0 +1,396 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifdef HAVE_DBUS_GLIB + +#include "webrtc/base/dbus.h" + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Avoid static object construction/destruction on startup/shutdown. +static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT; +static LibDBusGlibSymbolTable *g_dbus_symbol = NULL; + +// Releases DBus-Glib symbols. +static void ReleaseDBusGlibSymbol() { + if (g_dbus_symbol != NULL) { + delete g_dbus_symbol; + g_dbus_symbol = NULL; + } +} + +// Loads DBus-Glib symbols. +static void InitializeDBusGlibSymbol() { + // This is thread safe. + if (NULL == g_dbus_symbol) { + g_dbus_symbol = new LibDBusGlibSymbolTable(); + + // Loads dbus-glib + if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) { + LOG(LS_WARNING) << "Failed to load dbus-glib symbol table."; + ReleaseDBusGlibSymbol(); + } else { + // Nothing we can do if atexit() failed. Just ignore its returned value. + atexit(ReleaseDBusGlibSymbol); + } + } +} + +inline static LibDBusGlibSymbolTable *GetSymbols() { + return DBusMonitor::GetDBusGlibSymbolTable(); +} + +// Implementation of class DBusSigMessageData +DBusSigMessageData::DBusSigMessageData(DBusMessage *message) + : TypedMessageData(message) { + GetSymbols()->dbus_message_ref()(data()); +} + +DBusSigMessageData::~DBusSigMessageData() { + GetSymbols()->dbus_message_unref()(data()); +} + +// Implementation of class DBusSigFilter + +// Builds a DBus filter string from given DBus path, interface and member. +std::string DBusSigFilter::BuildFilterString(const std::string &path, + const std::string &interface, + const std::string &member) { + std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'"); + if (!path.empty()) { + ret += ("," DBUS_PATH "='"); + ret += path; + ret += "'"; + } + if (!interface.empty()) { + ret += ("," DBUS_INTERFACE "='"); + ret += interface; + ret += "'"; + } + if (!member.empty()) { + ret += ("," DBUS_MEMBER "='"); + ret += member; + ret += "'"; + } + return ret; +} + +// Forwards the message to the given instance. +DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn, + DBusMessage *message, + void *instance) { + ASSERT(instance); + if (instance) { + return static_cast(instance)->Callback(message); + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +// Posts a message to caller thread. +DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) { + if (caller_thread_) { + caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message)); + } + // Don't "eat" the message here. Let it pop up. + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +// From MessageHandler. +void DBusSigFilter::OnMessage(Message *message) { + if (message != NULL && DSM_SIGNAL == message->message_id) { + DBusSigMessageData *msg = + static_cast(message->pdata); + if (msg) { + ProcessSignal(msg->data()); + delete msg; + } + } +} + +// Definition of private class DBusMonitoringThread. +// It creates a worker-thread to listen signals on DBus. The worker-thread will +// be running in a priate GMainLoop forever until either Stop() has been invoked +// or it hits an error. +class DBusMonitor::DBusMonitoringThread : public rtc::Thread { + public: + explicit DBusMonitoringThread(DBusMonitor *monitor, + GMainContext *context, + GMainLoop *mainloop, + std::vector *filter_list) + : monitor_(monitor), + context_(context), + mainloop_(mainloop), + connection_(NULL), + idle_source_(NULL), + filter_list_(filter_list) { + ASSERT(monitor_); + ASSERT(context_); + ASSERT(mainloop_); + ASSERT(filter_list_); + } + + virtual ~DBusMonitoringThread() { + Stop(); + } + + // Override virtual method of Thread. Context: worker-thread. + virtual void Run() { + ASSERT(NULL == connection_); + + // Setup DBus connection and start monitoring. + monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING); + if (!Setup()) { + LOG(LS_ERROR) << "DBus monitoring setup failed."; + monitor_->OnMonitoringStatusChanged(DMS_FAILED); + CleanUp(); + return; + } + monitor_->OnMonitoringStatusChanged(DMS_RUNNING); + g_main_loop_run(mainloop_); + monitor_->OnMonitoringStatusChanged(DMS_STOPPED); + + // Done normally. Clean up DBus connection. + CleanUp(); + return; + } + + // Override virtual method of Thread. Context: caller-thread. + virtual void Stop() { + ASSERT(NULL == idle_source_); + // Add an idle source and let the gmainloop quit on idle. + idle_source_ = g_idle_source_new(); + if (idle_source_) { + g_source_set_callback(idle_source_, &Idle, this, NULL); + g_source_attach(idle_source_, context_); + } else { + LOG(LS_ERROR) << "g_idle_source_new() failed."; + QuitGMainloop(); // Try to quit anyway. + } + + Thread::Stop(); // Wait for the thread. + } + + private: + // Registers all DBus filters. + void RegisterAllFilters() { + ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()( + connection_)); + + for (std::vector::iterator it = filter_list_->begin(); + it != filter_list_->end(); ++it) { + DBusSigFilter *filter = (*it); + if (!filter) { + LOG(LS_ERROR) << "DBusSigFilter list corrupted."; + continue; + } + + GetSymbols()->dbus_bus_add_match()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), + filter->filter().c_str(), NULL); + + if (!GetSymbols()->dbus_connection_add_filter()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), + &DBusSigFilter::DBusCallback, filter, NULL)) { + LOG(LS_ERROR) << "dbus_connection_add_filter() failed." + << "Filter: " << filter->filter(); + continue; + } + } + } + + // Unregisters all DBus filters. + void UnRegisterAllFilters() { + ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()( + connection_)); + + for (std::vector::iterator it = filter_list_->begin(); + it != filter_list_->end(); ++it) { + DBusSigFilter *filter = (*it); + if (!filter) { + LOG(LS_ERROR) << "DBusSigFilter list corrupted."; + continue; + } + GetSymbols()->dbus_connection_remove_filter()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), + &DBusSigFilter::DBusCallback, filter); + } + } + + // Sets up the monitoring thread. + bool Setup() { + g_main_context_push_thread_default(context_); + + // Start connection to dbus. + // If dbus daemon is not running, returns false immediately. + connection_ = GetSymbols()->dbus_g_bus_get_private()(monitor_->type_, + context_, NULL); + if (NULL == connection_) { + LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection."; + return false; + } + if (NULL == GetSymbols()->dbus_g_connection_get_connection()(connection_)) { + LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. " + << "DBus daemon is probably not running."; + return false; + } + + // Application don't exit if DBus daemon die. + GetSymbols()->dbus_connection_set_exit_on_disconnect()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), FALSE); + + // Connect all filters. + RegisterAllFilters(); + + return true; + } + + // Cleans up the monitoring thread. + void CleanUp() { + if (idle_source_) { + // We did an attach() with the GSource, so we need to destroy() it. + g_source_destroy(idle_source_); + // We need to unref() the GSource to end the last reference we got. + g_source_unref(idle_source_); + idle_source_ = NULL; + } + if (connection_) { + if (GetSymbols()->dbus_g_connection_get_connection()(connection_)) { + UnRegisterAllFilters(); + GetSymbols()->dbus_connection_close()( + GetSymbols()->dbus_g_connection_get_connection()(connection_)); + } + GetSymbols()->dbus_g_connection_unref()(connection_); + connection_ = NULL; + } + g_main_loop_unref(mainloop_); + mainloop_ = NULL; + g_main_context_unref(context_); + context_ = NULL; + } + + // Handles callback on Idle. We only add this source when ready to stop. + static gboolean Idle(gpointer data) { + static_cast(data)->QuitGMainloop(); + return TRUE; + } + + // We only hit this when ready to quit. + void QuitGMainloop() { + g_main_loop_quit(mainloop_); + } + + DBusMonitor *monitor_; + + GMainContext *context_; + GMainLoop *mainloop_; + DBusGConnection *connection_; + GSource *idle_source_; + + std::vector *filter_list_; +}; + +// Implementation of class DBusMonitor + +// Returns DBus-Glib symbol handle. Initialize it first if hasn't. +LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() { + // This is multi-thread safe. + pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol); + + return g_dbus_symbol; +}; + +// Creates an instance of DBusMonitor +DBusMonitor *DBusMonitor::Create(DBusBusType type) { + if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) { + return NULL; + } + return new DBusMonitor(type); +} + +DBusMonitor::DBusMonitor(DBusBusType type) + : type_(type), + status_(DMS_NOT_INITIALIZED), + monitoring_thread_(NULL) { + ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION); +} + +DBusMonitor::~DBusMonitor() { + StopMonitoring(); +} + +bool DBusMonitor::AddFilter(DBusSigFilter *filter) { + if (monitoring_thread_) { + return false; + } + if (!filter) { + return false; + } + filter_list_.push_back(filter); + return true; +} + +bool DBusMonitor::StartMonitoring() { + if (!monitoring_thread_) { + g_type_init(); + g_thread_init(NULL); + GetSymbols()->dbus_g_thread_init()(); + + GMainContext *context = g_main_context_new(); + if (NULL == context) { + LOG(LS_ERROR) << "g_main_context_new() failed."; + return false; + } + + GMainLoop *mainloop = g_main_loop_new(context, FALSE); + if (NULL == mainloop) { + LOG(LS_ERROR) << "g_main_loop_new() failed."; + g_main_context_unref(context); + return false; + } + + monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop, + &filter_list_); + if (monitoring_thread_ == NULL) { + LOG(LS_ERROR) << "Failed to create DBus monitoring thread."; + g_main_context_unref(context); + g_main_loop_unref(mainloop); + return false; + } + monitoring_thread_->Start(); + } + return true; +} + +bool DBusMonitor::StopMonitoring() { + if (monitoring_thread_) { + monitoring_thread_->Stop(); + monitoring_thread_ = NULL; + } + return true; +} + +DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() { + return status_; +} + +void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) { + status_ = status; +} + +#undef LATE + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB diff --git a/webrtc/base/dbus.h b/webrtc/base/dbus.h new file mode 100644 index 000000000..fb90638bc --- /dev/null +++ b/webrtc/base/dbus.h @@ -0,0 +1,168 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_DBUS_H_ +#define WEBRTC_BASE_DBUS_H_ + +#ifdef HAVE_DBUS_GLIB + +#include + +#include +#include + +#include "webrtc/base/libdbusglibsymboltable.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#define DBUS_TYPE "type" +#define DBUS_SIGNAL "signal" +#define DBUS_PATH "path" +#define DBUS_INTERFACE "interface" +#define DBUS_MEMBER "member" + +#ifdef CHROMEOS +#define CROS_PM_PATH "/" +#define CROS_PM_INTERFACE "org.chromium.PowerManager" +#define CROS_SIG_POWERCHANGED "PowerStateChanged" +#define CROS_VALUE_SLEEP "mem" +#define CROS_VALUE_RESUME "on" +#else +#define UP_PATH "/org/freedesktop/UPower" +#define UP_INTERFACE "org.freedesktop.UPower" +#define UP_SIG_SLEEPING "Sleeping" +#define UP_SIG_RESUMING "Resuming" +#endif // CHROMEOS + +// Wraps a DBus messages. +class DBusSigMessageData : public TypedMessageData { + public: + explicit DBusSigMessageData(DBusMessage *message); + ~DBusSigMessageData(); +}; + +// DBusSigFilter is an abstract class that defines the interface of DBus +// signal handling. +// The subclasses implement ProcessSignal() for various purposes. +// When a DBus signal comes, a DSM_SIGNAL message is posted to the caller thread +// which will then invokes ProcessSignal(). +class DBusSigFilter : protected MessageHandler { + public: + enum DBusSigMessage { DSM_SIGNAL }; + + // This filter string should ususally come from BuildFilterString() + explicit DBusSigFilter(const std::string &filter) + : caller_thread_(Thread::Current()), filter_(filter) { + } + + // Builds a DBus monitor filter string from given DBus path, interface, and + // member. + // See http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html + static std::string BuildFilterString(const std::string &path, + const std::string &interface, + const std::string &member); + + // Handles callback on DBus messages by DBus system. + static DBusHandlerResult DBusCallback(DBusConnection *dbus_conn, + DBusMessage *message, + void *instance); + + // Handles callback on DBus messages to each DBusSigFilter instance. + DBusHandlerResult Callback(DBusMessage *message); + + // From MessageHandler. + virtual void OnMessage(Message *message); + + // Returns the DBus monitor filter string. + const std::string &filter() const { return filter_; } + + private: + // On caller thread. + virtual void ProcessSignal(DBusMessage *message) = 0; + + Thread *caller_thread_; + const std::string filter_; +}; + +// DBusMonitor is a class for DBus signal monitoring. +// +// The caller-thread calls AddFilter() first to add the signals that it wants to +// monitor and then calls StartMonitoring() to start the monitoring. +// This will create a worker-thread which listens on DBus connection and sends +// DBus signals back through the callback. +// The worker-thread will be running forever until either StopMonitoring() is +// called from the caller-thread or the worker-thread hit some error. +// +// Programming model: +// 1. Caller-thread: Creates an object of DBusMonitor. +// 2. Caller-thread: Calls DBusMonitor::AddFilter() one or several times. +// 3. Caller-thread: StartMonitoring(). +// ... +// 4. Worker-thread: DBus signal recieved. Post a message to caller-thread. +// 5. Caller-thread: DBusFilterBase::ProcessSignal() is invoked. +// ... +// 6. Caller-thread: StopMonitoring(). +// +// Assumption: +// AddFilter(), StartMonitoring(), and StopMonitoring() methods are called by +// a single thread. Hence, there is no need to make them thread safe. +class DBusMonitor { + public: + // Status of DBus monitoring. + enum DBusMonitorStatus { + DMS_NOT_INITIALIZED, // Not initialized. + DMS_INITIALIZING, // Initializing the monitoring thread. + DMS_RUNNING, // Monitoring. + DMS_STOPPED, // Not monitoring. Stopped normally. + DMS_FAILED, // Not monitoring. Failed. + }; + + // Returns the DBus-Glib symbol table. + // We should only use this function to access DBus-Glib symbols. + static LibDBusGlibSymbolTable *GetDBusGlibSymbolTable(); + + // Creates an instance of DBusMonitor. + static DBusMonitor *Create(DBusBusType type); + ~DBusMonitor(); + + // Adds a filter to DBusMonitor. + bool AddFilter(DBusSigFilter *filter); + + // Starts DBus message monitoring. + bool StartMonitoring(); + + // Stops DBus message monitoring. + bool StopMonitoring(); + + // Gets the status of DBus monitoring. + DBusMonitorStatus GetStatus(); + + private: + // Forward declaration. Defined in the .cc file. + class DBusMonitoringThread; + + explicit DBusMonitor(DBusBusType type); + + // Updates status_ when monitoring status has changed. + void OnMonitoringStatusChanged(DBusMonitorStatus status); + + DBusBusType type_; + DBusMonitorStatus status_; + DBusMonitoringThread *monitoring_thread_; + std::vector filter_list_; +}; + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB + +#endif // WEBRTC_BASE_DBUS_H_ diff --git a/webrtc/base/dbus_unittest.cc b/webrtc/base/dbus_unittest.cc new file mode 100644 index 000000000..505ddbbc8 --- /dev/null +++ b/webrtc/base/dbus_unittest.cc @@ -0,0 +1,232 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifdef HAVE_DBUS_GLIB + +#include "webrtc/base/dbus.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#define SIG_NAME "NameAcquired" + +static const uint32 kTimeoutMs = 5000U; + +class DBusSigFilterTest : public DBusSigFilter { + public: + // DBusSigFilterTest listens on DBus service itself for "NameAcquired" signal. + // This signal should be received when the application connects to DBus + // service and gains ownership of a name. + // http://dbus.freedesktop.org/doc/dbus-specification.html + DBusSigFilterTest() + : DBusSigFilter(GetFilter()), + message_received_(false) { + } + + bool MessageReceived() { + return message_received_; + } + + private: + static std::string GetFilter() { + return rtc::DBusSigFilter::BuildFilterString("", "", SIG_NAME); + } + + // Implement virtual method of DBusSigFilter. On caller thread. + virtual void ProcessSignal(DBusMessage *message) { + EXPECT_TRUE(message != NULL); + message_received_ = true; + } + + bool message_received_; +}; + +TEST(DBusMonitorTest, StartStopStartStop) { + DBusSigFilterTest filter; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter)); + + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_NOT_INITIALIZED); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_RUNNING); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +// DBusMonitorTest listens on DBus service itself for "NameAcquired" signal. +// This signal should be received when the application connects to DBus +// service and gains ownership of a name. +// This test is to make sure that we capture the "NameAcquired" signal. +TEST(DBusMonitorTest, ReceivedNameAcquiredSignal) { + DBusSigFilterTest filter; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, ConcurrentMonitors) { + DBusSigFilterTest filter1; + rtc::scoped_ptr monitor1; + monitor1.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor1) { + EXPECT_TRUE(monitor1->AddFilter(&filter1)); + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor2; + monitor2.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + EXPECT_TRUE(monitor2->AddFilter(&filter2)); + + EXPECT_TRUE(monitor1->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor1->GetStatus(), kTimeoutMs); + EXPECT_TRUE(monitor2->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor2->GetStatus(), kTimeoutMs); + + EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor2->StopMonitoring()); + EXPECT_EQ(monitor2->GetStatus(), DBusMonitor::DMS_STOPPED); + + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor1->StopMonitoring()); + EXPECT_EQ(monitor1->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, ConcurrentFilters) { + DBusSigFilterTest filter1; + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter1)); + EXPECT_TRUE(monitor->AddFilter(&filter2)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs); + + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, NoAddFilterIfRunning) { + DBusSigFilterTest filter1; + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter1)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_FALSE(monitor->AddFilter(&filter2)); + + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, AddFilterAfterStop) { + DBusSigFilterTest filter1; + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter1)); + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + + EXPECT_TRUE(monitor->AddFilter(&filter2)); + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, StopRightAfterStart) { + DBusSigFilterTest filter; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_TRUE(monitor->StopMonitoring()); + + // Stop the monitoring thread right after it had been started. + // If the monitoring thread got a chance to receive a DBus signal, it would + // post a message to the main thread and signal the main thread wakeup. + // This message will be cleaned out automatically when the filter get + // destructed. Here we also consume the wakeup signal (if there is one) so + // that the testing (main) thread is reset to a clean state. + rtc::Thread::Current()->ProcessMessages(1); + } else { + LOG(LS_WARNING) << "DBus Monitor not started."; + } +} + +TEST(DBusSigFilter, BuildFilterString) { + EXPECT_EQ(DBusSigFilter::BuildFilterString("", "", ""), + (DBUS_TYPE "='" DBUS_SIGNAL "'")); + EXPECT_EQ(DBusSigFilter::BuildFilterString("p", "", ""), + (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'")); + EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i", ""), + (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'," + DBUS_INTERFACE "='i'")); + EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i","m"), + (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'," + DBUS_INTERFACE "='i'," DBUS_MEMBER "='m'")); +} + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB diff --git a/webrtc/base/diskcache.cc b/webrtc/base/diskcache.cc new file mode 100644 index 000000000..f893ce73d --- /dev/null +++ b/webrtc/base/diskcache.cc @@ -0,0 +1,347 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/diskcache.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +#ifdef _DEBUG +#define TRANSPARENT_CACHE_NAMES 1 +#else // !_DEBUG +#define TRANSPARENT_CACHE_NAMES 0 +#endif // !_DEBUG + +namespace rtc { + +class DiskCache; + +/////////////////////////////////////////////////////////////////////////////// +// DiskCacheAdapter +/////////////////////////////////////////////////////////////////////////////// + +class DiskCacheAdapter : public StreamAdapterInterface { +public: + DiskCacheAdapter(const DiskCache* cache, const std::string& id, size_t index, + StreamInterface* stream) + : StreamAdapterInterface(stream), cache_(cache), id_(id), index_(index) + { } + virtual ~DiskCacheAdapter() { + Close(); + cache_->ReleaseResource(id_, index_); + } + +private: + const DiskCache* cache_; + std::string id_; + size_t index_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// DiskCache +/////////////////////////////////////////////////////////////////////////////// + +DiskCache::DiskCache() : max_cache_(0), total_size_(0), total_accessors_(0) { +} + +DiskCache::~DiskCache() { + ASSERT(0 == total_accessors_); +} + +bool DiskCache::Initialize(const std::string& folder, size_t size) { + if (!folder_.empty() || !Filesystem::CreateFolder(folder)) + return false; + + folder_ = folder; + max_cache_ = size; + ASSERT(0 == total_size_); + + if (!InitializeEntries()) + return false; + + return CheckLimit(); +} + +bool DiskCache::Purge() { + if (folder_.empty()) + return false; + + if (total_accessors_ > 0) { + LOG_F(LS_WARNING) << "Cache files open"; + return false; + } + + if (!PurgeFiles()) + return false; + + map_.clear(); + return true; +} + +bool DiskCache::LockResource(const std::string& id) { + Entry* entry = GetOrCreateEntry(id, true); + if (LS_LOCKED == entry->lock_state) + return false; + if ((LS_UNLOCKED == entry->lock_state) && (entry->accessors > 0)) + return false; + if ((total_size_ > max_cache_) && !CheckLimit()) { + LOG_F(LS_WARNING) << "Cache overfull"; + return false; + } + entry->lock_state = LS_LOCKED; + return true; +} + +StreamInterface* DiskCache::WriteResource(const std::string& id, size_t index) { + Entry* entry = GetOrCreateEntry(id, false); + if (LS_LOCKED != entry->lock_state) + return NULL; + + size_t previous_size = 0; + std::string filename(IdToFilename(id, index)); + FileStream::GetSize(filename, &previous_size); + ASSERT(previous_size <= entry->size); + if (previous_size > entry->size) { + previous_size = entry->size; + } + + scoped_ptr file(new FileStream); + if (!file->Open(filename, "wb", NULL)) { + LOG_F(LS_ERROR) << "Couldn't create cache file"; + return NULL; + } + + entry->streams = stdmax(entry->streams, index + 1); + entry->size -= previous_size; + total_size_ -= previous_size; + + entry->accessors += 1; + total_accessors_ += 1; + return new DiskCacheAdapter(this, id, index, file.release()); +} + +bool DiskCache::UnlockResource(const std::string& id) { + Entry* entry = GetOrCreateEntry(id, false); + if (LS_LOCKED != entry->lock_state) + return false; + + if (entry->accessors > 0) { + entry->lock_state = LS_UNLOCKING; + } else { + entry->lock_state = LS_UNLOCKED; + entry->last_modified = time(0); + CheckLimit(); + } + return true; +} + +StreamInterface* DiskCache::ReadResource(const std::string& id, + size_t index) const { + const Entry* entry = GetEntry(id); + if (LS_UNLOCKED != entry->lock_state) + return NULL; + if (index >= entry->streams) + return NULL; + + scoped_ptr file(new FileStream); + if (!file->Open(IdToFilename(id, index), "rb", NULL)) + return NULL; + + entry->accessors += 1; + total_accessors_ += 1; + return new DiskCacheAdapter(this, id, index, file.release()); +} + +bool DiskCache::HasResource(const std::string& id) const { + const Entry* entry = GetEntry(id); + return (NULL != entry) && (entry->streams > 0); +} + +bool DiskCache::HasResourceStream(const std::string& id, size_t index) const { + const Entry* entry = GetEntry(id); + if ((NULL == entry) || (index >= entry->streams)) + return false; + + std::string filename = IdToFilename(id, index); + + return FileExists(filename); +} + +bool DiskCache::DeleteResource(const std::string& id) { + Entry* entry = GetOrCreateEntry(id, false); + if (!entry) + return true; + + if ((LS_UNLOCKED != entry->lock_state) || (entry->accessors > 0)) + return false; + + bool success = true; + for (size_t index = 0; index < entry->streams; ++index) { + std::string filename = IdToFilename(id, index); + + if (!FileExists(filename)) + continue; + + if (!DeleteFile(filename)) { + LOG_F(LS_ERROR) << "Couldn't remove cache file: " << filename; + success = false; + } + } + + total_size_ -= entry->size; + map_.erase(id); + return success; +} + +bool DiskCache::CheckLimit() { +#ifdef _DEBUG + // Temporary check to make sure everything is working correctly. + size_t cache_size = 0; + for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) { + cache_size += it->second.size; + } + ASSERT(cache_size == total_size_); +#endif // _DEBUG + + // TODO: Replace this with a non-brain-dead algorithm for clearing out the + // oldest resources... something that isn't O(n^2) + while (total_size_ > max_cache_) { + EntryMap::iterator oldest = map_.end(); + for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) { + if ((LS_UNLOCKED != it->second.lock_state) || (it->second.accessors > 0)) + continue; + oldest = it; + break; + } + if (oldest == map_.end()) { + LOG_F(LS_WARNING) << "All resources are locked!"; + return false; + } + for (EntryMap::iterator it = oldest++; it != map_.end(); ++it) { + if (it->second.last_modified < oldest->second.last_modified) { + oldest = it; + } + } + if (!DeleteResource(oldest->first)) { + LOG_F(LS_ERROR) << "Couldn't delete from cache!"; + return false; + } + } + return true; +} + +std::string DiskCache::IdToFilename(const std::string& id, size_t index) const { +#ifdef TRANSPARENT_CACHE_NAMES + // This escapes colons and other filesystem characters, so the user can't open + // special devices (like "COM1:"), or access other directories. + size_t buffer_size = id.length()*3 + 1; + char* buffer = new char[buffer_size]; + encode(buffer, buffer_size, id.data(), id.length(), + unsafe_filename_characters(), '%'); + // TODO: ASSERT(strlen(buffer) < FileSystem::MaxBasenameLength()); +#else // !TRANSPARENT_CACHE_NAMES + // We might want to just use a hash of the filename at some point, both for + // obfuscation, and to avoid both filename length and escaping issues. + ASSERT(false); +#endif // !TRANSPARENT_CACHE_NAMES + + char extension[32]; + sprintfn(extension, ARRAY_SIZE(extension), ".%u", index); + + Pathname pathname; + pathname.SetFolder(folder_); + pathname.SetBasename(buffer); + pathname.SetExtension(extension); + +#ifdef TRANSPARENT_CACHE_NAMES + delete [] buffer; +#endif // TRANSPARENT_CACHE_NAMES + + return pathname.pathname(); +} + +bool DiskCache::FilenameToId(const std::string& filename, std::string* id, + size_t* index) const { + Pathname pathname(filename); + unsigned tempdex; + if (1 != sscanf(pathname.extension().c_str(), ".%u", &tempdex)) + return false; + + *index = static_cast(tempdex); + + size_t buffer_size = pathname.basename().length() + 1; + char* buffer = new char[buffer_size]; + decode(buffer, buffer_size, pathname.basename().data(), + pathname.basename().length(), '%'); + id->assign(buffer); + delete [] buffer; + return true; +} + +DiskCache::Entry* DiskCache::GetOrCreateEntry(const std::string& id, + bool create) { + EntryMap::iterator it = map_.find(id); + if (it != map_.end()) + return &it->second; + if (!create) + return NULL; + Entry e; + e.lock_state = LS_UNLOCKED; + e.accessors = 0; + e.size = 0; + e.streams = 0; + e.last_modified = time(0); + it = map_.insert(EntryMap::value_type(id, e)).first; + return &it->second; +} + +void DiskCache::ReleaseResource(const std::string& id, size_t index) const { + const Entry* entry = GetEntry(id); + if (!entry) { + LOG_F(LS_WARNING) << "Missing cache entry"; + ASSERT(false); + return; + } + + entry->accessors -= 1; + total_accessors_ -= 1; + + if (LS_UNLOCKED != entry->lock_state) { + // This is safe, because locked resources only issue WriteResource, which + // is non-const. Think about a better way to handle it. + DiskCache* this2 = const_cast(this); + Entry* entry2 = this2->GetOrCreateEntry(id, false); + + size_t new_size = 0; + std::string filename(IdToFilename(id, index)); + FileStream::GetSize(filename, &new_size); + entry2->size += new_size; + this2->total_size_ += new_size; + + if ((LS_UNLOCKING == entry->lock_state) && (0 == entry->accessors)) { + entry2->last_modified = time(0); + entry2->lock_state = LS_UNLOCKED; + this2->CheckLimit(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/diskcache.h b/webrtc/base/diskcache.h new file mode 100644 index 000000000..4ac1be15d --- /dev/null +++ b/webrtc/base/diskcache.h @@ -0,0 +1,125 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_DISKCACHE_H__ +#define WEBRTC_BASE_DISKCACHE_H__ + +#include +#include + +#if defined(WEBRTC_WIN) +#undef UnlockResource +#endif // WEBRTC_WIN + +namespace rtc { + +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// DiskCache - An LRU cache of streams, stored on disk. +// +// Streams are identified by a unique resource id. Multiple streams can be +// associated with each resource id, distinguished by an index. When old +// resources are flushed from the cache, all streams associated with those +// resources are removed together. +// DiskCache is designed to persist across executions of the program. It is +// safe for use from an arbitrary number of users on a single thread, but not +// from multiple threads or other processes. +/////////////////////////////////////////////////////////////////////////////// + +class DiskCache { +public: + DiskCache(); + virtual ~DiskCache(); + + bool Initialize(const std::string& folder, size_t size); + bool Purge(); + + bool LockResource(const std::string& id); + StreamInterface* WriteResource(const std::string& id, size_t index); + bool UnlockResource(const std::string& id); + + StreamInterface* ReadResource(const std::string& id, size_t index) const; + + bool HasResource(const std::string& id) const; + bool HasResourceStream(const std::string& id, size_t index) const; + bool DeleteResource(const std::string& id); + + protected: + virtual bool InitializeEntries() = 0; + virtual bool PurgeFiles() = 0; + + virtual bool FileExists(const std::string& filename) const = 0; + virtual bool DeleteFile(const std::string& filename) const = 0; + + enum LockState { LS_UNLOCKED, LS_LOCKED, LS_UNLOCKING }; + struct Entry { + LockState lock_state; + mutable size_t accessors; + size_t size; + size_t streams; + time_t last_modified; + }; + typedef std::map EntryMap; + friend class DiskCacheAdapter; + + bool CheckLimit(); + + std::string IdToFilename(const std::string& id, size_t index) const; + bool FilenameToId(const std::string& filename, std::string* id, + size_t* index) const; + + const Entry* GetEntry(const std::string& id) const { + return const_cast(this)->GetOrCreateEntry(id, false); + } + Entry* GetOrCreateEntry(const std::string& id, bool create); + + void ReleaseResource(const std::string& id, size_t index) const; + + std::string folder_; + size_t max_cache_, total_size_; + EntryMap map_; + mutable size_t total_accessors_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// CacheLock - Automatically manage locking and unlocking, with optional +// rollback semantics +/////////////////////////////////////////////////////////////////////////////// + +class CacheLock { +public: + CacheLock(DiskCache* cache, const std::string& id, bool rollback = false) + : cache_(cache), id_(id), rollback_(rollback) + { + locked_ = cache_->LockResource(id_); + } + ~CacheLock() { + if (locked_) { + cache_->UnlockResource(id_); + if (rollback_) { + cache_->DeleteResource(id_); + } + } + } + bool IsLocked() const { return locked_; } + void Commit() { rollback_ = false; } + +private: + DiskCache* cache_; + std::string id_; + bool rollback_, locked_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_DISKCACHE_H__ diff --git a/webrtc/base/diskcache_win32.cc b/webrtc/base/diskcache_win32.cc new file mode 100644 index 000000000..22012ccc1 --- /dev/null +++ b/webrtc/base/diskcache_win32.cc @@ -0,0 +1,86 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/win32.h" +#include +#include +#include + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/diskcache.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +#include "webrtc/base/diskcache_win32.h" + +namespace rtc { + +bool DiskCacheWin32::InitializeEntries() { + // Note: We could store the cache information in a separate file, for faster + // initialization. Figuring it out empirically works, too. + + std::wstring path16 = ToUtf16(folder_); + path16.append(1, '*'); + + WIN32_FIND_DATA find_data; + HANDLE find_handle = FindFirstFile(path16.c_str(), &find_data); + if (find_handle != INVALID_HANDLE_VALUE) { + do { + size_t index; + std::string id; + if (!FilenameToId(ToUtf8(find_data.cFileName), &id, &index)) + continue; + + Entry* entry = GetOrCreateEntry(id, true); + entry->size += find_data.nFileSizeLow; + total_size_ += find_data.nFileSizeLow; + entry->streams = _max(entry->streams, index + 1); + FileTimeToUnixTime(find_data.ftLastWriteTime, &entry->last_modified); + + } while (FindNextFile(find_handle, &find_data)); + + FindClose(find_handle); + } + + return true; +} + +bool DiskCacheWin32::PurgeFiles() { + std::wstring path16 = ToUtf16(folder_); + path16.append(1, '*'); + path16.append(1, '\0'); + + SHFILEOPSTRUCT file_op = { 0 }; + file_op.wFunc = FO_DELETE; + file_op.pFrom = path16.c_str(); + file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT + | FOF_NORECURSION | FOF_FILESONLY; + if (0 != SHFileOperation(&file_op)) { + LOG_F(LS_ERROR) << "Couldn't delete cache files"; + return false; + } + + return true; +} + +bool DiskCacheWin32::FileExists(const std::string& filename) const { + DWORD result = ::GetFileAttributes(ToUtf16(filename).c_str()); + return (INVALID_FILE_ATTRIBUTES != result); +} + +bool DiskCacheWin32::DeleteFile(const std::string& filename) const { + return ::DeleteFile(ToUtf16(filename).c_str()) != 0; +} + +} diff --git a/webrtc/base/diskcache_win32.h b/webrtc/base/diskcache_win32.h new file mode 100644 index 000000000..42cb9b02c --- /dev/null +++ b/webrtc/base/diskcache_win32.h @@ -0,0 +1,29 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_DISKCACHEWIN32_H__ +#define WEBRTC_BASE_DISKCACHEWIN32_H__ + +#include "webrtc/base/diskcache.h" + +namespace rtc { + +class DiskCacheWin32 : public DiskCache { + protected: + virtual bool InitializeEntries(); + virtual bool PurgeFiles(); + + virtual bool FileExists(const std::string& filename) const; + virtual bool DeleteFile(const std::string& filename) const; +}; + +} + +#endif // WEBRTC_BASE_DISKCACHEWIN32_H__ diff --git a/webrtc/base/dscp.h b/webrtc/base/dscp.h new file mode 100644 index 000000000..970ff93b9 --- /dev/null +++ b/webrtc/base/dscp.h @@ -0,0 +1,45 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_DSCP_H_ +#define WEBRTC_BASE_DSCP_H_ + +namespace rtc { +// Differentiated Services Code Point. +// See http://tools.ietf.org/html/rfc2474 for details. +enum DiffServCodePoint { + DSCP_NO_CHANGE = -1, + DSCP_DEFAULT = 0, // Same as DSCP_CS0 + DSCP_CS0 = 0, // The default + DSCP_CS1 = 8, // Bulk/background traffic + DSCP_AF11 = 10, + DSCP_AF12 = 12, + DSCP_AF13 = 14, + DSCP_CS2 = 16, + DSCP_AF21 = 18, + DSCP_AF22 = 20, + DSCP_AF23 = 22, + DSCP_CS3 = 24, + DSCP_AF31 = 26, + DSCP_AF32 = 28, + DSCP_AF33 = 30, + DSCP_CS4 = 32, + DSCP_AF41 = 34, // Video + DSCP_AF42 = 36, // Video + DSCP_AF43 = 38, // Video + DSCP_CS5 = 40, // Video + DSCP_EF = 46, // Voice + DSCP_CS6 = 48, // Voice + DSCP_CS7 = 56, // Control messages +}; + +} // namespace rtc + + #endif // WEBRTC_BASE_DSCP_H_ diff --git a/webrtc/base/event.cc b/webrtc/base/event.cc new file mode 100644 index 000000000..393142ea2 --- /dev/null +++ b/webrtc/base/event.cc @@ -0,0 +1,135 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/event.h" + +#if defined(WEBRTC_WIN) +#include +#elif defined(WEBRTC_POSIX) +#include +#include +#include +#else +#error "Must define either WEBRTC_WIN or WEBRTC_POSIX." +#endif + +namespace rtc { + +#if defined(WEBRTC_WIN) + +Event::Event(bool manual_reset, bool initially_signaled) + : is_manual_reset_(manual_reset), + is_initially_signaled_(initially_signaled) { + event_handle_ = ::CreateEvent(NULL, // Security attributes. + is_manual_reset_, + is_initially_signaled_, + NULL); // Name. + ASSERT(event_handle_ != NULL); +} + +Event::~Event() { + CloseHandle(event_handle_); +} + +void Event::Set() { + SetEvent(event_handle_); +} + +void Event::Reset() { + ResetEvent(event_handle_); +} + +bool Event::Wait(int cms) { + DWORD ms = (cms == kForever)? INFINITE : cms; + return (WaitForSingleObject(event_handle_, ms) == WAIT_OBJECT_0); +} + +#elif defined(WEBRTC_POSIX) + +Event::Event(bool manual_reset, bool initially_signaled) + : is_manual_reset_(manual_reset), + event_status_(initially_signaled) { + VERIFY(pthread_mutex_init(&event_mutex_, NULL) == 0); + VERIFY(pthread_cond_init(&event_cond_, NULL) == 0); +} + +Event::~Event() { + pthread_mutex_destroy(&event_mutex_); + pthread_cond_destroy(&event_cond_); +} + +void Event::Set() { + pthread_mutex_lock(&event_mutex_); + event_status_ = true; + pthread_cond_broadcast(&event_cond_); + pthread_mutex_unlock(&event_mutex_); +} + +void Event::Reset() { + pthread_mutex_lock(&event_mutex_); + event_status_ = false; + pthread_mutex_unlock(&event_mutex_); +} + +bool Event::Wait(int cms) { + pthread_mutex_lock(&event_mutex_); + int error = 0; + + if (cms != kForever) { + // Converting from seconds and microseconds (1e-6) plus + // milliseconds (1e-3) to seconds and nanoseconds (1e-9). + + struct timespec ts; +#if HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE + // Use relative time version, which tends to be more efficient for + // pthread implementations where provided (like on Android). + ts.tv_sec = cms / 1000; + ts.tv_nsec = (cms % 1000) * 1000000; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + + ts.tv_sec = tv.tv_sec + (cms / 1000); + ts.tv_nsec = tv.tv_usec * 1000 + (cms % 1000) * 1000000; + + // Handle overflow. + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } +#endif + + while (!event_status_ && error == 0) { +#if HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE + error = pthread_cond_timedwait_relative_np( + &event_cond_, &event_mutex_, &ts); +#else + error = pthread_cond_timedwait(&event_cond_, &event_mutex_, &ts); +#endif + } + } else { + while (!event_status_ && error == 0) + error = pthread_cond_wait(&event_cond_, &event_mutex_); + } + + // NOTE(liulk): Exactly one thread will auto-reset this event. All + // the other threads will think it's unsignaled. This seems to be + // consistent with auto-reset events in WEBRTC_WIN + if (error == 0 && !is_manual_reset_) + event_status_ = false; + + pthread_mutex_unlock(&event_mutex_); + + return (error == 0); +} + +#endif + +} // namespace rtc diff --git a/webrtc/base/event.h b/webrtc/base/event.h new file mode 100644 index 000000000..f2691a2f8 --- /dev/null +++ b/webrtc/base/event.h @@ -0,0 +1,51 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_EVENT_H__ +#define WEBRTC_BASE_EVENT_H__ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" // NOLINT: consider this a system header. +#elif defined(WEBRTC_POSIX) +#include +#else +#error "Must define either WEBRTC_WIN or WEBRTC_POSIX." +#endif + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" + +namespace rtc { + +class Event { + public: + Event(bool manual_reset, bool initially_signaled); + ~Event(); + + void Set(); + void Reset(); + bool Wait(int cms); + + private: + bool is_manual_reset_; + +#if defined(WEBRTC_WIN) + bool is_initially_signaled_; + HANDLE event_handle_; +#elif defined(WEBRTC_POSIX) + bool event_status_; + pthread_mutex_t event_mutex_; + pthread_cond_t event_cond_; +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_EVENT_H__ diff --git a/webrtc/base/event_unittest.cc b/webrtc/base/event_unittest.cc new file mode 100644 index 000000000..996ad2f95 --- /dev/null +++ b/webrtc/base/event_unittest.cc @@ -0,0 +1,42 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/event.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +TEST(EventTest, InitiallySignaled) { + Event event(false, true); + ASSERT_TRUE(event.Wait(0)); +} + +TEST(EventTest, ManualReset) { + Event event(true, false); + ASSERT_FALSE(event.Wait(0)); + + event.Set(); + ASSERT_TRUE(event.Wait(0)); + ASSERT_TRUE(event.Wait(0)); + + event.Reset(); + ASSERT_FALSE(event.Wait(0)); +} + +TEST(EventTest, AutoReset) { + Event event(false, false); + ASSERT_FALSE(event.Wait(0)); + + event.Set(); + ASSERT_TRUE(event.Wait(0)); + ASSERT_FALSE(event.Wait(0)); +} + +} // namespace rtc diff --git a/webrtc/base/fakecpumonitor.h b/webrtc/base/fakecpumonitor.h new file mode 100644 index 000000000..c6ea0f293 --- /dev/null +++ b/webrtc/base/fakecpumonitor.h @@ -0,0 +1,32 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FAKECPUMONITOR_H_ +#define WEBRTC_BASE_FAKECPUMONITOR_H_ + +#include "webrtc/base/cpumonitor.h" + +namespace rtc { + +class FakeCpuMonitor : public rtc::CpuMonitor { + public: + explicit FakeCpuMonitor(Thread* thread) + : CpuMonitor(thread) { + } + ~FakeCpuMonitor() { + } + + virtual void OnMessage(rtc::Message* msg) { + } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKECPUMONITOR_H_ diff --git a/webrtc/base/fakenetwork.h b/webrtc/base/fakenetwork.h new file mode 100644 index 000000000..60773b409 --- /dev/null +++ b/webrtc/base/fakenetwork.h @@ -0,0 +1,119 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FAKENETWORK_H_ +#define WEBRTC_BASE_FAKENETWORK_H_ + +#include +#include + +#include "webrtc/base/network.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +const int kFakeIPv4NetworkPrefixLength = 24; +const int kFakeIPv6NetworkPrefixLength = 64; + +// Fake network manager that allows us to manually specify the IPs to use. +class FakeNetworkManager : public NetworkManagerBase, + public MessageHandler { + public: + FakeNetworkManager() + : thread_(Thread::Current()), + next_index_(0), + started_(false), + sent_first_update_(false) { + } + + typedef std::vector IfaceList; + + void AddInterface(const SocketAddress& iface) { + // ensure a unique name for the interface + SocketAddress address("test" + rtc::ToString(next_index_++), 0); + address.SetResolvedIP(iface.ipaddr()); + ifaces_.push_back(address); + DoUpdateNetworks(); + } + + void RemoveInterface(const SocketAddress& iface) { + for (IfaceList::iterator it = ifaces_.begin(); + it != ifaces_.end(); ++it) { + if (it->EqualIPs(iface)) { + ifaces_.erase(it); + break; + } + } + DoUpdateNetworks(); + } + + virtual void StartUpdating() { + if (started_) { + if (sent_first_update_) + SignalNetworksChanged(); + return; + } + + started_ = true; + sent_first_update_ = false; + thread_->Post(this); + } + + virtual void StopUpdating() { + started_ = false; + } + + // MessageHandler interface. + virtual void OnMessage(Message* msg) { + DoUpdateNetworks(); + } + + private: + void DoUpdateNetworks() { + if (!started_) + return; + std::vector networks; + for (IfaceList::iterator it = ifaces_.begin(); + it != ifaces_.end(); ++it) { + int prefix_length = 0; + if (it->ipaddr().family() == AF_INET) { + prefix_length = kFakeIPv4NetworkPrefixLength; + } else if (it->ipaddr().family() == AF_INET6) { + prefix_length = kFakeIPv6NetworkPrefixLength; + } + IPAddress prefix = TruncateIP(it->ipaddr(), prefix_length); + scoped_ptr net(new Network(it->hostname(), + it->hostname(), + prefix, + prefix_length)); + net->AddIP(it->ipaddr()); + networks.push_back(net.release()); + } + bool changed; + MergeNetworkList(networks, &changed); + if (changed || !sent_first_update_) { + SignalNetworksChanged(); + sent_first_update_ = true; + } + } + + Thread* thread_; + IfaceList ifaces_; + int next_index_; + bool started_; + bool sent_first_update_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKENETWORK_H_ diff --git a/webrtc/base/fakesslidentity.h b/webrtc/base/fakesslidentity.h new file mode 100644 index 000000000..717cb6c3b --- /dev/null +++ b/webrtc/base/fakesslidentity.h @@ -0,0 +1,94 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FAKESSLIDENTITY_H_ +#define WEBRTC_BASE_FAKESSLIDENTITY_H_ + +#include +#include + +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +class FakeSSLCertificate : public rtc::SSLCertificate { + public: + // SHA-1 is the default digest algorithm because it is available in all build + // configurations used for unit testing. + explicit FakeSSLCertificate(const std::string& data) + : data_(data), digest_algorithm_(DIGEST_SHA_1) {} + explicit FakeSSLCertificate(const std::vector& certs) + : data_(certs.front()), digest_algorithm_(DIGEST_SHA_1) { + std::vector::const_iterator it; + // Skip certs[0]. + for (it = certs.begin() + 1; it != certs.end(); ++it) { + certs_.push_back(FakeSSLCertificate(*it)); + } + } + virtual FakeSSLCertificate* GetReference() const { + return new FakeSSLCertificate(*this); + } + virtual std::string ToPEMString() const { + return data_; + } + virtual void ToDER(Buffer* der_buffer) const { + std::string der_string; + VERIFY(SSLIdentity::PemToDer(kPemTypeCertificate, data_, &der_string)); + der_buffer->SetData(der_string.c_str(), der_string.size()); + } + void set_digest_algorithm(const std::string& algorithm) { + digest_algorithm_ = algorithm; + } + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const { + *algorithm = digest_algorithm_; + return true; + } + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + *length = rtc::ComputeDigest(algorithm, data_.c_str(), data_.size(), + digest, size); + return (*length != 0); + } + virtual bool GetChain(SSLCertChain** chain) const { + if (certs_.empty()) + return false; + std::vector new_certs(certs_.size()); + std::transform(certs_.begin(), certs_.end(), new_certs.begin(), DupCert); + *chain = new SSLCertChain(new_certs); + return true; + } + + private: + static FakeSSLCertificate* DupCert(FakeSSLCertificate cert) { + return cert.GetReference(); + } + std::string data_; + std::vector certs_; + std::string digest_algorithm_; +}; + +class FakeSSLIdentity : public rtc::SSLIdentity { + public: + explicit FakeSSLIdentity(const std::string& data) : cert_(data) {} + explicit FakeSSLIdentity(const FakeSSLCertificate& cert) : cert_(cert) {} + virtual FakeSSLIdentity* GetReference() const { + return new FakeSSLIdentity(*this); + } + virtual const FakeSSLCertificate& certificate() const { return cert_; } + private: + FakeSSLCertificate cert_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKESSLIDENTITY_H_ diff --git a/webrtc/base/faketaskrunner.h b/webrtc/base/faketaskrunner.h new file mode 100644 index 000000000..5408ab8b2 --- /dev/null +++ b/webrtc/base/faketaskrunner.h @@ -0,0 +1,38 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// A fake TaskRunner for use in unit tests. + +#ifndef WEBRTC_BASE_FAKETASKRUNNER_H_ +#define WEBRTC_BASE_FAKETASKRUNNER_H_ + +#include "webrtc/base/taskparent.h" +#include "webrtc/base/taskrunner.h" + +namespace rtc { + +class FakeTaskRunner : public TaskRunner { + public: + FakeTaskRunner() : current_time_(0) {} + virtual ~FakeTaskRunner() {} + + virtual void WakeTasks() { RunTasks(); } + + virtual int64 CurrentTime() { + // Implement if needed. + return current_time_++; + } + + int64 current_time_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKETASKRUNNER_H_ diff --git a/webrtc/base/filelock.cc b/webrtc/base/filelock.cc new file mode 100644 index 000000000..fc921febc --- /dev/null +++ b/webrtc/base/filelock.cc @@ -0,0 +1,62 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/filelock.h" + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +FileLock::FileLock(const std::string& path, FileStream* file) + : path_(path), file_(file) { +} + +FileLock::~FileLock() { + MaybeUnlock(); +} + +void FileLock::Unlock() { + LOG_F(LS_INFO); + MaybeUnlock(); +} + +void FileLock::MaybeUnlock() { + if (file_) { + LOG(LS_INFO) << "Unlocking:" << path_; + file_->Close(); + Filesystem::DeleteFile(path_); + file_.reset(); + } +} + +FileLock* FileLock::TryLock(const std::string& path) { + FileStream* stream = new FileStream(); + bool ok = false; +#if defined(WEBRTC_WIN) + // Open and lock in a single operation. + ok = stream->OpenShare(path, "a", _SH_DENYRW, NULL); +#else // WEBRTC_LINUX && !WEBRTC_ANDROID and WEBRTC_MAC && !defined(WEBRTC_IOS) + ok = stream->Open(path, "a", NULL) && stream->TryLock(); +#endif + if (ok) { + return new FileLock(path, stream); + } else { + // Something failed, either we didn't succeed to open the + // file or we failed to lock it. Anyway remove the heap + // allocated object and then return NULL to indicate failure. + delete stream; + return NULL; + } +} + +} // namespace rtc diff --git a/webrtc/base/filelock.h b/webrtc/base/filelock.h new file mode 100644 index 000000000..46c58ea4a --- /dev/null +++ b/webrtc/base/filelock.h @@ -0,0 +1,53 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FILELOCK_H_ +#define WEBRTC_BASE_FILELOCK_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class FileStream; + +// Implements a very simple cross process lock based on a file. +// When Lock(...) is called we try to open/create the file in read/write +// mode without any sharing. (Or locking it with flock(...) on Unix) +// If the process crash the OS will make sure that the file descriptor +// is released and another process can accuire the lock. +// This doesn't work on ancient OSX/Linux versions if used on NFS. +// (Nfs-client before: ~2.6 and Linux Kernel < 2.6.) +class FileLock { + public: + virtual ~FileLock(); + + // Attempts to lock the file. The caller owns the returned + // lock object. Returns NULL if the file already was locked. + static FileLock* TryLock(const std::string& path); + void Unlock(); + + protected: + FileLock(const std::string& path, FileStream* file); + + private: + void MaybeUnlock(); + + std::string path_; + scoped_ptr file_; + + DISALLOW_EVIL_CONSTRUCTORS(FileLock); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FILELOCK_H_ diff --git a/webrtc/base/filelock_unittest.cc b/webrtc/base/filelock_unittest.cc new file mode 100644 index 000000000..eecbf07da --- /dev/null +++ b/webrtc/base/filelock_unittest.cc @@ -0,0 +1,87 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/event.h" +#include "webrtc/base/filelock.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +const static std::string kLockFile = "TestLockFile"; +const static int kTimeoutMS = 5000; + +class FileLockTest : public testing::Test, public Runnable { + public: + FileLockTest() : done_(false, false), thread_lock_failed_(false) { + } + + virtual void Run(Thread* t) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + // The lock is already owned by the main thread of + // this test, therefore the TryLock(...) call should fail. + thread_lock_failed_ = lock.get() == NULL; + done_.Set(); + } + + protected: + virtual void SetUp() { + thread_lock_failed_ = false; + Filesystem::GetAppTempFolder(&temp_dir_); + temp_file_ = Pathname(temp_dir_.pathname(), kLockFile); + } + + void LockOnThread() { + locker_.Start(this); + done_.Wait(kTimeoutMS); + } + + Event done_; + Thread locker_; + bool thread_lock_failed_; + Pathname temp_dir_; + Pathname temp_file_; +}; + +TEST_F(FileLockTest, TestLockFileDeleted) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock.get() != NULL); + EXPECT_FALSE(Filesystem::IsAbsent(temp_file_.pathname())); + lock->Unlock(); + EXPECT_TRUE(Filesystem::IsAbsent(temp_file_.pathname())); +} + +TEST_F(FileLockTest, TestLock) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock.get() != NULL); +} + +TEST_F(FileLockTest, TestLockX2) { + scoped_ptr lock1(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock1.get() != NULL); + + scoped_ptr lock2(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock2.get() == NULL); +} + +TEST_F(FileLockTest, TestThreadedLock) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock.get() != NULL); + + LockOnThread(); + EXPECT_TRUE(thread_lock_failed_); +} + +} // namespace rtc diff --git a/webrtc/base/fileutils.cc b/webrtc/base/fileutils.cc new file mode 100644 index 000000000..60bd0f8f4 --- /dev/null +++ b/webrtc/base/fileutils.cc @@ -0,0 +1,307 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#if defined(WEBRTC_WIN) +// TODO(grunell): Remove io.h includes when Chromium has started +// to use AEC in each source. http://crbug.com/264611. +#include +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/pathutils.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/stream.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32filesystem.h" +#else +#include "webrtc/base/unixfilesystem.h" +#endif + +#if !defined(WEBRTC_WIN) +#define MAX_PATH 260 +#endif + +namespace rtc { + +////////////////////////// +// Directory Iterator // +////////////////////////// + +// A DirectoryIterator is created with a given directory. It originally points +// to the first file in the directory, and can be advanecd with Next(). This +// allows you to get information about each file. + + // Constructor +DirectoryIterator::DirectoryIterator() +#ifdef WEBRTC_WIN + : handle_(INVALID_HANDLE_VALUE) { +#else + : dir_(NULL), dirent_(NULL) { +#endif +} + + // Destructor +DirectoryIterator::~DirectoryIterator() { +#if defined(WEBRTC_WIN) + if (handle_ != INVALID_HANDLE_VALUE) + ::FindClose(handle_); +#else + if (dir_) + closedir(dir_); +#endif +} + + // Starts traversing a directory. + // dir is the directory to traverse + // returns true if the directory exists and is valid +bool DirectoryIterator::Iterate(const Pathname &dir) { + directory_ = dir.pathname(); +#if defined(WEBRTC_WIN) + if (handle_ != INVALID_HANDLE_VALUE) + ::FindClose(handle_); + std::string d = dir.pathname() + '*'; + handle_ = ::FindFirstFile(ToUtf16(d).c_str(), &data_); + if (handle_ == INVALID_HANDLE_VALUE) + return false; +#else + if (dir_ != NULL) + closedir(dir_); + dir_ = ::opendir(directory_.c_str()); + if (dir_ == NULL) + return false; + dirent_ = readdir(dir_); + if (dirent_ == NULL) + return false; + + if (::stat(std::string(directory_ + Name()).c_str(), &stat_) != 0) + return false; +#endif + return true; +} + + // Advances to the next file + // returns true if there were more files in the directory. +bool DirectoryIterator::Next() { +#if defined(WEBRTC_WIN) + return ::FindNextFile(handle_, &data_) == TRUE; +#else + dirent_ = ::readdir(dir_); + if (dirent_ == NULL) + return false; + + return ::stat(std::string(directory_ + Name()).c_str(), &stat_) == 0; +#endif +} + + // returns true if the file currently pointed to is a directory +bool DirectoryIterator::IsDirectory() const { +#if defined(WEBRTC_WIN) + return (data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FALSE; +#else + return S_ISDIR(stat_.st_mode); +#endif +} + + // returns the name of the file currently pointed to +std::string DirectoryIterator::Name() const { +#if defined(WEBRTC_WIN) + return ToUtf8(data_.cFileName); +#else + assert(dirent_ != NULL); + return dirent_->d_name; +#endif +} + + // returns the size of the file currently pointed to +size_t DirectoryIterator::FileSize() const { +#if !defined(WEBRTC_WIN) + return stat_.st_size; +#else + return data_.nFileSizeLow; +#endif +} + + // returns the last modified time of this file +time_t DirectoryIterator::FileModifyTime() const { +#if defined(WEBRTC_WIN) + time_t val; + FileTimeToUnixTime(data_.ftLastWriteTime, &val); + return val; +#else + return stat_.st_mtime; +#endif +} + +FilesystemInterface* Filesystem::default_filesystem_ = NULL; + +FilesystemInterface *Filesystem::EnsureDefaultFilesystem() { + if (!default_filesystem_) { +#if defined(WEBRTC_WIN) + default_filesystem_ = new Win32Filesystem(); +#else + default_filesystem_ = new UnixFilesystem(); +#endif + } + return default_filesystem_; +} + +bool FilesystemInterface::CopyFolder(const Pathname &old_path, + const Pathname &new_path) { + bool success = true; + VERIFY(IsFolder(old_path)); + Pathname new_dir; + new_dir.SetFolder(new_path.pathname()); + Pathname old_dir; + old_dir.SetFolder(old_path.pathname()); + if (!CreateFolder(new_dir)) + return false; + DirectoryIterator *di = IterateDirectory(); + if (!di) + return false; + if (di->Iterate(old_dir.pathname())) { + do { + if (di->Name() == "." || di->Name() == "..") + continue; + Pathname source; + Pathname dest; + source.SetFolder(old_dir.pathname()); + dest.SetFolder(new_path.pathname()); + source.SetFilename(di->Name()); + dest.SetFilename(di->Name()); + if (!CopyFileOrFolder(source, dest)) + success = false; + } while (di->Next()); + } + delete di; + return success; +} + +bool FilesystemInterface::DeleteFolderContents(const Pathname &folder) { + bool success = true; + VERIFY(IsFolder(folder)); + DirectoryIterator *di = IterateDirectory(); + if (!di) + return false; + if (di->Iterate(folder)) { + do { + if (di->Name() == "." || di->Name() == "..") + continue; + Pathname subdir; + subdir.SetFolder(folder.pathname()); + if (di->IsDirectory()) { + subdir.AppendFolder(di->Name()); + if (!DeleteFolderAndContents(subdir)) { + success = false; + } + } else { + subdir.SetFilename(di->Name()); + if (!DeleteFile(subdir)) { + success = false; + } + } + } while (di->Next()); + } + delete di; + return success; +} + +bool FilesystemInterface::CleanAppTempFolder() { + Pathname path; + if (!GetAppTempFolder(&path)) + return false; + if (IsAbsent(path)) + return true; + if (!IsTemporaryPath(path)) { + ASSERT(false); + return false; + } + return DeleteFolderContents(path); +} + +Pathname Filesystem::GetCurrentDirectory() { + return EnsureDefaultFilesystem()->GetCurrentDirectory(); +} + +bool CreateUniqueFile(Pathname& path, bool create_empty) { + LOG(LS_INFO) << "Path " << path.pathname() << std::endl; + // If no folder is supplied, use the temporary folder + if (path.folder().empty()) { + Pathname temporary_path; + if (!Filesystem::GetTemporaryFolder(temporary_path, true, NULL)) { + printf("Get temp failed\n"); + return false; + } + path.SetFolder(temporary_path.pathname()); + } + + // If no filename is supplied, use a temporary name + if (path.filename().empty()) { + std::string folder(path.folder()); + std::string filename = Filesystem::TempFilename(folder, "gt"); + path.SetPathname(filename); + if (!create_empty) { + Filesystem::DeleteFile(path.pathname()); + } + return true; + } + + // Otherwise, create a unique name based on the given filename + // foo.txt -> foo-N.txt + const std::string basename = path.basename(); + const size_t MAX_VERSION = 100; + size_t version = 0; + while (version < MAX_VERSION) { + std::string pathname = path.pathname(); + + if (!Filesystem::IsFile(pathname)) { + if (create_empty) { + FileStream* fs = Filesystem::OpenFile(pathname, "w"); + delete fs; + } + return true; + } + version += 1; + char version_base[MAX_PATH]; + sprintfn(version_base, ARRAY_SIZE(version_base), "%s-%u", + basename.c_str(), version); + path.SetBasename(version_base); + } + return true; +} + +// Taken from Chromium's base/platform_file_*.cc. +// TODO(grunell): Remove when Chromium has started to use AEC in each source. +// http://crbug.com/264611. +FILE* FdopenPlatformFileForWriting(PlatformFile file) { +#if defined(WEBRTC_WIN) + if (file == kInvalidPlatformFileValue) + return NULL; + int fd = _open_osfhandle(reinterpret_cast(file), 0); + if (fd < 0) + return NULL; + return _fdopen(fd, "w"); +#else + return fdopen(file, "w"); +#endif +} + +bool ClosePlatformFile(PlatformFile file) { +#if defined(WEBRTC_WIN) + return CloseHandle(file) != 0; +#else + return close(file); +#endif +} + +} // namespace rtc diff --git a/webrtc/base/fileutils.h b/webrtc/base/fileutils.h new file mode 100644 index 000000000..c0a3f88c6 --- /dev/null +++ b/webrtc/base/fileutils.h @@ -0,0 +1,459 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FILEUTILS_H_ +#define WEBRTC_BASE_FILEUTILS_H_ + +#include + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#else +#include +#include +#include +#include +#include +#endif + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class FileStream; +class Pathname; + +////////////////////////// +// Directory Iterator // +////////////////////////// + +// A DirectoryIterator is created with a given directory. It originally points +// to the first file in the directory, and can be advanecd with Next(). This +// allows you to get information about each file. + +class DirectoryIterator { + friend class Filesystem; + public: + // Constructor + DirectoryIterator(); + // Destructor + virtual ~DirectoryIterator(); + + // Starts traversing a directory + // dir is the directory to traverse + // returns true if the directory exists and is valid + // The iterator will point to the first entry in the directory + virtual bool Iterate(const Pathname &path); + + // Advances to the next file + // returns true if there were more files in the directory. + virtual bool Next(); + + // returns true if the file currently pointed to is a directory + virtual bool IsDirectory() const; + + // returns the name of the file currently pointed to + virtual std::string Name() const; + + // returns the size of the file currently pointed to + virtual size_t FileSize() const; + + // returns the last modified time of the file currently pointed to + virtual time_t FileModifyTime() const; + + // checks whether current file is a special directory file "." or ".." + bool IsDots() const { + std::string filename(Name()); + return (filename.compare(".") == 0) || (filename.compare("..") == 0); + } + + private: + std::string directory_; +#if defined(WEBRTC_WIN) + WIN32_FIND_DATA data_; + HANDLE handle_; +#else + DIR *dir_; + struct dirent *dirent_; + struct stat stat_; +#endif +}; + +enum FileTimeType { FTT_CREATED, FTT_MODIFIED, FTT_ACCESSED }; + +class FilesystemInterface { + public: + virtual ~FilesystemInterface() {} + + // Returns a DirectoryIterator for a given pathname. + // TODO: Do fancy abstracted stuff + virtual DirectoryIterator *IterateDirectory() { + return new DirectoryIterator(); + } + + // Opens a file. Returns an open StreamInterface if function succeeds. + // Otherwise, returns NULL. + // TODO: Add an error param to indicate failure reason, similar to + // FileStream::Open + virtual FileStream *OpenFile(const Pathname &filename, + const std::string &mode) = 0; + + // Atomically creates an empty file accessible only to the current user if one + // does not already exist at the given path, otherwise fails. This is the only + // secure way to create a file in a shared temp directory (e.g., C:\Temp on + // Windows or /tmp on Linux). + // Note that if it is essential that a file be successfully created then the + // app must generate random names and retry on failure, or else it will be + // vulnerable to a trivial DoS. + virtual bool CreatePrivateFile(const Pathname &filename) = 0; + + // This will attempt to delete the path located at filename. + // It ASSERTS and returns false if the path points to a folder or a + // non-existent file. + virtual bool DeleteFile(const Pathname &filename) = 0; + + // This will attempt to delete the empty folder located at 'folder' + // It ASSERTS and returns false if the path points to a file or a non-existent + // folder. It fails normally if the folder is not empty or can otherwise + // not be deleted. + virtual bool DeleteEmptyFolder(const Pathname &folder) = 0; + + // This will call IterateDirectory, to get a directory iterator, and then + // call DeleteFolderAndContents and DeleteFile on every path contained in this + // folder. If the folder is empty, this returns true. + virtual bool DeleteFolderContents(const Pathname &folder); + + // This deletes the contents of a folder, recursively, and then deletes + // the folder itself. + virtual bool DeleteFolderAndContents(const Pathname &folder) { + return DeleteFolderContents(folder) && DeleteEmptyFolder(folder); + } + + // This will delete whatever is located at path, be it a file or a folder. + // If it is a folder, it will delete it recursively by calling + // DeleteFolderAndContents + bool DeleteFileOrFolder(const Pathname &path) { + if (IsFolder(path)) + return DeleteFolderAndContents(path); + else + return DeleteFile(path); + } + + // Creates a directory. This will call itself recursively to create /foo/bar + // even if /foo does not exist. Returns true if the function succeeds. + virtual bool CreateFolder(const Pathname &pathname) = 0; + + // This moves a file from old_path to new_path, where "old_path" is a + // plain file. This ASSERTs and returns false if old_path points to a + // directory, and returns true if the function succeeds. + // If the new path is on a different volume than the old path, this function + // will attempt to copy and, if that succeeds, delete the old path. + virtual bool MoveFolder(const Pathname &old_path, + const Pathname &new_path) = 0; + + // This moves a directory from old_path to new_path, where "old_path" is a + // directory. This ASSERTs and returns false if old_path points to a plain + // file, and returns true if the function succeeds. + // If the new path is on a different volume, this function will attempt to + // copy and if that succeeds, delete the old path. + virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path) = 0; + + // This attempts to move whatever is located at old_path to new_path, + // be it a file or folder. + bool MoveFileOrFolder(const Pathname &old_path, const Pathname &new_path) { + if (IsFile(old_path)) { + return MoveFile(old_path, new_path); + } else { + return MoveFolder(old_path, new_path); + } + } + + // This copies a file from old_path to new_path. This method ASSERTs and + // returns false if old_path is a folder, and returns true if the copy + // succeeds. + virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path) = 0; + + // This copies a folder from old_path to new_path. + bool CopyFolder(const Pathname &old_path, const Pathname &new_path); + + bool CopyFileOrFolder(const Pathname &old_path, const Pathname &new_path) { + if (IsFile(old_path)) + return CopyFile(old_path, new_path); + else + return CopyFolder(old_path, new_path); + } + + // Returns true if pathname refers to a directory + virtual bool IsFolder(const Pathname& pathname) = 0; + + // Returns true if pathname refers to a file + virtual bool IsFile(const Pathname& pathname) = 0; + + // Returns true if pathname refers to no filesystem object, every parent + // directory either exists, or is also absent. + virtual bool IsAbsent(const Pathname& pathname) = 0; + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPath(const Pathname& pathname) = 0; + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exits) + virtual bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append) = 0; + + virtual std::string TempFilename(const Pathname &dir, + const std::string &prefix) = 0; + + // Determines the size of the file indicated by path. + virtual bool GetFileSize(const Pathname& path, size_t* size) = 0; + + // Determines a timestamp associated with the file indicated by path. + virtual bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) = 0; + + // Returns the path to the running application. + // Note: This is not guaranteed to work on all platforms. Be aware of the + // limitations before using it, and robustly handle failure. + virtual bool GetAppPathname(Pathname* path) = 0; + + // Get a folder that is unique to the current application, which is suitable + // for sharing data between executions of the app. If the per_user arg is + // true, the folder is also specific to the current user. + virtual bool GetAppDataFolder(Pathname* path, bool per_user) = 0; + + // Get a temporary folder that is unique to the current user and application. + // TODO: Re-evaluate the goals of this function. We probably just need any + // directory that won't collide with another existing directory, and which + // will be cleaned up when the program exits. + virtual bool GetAppTempFolder(Pathname* path) = 0; + + // Delete the contents of the folder returned by GetAppTempFolder + bool CleanAppTempFolder(); + + virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) = 0; + + // Returns the absolute path of the current directory. + virtual Pathname GetCurrentDirectory() = 0; + + // Note: These might go into some shared config section later, but they're + // used by some methods in this interface, so we're leaving them here for now. + void SetOrganizationName(const std::string& organization) { + organization_name_ = organization; + } + void GetOrganizationName(std::string* organization) { + ASSERT(NULL != organization); + *organization = organization_name_; + } + void SetApplicationName(const std::string& application) { + application_name_ = application; + } + void GetApplicationName(std::string* application) { + ASSERT(NULL != application); + *application = application_name_; + } + + protected: + std::string organization_name_; + std::string application_name_; +}; + +class Filesystem { + public: + static FilesystemInterface *default_filesystem() { + ASSERT(default_filesystem_ != NULL); + return default_filesystem_; + } + + static void set_default_filesystem(FilesystemInterface *filesystem) { + default_filesystem_ = filesystem; + } + + static FilesystemInterface *swap_default_filesystem( + FilesystemInterface *filesystem) { + FilesystemInterface *cur = default_filesystem_; + default_filesystem_ = filesystem; + return cur; + } + + static DirectoryIterator *IterateDirectory() { + return EnsureDefaultFilesystem()->IterateDirectory(); + } + + static bool CreateFolder(const Pathname &pathname) { + return EnsureDefaultFilesystem()->CreateFolder(pathname); + } + + static FileStream *OpenFile(const Pathname &filename, + const std::string &mode) { + return EnsureDefaultFilesystem()->OpenFile(filename, mode); + } + + static bool CreatePrivateFile(const Pathname &filename) { + return EnsureDefaultFilesystem()->CreatePrivateFile(filename); + } + + static bool DeleteFile(const Pathname &filename) { + return EnsureDefaultFilesystem()->DeleteFile(filename); + } + + static bool DeleteEmptyFolder(const Pathname &folder) { + return EnsureDefaultFilesystem()->DeleteEmptyFolder(folder); + } + + static bool DeleteFolderContents(const Pathname &folder) { + return EnsureDefaultFilesystem()->DeleteFolderContents(folder); + } + + static bool DeleteFolderAndContents(const Pathname &folder) { + return EnsureDefaultFilesystem()->DeleteFolderAndContents(folder); + } + + static bool MoveFolder(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->MoveFolder(old_path, new_path); + } + + static bool MoveFile(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->MoveFile(old_path, new_path); + } + + static bool CopyFolder(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->CopyFolder(old_path, new_path); + } + + static bool CopyFile(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->CopyFile(old_path, new_path); + } + + static bool IsFolder(const Pathname& pathname) { + return EnsureDefaultFilesystem()->IsFolder(pathname); + } + + static bool IsFile(const Pathname &pathname) { + return EnsureDefaultFilesystem()->IsFile(pathname); + } + + static bool IsAbsent(const Pathname &pathname) { + return EnsureDefaultFilesystem()->IsAbsent(pathname); + } + + static bool IsTemporaryPath(const Pathname& pathname) { + return EnsureDefaultFilesystem()->IsTemporaryPath(pathname); + } + + static bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append) { + return EnsureDefaultFilesystem()->GetTemporaryFolder(path, create, append); + } + + static std::string TempFilename(const Pathname &dir, + const std::string &prefix) { + return EnsureDefaultFilesystem()->TempFilename(dir, prefix); + } + + static bool GetFileSize(const Pathname& path, size_t* size) { + return EnsureDefaultFilesystem()->GetFileSize(path, size); + } + + static bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) { + return EnsureDefaultFilesystem()->GetFileTime(path, which, time); + } + + static bool GetAppPathname(Pathname* path) { + return EnsureDefaultFilesystem()->GetAppPathname(path); + } + + static bool GetAppDataFolder(Pathname* path, bool per_user) { + return EnsureDefaultFilesystem()->GetAppDataFolder(path, per_user); + } + + static bool GetAppTempFolder(Pathname* path) { + return EnsureDefaultFilesystem()->GetAppTempFolder(path); + } + + static bool CleanAppTempFolder() { + return EnsureDefaultFilesystem()->CleanAppTempFolder(); + } + + static bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { + return EnsureDefaultFilesystem()->GetDiskFreeSpace(path, freebytes); + } + + // Definition has to be in the .cc file due to returning forward-declared + // Pathname by value. + static Pathname GetCurrentDirectory(); + + static void SetOrganizationName(const std::string& organization) { + EnsureDefaultFilesystem()->SetOrganizationName(organization); + } + + static void GetOrganizationName(std::string* organization) { + EnsureDefaultFilesystem()->GetOrganizationName(organization); + } + + static void SetApplicationName(const std::string& application) { + EnsureDefaultFilesystem()->SetApplicationName(application); + } + + static void GetApplicationName(std::string* application) { + EnsureDefaultFilesystem()->GetApplicationName(application); + } + + private: + static FilesystemInterface* default_filesystem_; + + static FilesystemInterface *EnsureDefaultFilesystem(); + DISALLOW_IMPLICIT_CONSTRUCTORS(Filesystem); +}; + +class FilesystemScope{ + public: + explicit FilesystemScope(FilesystemInterface *new_fs) { + old_fs_ = Filesystem::swap_default_filesystem(new_fs); + } + ~FilesystemScope() { + Filesystem::set_default_filesystem(old_fs_); + } + private: + FilesystemInterface* old_fs_; + DISALLOW_IMPLICIT_CONSTRUCTORS(FilesystemScope); +}; + +// Generates a unique filename based on the input path. If no path component +// is specified, it uses the temporary directory. If a filename is provided, +// up to 100 variations of form basename-N.extension are tried. When +// create_empty is true, an empty file of this name is created (which +// decreases the chance of a temporary filename collision with another +// process). +bool CreateUniqueFile(Pathname& path, bool create_empty); + +// Taken from Chromium's base/platform_file.h. +// Don't use ClosePlatformFile to close a file opened with FdopenPlatformFile. +// Use fclose instead. +// TODO(grunell): Remove when Chromium has started to use AEC in each source. +// http://crbug.com/264611. +#if defined(WEBRTC_WIN) +typedef HANDLE PlatformFile; +const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE; +#elif defined(WEBRTC_POSIX) +typedef int PlatformFile; +const PlatformFile kInvalidPlatformFileValue = -1; +#else +#error Unsupported platform +#endif + +FILE* FdopenPlatformFileForWriting(PlatformFile file); +bool ClosePlatformFile(PlatformFile file); + +} // namespace rtc + +#endif // WEBRTC_BASE_FILEUTILS_H_ diff --git a/webrtc/base/fileutils_mock.h b/webrtc/base/fileutils_mock.h new file mode 100644 index 000000000..e9d20a75f --- /dev/null +++ b/webrtc/base/fileutils_mock.h @@ -0,0 +1,253 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FILEUTILS_MOCK_H_ +#define WEBRTC_BASE_FILEUTILS_MOCK_H_ + +#include +#include +#include + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +class FakeFileStream : public FileStream { + public: + explicit FakeFileStream(const std::string & contents) : + string_stream_(contents) + {} + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + return string_stream_.Read(buffer, buffer_len, read, error); + } + + virtual void Close() { + return string_stream_.Close(); + } + virtual bool GetSize(size_t* size) const { + return string_stream_.GetSize(size); + } + + private: + StringStream string_stream_; +}; + +class FakeDirectoryIterator : public DirectoryIterator { + public: + typedef std::pair File; + + /* + * files should be sorted by directory + * put '/' at the end of file if you want it to be a directory + * + * Sample list: + * /var/dir/file1 + * /var/dir/file2 + * /var/dir/subdir1/ + * /var/dir/subdir2/ + * /var/dir2/file2 + * /var/dir3/ + * + * you can call Iterate for any path: /var, /var/dir, /var/dir2 + * unrelated files will be ignored + */ + explicit FakeDirectoryIterator(const std::vector& all_files) : + all_files_(all_files) {} + + virtual bool Iterate(const Pathname& path) { + path_iterator_ = all_files_.begin(); + path_ = path.pathname(); + + // make sure path ends end with '/' + if (path_.rfind(Pathname::DefaultFolderDelimiter()) != path_.size() - 1) + path_ += Pathname::DefaultFolderDelimiter(); + + return FakeDirectoryIterator::Search(std::string("")); + } + + virtual bool Next() { + std::string current_name = Name(); + path_iterator_++; + return FakeDirectoryIterator::Search(current_name); + } + + bool Search(const std::string& current_name) { + for (; path_iterator_ != all_files_.end(); path_iterator_++) { + if (path_iterator_->first.find(path_) == 0 + && Name().compare(current_name) != 0) { + return true; + } + } + + return false; + } + + virtual bool IsDirectory() const { + std::string sub_path = path_iterator_->first; + + return std::string::npos != + sub_path.find(Pathname::DefaultFolderDelimiter(), path_.size()); + } + + virtual std::string Name() const { + std::string sub_path = path_iterator_->first; + + // path - top level path (ex. /var/lib) + // sub_path - subpath under top level path (ex. /var/lib/dir/dir/file ) + // find shortest non-trivial common path. (ex. /var/lib/dir) + size_t start = path_.size(); + size_t end = sub_path.find(Pathname::DefaultFolderDelimiter(), start); + + if (end != std::string::npos) { + return sub_path.substr(start, end - start); + } else { + return sub_path.substr(start); + } + } + + private: + const std::vector all_files_; + + std::string path_; + std::vector::const_iterator path_iterator_; +}; + +class FakeFileSystem : public FilesystemInterface { + public: + typedef std::pair File; + + explicit FakeFileSystem(const std::vector& all_files) : + all_files_(all_files) {} + + virtual DirectoryIterator *IterateDirectory() { + return new FakeDirectoryIterator(all_files_); + } + + virtual FileStream * OpenFile( + const Pathname &filename, + const std::string &mode) { + std::vector::const_iterator i_files = all_files_.begin(); + std::string path = filename.pathname(); + + for (; i_files != all_files_.end(); i_files++) { + if (i_files->first.compare(path) == 0) { + return new FakeFileStream(i_files->second); + } + } + + return NULL; + } + + bool CreatePrivateFile(const Pathname &filename) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteFile(const Pathname &filename) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteEmptyFolder(const Pathname &folder) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteFolderContents(const Pathname &folder) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteFolderAndContents(const Pathname &folder) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool CreateFolder(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool MoveFolder(const Pathname &old_path, const Pathname &new_path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool MoveFile(const Pathname &old_path, const Pathname &new_path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool CopyFile(const Pathname &old_path, const Pathname &new_path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsFolder(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsFile(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsAbsent(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsTemporaryPath(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + std::string TempFilename(const Pathname &dir, const std::string &prefix) { + EXPECT_TRUE(false) << "Unsupported operation"; + return std::string(); + } + bool GetFileSize(const Pathname &path, size_t *size) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetFileTime(const Pathname &path, FileTimeType which, + time_t* time) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetAppPathname(Pathname *path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetAppDataFolder(Pathname *path, bool per_user) { + EXPECT_TRUE(per_user) << "Unsupported operation"; +#if defined(WEBRTC_WIN) + path->SetPathname("c:\\Users\\test_user", ""); +#else + path->SetPathname("/home/user/test_user", ""); +#endif + return true; + } + bool GetAppTempFolder(Pathname *path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetDiskFreeSpace(const Pathname &path, int64 *freebytes) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + Pathname GetCurrentDirectory() { + return Pathname(); + } + + private: + const std::vector all_files_; +}; +} // namespace rtc + +#endif // WEBRTC_BASE_FILEUTILS_MOCK_H_ diff --git a/webrtc/base/fileutils_unittest.cc b/webrtc/base/fileutils_unittest.cc new file mode 100644 index 000000000..9076bc787 --- /dev/null +++ b/webrtc/base/fileutils_unittest.cc @@ -0,0 +1,131 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +// Make sure we can get a temp folder for the later tests. +TEST(FilesystemTest, GetTemporaryFolder) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); +} + +// Test creating a temp file, reading it back in, and deleting it. +TEST(FilesystemTest, TestOpenFile) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetPathname(Filesystem::TempFilename(path, "ut")); + + FileStream* fs; + char buf[256]; + size_t bytes; + + fs = Filesystem::OpenFile(path, "wb"); + ASSERT_TRUE(fs != NULL); + EXPECT_EQ(SR_SUCCESS, fs->Write("test", 4, &bytes, NULL)); + EXPECT_EQ(4U, bytes); + delete fs; + + EXPECT_TRUE(Filesystem::IsFile(path)); + + fs = Filesystem::OpenFile(path, "rb"); + ASSERT_TRUE(fs != NULL); + EXPECT_EQ(SR_SUCCESS, fs->Read(buf, sizeof(buf), &bytes, NULL)); + EXPECT_EQ(4U, bytes); + delete fs; + + EXPECT_TRUE(Filesystem::DeleteFile(path)); + EXPECT_FALSE(Filesystem::IsFile(path)); +} + +// Test opening a non-existent file. +TEST(FilesystemTest, TestOpenBadFile) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetFilename("not an actual file"); + + EXPECT_FALSE(Filesystem::IsFile(path)); + + FileStream* fs = Filesystem::OpenFile(path, "rb"); + EXPECT_FALSE(fs != NULL); +} + +// Test that CreatePrivateFile fails for existing files and succeeds for +// non-existent ones. +TEST(FilesystemTest, TestCreatePrivateFile) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetFilename("private_file_test"); + + // First call should succeed because the file doesn't exist yet. + EXPECT_TRUE(Filesystem::CreatePrivateFile(path)); + // Next call should fail, because now it exists. + EXPECT_FALSE(Filesystem::CreatePrivateFile(path)); + + // Verify that we have permission to open the file for reading and writing. + scoped_ptr fs(Filesystem::OpenFile(path, "wb")); + EXPECT_TRUE(fs.get() != NULL); + // Have to close the file on Windows before it will let us delete it. + fs.reset(); + + // Verify that we have permission to delete the file. + EXPECT_TRUE(Filesystem::DeleteFile(path)); +} + +// Test checking for free disk space. +TEST(FilesystemTest, TestGetDiskFreeSpace) { + // Note that we should avoid picking any file/folder which could be located + // at the remotely mounted drive/device. + Pathname path; + ASSERT_TRUE(Filesystem::GetAppDataFolder(&path, true)); + + int64 free1 = 0; + EXPECT_TRUE(Filesystem::IsFolder(path)); + EXPECT_FALSE(Filesystem::IsFile(path)); + EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free1)); + EXPECT_GT(free1, 0); + + int64 free2 = 0; + path.AppendFolder("this_folder_doesnt_exist"); + EXPECT_FALSE(Filesystem::IsFolder(path)); + EXPECT_TRUE(Filesystem::IsAbsent(path)); + EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free2)); + // These should be the same disk, and disk free space should not have changed + // by more than 1% between the two calls. + EXPECT_LT(static_cast(free1 * .9), free2); + EXPECT_LT(free2, static_cast(free1 * 1.1)); + + int64 free3 = 0; + path.clear(); + EXPECT_TRUE(path.empty()); + EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free3)); + // Current working directory may not be where exe is. + // EXPECT_LT(static_cast(free1 * .9), free3); + // EXPECT_LT(free3, static_cast(free1 * 1.1)); + EXPECT_GT(free3, 0); +} + +// Tests that GetCurrentDirectory() returns something. +TEST(FilesystemTest, TestGetCurrentDirectory) { + EXPECT_FALSE(Filesystem::GetCurrentDirectory().empty()); +} + +// Tests that GetAppPathname returns something. +TEST(FilesystemTest, TestGetAppPathname) { + Pathname path; + EXPECT_TRUE(Filesystem::GetAppPathname(&path)); + EXPECT_FALSE(path.empty()); +} + +} // namespace rtc diff --git a/webrtc/base/firewallsocketserver.cc b/webrtc/base/firewallsocketserver.cc new file mode 100644 index 000000000..31c18d981 --- /dev/null +++ b/webrtc/base/firewallsocketserver.cc @@ -0,0 +1,239 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/firewallsocketserver.h" + +#include + +#include + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +class FirewallSocket : public AsyncSocketAdapter { + public: + FirewallSocket(FirewallSocketServer* server, AsyncSocket* socket, int type) + : AsyncSocketAdapter(socket), server_(server), type_(type) { + } + + virtual int Connect(const SocketAddress& addr) { + if (type_ == SOCK_STREAM) { + if (!server_->Check(FP_TCP, GetLocalAddress(), addr)) { + LOG(LS_VERBOSE) << "FirewallSocket outbound TCP connection from " + << GetLocalAddress().ToSensitiveString() << " to " + << addr.ToSensitiveString() << " denied"; + // TODO: Handle this asynchronously. + SetError(EHOSTUNREACH); + return SOCKET_ERROR; + } + } + return AsyncSocketAdapter::Connect(addr); + } + virtual int Send(const void* pv, size_t cb) { + return SendTo(pv, cb, GetRemoteAddress()); + } + virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) { + if (type_ == SOCK_DGRAM) { + if (!server_->Check(FP_UDP, GetLocalAddress(), addr)) { + LOG(LS_VERBOSE) << "FirewallSocket outbound UDP packet from " + << GetLocalAddress().ToSensitiveString() << " to " + << addr.ToSensitiveString() << " dropped"; + return static_cast(cb); + } + } + return AsyncSocketAdapter::SendTo(pv, cb, addr); + } + virtual int Recv(void* pv, size_t cb) { + SocketAddress addr; + return RecvFrom(pv, cb, &addr); + } + virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) { + if (type_ == SOCK_DGRAM) { + while (true) { + int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + if (res <= 0) + return res; + if (server_->Check(FP_UDP, *paddr, GetLocalAddress())) + return res; + LOG(LS_VERBOSE) << "FirewallSocket inbound UDP packet from " + << paddr->ToSensitiveString() << " to " + << GetLocalAddress().ToSensitiveString() << " dropped"; + } + } + return AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + } + + virtual int Listen(int backlog) { + if (!server_->tcp_listen_enabled()) { + LOG(LS_VERBOSE) << "FirewallSocket listen attempt denied"; + return -1; + } + + return AsyncSocketAdapter::Listen(backlog); + } + virtual AsyncSocket* Accept(SocketAddress* paddr) { + SocketAddress addr; + while (AsyncSocket* sock = AsyncSocketAdapter::Accept(&addr)) { + if (server_->Check(FP_TCP, addr, GetLocalAddress())) { + if (paddr) + *paddr = addr; + return sock; + } + sock->Close(); + delete sock; + LOG(LS_VERBOSE) << "FirewallSocket inbound TCP connection from " + << addr.ToSensitiveString() << " to " + << GetLocalAddress().ToSensitiveString() << " denied"; + } + return 0; + } + + private: + FirewallSocketServer* server_; + int type_; +}; + +FirewallSocketServer::FirewallSocketServer(SocketServer* server, + FirewallManager* manager, + bool should_delete_server) + : server_(server), manager_(manager), + should_delete_server_(should_delete_server), + udp_sockets_enabled_(true), tcp_sockets_enabled_(true), + tcp_listen_enabled_(true) { + if (manager_) + manager_->AddServer(this); +} + +FirewallSocketServer::~FirewallSocketServer() { + if (manager_) + manager_->RemoveServer(this); + + if (server_ && should_delete_server_) { + delete server_; + server_ = NULL; + } +} + +void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p, + FirewallDirection d, + const SocketAddress& addr) { + SocketAddress src, dst; + if (d == FD_IN) { + dst = addr; + } else { + src = addr; + } + AddRule(allow, p, src, dst); +} + + +void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p, + const SocketAddress& src, + const SocketAddress& dst) { + Rule r; + r.allow = allow; + r.p = p; + r.src = src; + r.dst = dst; + CritScope scope(&crit_); + rules_.push_back(r); +} + +void FirewallSocketServer::ClearRules() { + CritScope scope(&crit_); + rules_.clear(); +} + +bool FirewallSocketServer::Check(FirewallProtocol p, + const SocketAddress& src, + const SocketAddress& dst) { + CritScope scope(&crit_); + for (size_t i = 0; i < rules_.size(); ++i) { + const Rule& r = rules_[i]; + if ((r.p != p) && (r.p != FP_ANY)) + continue; + if ((r.src.ipaddr() != src.ipaddr()) && !r.src.IsNil()) + continue; + if ((r.src.port() != src.port()) && (r.src.port() != 0)) + continue; + if ((r.dst.ipaddr() != dst.ipaddr()) && !r.dst.IsNil()) + continue; + if ((r.dst.port() != dst.port()) && (r.dst.port() != 0)) + continue; + return r.allow; + } + return true; +} + +Socket* FirewallSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* FirewallSocketServer::CreateSocket(int family, int type) { + return WrapSocket(server_->CreateAsyncSocket(family, type), type); +} + +AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int family, int type) { + return WrapSocket(server_->CreateAsyncSocket(family, type), type); +} + +AsyncSocket* FirewallSocketServer::WrapSocket(AsyncSocket* sock, int type) { + if (!sock || + (type == SOCK_STREAM && !tcp_sockets_enabled_) || + (type == SOCK_DGRAM && !udp_sockets_enabled_)) { + LOG(LS_VERBOSE) << "FirewallSocketServer socket creation denied"; + delete sock; + return NULL; + } + return new FirewallSocket(this, sock, type); +} + +FirewallManager::FirewallManager() { +} + +FirewallManager::~FirewallManager() { + assert(servers_.empty()); +} + +void FirewallManager::AddServer(FirewallSocketServer* server) { + CritScope scope(&crit_); + servers_.push_back(server); +} + +void FirewallManager::RemoveServer(FirewallSocketServer* server) { + CritScope scope(&crit_); + servers_.erase(std::remove(servers_.begin(), servers_.end(), server), + servers_.end()); +} + +void FirewallManager::AddRule(bool allow, FirewallProtocol p, + FirewallDirection d, const SocketAddress& addr) { + CritScope scope(&crit_); + for (std::vector::const_iterator it = + servers_.begin(); it != servers_.end(); ++it) { + (*it)->AddRule(allow, p, d, addr); + } +} + +void FirewallManager::ClearRules() { + CritScope scope(&crit_); + for (std::vector::const_iterator it = + servers_.begin(); it != servers_.end(); ++it) { + (*it)->ClearRules(); + } +} + +} // namespace rtc diff --git a/webrtc/base/firewallsocketserver.h b/webrtc/base/firewallsocketserver.h new file mode 100644 index 000000000..500b7397d --- /dev/null +++ b/webrtc/base/firewallsocketserver.h @@ -0,0 +1,120 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FIREWALLSOCKETSERVER_H_ +#define WEBRTC_BASE_FIREWALLSOCKETSERVER_H_ + +#include +#include "webrtc/base/socketserver.h" +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +class FirewallManager; + +// This SocketServer shim simulates a rule-based firewall server. + +enum FirewallProtocol { FP_UDP, FP_TCP, FP_ANY }; +enum FirewallDirection { FD_IN, FD_OUT, FD_ANY }; + +class FirewallSocketServer : public SocketServer { + public: + FirewallSocketServer(SocketServer * server, + FirewallManager * manager = NULL, + bool should_delete_server = false); + virtual ~FirewallSocketServer(); + + SocketServer* socketserver() const { return server_; } + void set_socketserver(SocketServer* server) { + if (server_ && should_delete_server_) { + delete server_; + server_ = NULL; + should_delete_server_ = false; + } + server_ = server; + } + + // Settings to control whether CreateSocket or Socket::Listen succeed. + void set_udp_sockets_enabled(bool enabled) { udp_sockets_enabled_ = enabled; } + void set_tcp_sockets_enabled(bool enabled) { tcp_sockets_enabled_ = enabled; } + bool tcp_listen_enabled() const { return tcp_listen_enabled_; } + void set_tcp_listen_enabled(bool enabled) { tcp_listen_enabled_ = enabled; } + + // Rules govern the behavior of Connect/Accept/Send/Recv attempts. + void AddRule(bool allow, FirewallProtocol p = FP_ANY, + FirewallDirection d = FD_ANY, + const SocketAddress& addr = SocketAddress()); + void AddRule(bool allow, FirewallProtocol p, + const SocketAddress& src, const SocketAddress& dst); + void ClearRules(); + + bool Check(FirewallProtocol p, + const SocketAddress& src, const SocketAddress& dst); + + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual void SetMessageQueue(MessageQueue* queue) { + server_->SetMessageQueue(queue); + } + virtual bool Wait(int cms, bool process_io) { + return server_->Wait(cms, process_io); + } + virtual void WakeUp() { + return server_->WakeUp(); + } + + Socket * WrapSocket(Socket * sock, int type); + AsyncSocket * WrapSocket(AsyncSocket * sock, int type); + + private: + SocketServer * server_; + FirewallManager * manager_; + CriticalSection crit_; + struct Rule { + bool allow; + FirewallProtocol p; + FirewallDirection d; + SocketAddress src; + SocketAddress dst; + }; + std::vector rules_; + bool should_delete_server_; + bool udp_sockets_enabled_; + bool tcp_sockets_enabled_; + bool tcp_listen_enabled_; +}; + +// FirewallManager allows you to manage firewalls in multiple threads together + +class FirewallManager { + public: + FirewallManager(); + ~FirewallManager(); + + void AddServer(FirewallSocketServer * server); + void RemoveServer(FirewallSocketServer * server); + + void AddRule(bool allow, FirewallProtocol p = FP_ANY, + FirewallDirection d = FD_ANY, + const SocketAddress& addr = SocketAddress()); + void ClearRules(); + + private: + CriticalSection crit_; + std::vector servers_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FIREWALLSOCKETSERVER_H_ diff --git a/webrtc/base/flags.cc b/webrtc/base/flags.cc new file mode 100644 index 000000000..bc7a83030 --- /dev/null +++ b/webrtc/base/flags.cc @@ -0,0 +1,298 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif + +#include "webrtc/base/flags.h" + + +// ----------------------------------------------------------------------------- +// Implementation of Flag + +Flag::Flag(const char* file, const char* name, const char* comment, + Type type, void* variable, FlagValue default__) + : file_(file), + name_(name), + comment_(comment), + type_(type), + variable_(reinterpret_cast(variable)), + default_(default__) { + FlagList::Register(this); +} + + +void Flag::SetToDefault() { + // Note that we cannot simply do '*variable_ = default_;' since + // flag variables are not really of type FlagValue and thus may + // be smaller! The FlagValue union is simply 'overlayed' on top + // of a flag variable for convenient access. Since union members + // are guarantee to be aligned at the beginning, this works. + switch (type_) { + case Flag::BOOL: + variable_->b = default_.b; + return; + case Flag::INT: + variable_->i = default_.i; + return; + case Flag::FLOAT: + variable_->f = default_.f; + return; + case Flag::STRING: + variable_->s = default_.s; + return; + } + UNREACHABLE(); +} + + +static const char* Type2String(Flag::Type type) { + switch (type) { + case Flag::BOOL: return "bool"; + case Flag::INT: return "int"; + case Flag::FLOAT: return "float"; + case Flag::STRING: return "string"; + } + UNREACHABLE(); + return NULL; +} + + +static void PrintFlagValue(Flag::Type type, FlagValue* p) { + switch (type) { + case Flag::BOOL: + printf("%s", (p->b ? "true" : "false")); + return; + case Flag::INT: + printf("%d", p->i); + return; + case Flag::FLOAT: + printf("%f", p->f); + return; + case Flag::STRING: + printf("%s", p->s); + return; + } + UNREACHABLE(); +} + + +void Flag::Print(bool print_current_value) { + printf(" --%s (%s) type: %s default: ", name_, comment_, + Type2String(type_)); + PrintFlagValue(type_, &default_); + if (print_current_value) { + printf(" current value: "); + PrintFlagValue(type_, variable_); + } + printf("\n"); +} + + +// ----------------------------------------------------------------------------- +// Implementation of FlagList + +Flag* FlagList::list_ = NULL; + + +FlagList::FlagList() { + list_ = NULL; +} + +void FlagList::Print(const char* file, bool print_current_value) { + // Since flag registration is likely by file (= C++ file), + // we don't need to sort by file and still get grouped output. + const char* current = NULL; + for (Flag* f = list_; f != NULL; f = f->next()) { + if (file == NULL || file == f->file()) { + if (current != f->file()) { + printf("Flags from %s:\n", f->file()); + current = f->file(); + } + f->Print(print_current_value); + } + } +} + + +Flag* FlagList::Lookup(const char* name) { + Flag* f = list_; + while (f != NULL && strcmp(name, f->name()) != 0) + f = f->next(); + return f; +} + + +void FlagList::SplitArgument(const char* arg, + char* buffer, int buffer_size, + const char** name, const char** value, + bool* is_bool) { + *name = NULL; + *value = NULL; + *is_bool = false; + + if (*arg == '-') { + // find the begin of the flag name + arg++; // remove 1st '-' + if (*arg == '-') + arg++; // remove 2nd '-' + if (arg[0] == 'n' && arg[1] == 'o') { + arg += 2; // remove "no" + *is_bool = true; + } + *name = arg; + + // find the end of the flag name + while (*arg != '\0' && *arg != '=') + arg++; + + // get the value if any + if (*arg == '=') { + // make a copy so we can NUL-terminate flag name + int n = static_cast(arg - *name); + if (n >= buffer_size) + Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size"); + memcpy(buffer, *name, n * sizeof(char)); + buffer[n] = '\0'; + *name = buffer; + // get the value + *value = arg + 1; + } + } +} + + +int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv, + bool remove_flags) { + // parse arguments + for (int i = 1; i < *argc; /* see below */) { + int j = i; // j > 0 + const char* arg = argv[i++]; + + // split arg into flag components + char buffer[1024]; + const char* name; + const char* value; + bool is_bool; + SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool); + + if (name != NULL) { + // lookup the flag + Flag* flag = Lookup(name); + if (flag == NULL) { + fprintf(stderr, "Error: unrecognized flag %s\n", arg); + return j; + } + + // if we still need a flag value, use the next argument if available + if (flag->type() != Flag::BOOL && value == NULL) { + if (i < *argc) { + value = argv[i++]; + } else { + fprintf(stderr, "Error: missing value for flag %s of type %s\n", + arg, Type2String(flag->type())); + return j; + } + } + + // set the flag + char empty[] = { '\0' }; + char* endp = empty; + switch (flag->type()) { + case Flag::BOOL: + *flag->bool_variable() = !is_bool; + break; + case Flag::INT: + *flag->int_variable() = strtol(value, &endp, 10); + break; + case Flag::FLOAT: + *flag->float_variable() = strtod(value, &endp); + break; + case Flag::STRING: + *flag->string_variable() = value; + break; + } + + // handle errors + if ((flag->type() == Flag::BOOL && value != NULL) || + (flag->type() != Flag::BOOL && is_bool) || + *endp != '\0') { + fprintf(stderr, "Error: illegal value for flag %s of type %s\n", + arg, Type2String(flag->type())); + return j; + } + + // remove the flag & value from the command + if (remove_flags) + while (j < i) + argv[j++] = NULL; + } + } + + // shrink the argument list + if (remove_flags) { + int j = 1; + for (int i = 1; i < *argc; i++) { + if (argv[i] != NULL) + argv[j++] = argv[i]; + } + *argc = j; + } + + // parsed all flags successfully + return 0; +} + +void FlagList::Register(Flag* flag) { + assert(flag != NULL && strlen(flag->name()) > 0); + if (Lookup(flag->name()) != NULL) + Fatal(flag->file(), 0, "flag %s declared twice", flag->name()); + flag->next_ = list_; + list_ = flag; +} + +#if defined(WEBRTC_WIN) +WindowsCommandLineArguments::WindowsCommandLineArguments() { + // start by getting the command line. + LPTSTR command_line = ::GetCommandLine(); + // now, convert it to a list of wide char strings. + LPWSTR *wide_argv = ::CommandLineToArgvW(command_line, &argc_); + // now allocate an array big enough to hold that many string pointers. + argv_ = new char*[argc_]; + + // iterate over the returned wide strings; + for(int i = 0; i < argc_; ++i) { + std::string s = rtc::ToUtf8(wide_argv[i], wcslen(wide_argv[i])); + char *buffer = new char[s.length() + 1]; + rtc::strcpyn(buffer, s.length() + 1, s.c_str()); + + // make sure the argv array has the right string at this point. + argv_[i] = buffer; + } + LocalFree(wide_argv); +} + +WindowsCommandLineArguments::~WindowsCommandLineArguments() { + // need to free each string in the array, and then the array. + for(int i = 0; i < argc_; i++) { + delete[] argv_[i]; + } + + delete[] argv_; +} +#endif // WEBRTC_WIN + diff --git a/webrtc/base/flags.h b/webrtc/base/flags.h new file mode 100644 index 000000000..ac91d8ad6 --- /dev/null +++ b/webrtc/base/flags.h @@ -0,0 +1,267 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +// Originally comes from shared/commandlineflags/flags.h + +// Flags are defined and declared using DEFINE_xxx and DECLARE_xxx macros, +// where xxx is the flag type. Flags are referred to via FLAG_yyy, +// where yyy is the flag name. For intialization and iteration of flags, +// see the FlagList class. For full programmatic access to any +// flag, see the Flag class. +// +// The implementation only relies and basic C++ functionality +// and needs no special library or STL support. + +#ifndef WEBRTC_BASE_FLAGS_H__ +#define WEBRTC_BASE_FLAGS_H__ + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/common.h" + +// Internal use only. +union FlagValue { + // Note: Because in C++ non-bool values are silently converted into + // bool values ('bool b = "false";' results in b == true!), we pass + // and int argument to New_BOOL as this appears to be safer - sigh. + // In particular, it prevents the (not uncommon!) bug where a bool + // flag is defined via: DEFINE_bool(flag, "false", "some comment");. + static FlagValue New_BOOL(int b) { + FlagValue v; + v.b = (b != 0); + return v; + } + + static FlagValue New_INT(int i) { + FlagValue v; + v.i = i; + return v; + } + + static FlagValue New_FLOAT(float f) { + FlagValue v; + v.f = f; + return v; + } + + static FlagValue New_STRING(const char* s) { + FlagValue v; + v.s = s; + return v; + } + + bool b; + int i; + double f; + const char* s; +}; + + +// Each flag can be accessed programmatically via a Flag object. +class Flag { + public: + enum Type { BOOL, INT, FLOAT, STRING }; + + // Internal use only. + Flag(const char* file, const char* name, const char* comment, + Type type, void* variable, FlagValue default_); + + // General flag information + const char* file() const { return file_; } + const char* name() const { return name_; } + const char* comment() const { return comment_; } + + // Flag type + Type type() const { return type_; } + + // Flag variables + bool* bool_variable() const { + assert(type_ == BOOL); + return &variable_->b; + } + + int* int_variable() const { + assert(type_ == INT); + return &variable_->i; + } + + double* float_variable() const { + assert(type_ == FLOAT); + return &variable_->f; + } + + const char** string_variable() const { + assert(type_ == STRING); + return &variable_->s; + } + + // Default values + bool bool_default() const { + assert(type_ == BOOL); + return default_.b; + } + + int int_default() const { + assert(type_ == INT); + return default_.i; + } + + double float_default() const { + assert(type_ == FLOAT); + return default_.f; + } + + const char* string_default() const { + assert(type_ == STRING); + return default_.s; + } + + // Resets a flag to its default value + void SetToDefault(); + + // Iteration support + Flag* next() const { return next_; } + + // Prints flag information. The current flag value is only printed + // if print_current_value is set. + void Print(bool print_current_value); + + private: + const char* file_; + const char* name_; + const char* comment_; + + Type type_; + FlagValue* variable_; + FlagValue default_; + + Flag* next_; + + friend class FlagList; // accesses next_ +}; + + +// Internal use only. +#define DEFINE_FLAG(type, c_type, name, default, comment) \ + /* define and initialize the flag */ \ + c_type FLAG_##name = (default); \ + /* register the flag */ \ + static Flag Flag_##name(__FILE__, #name, (comment), \ + Flag::type, &FLAG_##name, \ + FlagValue::New_##type(default)) + + +// Internal use only. +#define DECLARE_FLAG(c_type, name) \ + /* declare the external flag */ \ + extern c_type FLAG_##name + + +// Use the following macros to define a new flag: +#define DEFINE_bool(name, default, comment) \ + DEFINE_FLAG(BOOL, bool, name, default, comment) +#define DEFINE_int(name, default, comment) \ + DEFINE_FLAG(INT, int, name, default, comment) +#define DEFINE_float(name, default, comment) \ + DEFINE_FLAG(FLOAT, double, name, default, comment) +#define DEFINE_string(name, default, comment) \ + DEFINE_FLAG(STRING, const char*, name, default, comment) + + +// Use the following macros to declare a flag defined elsewhere: +#define DECLARE_bool(name) DECLARE_FLAG(bool, name) +#define DECLARE_int(name) DECLARE_FLAG(int, name) +#define DECLARE_float(name) DECLARE_FLAG(double, name) +#define DECLARE_string(name) DECLARE_FLAG(const char*, name) + + +// The global list of all flags. +class FlagList { + public: + FlagList(); + + // The NULL-terminated list of all flags. Traverse with Flag::next(). + static Flag* list() { return list_; } + + // If file != NULL, prints information for all flags defined in file; + // otherwise prints information for all flags in all files. The current + // flag value is only printed if print_current_value is set. + static void Print(const char* file, bool print_current_value); + + // Lookup a flag by name. Returns the matching flag or NULL. + static Flag* Lookup(const char* name); + + // Helper function to parse flags: Takes an argument arg and splits it into + // a flag name and flag value (or NULL if they are missing). is_bool is set + // if the arg started with "-no" or "--no". The buffer may be used to NUL- + // terminate the name, it must be large enough to hold any possible name. + static void SplitArgument(const char* arg, + char* buffer, int buffer_size, + const char** name, const char** value, + bool* is_bool); + + // Set the flag values by parsing the command line. If remove_flags + // is set, the flags and associated values are removed from (argc, + // argv). Returns 0 if no error occurred. Otherwise, returns the + // argv index > 0 for the argument where an error occurred. In that + // case, (argc, argv) will remain unchanged indepdendent of the + // remove_flags value, and no assumptions about flag settings should + // be made. + // + // The following syntax for flags is accepted (both '-' and '--' are ok): + // + // --flag (bool flags only) + // --noflag (bool flags only) + // --flag=value (non-bool flags only, no spaces around '=') + // --flag value (non-bool flags only) + static int SetFlagsFromCommandLine(int* argc, + const char** argv, + bool remove_flags); + static inline int SetFlagsFromCommandLine(int* argc, + char** argv, + bool remove_flags) { + return SetFlagsFromCommandLine(argc, const_cast(argv), + remove_flags); + } + + // Registers a new flag. Called during program initialization. Not + // thread-safe. + static void Register(Flag* flag); + + private: + static Flag* list_; +}; + +#if defined(WEBRTC_WIN) +// A helper class to translate Windows command line arguments into UTF8, +// which then allows us to just pass them to the flags system. +// This encapsulates all the work of getting the command line and translating +// it to an array of 8-bit strings; all you have to do is create one of these, +// and then call argc() and argv(). +class WindowsCommandLineArguments { + public: + WindowsCommandLineArguments(); + ~WindowsCommandLineArguments(); + + int argc() { return argc_; } + char **argv() { return argv_; } + private: + int argc_; + char **argv_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(WindowsCommandLineArguments); +}; +#endif // WEBRTC_WIN + + +#endif // SHARED_COMMANDLINEFLAGS_FLAGS_H__ diff --git a/webrtc/base/genericslot.h b/webrtc/base/genericslot.h new file mode 100755 index 000000000..76205b33c --- /dev/null +++ b/webrtc/base/genericslot.h @@ -0,0 +1,241 @@ +// This file was GENERATED by command: +// pump.py genericslot.h.pump +// DO NOT EDIT BY HAND!!! + +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_GENERICSLOT_H_ +#define WEBRTC_BASE_GENERICSLOT_H_ + +// To generate genericslot.h from genericslot.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py genericslot.h.pump + +// Generic-slots are pure slots that can be hooked up to signals. They're +// mainly intended to be used in tests where we want to check if a signal +// was invoked and what arguments were passed. NOTE: They do not do any +// lifetime management of the arguments received via callbacks. +// +// Example: +// /* Some signal */ +// sigslot::signal1 foo; +// +// /* We want to monitor foo in some test */ +// rtc::GenericSlot1 slot(&foo, 0); +// foo.emit(5); +// EXPECT_TRUE(slot.callback_received()); +// EXPECT_EQ(5, *(slot.arg1())); +// + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +template +class GenericSlot1 : public sigslot::has_slots<> { + public: + GenericSlot1(sigslot::signal1* signal, + const A1& arg1_initial) + : arg1_initial_(arg1_initial) { + Reset(); + signal->connect(this, &GenericSlot1::OnSignalCallback); + } + + void Reset() { + callback_received_ = false; + arg1_ = arg1_initial_; + } + + bool callback_received() const { return callback_received_; } + const A1& arg1() const { return arg1_; } + + private: + void OnSignalCallback(A1 arg1) { + callback_received_ = true; + arg1_ = arg1; + } + + bool callback_received_; + A1 arg1_initial_, arg1_; + + DISALLOW_COPY_AND_ASSIGN(GenericSlot1); +}; + +template +class GenericSlot2 : public sigslot::has_slots<> { + public: + GenericSlot2(sigslot::signal2* signal, + const A1& arg1_initial, const A2& arg2_initial) + : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial) { + Reset(); + signal->connect(this, &GenericSlot2::OnSignalCallback); + } + + void Reset() { + callback_received_ = false; + arg1_ = arg1_initial_; + arg2_ = arg2_initial_; + } + + bool callback_received() const { return callback_received_; } + const A1& arg1() const { return arg1_; } + const A2& arg2() const { return arg2_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2) { + callback_received_ = true; + arg1_ = arg1; + arg2_ = arg2; + } + + bool callback_received_; + A1 arg1_initial_, arg1_; + A2 arg2_initial_, arg2_; + + DISALLOW_COPY_AND_ASSIGN(GenericSlot2); +}; + +template +class GenericSlot3 : public sigslot::has_slots<> { + public: + GenericSlot3(sigslot::signal3* signal, + const A1& arg1_initial, const A2& arg2_initial, + const A3& arg3_initial) + : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial), + arg3_initial_(arg3_initial) { + Reset(); + signal->connect(this, &GenericSlot3::OnSignalCallback); + } + + void Reset() { + callback_received_ = false; + arg1_ = arg1_initial_; + arg2_ = arg2_initial_; + arg3_ = arg3_initial_; + } + + bool callback_received() const { return callback_received_; } + const A1& arg1() const { return arg1_; } + const A2& arg2() const { return arg2_; } + const A3& arg3() const { return arg3_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3) { + callback_received_ = true; + arg1_ = arg1; + arg2_ = arg2; + arg3_ = arg3; + } + + bool callback_received_; + A1 arg1_initial_, arg1_; + A2 arg2_initial_, arg2_; + A3 arg3_initial_, arg3_; + + DISALLOW_COPY_AND_ASSIGN(GenericSlot3); +}; + +template +class GenericSlot4 : public sigslot::has_slots<> { + public: + GenericSlot4(sigslot::signal4* signal, + const A1& arg1_initial, const A2& arg2_initial, + const A3& arg3_initial, const A4& arg4_initial) + : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial), + arg3_initial_(arg3_initial), arg4_initial_(arg4_initial) { + Reset(); + signal->connect(this, &GenericSlot4::OnSignalCallback); + } + + void Reset() { + callback_received_ = false; + arg1_ = arg1_initial_; + arg2_ = arg2_initial_; + arg3_ = arg3_initial_; + arg4_ = arg4_initial_; + } + + bool callback_received() const { return callback_received_; } + const A1& arg1() const { return arg1_; } + const A2& arg2() const { return arg2_; } + const A3& arg3() const { return arg3_; } + const A4& arg4() const { return arg4_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4) { + callback_received_ = true; + arg1_ = arg1; + arg2_ = arg2; + arg3_ = arg3; + arg4_ = arg4; + } + + bool callback_received_; + A1 arg1_initial_, arg1_; + A2 arg2_initial_, arg2_; + A3 arg3_initial_, arg3_; + A4 arg4_initial_, arg4_; + + DISALLOW_COPY_AND_ASSIGN(GenericSlot4); +}; + +template +class GenericSlot5 : public sigslot::has_slots<> { + public: + GenericSlot5(sigslot::signal5* signal, + const A1& arg1_initial, const A2& arg2_initial, + const A3& arg3_initial, const A4& arg4_initial, + const A5& arg5_initial) + : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial), + arg3_initial_(arg3_initial), arg4_initial_(arg4_initial), + arg5_initial_(arg5_initial) { + Reset(); + signal->connect(this, &GenericSlot5::OnSignalCallback); + } + + void Reset() { + callback_received_ = false; + arg1_ = arg1_initial_; + arg2_ = arg2_initial_; + arg3_ = arg3_initial_; + arg4_ = arg4_initial_; + arg5_ = arg5_initial_; + } + + bool callback_received() const { return callback_received_; } + const A1& arg1() const { return arg1_; } + const A2& arg2() const { return arg2_; } + const A3& arg3() const { return arg3_; } + const A4& arg4() const { return arg4_; } + const A5& arg5() const { return arg5_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) { + callback_received_ = true; + arg1_ = arg1; + arg2_ = arg2; + arg3_ = arg3; + arg4_ = arg4; + arg5_ = arg5; + } + + bool callback_received_; + A1 arg1_initial_, arg1_; + A2 arg2_initial_, arg2_; + A3 arg3_initial_, arg3_; + A4 arg4_initial_, arg4_; + A5 arg5_initial_, arg5_; + + DISALLOW_COPY_AND_ASSIGN(GenericSlot5); +}; +} // namespace rtc + +#endif // WEBRTC_BASE_GENERICSLOT_H_ diff --git a/webrtc/base/genericslot.h.pump b/webrtc/base/genericslot.h.pump new file mode 100755 index 000000000..4d1533c58 --- /dev/null +++ b/webrtc/base/genericslot.h.pump @@ -0,0 +1,84 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_GENERICSLOT_H_ +#define WEBRTC_BASE_GENERICSLOT_H_ + +// To generate genericslot.h from genericslot.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py genericslot.h.pump + +// Generic-slots are pure slots that can be hooked up to signals. They're +// mainly intended to be used in tests where we want to check if a signal +// was invoked and what arguments were passed. NOTE: They do not do any +// lifetime management of the arguments received via callbacks. +// +// Example: +// /* Some signal */ +// sigslot::signal1 foo; +// +// /* We want to monitor foo in some test */ +// rtc::GenericSlot1 slot(&foo, 0); +// foo.emit(5); +// EXPECT_TRUE(slot.callback_received()); +// EXPECT_EQ(5, *(slot.arg1())); +// + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +$var n = 5 +$range i 1..n +$for i [[ +$range j 1..i + +template <$for j , [[class A$j]]> +class GenericSlot$i : public sigslot::has_slots<> { + public: + GenericSlot$i(sigslot::signal$i<$for j , [[A$j]]>* signal, + $for j , [[const A$j& arg$j[[]]_initial]]) + : $for j , [[arg$j[[]]_initial_(arg$j[[]]_initial)]] { + Reset(); + signal->connect(this, &GenericSlot$i::OnSignalCallback); + } + + void Reset() { + callback_received_ = false;$for j [[ + + arg$j[[]]_ = arg$j[[]]_initial_; ]] + + } + + bool callback_received() const { return callback_received_; }$for j [[ + + const A$j& arg$j() const { return arg$j[[]]_; }]] + + + private: + void OnSignalCallback($for j , [[A$j arg$j]]) { + callback_received_ = true;$for j [[ + + arg$j[[]]_ = arg$j;]] + + } + + bool callback_received_;$for j [[ + + A$j arg$j[[]]_initial_, arg$j[[]]_;]] + + + DISALLOW_COPY_AND_ASSIGN(GenericSlot$i); +}; + +]] +} // namespace rtc + +#endif // WEBRTC_BASE_GENERICSLOT_H_ diff --git a/webrtc/base/genericslot_unittest.cc b/webrtc/base/genericslot_unittest.cc new file mode 100755 index 000000000..33d7db8bf --- /dev/null +++ b/webrtc/base/genericslot_unittest.cc @@ -0,0 +1,37 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "webrtc/base/genericslot.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +TEST(GenericSlotTest, TestSlot1) { + sigslot::signal1 source1; + GenericSlot1 slot1(&source1, 1); + EXPECT_FALSE(slot1.callback_received()); + source1.emit(10); + EXPECT_TRUE(slot1.callback_received()); + EXPECT_EQ(10, slot1.arg1()); +} + +TEST(GenericSlotTest, TestSlot2) { + sigslot::signal2 source2; + GenericSlot2 slot2(&source2, 1, '0'); + EXPECT_FALSE(slot2.callback_received()); + source2.emit(10, 'x'); + EXPECT_TRUE(slot2.callback_received()); + EXPECT_EQ(10, slot2.arg1()); + EXPECT_EQ('x', slot2.arg2()); +} + +// By induction we assume the rest work too... + +} // namespace rtc diff --git a/webrtc/base/gunit.h b/webrtc/base/gunit.h new file mode 100644 index 000000000..9766c2c3a --- /dev/null +++ b/webrtc/base/gunit.h @@ -0,0 +1,95 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_GUNIT_H_ +#define WEBRTC_BASE_GUNIT_H_ + +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" +#if defined(WEBRTC_ANDROID) || defined(GTEST_RELATIVE_PATH) +#include "gtest/gtest.h" +#else +#include "testing/base/public/gunit.h" +#endif + +// forward declarations +namespace rtc { +class Pathname; +} + +// Wait until "ex" is true, or "timeout" expires. +#define WAIT(ex, timeout) \ + for (uint32 start = rtc::Time(); \ + !(ex) && rtc::Time() < start + timeout;) \ + rtc::Thread::Current()->ProcessMessages(1); + +// This returns the result of the test in res, so that we don't re-evaluate +// the expression in the XXXX_WAIT macros below, since that causes problems +// when the expression is only true the first time you check it. +#define WAIT_(ex, timeout, res) \ + do { \ + uint32 start = rtc::Time(); \ + res = (ex); \ + while (!res && rtc::Time() < start + timeout) { \ + rtc::Thread::Current()->ProcessMessages(1); \ + res = (ex); \ + } \ + } while (0); + +// The typical EXPECT_XXXX and ASSERT_XXXXs, but done until true or a timeout. +#define EXPECT_TRUE_WAIT(ex, timeout) \ + do { \ + bool res; \ + WAIT_(ex, timeout, res); \ + if (!res) EXPECT_TRUE(ex); \ + } while (0); + +#define EXPECT_EQ_WAIT(v1, v2, timeout) \ + do { \ + bool res; \ + WAIT_(v1 == v2, timeout, res); \ + if (!res) EXPECT_EQ(v1, v2); \ + } while (0); + +#define ASSERT_TRUE_WAIT(ex, timeout) \ + do { \ + bool res; \ + WAIT_(ex, timeout, res); \ + if (!res) ASSERT_TRUE(ex); \ + } while (0); + +#define ASSERT_EQ_WAIT(v1, v2, timeout) \ + do { \ + bool res; \ + WAIT_(v1 == v2, timeout, res); \ + if (!res) ASSERT_EQ(v1, v2); \ + } while (0); + +// Version with a "soft" timeout and a margin. This logs if the timeout is +// exceeded, but it only fails if the expression still isn't true after the +// margin time passes. +#define EXPECT_TRUE_WAIT_MARGIN(ex, timeout, margin) \ + do { \ + bool res; \ + WAIT_(ex, timeout, res); \ + if (res) { \ + break; \ + } \ + LOG(LS_WARNING) << "Expression " << #ex << " still not true after " << \ + timeout << "ms; waiting an additional " << margin << "ms"; \ + WAIT_(ex, margin, res); \ + if (!res) { \ + EXPECT_TRUE(ex); \ + } \ + } while (0); + +rtc::Pathname GetTalkDirectory(); + +#endif // WEBRTC_BASE_GUNIT_H_ diff --git a/webrtc/base/gunit_prod.h b/webrtc/base/gunit_prod.h new file mode 100644 index 000000000..dc39bbd0e --- /dev/null +++ b/webrtc/base/gunit_prod.h @@ -0,0 +1,24 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_GUNIT_PROD_H_ +#define WEBRTC_BASE_GUNIT_PROD_H_ + +#if defined(WEBRTC_ANDROID) +// Android doesn't use gtest at all, so anything that relies on gtest should +// check this define first. +#define NO_GTEST +#elif defined (GTEST_RELATIVE_PATH) +#include "gtest/gtest_prod.h" +#else +#include "testing/base/gunit_prod.h" +#endif + +#endif // WEBRTC_BASE_GUNIT_PROD_H_ diff --git a/webrtc/base/helpers.cc b/webrtc/base/helpers.cc new file mode 100644 index 000000000..8b14cdfd6 --- /dev/null +++ b/webrtc/base/helpers.cc @@ -0,0 +1,296 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/helpers.h" + +#include + +#if defined(FEATURE_ENABLE_SSL) +#include "webrtc/base/sslconfig.h" +#if defined(SSL_USE_OPENSSL) +#include +#elif defined(SSL_USE_NSS_RNG) +#include "pk11func.h" +#else +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif // WEBRTC_WIN +#endif // else +#endif // FEATURE_ENABLED_SSL + +#include "webrtc/base/base64.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/timeutils.h" + +// Protect against max macro inclusion. +#undef max + +namespace rtc { + +// Base class for RNG implementations. +class RandomGenerator { + public: + virtual ~RandomGenerator() {} + virtual bool Init(const void* seed, size_t len) = 0; + virtual bool Generate(void* buf, size_t len) = 0; +}; + +#if defined(SSL_USE_OPENSSL) +// The OpenSSL RNG. Need to make sure it doesn't run out of entropy. +class SecureRandomGenerator : public RandomGenerator { + public: + SecureRandomGenerator() : inited_(false) { + } + ~SecureRandomGenerator() { + } + virtual bool Init(const void* seed, size_t len) { + // By default, seed from the system state. + if (!inited_) { + if (RAND_poll() <= 0) { + return false; + } + inited_ = true; + } + // Allow app data to be mixed in, if provided. + if (seed) { + RAND_seed(seed, len); + } + return true; + } + virtual bool Generate(void* buf, size_t len) { + if (!inited_ && !Init(NULL, 0)) { + return false; + } + return (RAND_bytes(reinterpret_cast(buf), len) > 0); + } + + private: + bool inited_; +}; + +#elif defined(SSL_USE_NSS_RNG) +// The NSS RNG. +class SecureRandomGenerator : public RandomGenerator { + public: + SecureRandomGenerator() {} + ~SecureRandomGenerator() {} + virtual bool Init(const void* seed, size_t len) { + return true; + } + virtual bool Generate(void* buf, size_t len) { + return (PK11_GenerateRandom(reinterpret_cast(buf), + static_cast(len)) == SECSuccess); + } +}; + +#else +#if defined(WEBRTC_WIN) +class SecureRandomGenerator : public RandomGenerator { + public: + SecureRandomGenerator() : advapi32_(NULL), rtl_gen_random_(NULL) {} + ~SecureRandomGenerator() { + FreeLibrary(advapi32_); + } + + virtual bool Init(const void* seed, size_t seed_len) { + // We don't do any additional seeding on Win32, we just use the CryptoAPI + // RNG (which is exposed as a hidden function off of ADVAPI32 so that we + // don't need to drag in all of CryptoAPI) + if (rtl_gen_random_) { + return true; + } + + advapi32_ = LoadLibrary(L"advapi32.dll"); + if (!advapi32_) { + return false; + } + + rtl_gen_random_ = reinterpret_cast( + GetProcAddress(advapi32_, "SystemFunction036")); + if (!rtl_gen_random_) { + FreeLibrary(advapi32_); + return false; + } + + return true; + } + virtual bool Generate(void* buf, size_t len) { + if (!rtl_gen_random_ && !Init(NULL, 0)) { + return false; + } + return (rtl_gen_random_(buf, static_cast(len)) != FALSE); + } + + private: + typedef BOOL (WINAPI *RtlGenRandomProc)(PVOID, ULONG); + HINSTANCE advapi32_; + RtlGenRandomProc rtl_gen_random_; +}; + +#elif !defined(FEATURE_ENABLE_SSL) + +// No SSL implementation -- use rand() +class SecureRandomGenerator : public RandomGenerator { + public: + virtual bool Init(const void* seed, size_t len) { + if (len >= 4) { + srand(*reinterpret_cast(seed)); + } else { + srand(*reinterpret_cast(seed)); + } + return true; + } + virtual bool Generate(void* buf, size_t len) { + char* bytes = reinterpret_cast(buf); + for (size_t i = 0; i < len; ++i) { + bytes[i] = static_cast(rand()); + } + return true; + } +}; + +#else + +#error No SSL implementation has been selected! + +#endif // WEBRTC_WIN +#endif + +// A test random generator, for predictable output. +class TestRandomGenerator : public RandomGenerator { + public: + TestRandomGenerator() : seed_(7) { + } + ~TestRandomGenerator() { + } + virtual bool Init(const void* seed, size_t len) { + return true; + } + virtual bool Generate(void* buf, size_t len) { + for (size_t i = 0; i < len; ++i) { + static_cast(buf)[i] = static_cast(GetRandom()); + } + return true; + } + + private: + int GetRandom() { + return ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff; + } + int seed_; +}; + +// TODO: Use Base64::Base64Table instead. +static const char BASE64[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' +}; + +namespace { + +// This round about way of creating a global RNG is to safe-guard against +// indeterminant static initialization order. +scoped_ptr& GetGlobalRng() { + LIBJINGLE_DEFINE_STATIC_LOCAL(scoped_ptr, global_rng, + (new SecureRandomGenerator())); + return global_rng; +} + +RandomGenerator& Rng() { + return *GetGlobalRng(); +} + +} // namespace + +void SetRandomTestMode(bool test) { + if (!test) { + GetGlobalRng().reset(new SecureRandomGenerator()); + } else { + GetGlobalRng().reset(new TestRandomGenerator()); + } +} + +bool InitRandom(int seed) { + return InitRandom(reinterpret_cast(&seed), sizeof(seed)); +} + +bool InitRandom(const char* seed, size_t len) { + if (!Rng().Init(seed, len)) { + LOG(LS_ERROR) << "Failed to init random generator!"; + return false; + } + return true; +} + +std::string CreateRandomString(size_t len) { + std::string str; + CreateRandomString(len, &str); + return str; +} + +bool CreateRandomString(size_t len, + const char* table, int table_size, + std::string* str) { + str->clear(); + scoped_ptr bytes(new uint8[len]); + if (!Rng().Generate(bytes.get(), len)) { + LOG(LS_ERROR) << "Failed to generate random string!"; + return false; + } + str->reserve(len); + for (size_t i = 0; i < len; ++i) { + str->push_back(table[bytes[i] % table_size]); + } + return true; +} + +bool CreateRandomString(size_t len, std::string* str) { + return CreateRandomString(len, BASE64, 64, str); +} + +bool CreateRandomString(size_t len, const std::string& table, + std::string* str) { + return CreateRandomString(len, table.c_str(), + static_cast(table.size()), str); +} + +uint32 CreateRandomId() { + uint32 id; + if (!Rng().Generate(&id, sizeof(id))) { + LOG(LS_ERROR) << "Failed to generate random id!"; + } + return id; +} + +uint64 CreateRandomId64() { + return static_cast(CreateRandomId()) << 32 | CreateRandomId(); +} + +uint32 CreateRandomNonZeroId() { + uint32 id; + do { + id = CreateRandomId(); + } while (id == 0); + return id; +} + +double CreateRandomDouble() { + return CreateRandomId() / (std::numeric_limits::max() + + std::numeric_limits::epsilon()); +} + +} // namespace rtc diff --git a/webrtc/base/helpers.h b/webrtc/base/helpers.h new file mode 100644 index 000000000..e46d12a33 --- /dev/null +++ b/webrtc/base/helpers.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_HELPERS_H_ +#define WEBRTC_BASE_HELPERS_H_ + +#include +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// For testing, we can return predictable data. +void SetRandomTestMode(bool test); + +// Initializes the RNG, and seeds it with the specified entropy. +bool InitRandom(int seed); +bool InitRandom(const char* seed, size_t len); + +// Generates a (cryptographically) random string of the given length. +// We generate base64 values so that they will be printable. +// WARNING: could silently fail. Use the version below instead. +std::string CreateRandomString(size_t length); + +// Generates a (cryptographically) random string of the given length. +// We generate base64 values so that they will be printable. +// Return false if the random number generator failed. +bool CreateRandomString(size_t length, std::string* str); + +// Generates a (cryptographically) random string of the given length, +// with characters from the given table. Return false if the random +// number generator failed. +bool CreateRandomString(size_t length, const std::string& table, + std::string* str); + +// Generates a random id. +uint32 CreateRandomId(); + +// Generates a 64 bit random id. +uint64 CreateRandomId64(); + +// Generates a random id > 0. +uint32 CreateRandomNonZeroId(); + +// Generates a random double between 0.0 (inclusive) and 1.0 (exclusive). +double CreateRandomDouble(); + +} // namespace rtc + +#endif // WEBRTC_BASE_HELPERS_H_ diff --git a/webrtc/base/helpers_unittest.cc b/webrtc/base/helpers_unittest.cc new file mode 100644 index 000000000..7c20540c5 --- /dev/null +++ b/webrtc/base/helpers_unittest.cc @@ -0,0 +1,78 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/ssladapter.h" + +namespace rtc { + +class RandomTest : public testing::Test { + public: + static void SetUpTestCase() { + rtc::InitializeSSL(); + } + + static void TearDownTestCase() { + rtc::CleanupSSL(); + } +}; + +TEST_F(RandomTest, TestCreateRandomId) { + CreateRandomId(); +} + +TEST_F(RandomTest, TestCreateRandomDouble) { + for (int i = 0; i < 100; ++i) { + double r = CreateRandomDouble(); + EXPECT_GE(r, 0.0); + EXPECT_LT(r, 1.0); + } +} + +TEST_F(RandomTest, TestCreateNonZeroRandomId) { + EXPECT_NE(0U, CreateRandomNonZeroId()); +} + +TEST_F(RandomTest, TestCreateRandomString) { + std::string random = CreateRandomString(256); + EXPECT_EQ(256U, random.size()); + std::string random2; + EXPECT_TRUE(CreateRandomString(256, &random2)); + EXPECT_NE(random, random2); + EXPECT_EQ(256U, random2.size()); +} + +TEST_F(RandomTest, TestCreateRandomForTest) { + // Make sure we get the output we expect. + SetRandomTestMode(true); + EXPECT_EQ(2154761789U, CreateRandomId()); + EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16)); + + // Reset and make sure we get the same output. + SetRandomTestMode(true); + EXPECT_EQ(2154761789U, CreateRandomId()); + EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16)); + + // Test different character sets. + SetRandomTestMode(true); + std::string str; + EXPECT_TRUE(CreateRandomString(16, "a", &str)); + EXPECT_EQ("aaaaaaaaaaaaaaaa", str); + EXPECT_TRUE(CreateRandomString(16, "abc", &str)); + EXPECT_EQ("acbccaaaabbaacbb", str); + + // Turn off test mode for other tests. + SetRandomTestMode(false); +} + +} // namespace rtc diff --git a/webrtc/base/httpbase.cc b/webrtc/base/httpbase.cc new file mode 100644 index 000000000..5de2b79d7 --- /dev/null +++ b/webrtc/base/httpbase.cc @@ -0,0 +1,877 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#else // !WEBRTC_WIN +#define SEC_E_CERT_EXPIRED (-2146893016) +#endif // !WEBRTC_WIN + +#include "webrtc/base/common.h" +#include "webrtc/base/httpbase.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Helpers +////////////////////////////////////////////////////////////////////// + +bool MatchHeader(const char* str, size_t len, HttpHeader header) { + const char* const header_str = ToString(header); + const size_t header_len = strlen(header_str); + return (len == header_len) && (_strnicmp(str, header_str, header_len) == 0); +} + +enum { + MSG_READ +}; + +////////////////////////////////////////////////////////////////////// +// HttpParser +////////////////////////////////////////////////////////////////////// + +HttpParser::HttpParser() { + reset(); +} + +HttpParser::~HttpParser() { +} + +void +HttpParser::reset() { + state_ = ST_LEADER; + chunked_ = false; + data_size_ = SIZE_UNKNOWN; +} + +HttpParser::ProcessResult +HttpParser::Process(const char* buffer, size_t len, size_t* processed, + HttpError* error) { + *processed = 0; + *error = HE_NONE; + + if (state_ >= ST_COMPLETE) { + ASSERT(false); + return PR_COMPLETE; + } + + while (true) { + if (state_ < ST_DATA) { + size_t pos = *processed; + while ((pos < len) && (buffer[pos] != '\n')) { + pos += 1; + } + if (pos >= len) { + break; // don't have a full header + } + const char* line = buffer + *processed; + size_t len = (pos - *processed); + *processed = pos + 1; + while ((len > 0) && isspace(static_cast(line[len-1]))) { + len -= 1; + } + ProcessResult result = ProcessLine(line, len, error); + LOG(LS_VERBOSE) << "Processed line, result=" << result; + + if (PR_CONTINUE != result) { + return result; + } + } else if (data_size_ == 0) { + if (chunked_) { + state_ = ST_CHUNKTERM; + } else { + return PR_COMPLETE; + } + } else { + size_t available = len - *processed; + if (available <= 0) { + break; // no more data + } + if ((data_size_ != SIZE_UNKNOWN) && (available > data_size_)) { + available = data_size_; + } + size_t read = 0; + ProcessResult result = ProcessData(buffer + *processed, available, read, + error); + LOG(LS_VERBOSE) << "Processed data, result: " << result << " read: " + << read << " err: " << error; + + if (PR_CONTINUE != result) { + return result; + } + *processed += read; + if (data_size_ != SIZE_UNKNOWN) { + data_size_ -= read; + } + } + } + + return PR_CONTINUE; +} + +HttpParser::ProcessResult +HttpParser::ProcessLine(const char* line, size_t len, HttpError* error) { + LOG_F(LS_VERBOSE) << " state: " << state_ << " line: " + << std::string(line, len) << " len: " << len << " err: " + << error; + + switch (state_) { + case ST_LEADER: + state_ = ST_HEADERS; + return ProcessLeader(line, len, error); + + case ST_HEADERS: + if (len > 0) { + const char* value = strchrn(line, len, ':'); + if (!value) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + size_t nlen = (value - line); + const char* eol = line + len; + do { + value += 1; + } while ((value < eol) && isspace(static_cast(*value))); + size_t vlen = eol - value; + if (MatchHeader(line, nlen, HH_CONTENT_LENGTH)) { + // sscanf isn't safe with strings that aren't null-terminated, and there + // is no guarantee that |value| is. + // Create a local copy that is null-terminated. + std::string value_str(value, vlen); + unsigned int temp_size; + if (sscanf(value_str.c_str(), "%u", &temp_size) != 1) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + data_size_ = static_cast(temp_size); + } else if (MatchHeader(line, nlen, HH_TRANSFER_ENCODING)) { + if ((vlen == 7) && (_strnicmp(value, "chunked", 7) == 0)) { + chunked_ = true; + } else if ((vlen == 8) && (_strnicmp(value, "identity", 8) == 0)) { + chunked_ = false; + } else { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + } + return ProcessHeader(line, nlen, value, vlen, error); + } else { + state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA; + return ProcessHeaderComplete(chunked_, data_size_, error); + } + break; + + case ST_CHUNKSIZE: + if (len > 0) { + char* ptr = NULL; + data_size_ = strtoul(line, &ptr, 16); + if (ptr != line + len) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA; + } else { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + break; + + case ST_CHUNKTERM: + if (len > 0) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } else { + state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA; + } + break; + + case ST_TRAILERS: + if (len == 0) { + return PR_COMPLETE; + } + // *error = onHttpRecvTrailer(); + break; + + default: + ASSERT(false); + break; + } + + return PR_CONTINUE; +} + +bool +HttpParser::is_valid_end_of_input() const { + return (state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN); +} + +void +HttpParser::complete(HttpError error) { + if (state_ < ST_COMPLETE) { + state_ = ST_COMPLETE; + OnComplete(error); + } +} + +////////////////////////////////////////////////////////////////////// +// HttpBase::DocumentStream +////////////////////////////////////////////////////////////////////// + +class BlockingMemoryStream : public ExternalMemoryStream { +public: + BlockingMemoryStream(char* buffer, size_t size) + : ExternalMemoryStream(buffer, size) { } + + virtual StreamResult DoReserve(size_t size, int* error) { + return (buffer_length_ >= size) ? SR_SUCCESS : SR_BLOCK; + } +}; + +class HttpBase::DocumentStream : public StreamInterface { +public: + DocumentStream(HttpBase* base) : base_(base), error_(HE_DEFAULT) { } + + virtual StreamState GetState() const { + if (NULL == base_) + return SS_CLOSED; + if (HM_RECV == base_->mode_) + return SS_OPEN; + return SS_OPENING; + } + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (!base_) { + if (error) *error = error_; + return (HE_NONE == error_) ? SR_EOS : SR_ERROR; + } + + if (HM_RECV != base_->mode_) { + return SR_BLOCK; + } + + // DoReceiveLoop writes http document data to the StreamInterface* document + // member of HttpData. In this case, we want this data to be written + // directly to our buffer. To accomplish this, we wrap our buffer with a + // StreamInterface, and replace the existing document with our wrapper. + // When the method returns, we restore the old document. Ideally, we would + // pass our StreamInterface* to DoReceiveLoop, but due to the callbacks + // of HttpParser, we would still need to store the pointer temporarily. + scoped_ptr + stream(new BlockingMemoryStream(reinterpret_cast(buffer), + buffer_len)); + + // Replace the existing document with our wrapped buffer. + base_->data_->document.swap(stream); + + // Pump the I/O loop. DoReceiveLoop is guaranteed not to attempt to + // complete the I/O process, which means that our wrapper is not in danger + // of being deleted. To ensure this, DoReceiveLoop returns true when it + // wants complete to be called. We make sure to uninstall our wrapper + // before calling complete(). + HttpError http_error; + bool complete = base_->DoReceiveLoop(&http_error); + + // Reinstall the original output document. + base_->data_->document.swap(stream); + + // If we reach the end of the receive stream, we disconnect our stream + // adapter from the HttpBase, and further calls to read will either return + // EOS or ERROR, appropriately. Finally, we call complete(). + StreamResult result = SR_BLOCK; + if (complete) { + HttpBase* base = Disconnect(http_error); + if (error) *error = error_; + result = (HE_NONE == error_) ? SR_EOS : SR_ERROR; + base->complete(http_error); + } + + // Even if we are complete, if some data was read we must return SUCCESS. + // Future Reads will return EOS or ERROR based on the error_ variable. + size_t position; + stream->GetPosition(&position); + if (position > 0) { + if (read) *read = position; + result = SR_SUCCESS; + } + return result; + } + + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (error) *error = -1; + return SR_ERROR; + } + + virtual void Close() { + if (base_) { + HttpBase* base = Disconnect(HE_NONE); + if (HM_RECV == base->mode_ && base->http_stream_) { + // Read I/O could have been stalled on the user of this DocumentStream, + // so restart the I/O process now that we've removed ourselves. + base->http_stream_->PostEvent(SE_READ, 0); + } + } + } + + virtual bool GetAvailable(size_t* size) const { + if (!base_ || HM_RECV != base_->mode_) + return false; + size_t data_size = base_->GetDataRemaining(); + if (SIZE_UNKNOWN == data_size) + return false; + if (size) + *size = data_size; + return true; + } + + HttpBase* Disconnect(HttpError error) { + ASSERT(NULL != base_); + ASSERT(NULL != base_->doc_stream_); + HttpBase* base = base_; + base_->doc_stream_ = NULL; + base_ = NULL; + error_ = error; + return base; + } + +private: + HttpBase* base_; + HttpError error_; +}; + +////////////////////////////////////////////////////////////////////// +// HttpBase +////////////////////////////////////////////////////////////////////// + +HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL), + http_stream_(NULL), doc_stream_(NULL) { +} + +HttpBase::~HttpBase() { + ASSERT(HM_NONE == mode_); +} + +bool +HttpBase::isConnected() const { + return (http_stream_ != NULL) && (http_stream_->GetState() == SS_OPEN); +} + +bool +HttpBase::attach(StreamInterface* stream) { + if ((mode_ != HM_NONE) || (http_stream_ != NULL) || (stream == NULL)) { + ASSERT(false); + return false; + } + http_stream_ = stream; + http_stream_->SignalEvent.connect(this, &HttpBase::OnHttpStreamEvent); + mode_ = (http_stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE; + return true; +} + +StreamInterface* +HttpBase::detach() { + ASSERT(HM_NONE == mode_); + if (mode_ != HM_NONE) { + return NULL; + } + StreamInterface* stream = http_stream_; + http_stream_ = NULL; + if (stream) { + stream->SignalEvent.disconnect(this); + } + return stream; +} + +void +HttpBase::send(HttpData* data) { + ASSERT(HM_NONE == mode_); + if (mode_ != HM_NONE) { + return; + } else if (!isConnected()) { + OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED); + return; + } + + mode_ = HM_SEND; + data_ = data; + len_ = 0; + ignore_data_ = chunk_data_ = false; + + if (data_->document) { + data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent); + } + + std::string encoding; + if (data_->hasHeader(HH_TRANSFER_ENCODING, &encoding) + && (encoding == "chunked")) { + chunk_data_ = true; + } + + len_ = data_->formatLeader(buffer_, sizeof(buffer_)); + len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n"); + + header_ = data_->begin(); + if (header_ == data_->end()) { + // We must call this at least once, in the case where there are no headers. + queue_headers(); + } + + flush_data(); +} + +void +HttpBase::recv(HttpData* data) { + ASSERT(HM_NONE == mode_); + if (mode_ != HM_NONE) { + return; + } else if (!isConnected()) { + OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED); + return; + } + + mode_ = HM_RECV; + data_ = data; + len_ = 0; + ignore_data_ = chunk_data_ = false; + + reset(); + if (doc_stream_) { + doc_stream_->SignalEvent(doc_stream_, SE_OPEN | SE_READ, 0); + } else { + read_and_process_data(); + } +} + +void +HttpBase::abort(HttpError err) { + if (mode_ != HM_NONE) { + if (http_stream_ != NULL) { + http_stream_->Close(); + } + do_complete(err); + } +} + +StreamInterface* HttpBase::GetDocumentStream() { + if (doc_stream_) + return NULL; + doc_stream_ = new DocumentStream(this); + return doc_stream_; +} + +HttpError HttpBase::HandleStreamClose(int error) { + if (http_stream_ != NULL) { + http_stream_->Close(); + } + if (error == 0) { + if ((mode_ == HM_RECV) && is_valid_end_of_input()) { + return HE_NONE; + } else { + return HE_DISCONNECTED; + } + } else if (error == SOCKET_EACCES) { + return HE_AUTH; + } else if (error == SEC_E_CERT_EXPIRED) { + return HE_CERTIFICATE_EXPIRED; + } + LOG_F(LS_ERROR) << "(" << error << ")"; + return (HM_CONNECT == mode_) ? HE_CONNECT_FAILED : HE_SOCKET_ERROR; +} + +bool HttpBase::DoReceiveLoop(HttpError* error) { + ASSERT(HM_RECV == mode_); + ASSERT(NULL != error); + + // Do to the latency between receiving read notifications from + // pseudotcpchannel, we rely on repeated calls to read in order to acheive + // ideal throughput. The number of reads is limited to prevent starving + // the caller. + + size_t loop_count = 0; + const size_t kMaxReadCount = 20; + bool process_requires_more_data = false; + do { + // The most frequent use of this function is response to new data available + // on http_stream_. Therefore, we optimize by attempting to read from the + // network first (as opposed to processing existing data first). + + if (len_ < sizeof(buffer_)) { + // Attempt to buffer more data. + size_t read; + int read_error; + StreamResult read_result = http_stream_->Read(buffer_ + len_, + sizeof(buffer_) - len_, + &read, &read_error); + switch (read_result) { + case SR_SUCCESS: + ASSERT(len_ + read <= sizeof(buffer_)); + len_ += read; + break; + case SR_BLOCK: + if (process_requires_more_data) { + // We're can't make progress until more data is available. + return false; + } + // Attempt to process the data already in our buffer. + break; + case SR_EOS: + // Clean close, with no error. Fall through to HandleStreamClose. + read_error = 0; + case SR_ERROR: + *error = HandleStreamClose(read_error); + return true; + } + } else if (process_requires_more_data) { + // We have too much unprocessed data in our buffer. This should only + // occur when a single HTTP header is longer than the buffer size (32K). + // Anything longer than that is almost certainly an error. + *error = HE_OVERFLOW; + return true; + } + + // Process data in our buffer. Process is not guaranteed to process all + // the buffered data. In particular, it will wait until a complete + // protocol element (such as http header, or chunk size) is available, + // before processing it in its entirety. Also, it is valid and sometimes + // necessary to call Process with an empty buffer, since the state machine + // may have interrupted state transitions to complete. + size_t processed; + ProcessResult process_result = Process(buffer_, len_, &processed, + error); + ASSERT(processed <= len_); + len_ -= processed; + memmove(buffer_, buffer_ + processed, len_); + switch (process_result) { + case PR_CONTINUE: + // We need more data to make progress. + process_requires_more_data = true; + break; + case PR_BLOCK: + // We're stalled on writing the processed data. + return false; + case PR_COMPLETE: + // *error already contains the correct code. + return true; + } + } while (++loop_count <= kMaxReadCount); + + LOG_F(LS_WARNING) << "danger of starvation"; + return false; +} + +void +HttpBase::read_and_process_data() { + HttpError error; + if (DoReceiveLoop(&error)) { + complete(error); + } +} + +void +HttpBase::flush_data() { + ASSERT(HM_SEND == mode_); + + // When send_required is true, no more buffering can occur without a network + // write. + bool send_required = (len_ >= sizeof(buffer_)); + + while (true) { + ASSERT(len_ <= sizeof(buffer_)); + + // HTTP is inherently sensitive to round trip latency, since a frequent use + // case is for small requests and responses to be sent back and forth, and + // the lack of pipelining forces a single request to take a minimum of the + // round trip time. As a result, it is to our benefit to pack as much data + // into each packet as possible. Thus, we defer network writes until we've + // buffered as much data as possible. + + if (!send_required && (header_ != data_->end())) { + // First, attempt to queue more header data. + send_required = queue_headers(); + } + + if (!send_required && data_->document) { + // Next, attempt to queue document data. + + const size_t kChunkDigits = 8; + size_t offset, reserve; + if (chunk_data_) { + // Reserve characters at the start for X-byte hex value and \r\n + offset = len_ + kChunkDigits + 2; + // ... and 2 characters at the end for \r\n + reserve = offset + 2; + } else { + offset = len_; + reserve = offset; + } + + if (reserve >= sizeof(buffer_)) { + send_required = true; + } else { + size_t read; + int error; + StreamResult result = data_->document->Read(buffer_ + offset, + sizeof(buffer_) - reserve, + &read, &error); + if (result == SR_SUCCESS) { + ASSERT(reserve + read <= sizeof(buffer_)); + if (chunk_data_) { + // Prepend the chunk length in hex. + // Note: sprintfn appends a null terminator, which is why we can't + // combine it with the line terminator. + sprintfn(buffer_ + len_, kChunkDigits + 1, "%.*x", + kChunkDigits, read); + // Add line terminator to the chunk length. + memcpy(buffer_ + len_ + kChunkDigits, "\r\n", 2); + // Add line terminator to the end of the chunk. + memcpy(buffer_ + offset + read, "\r\n", 2); + } + len_ = reserve + read; + } else if (result == SR_BLOCK) { + // Nothing to do but flush data to the network. + send_required = true; + } else if (result == SR_EOS) { + if (chunk_data_) { + // Append the empty chunk and empty trailers, then turn off + // chunking. + ASSERT(len_ + 5 <= sizeof(buffer_)); + memcpy(buffer_ + len_, "0\r\n\r\n", 5); + len_ += 5; + chunk_data_ = false; + } else if (0 == len_) { + // No more data to read, and no more data to write. + do_complete(); + return; + } + // Although we are done reading data, there is still data which needs + // to be flushed to the network. + send_required = true; + } else { + LOG_F(LS_ERROR) << "Read error: " << error; + do_complete(HE_STREAM); + return; + } + } + } + + if (0 == len_) { + // No data currently available to send. + if (!data_->document) { + // If there is no source document, that means we're done. + do_complete(); + } + return; + } + + size_t written; + int error; + StreamResult result = http_stream_->Write(buffer_, len_, &written, &error); + if (result == SR_SUCCESS) { + ASSERT(written <= len_); + len_ -= written; + memmove(buffer_, buffer_ + written, len_); + send_required = false; + } else if (result == SR_BLOCK) { + if (send_required) { + // Nothing more we can do until network is writeable. + return; + } + } else { + ASSERT(result == SR_ERROR); + LOG_F(LS_ERROR) << "error"; + OnHttpStreamEvent(http_stream_, SE_CLOSE, error); + return; + } + } + + ASSERT(false); +} + +bool +HttpBase::queue_headers() { + ASSERT(HM_SEND == mode_); + while (header_ != data_->end()) { + size_t len = sprintfn(buffer_ + len_, sizeof(buffer_) - len_, + "%.*s: %.*s\r\n", + header_->first.size(), header_->first.data(), + header_->second.size(), header_->second.data()); + if (len_ + len < sizeof(buffer_) - 3) { + len_ += len; + ++header_; + } else if (len_ == 0) { + LOG(WARNING) << "discarding header that is too long: " << header_->first; + ++header_; + } else { + // Not enough room for the next header, write to network first. + return true; + } + } + // End of headers + len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n"); + return false; +} + +void +HttpBase::do_complete(HttpError err) { + ASSERT(mode_ != HM_NONE); + HttpMode mode = mode_; + mode_ = HM_NONE; + if (data_ && data_->document) { + data_->document->SignalEvent.disconnect(this); + } + data_ = NULL; + if ((HM_RECV == mode) && doc_stream_) { + ASSERT(HE_NONE != err); // We should have Disconnected doc_stream_ already. + DocumentStream* ds = doc_stream_; + ds->Disconnect(err); + ds->SignalEvent(ds, SE_CLOSE, err); + } + if (notify_) { + notify_->onHttpComplete(mode, err); + } +} + +// +// Stream Signals +// + +void +HttpBase::OnHttpStreamEvent(StreamInterface* stream, int events, int error) { + ASSERT(stream == http_stream_); + if ((events & SE_OPEN) && (mode_ == HM_CONNECT)) { + do_complete(); + return; + } + + if ((events & SE_WRITE) && (mode_ == HM_SEND)) { + flush_data(); + return; + } + + if ((events & SE_READ) && (mode_ == HM_RECV)) { + if (doc_stream_) { + doc_stream_->SignalEvent(doc_stream_, SE_READ, 0); + } else { + read_and_process_data(); + } + return; + } + + if ((events & SE_CLOSE) == 0) + return; + + HttpError http_error = HandleStreamClose(error); + if (mode_ == HM_RECV) { + complete(http_error); + } else if (mode_ != HM_NONE) { + do_complete(http_error); + } else if (notify_) { + notify_->onHttpClosed(http_error); + } +} + +void +HttpBase::OnDocumentEvent(StreamInterface* stream, int events, int error) { + ASSERT(stream == data_->document.get()); + if ((events & SE_WRITE) && (mode_ == HM_RECV)) { + read_and_process_data(); + return; + } + + if ((events & SE_READ) && (mode_ == HM_SEND)) { + flush_data(); + return; + } + + if (events & SE_CLOSE) { + LOG_F(LS_ERROR) << "Read error: " << error; + do_complete(HE_STREAM); + return; + } +} + +// +// HttpParser Implementation +// + +HttpParser::ProcessResult +HttpBase::ProcessLeader(const char* line, size_t len, HttpError* error) { + *error = data_->parseLeader(line, len); + return (HE_NONE == *error) ? PR_CONTINUE : PR_COMPLETE; +} + +HttpParser::ProcessResult +HttpBase::ProcessHeader(const char* name, size_t nlen, const char* value, + size_t vlen, HttpError* error) { + std::string sname(name, nlen), svalue(value, vlen); + data_->addHeader(sname, svalue); + return PR_CONTINUE; +} + +HttpParser::ProcessResult +HttpBase::ProcessHeaderComplete(bool chunked, size_t& data_size, + HttpError* error) { + StreamInterface* old_docstream = doc_stream_; + if (notify_) { + *error = notify_->onHttpHeaderComplete(chunked, data_size); + // The request must not be aborted as a result of this callback. + ASSERT(NULL != data_); + } + if ((HE_NONE == *error) && data_->document) { + data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent); + } + if (HE_NONE != *error) { + return PR_COMPLETE; + } + if (old_docstream != doc_stream_) { + // Break out of Process loop, since our I/O model just changed. + return PR_BLOCK; + } + return PR_CONTINUE; +} + +HttpParser::ProcessResult +HttpBase::ProcessData(const char* data, size_t len, size_t& read, + HttpError* error) { + if (ignore_data_ || !data_->document) { + read = len; + return PR_CONTINUE; + } + int write_error = 0; + switch (data_->document->Write(data, len, &read, &write_error)) { + case SR_SUCCESS: + return PR_CONTINUE; + case SR_BLOCK: + return PR_BLOCK; + case SR_EOS: + LOG_F(LS_ERROR) << "Unexpected EOS"; + *error = HE_STREAM; + return PR_COMPLETE; + case SR_ERROR: + default: + LOG_F(LS_ERROR) << "Write error: " << write_error; + *error = HE_STREAM; + return PR_COMPLETE; + } +} + +void +HttpBase::OnComplete(HttpError err) { + LOG_F(LS_VERBOSE); + do_complete(err); +} + +} // namespace rtc diff --git a/webrtc/base/httpbase.h b/webrtc/base/httpbase.h new file mode 100644 index 000000000..424a61f9f --- /dev/null +++ b/webrtc/base/httpbase.h @@ -0,0 +1,181 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#ifndef WEBRTC_BASE_HTTPBASE_H__ +#define WEBRTC_BASE_HTTPBASE_H__ + +#include "webrtc/base/httpcommon.h" + +namespace rtc { + +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// HttpParser - Parses an HTTP stream provided via Process and end_of_input, and +// generates events for: +// Structural Elements: Leader, Headers, Document Data +// Events: End of Headers, End of Document, Errors +/////////////////////////////////////////////////////////////////////////////// + +class HttpParser { +public: + enum ProcessResult { PR_CONTINUE, PR_BLOCK, PR_COMPLETE }; + HttpParser(); + virtual ~HttpParser(); + + void reset(); + ProcessResult Process(const char* buffer, size_t len, size_t* processed, + HttpError* error); + bool is_valid_end_of_input() const; + void complete(HttpError err); + + size_t GetDataRemaining() const { return data_size_; } + +protected: + ProcessResult ProcessLine(const char* line, size_t len, HttpError* error); + + // HttpParser Interface + virtual ProcessResult ProcessLeader(const char* line, size_t len, + HttpError* error) = 0; + virtual ProcessResult ProcessHeader(const char* name, size_t nlen, + const char* value, size_t vlen, + HttpError* error) = 0; + virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size, + HttpError* error) = 0; + virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read, + HttpError* error) = 0; + virtual void OnComplete(HttpError err) = 0; + +private: + enum State { + ST_LEADER, ST_HEADERS, + ST_CHUNKSIZE, ST_CHUNKTERM, ST_TRAILERS, + ST_DATA, ST_COMPLETE + } state_; + bool chunked_; + size_t data_size_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// IHttpNotify +/////////////////////////////////////////////////////////////////////////////// + +enum HttpMode { HM_NONE, HM_CONNECT, HM_RECV, HM_SEND }; + +class IHttpNotify { +public: + virtual ~IHttpNotify() {} + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) = 0; + virtual void onHttpComplete(HttpMode mode, HttpError err) = 0; + virtual void onHttpClosed(HttpError err) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// HttpBase - Provides a state machine for implementing HTTP-based components. +// Attach HttpBase to a StreamInterface which represents a bidirectional HTTP +// stream, and then call send() or recv() to initiate sending or receiving one +// side of an HTTP transaction. By default, HttpBase operates as an I/O pump, +// moving data from the HTTP stream to the HttpData object and vice versa. +// However, it can also operate in stream mode, in which case the user of the +// stream interface drives I/O via calls to Read(). +/////////////////////////////////////////////////////////////////////////////// + +class HttpBase +: private HttpParser, + public sigslot::has_slots<> +{ +public: + HttpBase(); + virtual ~HttpBase(); + + void notify(IHttpNotify* notify) { notify_ = notify; } + bool attach(StreamInterface* stream); + StreamInterface* stream() { return http_stream_; } + StreamInterface* detach(); + bool isConnected() const; + + void send(HttpData* data); + void recv(HttpData* data); + void abort(HttpError err); + + HttpMode mode() const { return mode_; } + + void set_ignore_data(bool ignore) { ignore_data_ = ignore; } + bool ignore_data() const { return ignore_data_; } + + // Obtaining this stream puts HttpBase into stream mode until the stream + // is closed. HttpBase can only expose one open stream interface at a time. + // Further calls will return NULL. + StreamInterface* GetDocumentStream(); + +protected: + // Do cleanup when the http stream closes (error may be 0 for a clean + // shutdown), and return the error code to signal. + HttpError HandleStreamClose(int error); + + // DoReceiveLoop acts as a data pump, pulling data from the http stream, + // pushing it through the HttpParser, and then populating the HttpData object + // based on the callbacks from the parser. One of the most interesting + // callbacks is ProcessData, which provides the actual http document body. + // This data is then written to the HttpData::document. As a result, data + // flows from the network to the document, with some incidental protocol + // parsing in between. + // Ideally, we would pass in the document* to DoReceiveLoop, to more easily + // support GetDocumentStream(). However, since the HttpParser is callback + // driven, we are forced to store the pointer somewhere until the callback + // is triggered. + // Returns true if the received document has finished, and + // HttpParser::complete should be called. + bool DoReceiveLoop(HttpError* err); + + void read_and_process_data(); + void flush_data(); + bool queue_headers(); + void do_complete(HttpError err = HE_NONE); + + void OnHttpStreamEvent(StreamInterface* stream, int events, int error); + void OnDocumentEvent(StreamInterface* stream, int events, int error); + + // HttpParser Interface + virtual ProcessResult ProcessLeader(const char* line, size_t len, + HttpError* error); + virtual ProcessResult ProcessHeader(const char* name, size_t nlen, + const char* value, size_t vlen, + HttpError* error); + virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size, + HttpError* error); + virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read, + HttpError* error); + virtual void OnComplete(HttpError err); + +private: + class DocumentStream; + friend class DocumentStream; + + enum { kBufferSize = 32 * 1024 }; + + HttpMode mode_; + HttpData* data_; + IHttpNotify* notify_; + StreamInterface* http_stream_; + DocumentStream* doc_stream_; + char buffer_[kBufferSize]; + size_t len_; + + bool ignore_data_, chunk_data_; + HttpData::const_iterator header_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPBASE_H__ diff --git a/webrtc/base/httpbase_unittest.cc b/webrtc/base/httpbase_unittest.cc new file mode 100644 index 000000000..6dab0c9ac --- /dev/null +++ b/webrtc/base/httpbase_unittest.cc @@ -0,0 +1,520 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpbase.h" +#include "webrtc/base/testutils.h" + +namespace rtc { + +const char* const kHttpResponse = + "HTTP/1.1 200\r\n" + "Connection: Keep-Alive\r\n" + "Content-Type: text/plain\r\n" + "Proxy-Authorization: 42\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "00000008\r\n" + "Goodbye!\r\n" + "0\r\n\r\n"; + +const char* const kHttpEmptyResponse = + "HTTP/1.1 200\r\n" + "Connection: Keep-Alive\r\n" + "Content-Length: 0\r\n" + "Proxy-Authorization: 42\r\n" + "\r\n"; + +const char* const kHttpResponsePrefix = + "HTTP/1.1 200\r\n" + "Connection: Keep-Alive\r\n" + "Content-Type: text/plain\r\n" + "Proxy-Authorization: 42\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "8\r\n" + "Goodbye!\r\n"; + +class HttpBaseTest : public testing::Test, public IHttpNotify { +public: + enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED }; + struct Event { + EventType event; + bool chunked; + size_t data_size; + HttpMode mode; + HttpError err; + }; + HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { } + + virtual void SetUp() { } + virtual void TearDown() { + delete http_stream; + // Avoid an ASSERT, in case a test doesn't clean up properly + base.abort(HE_NONE); + } + + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) { + LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size; + Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE}; + events.push_back(e); + if (obtain_stream) { + ObtainDocumentStream(); + } + return HE_NONE; + } + virtual void onHttpComplete(HttpMode mode, HttpError err) { + LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err; + Event e = { E_COMPLETE, false, 0, mode, err }; + events.push_back(e); + } + virtual void onHttpClosed(HttpError err) { + LOG_F(LS_VERBOSE) << "err: " << err; + Event e = { E_CLOSED, false, 0, HM_NONE, err }; + events.push_back(e); + } + + void SetupSource(const char* response); + + void VerifyHeaderComplete(size_t event_count, bool empty_doc); + void VerifyDocumentContents(const char* expected_data, + size_t expected_length = SIZE_UNKNOWN); + + void ObtainDocumentStream(); + void VerifyDocumentStreamIsOpening(); + void VerifyDocumentStreamOpenEvent(); + void ReadDocumentStreamData(const char* expected_data); + void VerifyDocumentStreamIsEOS(); + + void SetupDocument(const char* response); + void VerifySourceContents(const char* expected_data, + size_t expected_length = SIZE_UNKNOWN); + + void VerifyTransferComplete(HttpMode mode, HttpError error); + + HttpBase base; + MemoryStream* mem; + HttpResponseData data; + + // The source of http data, and source events + testing::StreamSource src; + std::vector events; + + // Document stream, and stream events + bool obtain_stream; + StreamInterface* http_stream; + testing::StreamSink sink; +}; + +void HttpBaseTest::SetupSource(const char* http_data) { + LOG_F(LS_VERBOSE) << "Enter"; + + src.SetState(SS_OPENING); + src.QueueString(http_data); + + base.notify(this); + base.attach(&src); + EXPECT_TRUE(events.empty()); + + src.SetState(SS_OPEN); + ASSERT_EQ(1U, events.size()); + EXPECT_EQ(E_COMPLETE, events[0].event); + EXPECT_EQ(HM_CONNECT, events[0].mode); + EXPECT_EQ(HE_NONE, events[0].err); + events.clear(); + + mem = new MemoryStream; + data.document.reset(mem); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_EQ(event_count, events.size()); + EXPECT_EQ(E_HEADER_COMPLETE, events[0].event); + + std::string header; + EXPECT_EQ(HVER_1_1, data.version); + EXPECT_EQ(static_cast(HC_OK), data.scode); + EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header)); + EXPECT_EQ("42", header); + EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header)); + EXPECT_EQ("Keep-Alive", header); + + if (empty_doc) { + EXPECT_FALSE(events[0].chunked); + EXPECT_EQ(0U, events[0].data_size); + + EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header)); + EXPECT_EQ("0", header); + } else { + EXPECT_TRUE(events[0].chunked); + EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size); + + EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header)); + EXPECT_EQ("text/plain", header); + EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header)); + EXPECT_EQ("chunked", header); + } + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentContents(const char* expected_data, + size_t expected_length) { + LOG_F(LS_VERBOSE) << "Enter"; + + if (SIZE_UNKNOWN == expected_length) { + expected_length = strlen(expected_data); + } + EXPECT_EQ(mem, data.document.get()); + + size_t length; + mem->GetSize(&length); + EXPECT_EQ(expected_length, length); + EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::ObtainDocumentStream() { + LOG_F(LS_VERBOSE) << "Enter"; + EXPECT_FALSE(http_stream); + http_stream = base.GetDocumentStream(); + ASSERT_TRUE(NULL != http_stream); + sink.Monitor(http_stream); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentStreamIsOpening() { + LOG_F(LS_VERBOSE) << "Enter"; + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(0, sink.Events(http_stream)); + EXPECT_EQ(SS_OPENING, http_stream->GetState()); + + size_t read = 0; + char buffer[5] = { 0 }; + EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentStreamOpenEvent() { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream)); + EXPECT_EQ(SS_OPEN, http_stream->GetState()); + + // HTTP headers haven't arrived yet + EXPECT_EQ(0U, events.size()); + EXPECT_EQ(static_cast(HC_INTERNAL_SERVER_ERROR), data.scode); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(SS_OPEN, http_stream->GetState()); + + // Pump the HTTP I/O using Read, and verify the results. + size_t verified_length = 0; + const size_t expected_length = strlen(expected_data); + while (verified_length < expected_length) { + size_t read = 0; + char buffer[5] = { 0 }; + size_t amt_to_read = _min(expected_length - verified_length, sizeof(buffer)); + EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL)); + EXPECT_EQ(amt_to_read, read); + EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read)); + verified_length += read; + } + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentStreamIsEOS() { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_TRUE(NULL != http_stream); + size_t read = 0; + char buffer[5] = { 0 }; + EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL)); + EXPECT_EQ(SS_CLOSED, http_stream->GetState()); + + // When EOS is caused by Read, we don't expect SE_CLOSE + EXPECT_EQ(0, sink.Events(http_stream)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::SetupDocument(const char* document_data) { + LOG_F(LS_VERBOSE) << "Enter"; + src.SetState(SS_OPEN); + + base.notify(this); + base.attach(&src); + EXPECT_TRUE(events.empty()); + + if (document_data) { + // Note: we could just call data.set_success("text/plain", mem), but that + // won't allow us to use the chunked transfer encoding. + mem = new MemoryStream(document_data); + data.document.reset(mem); + data.setHeader(HH_CONTENT_TYPE, "text/plain"); + data.setHeader(HH_TRANSFER_ENCODING, "chunked"); + } else { + data.setHeader(HH_CONTENT_LENGTH, "0"); + } + data.scode = HC_OK; + data.setHeader(HH_PROXY_AUTHORIZATION, "42"); + data.setHeader(HH_CONNECTION, "Keep-Alive"); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifySourceContents(const char* expected_data, + size_t expected_length) { + LOG_F(LS_VERBOSE) << "Enter"; + if (SIZE_UNKNOWN == expected_length) { + expected_length = strlen(expected_data); + } + std::string contents = src.ReadData(); + EXPECT_EQ(expected_length, contents.length()); + EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) { + LOG_F(LS_VERBOSE) << "Enter"; + // Verify that http operation has completed + ASSERT_TRUE(events.size() > 0); + size_t last_event = events.size() - 1; + EXPECT_EQ(E_COMPLETE, events[last_event].event); + EXPECT_EQ(mode, events[last_event].mode); + EXPECT_EQ(error, events[last_event].err); + LOG_F(LS_VERBOSE) << "Exit"; +} + +// +// Tests +// + +TEST_F(HttpBaseTest, SupportsSend) { + // Queue response document + SetupDocument("Goodbye!"); + + // Begin send + base.send(&data); + + // Send completed successfully + VerifyTransferComplete(HM_SEND, HE_NONE); + VerifySourceContents(kHttpResponse); +} + +TEST_F(HttpBaseTest, SupportsSendNoDocument) { + // Queue response document + SetupDocument(NULL); + + // Begin send + base.send(&data); + + // Send completed successfully + VerifyTransferComplete(HM_SEND, HE_NONE); + VerifySourceContents(kHttpEmptyResponse); +} + +TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) { + // This test is attempting to expose a bug that occurs when a particular + // base objects is used for receiving, and then used for sending. In + // particular, the HttpParser state is different after receiving. Simulate + // that here. + SetupSource(kHttpResponse); + base.recv(&data); + VerifyTransferComplete(HM_RECV, HE_NONE); + + src.Clear(); + data.clear(true); + events.clear(); + base.detach(); + + // Queue response document + SetupDocument("Goodbye!"); + + // Prevent entire response from being sent + const size_t kInterruptedLength = strlen(kHttpResponse) - 1; + src.SetWriteBlock(kInterruptedLength); + + // Begin send + base.send(&data); + + // Document is mostly complete, but no completion signal yet. + EXPECT_TRUE(events.empty()); + VerifySourceContents(kHttpResponse, kInterruptedLength); + + src.SetState(SS_CLOSED); + + // Send completed with disconnect error, and no additional data. + VerifyTransferComplete(HM_SEND, HE_DISCONNECTED); + EXPECT_TRUE(src.ReadData().empty()); +} + +TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) { + // Queue response document + SetupSource(kHttpResponse); + + // Begin receive + base.recv(&data); + + // Document completed successfully + VerifyHeaderComplete(2, false); + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents("Goodbye!"); +} + +TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) { + // Switch to pull mode + ObtainDocumentStream(); + VerifyDocumentStreamIsOpening(); + + // Queue response document + SetupSource(kHttpResponse); + VerifyDocumentStreamIsOpening(); + + // Begin receive + base.recv(&data); + + // Pull document data + VerifyDocumentStreamOpenEvent(); + ReadDocumentStreamData("Goodbye!"); + VerifyDocumentStreamIsEOS(); + + // Document completed successfully + VerifyHeaderComplete(2, false); + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents(""); +} + +TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) { + + // TODO: Remove extra logging once test failure is understood + int old_sev = rtc::LogMessage::GetLogToDebug(); + rtc::LogMessage::LogToDebug(LS_VERBOSE); + + + // Switch to pull mode + ObtainDocumentStream(); + VerifyDocumentStreamIsOpening(); + + // Queue response document + SetupSource(kHttpResponse); + VerifyDocumentStreamIsOpening(); + + // Begin receive + base.recv(&data); + + // Pull some of the data + VerifyDocumentStreamOpenEvent(); + ReadDocumentStreamData("Goodb"); + + // We've seen the header by now + VerifyHeaderComplete(1, false); + + // Close the pull stream, this will transition back to push I/O. + http_stream->Close(); + Thread::Current()->ProcessMessages(0); + + // Remainder of document completed successfully + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents("ye!"); + + rtc::LogMessage::LogToDebug(old_sev); +} + +TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) { + // Queue response document + SetupSource(kHttpResponse); + + // Switch to pull mode in response to header arrival + obtain_stream = true; + + // Begin receive + base.recv(&data); + + // We've already seen the header, but not data has arrived + VerifyHeaderComplete(1, false); + VerifyDocumentContents(""); + + // Pull the document data + ReadDocumentStreamData("Goodbye!"); + VerifyDocumentStreamIsEOS(); + + // Document completed successfully + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents(""); +} + +TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) { + // Queue empty response document + SetupSource(kHttpEmptyResponse); + + // Switch to pull mode in response to header arrival + obtain_stream = true; + + // Begin receive + base.recv(&data); + + // We've already seen the header, but not data has arrived + VerifyHeaderComplete(1, true); + VerifyDocumentContents(""); + + // The document is still open, until we attempt to read + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(SS_OPEN, http_stream->GetState()); + + // Attempt to read data, and discover EOS + VerifyDocumentStreamIsEOS(); + + // Document completed successfully + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents(""); +} + +TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) { + // Switch to pull mode + ObtainDocumentStream(); + VerifyDocumentStreamIsOpening(); + + // Queue response document + SetupSource(kHttpResponsePrefix); + VerifyDocumentStreamIsOpening(); + + // Begin receive + base.recv(&data); + + // Pull document data + VerifyDocumentStreamOpenEvent(); + ReadDocumentStreamData("Goodbye!"); + + // Simulate unexpected close + src.SetState(SS_CLOSED); + + // Observe error event on document stream + EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream)); + + // Future reads give an error + int error = 0; + char buffer[5] = { 0 }; + EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error)); + EXPECT_EQ(HE_DISCONNECTED, error); + + // Document completed with error + VerifyHeaderComplete(2, false); + VerifyTransferComplete(HM_RECV, HE_DISCONNECTED); + VerifyDocumentContents(""); +} + +} // namespace rtc diff --git a/webrtc/base/httpclient.cc b/webrtc/base/httpclient.cc new file mode 100644 index 000000000..625677210 --- /dev/null +++ b/webrtc/base/httpclient.cc @@ -0,0 +1,829 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/httpcommon-inl.h" + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/diskcache.h" +#include "webrtc/base/httpclient.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Helpers +////////////////////////////////////////////////////////////////////// + +namespace { + +const size_t kCacheHeader = 0; +const size_t kCacheBody = 1; + +// Convert decimal string to integer +bool HttpStringToUInt(const std::string& str, size_t* val) { + ASSERT(NULL != val); + char* eos = NULL; + *val = strtoul(str.c_str(), &eos, 10); + return (*eos == '\0'); +} + +bool HttpShouldCache(const HttpTransaction& t) { + bool verb_allows_cache = (t.request.verb == HV_GET) + || (t.request.verb == HV_HEAD); + bool is_range_response = t.response.hasHeader(HH_CONTENT_RANGE, NULL); + bool has_expires = t.response.hasHeader(HH_EXPIRES, NULL); + bool request_allows_cache = + has_expires || (std::string::npos != t.request.path.find('?')); + bool response_allows_cache = + has_expires || HttpCodeIsCacheable(t.response.scode); + + bool may_cache = verb_allows_cache + && request_allows_cache + && response_allows_cache + && !is_range_response; + + std::string value; + if (t.response.hasHeader(HH_CACHE_CONTROL, &value)) { + HttpAttributeList directives; + HttpParseAttributes(value.data(), value.size(), directives); + // Response Directives Summary: + // public - always cacheable + // private - do not cache in a shared cache + // no-cache - may cache, but must revalidate whether fresh or stale + // no-store - sensitive information, do not cache or store in any way + // max-age - supplants Expires for staleness + // s-maxage - use as max-age for shared caches, ignore otherwise + // must-revalidate - may cache, but must revalidate after stale + // proxy-revalidate - shared cache must revalidate + if (HttpHasAttribute(directives, "no-store", NULL)) { + may_cache = false; + } else if (HttpHasAttribute(directives, "public", NULL)) { + may_cache = true; + } + } + return may_cache; +} + +enum HttpCacheState { + HCS_FRESH, // In cache, may use + HCS_STALE, // In cache, must revalidate + HCS_NONE // Not in cache +}; + +HttpCacheState HttpGetCacheState(const HttpTransaction& t) { + // Temporaries + std::string s_temp; + time_t u_temp; + + // Current time + size_t now = time(0); + + HttpAttributeList cache_control; + if (t.response.hasHeader(HH_CACHE_CONTROL, &s_temp)) { + HttpParseAttributes(s_temp.data(), s_temp.size(), cache_control); + } + + // Compute age of cache document + time_t date; + if (!t.response.hasHeader(HH_DATE, &s_temp) + || !HttpDateToSeconds(s_temp, &date)) + return HCS_NONE; + + // TODO: Timestamp when cache request sent and response received? + time_t request_time = date; + time_t response_time = date; + + time_t apparent_age = 0; + if (response_time > date) { + apparent_age = response_time - date; + } + + size_t corrected_received_age = apparent_age; + size_t i_temp; + if (t.response.hasHeader(HH_AGE, &s_temp) + && HttpStringToUInt(s_temp, (&i_temp))) { + u_temp = static_cast(i_temp); + corrected_received_age = stdmax(apparent_age, u_temp); + } + + size_t response_delay = response_time - request_time; + size_t corrected_initial_age = corrected_received_age + response_delay; + size_t resident_time = now - response_time; + size_t current_age = corrected_initial_age + resident_time; + + // Compute lifetime of document + size_t lifetime; + if (HttpHasAttribute(cache_control, "max-age", &s_temp)) { + lifetime = atoi(s_temp.c_str()); + } else if (t.response.hasHeader(HH_EXPIRES, &s_temp) + && HttpDateToSeconds(s_temp, &u_temp)) { + lifetime = u_temp - date; + } else if (t.response.hasHeader(HH_LAST_MODIFIED, &s_temp) + && HttpDateToSeconds(s_temp, &u_temp)) { + // TODO: Issue warning 113 if age > 24 hours + lifetime = static_cast(now - u_temp) / 10; + } else { + return HCS_STALE; + } + + return (lifetime > current_age) ? HCS_FRESH : HCS_STALE; +} + +enum HttpValidatorStrength { + HVS_NONE, + HVS_WEAK, + HVS_STRONG +}; + +HttpValidatorStrength +HttpRequestValidatorLevel(const HttpRequestData& request) { + if (HV_GET != request.verb) + return HVS_STRONG; + return request.hasHeader(HH_RANGE, NULL) ? HVS_STRONG : HVS_WEAK; +} + +HttpValidatorStrength +HttpResponseValidatorLevel(const HttpResponseData& response) { + std::string value; + if (response.hasHeader(HH_ETAG, &value)) { + bool is_weak = (strnicmp(value.c_str(), "W/", 2) == 0); + return is_weak ? HVS_WEAK : HVS_STRONG; + } + if (response.hasHeader(HH_LAST_MODIFIED, &value)) { + time_t last_modified, date; + if (HttpDateToSeconds(value, &last_modified) + && response.hasHeader(HH_DATE, &value) + && HttpDateToSeconds(value, &date) + && (last_modified + 60 < date)) { + return HVS_STRONG; + } + return HVS_WEAK; + } + return HVS_NONE; +} + +std::string GetCacheID(const HttpRequestData& request) { + std::string id, url; + id.append(ToString(request.verb)); + id.append("_"); + request.getAbsoluteUri(&url); + id.append(url); + return id; +} + +} // anonymous namespace + +////////////////////////////////////////////////////////////////////// +// Public Helpers +////////////////////////////////////////////////////////////////////// + +bool HttpWriteCacheHeaders(const HttpResponseData* response, + StreamInterface* output, size_t* size) { + size_t length = 0; + // Write all unknown and end-to-end headers to a cache file + for (HttpData::const_iterator it = response->begin(); + it != response->end(); ++it) { + HttpHeader header; + if (FromString(header, it->first) && !HttpHeaderIsEndToEnd(header)) + continue; + length += it->first.length() + 2 + it->second.length() + 2; + if (!output) + continue; + std::string formatted_header(it->first); + formatted_header.append(": "); + formatted_header.append(it->second); + formatted_header.append("\r\n"); + StreamResult result = output->WriteAll(formatted_header.data(), + formatted_header.length(), + NULL, NULL); + if (SR_SUCCESS != result) { + return false; + } + } + if (output && (SR_SUCCESS != output->WriteAll("\r\n", 2, NULL, NULL))) { + return false; + } + length += 2; + if (size) + *size = length; + return true; +} + +bool HttpReadCacheHeaders(StreamInterface* input, HttpResponseData* response, + HttpData::HeaderCombine combine) { + while (true) { + std::string formatted_header; + StreamResult result = input->ReadLine(&formatted_header); + if ((SR_EOS == result) || (1 == formatted_header.size())) { + break; + } + if (SR_SUCCESS != result) { + return false; + } + size_t end_of_name = formatted_header.find(':'); + if (std::string::npos == end_of_name) { + LOG_F(LS_WARNING) << "Malformed cache header"; + continue; + } + size_t start_of_value = end_of_name + 1; + size_t end_of_value = formatted_header.length(); + while ((start_of_value < end_of_value) + && isspace(formatted_header[start_of_value])) + ++start_of_value; + while ((start_of_value < end_of_value) + && isspace(formatted_header[end_of_value-1])) + --end_of_value; + size_t value_length = end_of_value - start_of_value; + + std::string name(formatted_header.substr(0, end_of_name)); + std::string value(formatted_header.substr(start_of_value, value_length)); + response->changeHeader(name, value, combine); + } + return true; +} + +////////////////////////////////////////////////////////////////////// +// HttpClient +////////////////////////////////////////////////////////////////////// + +const size_t kDefaultRetries = 1; +const size_t kMaxRedirects = 5; + +HttpClient::HttpClient(const std::string& agent, StreamPool* pool, + HttpTransaction* transaction) + : agent_(agent), pool_(pool), + transaction_(transaction), free_transaction_(false), + retries_(kDefaultRetries), attempt_(0), redirects_(0), + redirect_action_(REDIRECT_DEFAULT), + uri_form_(URI_DEFAULT), cache_(NULL), cache_state_(CS_READY), + resolver_(NULL) { + base_.notify(this); + if (NULL == transaction_) { + free_transaction_ = true; + transaction_ = new HttpTransaction; + } +} + +HttpClient::~HttpClient() { + base_.notify(NULL); + base_.abort(HE_SHUTDOWN); + if (resolver_) { + resolver_->Destroy(false); + } + release(); + if (free_transaction_) + delete transaction_; +} + +void HttpClient::reset() { + server_.Clear(); + request().clear(true); + response().clear(true); + context_.reset(); + redirects_ = 0; + base_.abort(HE_OPERATION_CANCELLED); +} + +void HttpClient::OnResolveResult(AsyncResolverInterface* resolver) { + if (resolver != resolver_) { + return; + } + int error = resolver_->GetError(); + server_ = resolver_->address(); + resolver_->Destroy(false); + resolver_ = NULL; + if (error != 0) { + LOG(LS_ERROR) << "Error " << error << " resolving name: " + << server_; + onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED); + } else { + connect(); + } +} + +void HttpClient::StartDNSLookup() { + resolver_ = new AsyncResolver(); + resolver_->SignalDone.connect(this, &HttpClient::OnResolveResult); + resolver_->Start(server_); +} + +void HttpClient::set_server(const SocketAddress& address) { + server_ = address; + // Setting 'Host' here allows it to be overridden before starting the request, + // if necessary. + request().setHeader(HH_HOST, HttpAddress(server_, false), true); +} + +StreamInterface* HttpClient::GetDocumentStream() { + return base_.GetDocumentStream(); +} + +void HttpClient::start() { + if (base_.mode() != HM_NONE) { + // call reset() to abort an in-progress request + ASSERT(false); + return; + } + + ASSERT(!IsCacheActive()); + + if (request().hasHeader(HH_TRANSFER_ENCODING, NULL)) { + // Exact size must be known on the client. Instead of using chunked + // encoding, wrap data with auto-caching file or memory stream. + ASSERT(false); + return; + } + + attempt_ = 0; + + // If no content has been specified, using length of 0. + request().setHeader(HH_CONTENT_LENGTH, "0", false); + + if (!agent_.empty()) { + request().setHeader(HH_USER_AGENT, agent_, false); + } + + UriForm uri_form = uri_form_; + if (PROXY_HTTPS == proxy_.type) { + // Proxies require absolute form + uri_form = URI_ABSOLUTE; + request().version = HVER_1_0; + request().setHeader(HH_PROXY_CONNECTION, "Keep-Alive", false); + } else { + request().setHeader(HH_CONNECTION, "Keep-Alive", false); + } + + if (URI_ABSOLUTE == uri_form) { + // Convert to absolute uri form + std::string url; + if (request().getAbsoluteUri(&url)) { + request().path = url; + } else { + LOG(LS_WARNING) << "Couldn't obtain absolute uri"; + } + } else if (URI_RELATIVE == uri_form) { + // Convert to relative uri form + std::string host, path; + if (request().getRelativeUri(&host, &path)) { + request().setHeader(HH_HOST, host); + request().path = path; + } else { + LOG(LS_WARNING) << "Couldn't obtain relative uri"; + } + } + + if ((NULL != cache_) && CheckCache()) { + return; + } + + connect(); +} + +void HttpClient::connect() { + int stream_err; + if (server_.IsUnresolvedIP()) { + StartDNSLookup(); + return; + } + StreamInterface* stream = pool_->RequestConnectedStream(server_, &stream_err); + if (stream == NULL) { + ASSERT(0 != stream_err); + LOG(LS_ERROR) << "RequestConnectedStream error: " << stream_err; + onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED); + } else { + base_.attach(stream); + if (stream->GetState() == SS_OPEN) { + base_.send(&transaction_->request); + } + } +} + +void HttpClient::prepare_get(const std::string& url) { + reset(); + Url purl(url); + set_server(SocketAddress(purl.host(), purl.port())); + request().verb = HV_GET; + request().path = purl.full_path(); +} + +void HttpClient::prepare_post(const std::string& url, + const std::string& content_type, + StreamInterface* request_doc) { + reset(); + Url purl(url); + set_server(SocketAddress(purl.host(), purl.port())); + request().verb = HV_POST; + request().path = purl.full_path(); + request().setContent(content_type, request_doc); +} + +void HttpClient::release() { + if (StreamInterface* stream = base_.detach()) { + pool_->ReturnConnectedStream(stream); + } +} + +bool HttpClient::ShouldRedirect(std::string* location) const { + // TODO: Unittest redirection. + if ((REDIRECT_NEVER == redirect_action_) + || !HttpCodeIsRedirection(response().scode) + || !response().hasHeader(HH_LOCATION, location) + || (redirects_ >= kMaxRedirects)) + return false; + return (REDIRECT_ALWAYS == redirect_action_) + || (HC_SEE_OTHER == response().scode) + || (HV_HEAD == request().verb) + || (HV_GET == request().verb); +} + +bool HttpClient::BeginCacheFile() { + ASSERT(NULL != cache_); + ASSERT(CS_READY == cache_state_); + + std::string id = GetCacheID(request()); + CacheLock lock(cache_, id, true); + if (!lock.IsLocked()) { + LOG_F(LS_WARNING) << "Couldn't lock cache"; + return false; + } + + if (HE_NONE != WriteCacheHeaders(id)) { + return false; + } + + scoped_ptr stream(cache_->WriteResource(id, kCacheBody)); + if (!stream) { + LOG_F(LS_ERROR) << "Couldn't open body cache"; + return false; + } + lock.Commit(); + + // Let's secretly replace the response document with Folgers Crystals, + // er, StreamTap, so that we can mirror the data to our cache. + StreamInterface* output = response().document.release(); + if (!output) { + output = new NullStream; + } + StreamTap* tap = new StreamTap(output, stream.release()); + response().document.reset(tap); + return true; +} + +HttpError HttpClient::WriteCacheHeaders(const std::string& id) { + scoped_ptr stream(cache_->WriteResource(id, kCacheHeader)); + if (!stream) { + LOG_F(LS_ERROR) << "Couldn't open header cache"; + return HE_CACHE; + } + + if (!HttpWriteCacheHeaders(&transaction_->response, stream.get(), NULL)) { + LOG_F(LS_ERROR) << "Couldn't write header cache"; + return HE_CACHE; + } + + return HE_NONE; +} + +void HttpClient::CompleteCacheFile() { + // Restore previous response document + StreamTap* tap = static_cast(response().document.release()); + response().document.reset(tap->Detach()); + + int error; + StreamResult result = tap->GetTapResult(&error); + + // Delete the tap and cache stream (which completes cache unlock) + delete tap; + + if (SR_SUCCESS != result) { + LOG(LS_ERROR) << "Cache file error: " << error; + cache_->DeleteResource(GetCacheID(request())); + } +} + +bool HttpClient::CheckCache() { + ASSERT(NULL != cache_); + ASSERT(CS_READY == cache_state_); + + std::string id = GetCacheID(request()); + if (!cache_->HasResource(id)) { + // No cache file available + return false; + } + + HttpError error = ReadCacheHeaders(id, true); + + if (HE_NONE == error) { + switch (HttpGetCacheState(*transaction_)) { + case HCS_FRESH: + // Cache content is good, read from cache + break; + case HCS_STALE: + // Cache content may be acceptable. Issue a validation request. + if (PrepareValidate()) { + return false; + } + // Couldn't validate, fall through. + case HCS_NONE: + // Cache content is not useable. Issue a regular request. + response().clear(false); + return false; + } + } + + if (HE_NONE == error) { + error = ReadCacheBody(id); + cache_state_ = CS_READY; + } + + if (HE_CACHE == error) { + LOG_F(LS_WARNING) << "Cache failure, continuing with normal request"; + response().clear(false); + return false; + } + + SignalHttpClientComplete(this, error); + return true; +} + +HttpError HttpClient::ReadCacheHeaders(const std::string& id, bool override) { + scoped_ptr stream(cache_->ReadResource(id, kCacheHeader)); + if (!stream) { + return HE_CACHE; + } + + HttpData::HeaderCombine combine = + override ? HttpData::HC_REPLACE : HttpData::HC_AUTO; + + if (!HttpReadCacheHeaders(stream.get(), &transaction_->response, combine)) { + LOG_F(LS_ERROR) << "Error reading cache headers"; + return HE_CACHE; + } + + response().scode = HC_OK; + return HE_NONE; +} + +HttpError HttpClient::ReadCacheBody(const std::string& id) { + cache_state_ = CS_READING; + + HttpError error = HE_NONE; + + size_t data_size; + scoped_ptr stream(cache_->ReadResource(id, kCacheBody)); + if (!stream || !stream->GetAvailable(&data_size)) { + LOG_F(LS_ERROR) << "Unavailable cache body"; + error = HE_CACHE; + } else { + error = OnHeaderAvailable(false, false, data_size); + } + + if ((HE_NONE == error) + && (HV_HEAD != request().verb) + && response().document) { + char buffer[1024 * 64]; + StreamResult result = Flow(stream.get(), buffer, ARRAY_SIZE(buffer), + response().document.get()); + if (SR_SUCCESS != result) { + error = HE_STREAM; + } + } + + return error; +} + +bool HttpClient::PrepareValidate() { + ASSERT(CS_READY == cache_state_); + // At this point, request() contains the pending request, and response() + // contains the cached response headers. Reformat the request to validate + // the cached content. + HttpValidatorStrength vs_required = HttpRequestValidatorLevel(request()); + HttpValidatorStrength vs_available = HttpResponseValidatorLevel(response()); + if (vs_available < vs_required) { + return false; + } + std::string value; + if (response().hasHeader(HH_ETAG, &value)) { + request().addHeader(HH_IF_NONE_MATCH, value); + } + if (response().hasHeader(HH_LAST_MODIFIED, &value)) { + request().addHeader(HH_IF_MODIFIED_SINCE, value); + } + response().clear(false); + cache_state_ = CS_VALIDATING; + return true; +} + +HttpError HttpClient::CompleteValidate() { + ASSERT(CS_VALIDATING == cache_state_); + + std::string id = GetCacheID(request()); + + // Merge cached headers with new headers + HttpError error = ReadCacheHeaders(id, false); + if (HE_NONE != error) { + // Rewrite merged headers to cache + CacheLock lock(cache_, id); + error = WriteCacheHeaders(id); + } + if (HE_NONE != error) { + error = ReadCacheBody(id); + } + return error; +} + +HttpError HttpClient::OnHeaderAvailable(bool ignore_data, bool chunked, + size_t data_size) { + // If we are ignoring the data, this is an intermediate header. + // TODO: don't signal intermediate headers. Instead, do all header-dependent + // processing now, and either set up the next request, or fail outright. + // TODO: by default, only write response documents with a success code. + SignalHeaderAvailable(this, !ignore_data, ignore_data ? 0 : data_size); + if (!ignore_data && !chunked && (data_size != SIZE_UNKNOWN) + && response().document) { + // Attempt to pre-allocate space for the downloaded data. + if (!response().document->ReserveSize(data_size)) { + return HE_OVERFLOW; + } + } + return HE_NONE; +} + +// +// HttpBase Implementation +// + +HttpError HttpClient::onHttpHeaderComplete(bool chunked, size_t& data_size) { + if (CS_VALIDATING == cache_state_) { + if (HC_NOT_MODIFIED == response().scode) { + return CompleteValidate(); + } + // Should we remove conditional headers from request? + cache_state_ = CS_READY; + cache_->DeleteResource(GetCacheID(request())); + // Continue processing response as normal + } + + ASSERT(!IsCacheActive()); + if ((request().verb == HV_HEAD) || !HttpCodeHasBody(response().scode)) { + // HEAD requests and certain response codes contain no body + data_size = 0; + } + if (ShouldRedirect(NULL) + || ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode) + && (PROXY_HTTPS == proxy_.type))) { + // We're going to issue another request, so ignore the incoming data. + base_.set_ignore_data(true); + } + + HttpError error = OnHeaderAvailable(base_.ignore_data(), chunked, data_size); + if (HE_NONE != error) { + return error; + } + + if ((NULL != cache_) + && !base_.ignore_data() + && HttpShouldCache(*transaction_)) { + if (BeginCacheFile()) { + cache_state_ = CS_WRITING; + } + } + return HE_NONE; +} + +void HttpClient::onHttpComplete(HttpMode mode, HttpError err) { + if (((HE_DISCONNECTED == err) || (HE_CONNECT_FAILED == err) + || (HE_SOCKET_ERROR == err)) + && (HC_INTERNAL_SERVER_ERROR == response().scode) + && (attempt_ < retries_)) { + // If the response code has not changed from the default, then we haven't + // received anything meaningful from the server, so we are eligible for a + // retry. + ++attempt_; + if (request().document && !request().document->Rewind()) { + // Unable to replay the request document. + err = HE_STREAM; + } else { + release(); + connect(); + return; + } + } else if (err != HE_NONE) { + // fall through + } else if (mode == HM_CONNECT) { + base_.send(&transaction_->request); + return; + } else if ((mode == HM_SEND) || HttpCodeIsInformational(response().scode)) { + // If you're interested in informational headers, catch + // SignalHeaderAvailable. + base_.recv(&transaction_->response); + return; + } else { + if (!HttpShouldKeepAlive(response())) { + LOG(LS_VERBOSE) << "HttpClient: closing socket"; + base_.stream()->Close(); + } + std::string location; + if (ShouldRedirect(&location)) { + Url purl(location); + set_server(SocketAddress(purl.host(), purl.port())); + request().path = purl.full_path(); + if (response().scode == HC_SEE_OTHER) { + request().verb = HV_GET; + request().clearHeader(HH_CONTENT_TYPE); + request().clearHeader(HH_CONTENT_LENGTH); + request().document.reset(); + } else if (request().document && !request().document->Rewind()) { + // Unable to replay the request document. + ASSERT(REDIRECT_ALWAYS == redirect_action_); + err = HE_STREAM; + } + if (err == HE_NONE) { + ++redirects_; + context_.reset(); + response().clear(false); + release(); + start(); + return; + } + } else if ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode) + && (PROXY_HTTPS == proxy_.type)) { + std::string authorization, auth_method; + HttpData::const_iterator begin = response().begin(HH_PROXY_AUTHENTICATE); + HttpData::const_iterator end = response().end(HH_PROXY_AUTHENTICATE); + for (HttpData::const_iterator it = begin; it != end; ++it) { + HttpAuthContext *context = context_.get(); + HttpAuthResult res = HttpAuthenticate( + it->second.data(), it->second.size(), + proxy_.address, + ToString(request().verb), request().path, + proxy_.username, proxy_.password, + context, authorization, auth_method); + context_.reset(context); + if (res == HAR_RESPONSE) { + request().setHeader(HH_PROXY_AUTHORIZATION, authorization); + if (request().document && !request().document->Rewind()) { + err = HE_STREAM; + } else { + // Explicitly do not reset the HttpAuthContext + response().clear(false); + // TODO: Reuse socket when authenticating? + release(); + start(); + return; + } + } else if (res == HAR_IGNORE) { + LOG(INFO) << "Ignoring Proxy-Authenticate: " << auth_method; + continue; + } else { + break; + } + } + } + } + if (CS_WRITING == cache_state_) { + CompleteCacheFile(); + cache_state_ = CS_READY; + } else if (CS_READING == cache_state_) { + cache_state_ = CS_READY; + } + release(); + SignalHttpClientComplete(this, err); +} + +void HttpClient::onHttpClosed(HttpError err) { + // This shouldn't occur, since we return the stream to the pool upon command + // completion. + ASSERT(false); +} + +////////////////////////////////////////////////////////////////////// +// HttpClientDefault +////////////////////////////////////////////////////////////////////// + +HttpClientDefault::HttpClientDefault(SocketFactory* factory, + const std::string& agent, + HttpTransaction* transaction) + : ReuseSocketPool(factory ? factory : Thread::Current()->socketserver()), + HttpClient(agent, NULL, transaction) { + set_pool(this); +} + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/httpclient.h b/webrtc/base/httpclient.h new file mode 100644 index 000000000..b634b9345 --- /dev/null +++ b/webrtc/base/httpclient.h @@ -0,0 +1,202 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_HTTPCLIENT_H__ +#define WEBRTC_BASE_HTTPCLIENT_H__ + +#include "webrtc/base/common.h" +#include "webrtc/base/httpbase.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/socketpool.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Client-specific http utilities +////////////////////////////////////////////////////////////////////// + +// Write cache-relevant response headers to output stream. If size is non-null, +// it contains the length of the output in bytes. output may be null if only +// the length is desired. +bool HttpWriteCacheHeaders(const HttpResponseData* response, + StreamInterface* output, size_t* size); +// Read cached headers from a stream, and them merge them into the response +// object using the specified combine operation. +bool HttpReadCacheHeaders(StreamInterface* input, + HttpResponseData* response, + HttpData::HeaderCombine combine); + +////////////////////////////////////////////////////////////////////// +// HttpClient +// Implements an HTTP 1.1 client. +////////////////////////////////////////////////////////////////////// + +class DiskCache; +class HttpClient; +class IPNetPool; + +class SignalThread; +// What to do: Define STRICT_HTTP_ERROR=1 in your makefile. Use HttpError in +// your code (HttpErrorType should only be used for code that is shared +// with groups which have not yet migrated). +#if STRICT_HTTP_ERROR +typedef HttpError HttpErrorType; +#else // !STRICT_HTTP_ERROR +typedef int HttpErrorType; +#endif // !STRICT_HTTP_ERROR + +class HttpClient : private IHttpNotify, public sigslot::has_slots<> { +public: + // If HttpRequestData and HttpResponseData objects are provided, they must + // be freed by the caller. Otherwise, an internal object is allocated. + HttpClient(const std::string& agent, StreamPool* pool, + HttpTransaction* transaction = NULL); + virtual ~HttpClient(); + + void set_pool(StreamPool* pool) { pool_ = pool; } + + void set_agent(const std::string& agent) { agent_ = agent; } + const std::string& agent() const { return agent_; } + + void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; } + const ProxyInfo& proxy() const { return proxy_; } + + // Request retries occur when the connection closes before the beginning of + // an http response is received. In these cases, the http server may have + // timed out the keepalive connection before it received our request. Note + // that if a request document cannot be rewound, no retry is made. The + // default is 1. + void set_request_retries(size_t retries) { retries_ = retries; } + size_t request_retries() const { return retries_; } + + enum RedirectAction { REDIRECT_DEFAULT, REDIRECT_ALWAYS, REDIRECT_NEVER }; + void set_redirect_action(RedirectAction action) { redirect_action_ = action; } + RedirectAction redirect_action() const { return redirect_action_; } + // Deprecated + void set_fail_redirect(bool fail_redirect) { + redirect_action_ = REDIRECT_NEVER; + } + bool fail_redirect() const { return (REDIRECT_NEVER == redirect_action_); } + + enum UriForm { URI_DEFAULT, URI_ABSOLUTE, URI_RELATIVE }; + void set_uri_form(UriForm form) { uri_form_ = form; } + UriForm uri_form() const { return uri_form_; } + + void set_cache(DiskCache* cache) { ASSERT(!IsCacheActive()); cache_ = cache; } + bool cache_enabled() const { return (NULL != cache_); } + + // reset clears the server, request, and response structures. It will also + // abort an active request. + void reset(); + + void set_server(const SocketAddress& address); + const SocketAddress& server() const { return server_; } + + // Note: in order for HttpClient to retry a POST in response to + // an authentication challenge, a redirect response, or socket disconnection, + // the request document must support 'replaying' by calling Rewind() on it. + // In the case where just a subset of a stream should be used as the request + // document, the stream may be wrapped with the StreamSegment adapter. + HttpTransaction* transaction() { return transaction_; } + const HttpTransaction* transaction() const { return transaction_; } + HttpRequestData& request() { return transaction_->request; } + const HttpRequestData& request() const { return transaction_->request; } + HttpResponseData& response() { return transaction_->response; } + const HttpResponseData& response() const { return transaction_->response; } + + // convenience methods + void prepare_get(const std::string& url); + void prepare_post(const std::string& url, const std::string& content_type, + StreamInterface* request_doc); + + // Convert HttpClient to a pull-based I/O model. + StreamInterface* GetDocumentStream(); + + // After you finish setting up your request, call start. + void start(); + + // Signalled when the header has finished downloading, before the document + // content is processed. You may change the response document in response + // to this signal. The second parameter indicates whether this is an + // intermediate (false) or final (true) header. An intermediate header is + // one that generates another request, such as a redirect or authentication + // challenge. The third parameter indicates the length of the response + // document, or else SIZE_UNKNOWN. Note: Do NOT abort the request in response + // to this signal. + sigslot::signal3 SignalHeaderAvailable; + // Signalled when the current request finishes. On success, err is 0. + sigslot::signal2 SignalHttpClientComplete; + +protected: + void connect(); + void release(); + + bool ShouldRedirect(std::string* location) const; + + bool BeginCacheFile(); + HttpError WriteCacheHeaders(const std::string& id); + void CompleteCacheFile(); + + bool CheckCache(); + HttpError ReadCacheHeaders(const std::string& id, bool override); + HttpError ReadCacheBody(const std::string& id); + + bool PrepareValidate(); + HttpError CompleteValidate(); + + HttpError OnHeaderAvailable(bool ignore_data, bool chunked, size_t data_size); + + void StartDNSLookup(); + void OnResolveResult(AsyncResolverInterface* resolver); + + // IHttpNotify Interface + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size); + virtual void onHttpComplete(HttpMode mode, HttpError err); + virtual void onHttpClosed(HttpError err); + +private: + enum CacheState { CS_READY, CS_WRITING, CS_READING, CS_VALIDATING }; + bool IsCacheActive() const { return (cache_state_ > CS_READY); } + + std::string agent_; + StreamPool* pool_; + HttpBase base_; + SocketAddress server_; + ProxyInfo proxy_; + HttpTransaction* transaction_; + bool free_transaction_; + size_t retries_, attempt_, redirects_; + RedirectAction redirect_action_; + UriForm uri_form_; + scoped_ptr context_; + DiskCache* cache_; + CacheState cache_state_; + AsyncResolverInterface* resolver_; +}; + +////////////////////////////////////////////////////////////////////// +// HttpClientDefault - Default implementation of HttpClient +////////////////////////////////////////////////////////////////////// + +class HttpClientDefault : public ReuseSocketPool, public HttpClient { +public: + HttpClientDefault(SocketFactory* factory, const std::string& agent, + HttpTransaction* transaction = NULL); +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPCLIENT_H__ diff --git a/webrtc/base/httpcommon-inl.h b/webrtc/base/httpcommon-inl.h new file mode 100644 index 000000000..2f525ce79 --- /dev/null +++ b/webrtc/base/httpcommon-inl.h @@ -0,0 +1,131 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_HTTPCOMMON_INL_H__ +#define WEBRTC_BASE_HTTPCOMMON_INL_H__ + +#include "webrtc/base/common.h" +#include "webrtc/base/httpcommon.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Url +/////////////////////////////////////////////////////////////////////////////// + +template +void Url::do_set_url(const CTYPE* val, size_t len) { + if (ascnicmp(val, "http://", 7) == 0) { + val += 7; len -= 7; + secure_ = false; + } else if (ascnicmp(val, "https://", 8) == 0) { + val += 8; len -= 8; + secure_ = true; + } else { + clear(); + return; + } + const CTYPE* path = strchrn(val, len, static_cast('/')); + if (!path) { + path = val + len; + } + size_t address_length = (path - val); + do_set_address(val, address_length); + do_set_full_path(path, len - address_length); +} + +template +void Url::do_set_address(const CTYPE* val, size_t len) { + if (const CTYPE* at = strchrn(val, len, static_cast('@'))) { + // Everything before the @ is a user:password combo, so skip it. + len -= at - val + 1; + val = at + 1; + } + if (const CTYPE* colon = strchrn(val, len, static_cast(':'))) { + host_.assign(val, colon - val); + // Note: In every case, we're guaranteed that colon is followed by a null, + // or non-numeric character. + port_ = static_cast(::strtoul(colon + 1, NULL, 10)); + // TODO: Consider checking for invalid data following port number. + } else { + host_.assign(val, len); + port_ = HttpDefaultPort(secure_); + } +} + +template +void Url::do_set_full_path(const CTYPE* val, size_t len) { + const CTYPE* query = strchrn(val, len, static_cast('?')); + if (!query) { + query = val + len; + } + size_t path_length = (query - val); + if (0 == path_length) { + // TODO: consider failing in this case. + path_.assign(1, static_cast('/')); + } else { + ASSERT(val[0] == static_cast('/')); + path_.assign(val, path_length); + } + query_.assign(query, len - path_length); +} + +template +void Url::do_get_url(string* val) const { + CTYPE protocol[9]; + asccpyn(protocol, ARRAY_SIZE(protocol), secure_ ? "https://" : "http://"); + val->append(protocol); + do_get_address(val); + do_get_full_path(val); +} + +template +void Url::do_get_address(string* val) const { + val->append(host_); + if (port_ != HttpDefaultPort(secure_)) { + CTYPE format[5], port[32]; + asccpyn(format, ARRAY_SIZE(format), ":%hu"); + sprintfn(port, ARRAY_SIZE(port), format, port_); + val->append(port); + } +} + +template +void Url::do_get_full_path(string* val) const { + val->append(path_); + val->append(query_); +} + +template +bool Url::get_attribute(const string& name, string* value) const { + if (query_.empty()) + return false; + + std::string::size_type pos = query_.find(name, 1); + if (std::string::npos == pos) + return false; + + pos += name.length() + 1; + if ((pos > query_.length()) || (static_cast('=') != query_[pos-1])) + return false; + + std::string::size_type end = query_.find(static_cast('&'), pos); + if (std::string::npos == end) { + end = query_.length(); + } + value->assign(query_.substr(pos, end - pos)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPCOMMON_INL_H__ diff --git a/webrtc/base/httpcommon.cc b/webrtc/base/httpcommon.cc new file mode 100644 index 000000000..095cdafef --- /dev/null +++ b/webrtc/base/httpcommon.cc @@ -0,0 +1,1045 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#define SECURITY_WIN32 +#include +#endif + +#include "webrtc/base/httpcommon-inl.h" + +#include "webrtc/base/base64.h" +#include "webrtc/base/common.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/stringdigest.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +#if defined(WEBRTC_WIN) +extern const ConstantLabel SECURITY_ERRORS[]; +#endif + +////////////////////////////////////////////////////////////////////// +// Enum - TODO: expose globally later? +////////////////////////////////////////////////////////////////////// + +bool find_string(size_t& index, const std::string& needle, + const char* const haystack[], size_t max_index) { + for (index=0; index +struct Enum { + static const char** Names; + static size_t Size; + + static inline const char* Name(E val) { return Names[val]; } + static inline bool Parse(E& val, const std::string& name) { + size_t index; + if (!find_string(index, name, Names, Size)) + return false; + val = static_cast(index); + return true; + } + + E val; + + inline operator E&() { return val; } + inline Enum& operator=(E rhs) { val = rhs; return *this; } + + inline const char* name() const { return Name(val); } + inline bool assign(const std::string& name) { return Parse(val, name); } + inline Enum& operator=(const std::string& rhs) { assign(rhs); return *this; } +}; + +#define ENUM(e,n) \ + template<> const char** Enum::Names = n; \ + template<> size_t Enum::Size = sizeof(n)/sizeof(n[0]) + +////////////////////////////////////////////////////////////////////// +// HttpCommon +////////////////////////////////////////////////////////////////////// + +static const char* kHttpVersions[HVER_LAST+1] = { + "1.0", "1.1", "Unknown" +}; +ENUM(HttpVersion, kHttpVersions); + +static const char* kHttpVerbs[HV_LAST+1] = { + "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD" +}; +ENUM(HttpVerb, kHttpVerbs); + +static const char* kHttpHeaders[HH_LAST+1] = { + "Age", + "Cache-Control", + "Connection", + "Content-Disposition", + "Content-Length", + "Content-Range", + "Content-Type", + "Cookie", + "Date", + "ETag", + "Expires", + "Host", + "If-Modified-Since", + "If-None-Match", + "Keep-Alive", + "Last-Modified", + "Location", + "Proxy-Authenticate", + "Proxy-Authorization", + "Proxy-Connection", + "Range", + "Set-Cookie", + "TE", + "Trailers", + "Transfer-Encoding", + "Upgrade", + "User-Agent", + "WWW-Authenticate", +}; +ENUM(HttpHeader, kHttpHeaders); + +const char* ToString(HttpVersion version) { + return Enum::Name(version); +} + +bool FromString(HttpVersion& version, const std::string& str) { + return Enum::Parse(version, str); +} + +const char* ToString(HttpVerb verb) { + return Enum::Name(verb); +} + +bool FromString(HttpVerb& verb, const std::string& str) { + return Enum::Parse(verb, str); +} + +const char* ToString(HttpHeader header) { + return Enum::Name(header); +} + +bool FromString(HttpHeader& header, const std::string& str) { + return Enum::Parse(header, str); +} + +bool HttpCodeHasBody(uint32 code) { + return !HttpCodeIsInformational(code) + && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED); +} + +bool HttpCodeIsCacheable(uint32 code) { + switch (code) { + case HC_OK: + case HC_NON_AUTHORITATIVE: + case HC_PARTIAL_CONTENT: + case HC_MULTIPLE_CHOICES: + case HC_MOVED_PERMANENTLY: + case HC_GONE: + return true; + default: + return false; + } +} + +bool HttpHeaderIsEndToEnd(HttpHeader header) { + switch (header) { + case HH_CONNECTION: + case HH_KEEP_ALIVE: + case HH_PROXY_AUTHENTICATE: + case HH_PROXY_AUTHORIZATION: + case HH_PROXY_CONNECTION: // Note part of RFC... this is non-standard header + case HH_TE: + case HH_TRAILERS: + case HH_TRANSFER_ENCODING: + case HH_UPGRADE: + return false; + default: + return true; + } +} + +bool HttpHeaderIsCollapsible(HttpHeader header) { + switch (header) { + case HH_SET_COOKIE: + case HH_PROXY_AUTHENTICATE: + case HH_WWW_AUTHENTICATE: + return false; + default: + return true; + } +} + +bool HttpShouldKeepAlive(const HttpData& data) { + std::string connection; + if ((data.hasHeader(HH_PROXY_CONNECTION, &connection) + || data.hasHeader(HH_CONNECTION, &connection))) { + return (_stricmp(connection.c_str(), "Keep-Alive") == 0); + } + return (data.version >= HVER_1_1); +} + +namespace { + +inline bool IsEndOfAttributeName(size_t pos, size_t len, const char * data) { + if (pos >= len) + return true; + if (isspace(static_cast(data[pos]))) + return true; + // The reason for this complexity is that some attributes may contain trailing + // equal signs (like base64 tokens in Negotiate auth headers) + if ((pos+1 < len) && (data[pos] == '=') && + !isspace(static_cast(data[pos+1])) && + (data[pos+1] != '=')) { + return true; + } + return false; +} + +// TODO: unittest for EscapeAttribute and HttpComposeAttributes. + +std::string EscapeAttribute(const std::string& attribute) { + const size_t kMaxLength = attribute.length() * 2 + 1; + char* buffer = STACK_ARRAY(char, kMaxLength); + size_t len = escape(buffer, kMaxLength, attribute.data(), attribute.length(), + "\"", '\\'); + return std::string(buffer, len); +} + +} // anonymous namespace + +void HttpComposeAttributes(const HttpAttributeList& attributes, char separator, + std::string* composed) { + std::stringstream ss; + for (size_t i=0; i 0) { + ss << separator << " "; + } + ss << attributes[i].first; + if (!attributes[i].second.empty()) { + ss << "=\"" << EscapeAttribute(attributes[i].second) << "\""; + } + } + *composed = ss.str(); +} + +void HttpParseAttributes(const char * data, size_t len, + HttpAttributeList& attributes) { + size_t pos = 0; + while (true) { + // Skip leading whitespace + while ((pos < len) && isspace(static_cast(data[pos]))) { + ++pos; + } + + // End of attributes? + if (pos >= len) + return; + + // Find end of attribute name + size_t start = pos; + while (!IsEndOfAttributeName(pos, len, data)) { + ++pos; + } + + HttpAttribute attribute; + attribute.first.assign(data + start, data + pos); + + // Attribute has value? + if ((pos < len) && (data[pos] == '=')) { + ++pos; // Skip '=' + // Check if quoted value + if ((pos < len) && (data[pos] == '"')) { + while (++pos < len) { + if (data[pos] == '"') { + ++pos; + break; + } + if ((data[pos] == '\\') && (pos + 1 < len)) + ++pos; + attribute.second.append(1, data[pos]); + } + } else { + while ((pos < len) && + !isspace(static_cast(data[pos])) && + (data[pos] != ',')) { + attribute.second.append(1, data[pos++]); + } + } + } + + attributes.push_back(attribute); + if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ',' + } +} + +bool HttpHasAttribute(const HttpAttributeList& attributes, + const std::string& name, + std::string* value) { + for (HttpAttributeList::const_iterator it = attributes.begin(); + it != attributes.end(); ++it) { + if (it->first == name) { + if (value) { + *value = it->second; + } + return true; + } + } + return false; +} + +bool HttpHasNthAttribute(HttpAttributeList& attributes, + size_t index, + std::string* name, + std::string* value) { + if (index >= attributes.size()) + return false; + + if (name) + *name = attributes[index].first; + if (value) + *value = attributes[index].second; + return true; +} + +bool HttpDateToSeconds(const std::string& date, time_t* seconds) { + const char* const kTimeZones[] = { + "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y" + }; + const int kTimeZoneOffsets[] = { + 0, 0, -5, -4, -6, -5, -7, -6, -8, -7, + -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 + }; + + ASSERT(NULL != seconds); + struct tm tval; + memset(&tval, 0, sizeof(tval)); + char month[4], zone[6]; + memset(month, 0, sizeof(month)); + memset(zone, 0, sizeof(zone)); + + if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c", + &tval.tm_mday, month, &tval.tm_year, + &tval.tm_hour, &tval.tm_min, &tval.tm_sec, zone)) { + return false; + } + switch (toupper(month[2])) { + case 'N': tval.tm_mon = (month[1] == 'A') ? 0 : 5; break; + case 'B': tval.tm_mon = 1; break; + case 'R': tval.tm_mon = (month[0] == 'M') ? 2 : 3; break; + case 'Y': tval.tm_mon = 4; break; + case 'L': tval.tm_mon = 6; break; + case 'G': tval.tm_mon = 7; break; + case 'P': tval.tm_mon = 8; break; + case 'T': tval.tm_mon = 9; break; + case 'V': tval.tm_mon = 10; break; + case 'C': tval.tm_mon = 11; break; + } + tval.tm_year -= 1900; + size_t gmt, non_gmt = mktime(&tval); + if ((zone[0] == '+') || (zone[0] == '-')) { + if (!isdigit(zone[1]) || !isdigit(zone[2]) + || !isdigit(zone[3]) || !isdigit(zone[4])) { + return false; + } + int hours = (zone[1] - '0') * 10 + (zone[2] - '0'); + int minutes = (zone[3] - '0') * 10 + (zone[4] - '0'); + int offset = (hours * 60 + minutes) * 60; + gmt = non_gmt + ((zone[0] == '+') ? offset : -offset); + } else { + size_t zindex; + if (!find_string(zindex, zone, kTimeZones, ARRAY_SIZE(kTimeZones))) { + return false; + } + gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60; + } + // TODO: Android should support timezone, see b/2441195 +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(WEBRTC_ANDROID) || defined(BSD) + tm *tm_for_timezone = localtime((time_t *)&gmt); + *seconds = gmt + tm_for_timezone->tm_gmtoff; +#else + *seconds = gmt - timezone; +#endif + return true; +} + +std::string HttpAddress(const SocketAddress& address, bool secure) { + return (address.port() == HttpDefaultPort(secure)) + ? address.hostname() : address.ToString(); +} + +////////////////////////////////////////////////////////////////////// +// HttpData +////////////////////////////////////////////////////////////////////// + +void +HttpData::clear(bool release_document) { + // Clear headers first, since releasing a document may have far-reaching + // effects. + headers_.clear(); + if (release_document) { + document.reset(); + } +} + +void +HttpData::copy(const HttpData& src) { + headers_ = src.headers_; +} + +void +HttpData::changeHeader(const std::string& name, const std::string& value, + HeaderCombine combine) { + if (combine == HC_AUTO) { + HttpHeader header; + // Unrecognized headers are collapsible + combine = !FromString(header, name) || HttpHeaderIsCollapsible(header) + ? HC_YES : HC_NO; + } else if (combine == HC_REPLACE) { + headers_.erase(name); + combine = HC_NO; + } + // At this point, combine is one of (YES, NO, NEW) + if (combine != HC_NO) { + HeaderMap::iterator it = headers_.find(name); + if (it != headers_.end()) { + if (combine == HC_YES) { + it->second.append(","); + it->second.append(value); + } + return; + } + } + headers_.insert(HeaderMap::value_type(name, value)); +} + +size_t HttpData::clearHeader(const std::string& name) { + return headers_.erase(name); +} + +HttpData::iterator HttpData::clearHeader(iterator header) { + iterator deprecated = header++; + headers_.erase(deprecated); + return header; +} + +bool +HttpData::hasHeader(const std::string& name, std::string* value) const { + HeaderMap::const_iterator it = headers_.find(name); + if (it == headers_.end()) { + return false; + } else if (value) { + *value = it->second; + } + return true; +} + +void HttpData::setContent(const std::string& content_type, + StreamInterface* document) { + setHeader(HH_CONTENT_TYPE, content_type); + setDocumentAndLength(document); +} + +void HttpData::setDocumentAndLength(StreamInterface* document) { + // TODO: Consider calling Rewind() here? + ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL)); + ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL)); + ASSERT(document != NULL); + this->document.reset(document); + size_t content_length = 0; + if (this->document->GetAvailable(&content_length)) { + char buffer[32]; + sprintfn(buffer, sizeof(buffer), "%d", content_length); + setHeader(HH_CONTENT_LENGTH, buffer); + } else { + setHeader(HH_TRANSFER_ENCODING, "chunked"); + } +} + +// +// HttpRequestData +// + +void +HttpRequestData::clear(bool release_document) { + verb = HV_GET; + path.clear(); + HttpData::clear(release_document); +} + +void +HttpRequestData::copy(const HttpRequestData& src) { + verb = src.verb; + path = src.path; + HttpData::copy(src); +} + +size_t +HttpRequestData::formatLeader(char* buffer, size_t size) const { + ASSERT(path.find(' ') == std::string::npos); + return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(), + path.data(), ToString(version)); +} + +HttpError +HttpRequestData::parseLeader(const char* line, size_t len) { + unsigned int vmajor, vminor; + int vend, dstart, dend; + // sscanf isn't safe with strings that aren't null-terminated, and there is + // no guarantee that |line| is. Create a local copy that is null-terminated. + std::string line_str(line, len); + line = line_str.c_str(); + if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", + &vend, &dstart, &dend, &vmajor, &vminor) != 2) + || (vmajor != 1)) { + return HE_PROTOCOL; + } + if (vminor == 0) { + version = HVER_1_0; + } else if (vminor == 1) { + version = HVER_1_1; + } else { + return HE_PROTOCOL; + } + std::string sverb(line, vend); + if (!FromString(verb, sverb.c_str())) { + return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED? + } + path.assign(line + dstart, line + dend); + return HE_NONE; +} + +bool HttpRequestData::getAbsoluteUri(std::string* uri) const { + if (HV_CONNECT == verb) + return false; + Url url(path); + if (url.valid()) { + uri->assign(path); + return true; + } + std::string host; + if (!hasHeader(HH_HOST, &host)) + return false; + url.set_address(host); + url.set_full_path(path); + uri->assign(url.url()); + return url.valid(); +} + +bool HttpRequestData::getRelativeUri(std::string* host, + std::string* path) const +{ + if (HV_CONNECT == verb) + return false; + Url url(this->path); + if (url.valid()) { + host->assign(url.address()); + path->assign(url.full_path()); + return true; + } + if (!hasHeader(HH_HOST, host)) + return false; + path->assign(this->path); + return true; +} + +// +// HttpResponseData +// + +void +HttpResponseData::clear(bool release_document) { + scode = HC_INTERNAL_SERVER_ERROR; + message.clear(); + HttpData::clear(release_document); +} + +void +HttpResponseData::copy(const HttpResponseData& src) { + scode = src.scode; + message = src.message; + HttpData::copy(src); +} + +void +HttpResponseData::set_success(uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_CONTENT_LENGTH, "0", false); +} + +void +HttpResponseData::set_success(const std::string& content_type, + StreamInterface* document, + uint32 scode) { + this->scode = scode; + message.erase(message.begin(), message.end()); + setContent(content_type, document); +} + +void +HttpResponseData::set_redirect(const std::string& location, uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_LOCATION, location); + setHeader(HH_CONTENT_LENGTH, "0", false); +} + +void +HttpResponseData::set_error(uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_CONTENT_LENGTH, "0", false); +} + +size_t +HttpResponseData::formatLeader(char* buffer, size_t size) const { + size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode); + if (!message.empty()) { + len += sprintfn(buffer + len, size - len, " %.*s", + message.size(), message.data()); + } + return len; +} + +HttpError +HttpResponseData::parseLeader(const char* line, size_t len) { + size_t pos = 0; + unsigned int vmajor, vminor, temp_scode; + int temp_pos; + // sscanf isn't safe with strings that aren't null-terminated, and there is + // no guarantee that |line| is. Create a local copy that is null-terminated. + std::string line_str(line, len); + line = line_str.c_str(); + if (sscanf(line, "HTTP %u%n", + &temp_scode, &temp_pos) == 1) { + // This server's response has no version. :( NOTE: This happens for every + // response to requests made from Chrome plugins, regardless of the server's + // behaviour. + LOG(LS_VERBOSE) << "HTTP version missing from response"; + version = HVER_UNKNOWN; + } else if ((sscanf(line, "HTTP/%u.%u %u%n", + &vmajor, &vminor, &temp_scode, &temp_pos) == 3) + && (vmajor == 1)) { + // This server's response does have a version. + if (vminor == 0) { + version = HVER_1_0; + } else if (vminor == 1) { + version = HVER_1_1; + } else { + return HE_PROTOCOL; + } + } else { + return HE_PROTOCOL; + } + scode = temp_scode; + pos = static_cast(temp_pos); + while ((pos < len) && isspace(static_cast(line[pos]))) ++pos; + message.assign(line + pos, len - pos); + return HE_NONE; +} + +////////////////////////////////////////////////////////////////////// +// Http Authentication +////////////////////////////////////////////////////////////////////// + +#define TEST_DIGEST 0 +#if TEST_DIGEST +/* +const char * const DIGEST_CHALLENGE = + "Digest realm=\"testrealm@host.com\"," + " qop=\"auth,auth-int\"," + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"," + " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""; +const char * const DIGEST_METHOD = "GET"; +const char * const DIGEST_URI = + "/dir/index.html";; +const char * const DIGEST_CNONCE = + "0a4f113b"; +const char * const DIGEST_RESPONSE = + "6629fae49393a05397450978507c4ef1"; +//user_ = "Mufasa"; +//pass_ = "Circle Of Life"; +*/ +const char * const DIGEST_CHALLENGE = + "Digest realm=\"Squid proxy-caching web server\"," + " nonce=\"Nny4QuC5PwiSDixJ\"," + " qop=\"auth\"," + " stale=false"; +const char * const DIGEST_URI = + "/"; +const char * const DIGEST_CNONCE = + "6501d58e9a21cee1e7b5fec894ded024"; +const char * const DIGEST_RESPONSE = + "edffcb0829e755838b073a4a42de06bc"; +#endif + +std::string quote(const std::string& str) { + std::string result; + result.push_back('"'); + for (size_t i=0; iauth_method != auth_method)) + return HAR_IGNORE; + + // BASIC + if (_stricmp(auth_method.c_str(), "basic") == 0) { + if (context) + return HAR_CREDENTIALS; // Bad credentials + if (username.empty()) + return HAR_CREDENTIALS; // Missing credentials + + context = new HttpAuthContext(auth_method); + + // TODO: convert sensitive to a secure buffer that gets securely deleted + //std::string decoded = username + ":" + password; + size_t len = username.size() + password.GetLength() + 2; + char * sensitive = new char[len]; + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + response = auth_method; + response.append(" "); + // TODO: create a sensitive-source version of Base64::encode + response.append(Base64::Encode(sensitive)); + memset(sensitive, 0, len); + delete [] sensitive; + return HAR_RESPONSE; + } + + // DIGEST + if (_stricmp(auth_method.c_str(), "digest") == 0) { + if (context) + return HAR_CREDENTIALS; // Bad credentials + if (username.empty()) + return HAR_CREDENTIALS; // Missing credentials + + context = new HttpAuthContext(auth_method); + + std::string cnonce, ncount; +#if TEST_DIGEST + method = DIGEST_METHOD; + uri = DIGEST_URI; + cnonce = DIGEST_CNONCE; +#else + char buffer[256]; + sprintf(buffer, "%d", static_cast(time(0))); + cnonce = MD5(buffer); +#endif + ncount = "00000001"; + + std::string realm, nonce, qop, opaque; + HttpHasAttribute(args, "realm", &realm); + HttpHasAttribute(args, "nonce", &nonce); + bool has_qop = HttpHasAttribute(args, "qop", &qop); + bool has_opaque = HttpHasAttribute(args, "opaque", &opaque); + + // TODO: convert sensitive to be secure buffer + //std::string A1 = username + ":" + realm + ":" + password; + size_t len = username.size() + realm.size() + password.GetLength() + 3; + char * sensitive = new char[len]; // A1 + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + pos += strcpyn(sensitive + pos, len - pos, realm.c_str()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + std::string A2 = method + ":" + uri; + std::string middle; + if (has_qop) { + qop = "auth"; + middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop; + } else { + middle = nonce; + } + std::string HA1 = MD5(sensitive); + memset(sensitive, 0, len); + delete [] sensitive; + std::string HA2 = MD5(A2); + std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2); + +#if TEST_DIGEST + ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0); +#endif + + std::stringstream ss; + ss << auth_method; + ss << " username=" << quote(username); + ss << ", realm=" << quote(realm); + ss << ", nonce=" << quote(nonce); + ss << ", uri=" << quote(uri); + if (has_qop) { + ss << ", qop=" << qop; + ss << ", nc=" << ncount; + ss << ", cnonce=" << quote(cnonce); + } + ss << ", response=\"" << dig_response << "\""; + if (has_opaque) { + ss << ", opaque=" << quote(opaque); + } + response = ss.str(); + return HAR_RESPONSE; + } + +#if defined(WEBRTC_WIN) +#if 1 + bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0); + bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0); + // SPNEGO & NTLM + if (want_negotiate || want_ntlm) { + const size_t MAX_MESSAGE = 12000, MAX_SPN = 256; + char out_buf[MAX_MESSAGE], spn[MAX_SPN]; + +#if 0 // Requires funky windows versions + DWORD len = MAX_SPN; + if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), NULL, + server.port(), + 0, &len, spn) != ERROR_SUCCESS) { + LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed"; + return HAR_IGNORE; + } +#else + sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str()); +#endif + + SecBuffer out_sec; + out_sec.pvBuffer = out_buf; + out_sec.cbBuffer = sizeof(out_buf); + out_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc out_buf_desc; + out_buf_desc.ulVersion = 0; + out_buf_desc.cBuffers = 1; + out_buf_desc.pBuffers = &out_sec; + + const ULONG NEG_FLAGS_DEFAULT = + //ISC_REQ_ALLOCATE_MEMORY + ISC_REQ_CONFIDENTIALITY + //| ISC_REQ_EXTENDED_ERROR + //| ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + //| ISC_REQ_STREAM + //| ISC_REQ_USE_SUPPLIED_CREDS + ; + + ::TimeStamp lifetime; + SECURITY_STATUS ret = S_OK; + ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT; + + bool specify_credentials = !username.empty(); + size_t steps = 0; + + //uint32 now = Time(); + + NegotiateAuthContext * neg = static_cast(context); + if (neg) { + const size_t max_steps = 10; + if (++neg->steps >= max_steps) { + LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries"; + return HAR_ERROR; + } + steps = neg->steps; + + std::string challenge, decoded_challenge; + if (HttpHasNthAttribute(args, 1, &challenge, NULL) + && Base64::Decode(challenge, Base64::DO_STRICT, + &decoded_challenge, NULL)) { + SecBuffer in_sec; + in_sec.pvBuffer = const_cast(decoded_challenge.data()); + in_sec.cbBuffer = static_cast(decoded_challenge.size()); + in_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc in_buf_desc; + in_buf_desc.ulVersion = 0; + in_buf_desc.cBuffers = 1; + in_buf_desc.pBuffers = &in_sec; + + ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + return HAR_ERROR; + } + } else if (neg->specified_credentials) { + // Try again with default credentials + specify_credentials = false; + delete context; + context = neg = 0; + } else { + return HAR_CREDENTIALS; + } + } + + if (!neg) { + unsigned char userbuf[256], passbuf[256], domainbuf[16]; + SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0; + if (specify_credentials) { + memset(&auth_id, 0, sizeof(auth_id)); + size_t len = password.GetLength()+1; + char * sensitive = new char[len]; + password.CopyTo(sensitive, true); + std::string::size_type pos = username.find('\\'); + if (pos == std::string::npos) { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size())); + memcpy(userbuf, username.c_str(), auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = 0; + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } else { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size() - pos - 1)); + memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = static_cast( + _min(sizeof(domainbuf) - 1, pos)); + memcpy(domainbuf, username.c_str(), auth_id.DomainLength); + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } + memset(sensitive, 0, len); + delete [] sensitive; + auth_id.User = userbuf; + auth_id.Domain = domainbuf; + auth_id.Password = passbuf; + auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + pauth_id = &auth_id; + LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials"; + } else { + LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials"; + } + + CredHandle cred; + ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime); + //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now); + if (ret != SEC_E_OK) { + LOG(LS_ERROR) << "AcquireCredentialsHandle error: " + << ErrorName(ret, SECURITY_ERRORS); + return HAR_IGNORE; + } + + //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out; + + CtxtHandle ctx; + ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + FreeCredentialsHandle(&cred); + return HAR_IGNORE; + } + + ASSERT(!context); + context = neg = new NegotiateAuthContext(auth_method, cred, ctx); + neg->specified_credentials = specify_credentials; + neg->steps = steps; + } + + if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) { + ret = CompleteAuthToken(&neg->ctx, &out_buf_desc); + //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now); + LOG(LS_VERBOSE) << "CompleteAuthToken returned: " + << ErrorName(ret, SECURITY_ERRORS); + if (FAILED(ret)) { + return HAR_ERROR; + } + } + + //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms"; + + std::string decoded(out_buf, out_buf + out_sec.cbBuffer); + response = auth_method; + response.append(" "); + response.append(Base64::Encode(decoded)); + return HAR_RESPONSE; + } +#endif +#endif // WEBRTC_WIN + + return HAR_IGNORE; +} + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/httpcommon.h b/webrtc/base/httpcommon.h new file mode 100644 index 000000000..c43a9e276 --- /dev/null +++ b/webrtc/base/httpcommon.h @@ -0,0 +1,446 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_HTTPCOMMON_H__ +#define WEBRTC_BASE_HTTPCOMMON_H__ + +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +class CryptString; +class SocketAddress; + +////////////////////////////////////////////////////////////////////// +// Constants +////////////////////////////////////////////////////////////////////// + +enum HttpCode { + HC_OK = 200, + HC_NON_AUTHORITATIVE = 203, + HC_NO_CONTENT = 204, + HC_PARTIAL_CONTENT = 206, + + HC_MULTIPLE_CHOICES = 300, + HC_MOVED_PERMANENTLY = 301, + HC_FOUND = 302, + HC_SEE_OTHER = 303, + HC_NOT_MODIFIED = 304, + HC_MOVED_TEMPORARILY = 307, + + HC_BAD_REQUEST = 400, + HC_UNAUTHORIZED = 401, + HC_FORBIDDEN = 403, + HC_NOT_FOUND = 404, + HC_PROXY_AUTHENTICATION_REQUIRED = 407, + HC_GONE = 410, + + HC_INTERNAL_SERVER_ERROR = 500, + HC_NOT_IMPLEMENTED = 501, + HC_SERVICE_UNAVAILABLE = 503, +}; + +enum HttpVersion { + HVER_1_0, HVER_1_1, HVER_UNKNOWN, + HVER_LAST = HVER_UNKNOWN +}; + +enum HttpVerb { + HV_GET, HV_POST, HV_PUT, HV_DELETE, HV_CONNECT, HV_HEAD, + HV_LAST = HV_HEAD +}; + +enum HttpError { + HE_NONE, + HE_PROTOCOL, // Received non-valid HTTP data + HE_DISCONNECTED, // Connection closed unexpectedly + HE_OVERFLOW, // Received too much data for internal buffers + HE_CONNECT_FAILED, // The socket failed to connect. + HE_SOCKET_ERROR, // An error occurred on a connected socket + HE_SHUTDOWN, // Http object is being destroyed + HE_OPERATION_CANCELLED, // Connection aborted locally + HE_AUTH, // Proxy Authentication Required + HE_CERTIFICATE_EXPIRED, // During SSL negotiation + HE_STREAM, // Problem reading or writing to the document + HE_CACHE, // Problem reading from cache + HE_DEFAULT +}; + +enum HttpHeader { + HH_AGE, + HH_CACHE_CONTROL, + HH_CONNECTION, + HH_CONTENT_DISPOSITION, + HH_CONTENT_LENGTH, + HH_CONTENT_RANGE, + HH_CONTENT_TYPE, + HH_COOKIE, + HH_DATE, + HH_ETAG, + HH_EXPIRES, + HH_HOST, + HH_IF_MODIFIED_SINCE, + HH_IF_NONE_MATCH, + HH_KEEP_ALIVE, + HH_LAST_MODIFIED, + HH_LOCATION, + HH_PROXY_AUTHENTICATE, + HH_PROXY_AUTHORIZATION, + HH_PROXY_CONNECTION, + HH_RANGE, + HH_SET_COOKIE, + HH_TE, + HH_TRAILERS, + HH_TRANSFER_ENCODING, + HH_UPGRADE, + HH_USER_AGENT, + HH_WWW_AUTHENTICATE, + HH_LAST = HH_WWW_AUTHENTICATE +}; + +const uint16 HTTP_DEFAULT_PORT = 80; +const uint16 HTTP_SECURE_PORT = 443; + +////////////////////////////////////////////////////////////////////// +// Utility Functions +////////////////////////////////////////////////////////////////////// + +inline HttpError mkerr(HttpError err, HttpError def_err = HE_DEFAULT) { + return (err != HE_NONE) ? err : def_err; +} + +const char* ToString(HttpVersion version); +bool FromString(HttpVersion& version, const std::string& str); + +const char* ToString(HttpVerb verb); +bool FromString(HttpVerb& verb, const std::string& str); + +const char* ToString(HttpHeader header); +bool FromString(HttpHeader& header, const std::string& str); + +inline bool HttpCodeIsInformational(uint32 code) { return ((code / 100) == 1); } +inline bool HttpCodeIsSuccessful(uint32 code) { return ((code / 100) == 2); } +inline bool HttpCodeIsRedirection(uint32 code) { return ((code / 100) == 3); } +inline bool HttpCodeIsClientError(uint32 code) { return ((code / 100) == 4); } +inline bool HttpCodeIsServerError(uint32 code) { return ((code / 100) == 5); } + +bool HttpCodeHasBody(uint32 code); +bool HttpCodeIsCacheable(uint32 code); +bool HttpHeaderIsEndToEnd(HttpHeader header); +bool HttpHeaderIsCollapsible(HttpHeader header); + +struct HttpData; +bool HttpShouldKeepAlive(const HttpData& data); + +typedef std::pair HttpAttribute; +typedef std::vector HttpAttributeList; +void HttpComposeAttributes(const HttpAttributeList& attributes, char separator, + std::string* composed); +void HttpParseAttributes(const char * data, size_t len, + HttpAttributeList& attributes); +bool HttpHasAttribute(const HttpAttributeList& attributes, + const std::string& name, + std::string* value); +bool HttpHasNthAttribute(HttpAttributeList& attributes, + size_t index, + std::string* name, + std::string* value); + +// Convert RFC1123 date (DoW, DD Mon YYYY HH:MM:SS TZ) to unix timestamp +bool HttpDateToSeconds(const std::string& date, time_t* seconds); + +inline uint16 HttpDefaultPort(bool secure) { + return secure ? HTTP_SECURE_PORT : HTTP_DEFAULT_PORT; +} + +// Returns the http server notation for a given address +std::string HttpAddress(const SocketAddress& address, bool secure); + +// functional for insensitive std::string compare +struct iless { + bool operator()(const std::string& lhs, const std::string& rhs) const { + return (::_stricmp(lhs.c_str(), rhs.c_str()) < 0); + } +}; + +// put quotes around a string and escape any quotes inside it +std::string quote(const std::string& str); + +////////////////////////////////////////////////////////////////////// +// Url +////////////////////////////////////////////////////////////////////// + +template +class Url { +public: + typedef typename Traits::string string; + + // TODO: Implement Encode/Decode + static int Encode(const CTYPE* source, CTYPE* destination, size_t len); + static int Encode(const string& source, string& destination); + static int Decode(const CTYPE* source, CTYPE* destination, size_t len); + static int Decode(const string& source, string& destination); + + Url(const string& url) { do_set_url(url.c_str(), url.size()); } + Url(const string& path, const string& host, uint16 port = HTTP_DEFAULT_PORT) + : host_(host), port_(port), secure_(HTTP_SECURE_PORT == port) + { set_full_path(path); } + + bool valid() const { return !host_.empty(); } + void clear() { + host_.clear(); + port_ = HTTP_DEFAULT_PORT; + secure_ = false; + path_.assign(1, static_cast('/')); + query_.clear(); + } + + void set_url(const string& val) { + do_set_url(val.c_str(), val.size()); + } + string url() const { + string val; do_get_url(&val); return val; + } + + void set_address(const string& val) { + do_set_address(val.c_str(), val.size()); + } + string address() const { + string val; do_get_address(&val); return val; + } + + void set_full_path(const string& val) { + do_set_full_path(val.c_str(), val.size()); + } + string full_path() const { + string val; do_get_full_path(&val); return val; + } + + void set_host(const string& val) { host_ = val; } + const string& host() const { return host_; } + + void set_port(uint16 val) { port_ = val; } + uint16 port() const { return port_; } + + void set_secure(bool val) { secure_ = val; } + bool secure() const { return secure_; } + + void set_path(const string& val) { + if (val.empty()) { + path_.assign(1, static_cast('/')); + } else { + ASSERT(val[0] == static_cast('/')); + path_ = val; + } + } + const string& path() const { return path_; } + + void set_query(const string& val) { + ASSERT(val.empty() || (val[0] == static_cast('?'))); + query_ = val; + } + const string& query() const { return query_; } + + bool get_attribute(const string& name, string* value) const; + +private: + void do_set_url(const CTYPE* val, size_t len); + void do_set_address(const CTYPE* val, size_t len); + void do_set_full_path(const CTYPE* val, size_t len); + + void do_get_url(string* val) const; + void do_get_address(string* val) const; + void do_get_full_path(string* val) const; + + string host_, path_, query_; + uint16 port_; + bool secure_; +}; + +////////////////////////////////////////////////////////////////////// +// HttpData +////////////////////////////////////////////////////////////////////// + +struct HttpData { + typedef std::multimap HeaderMap; + typedef HeaderMap::const_iterator const_iterator; + typedef HeaderMap::iterator iterator; + + HttpVersion version; + scoped_ptr document; + + HttpData() : version(HVER_1_1) { } + + enum HeaderCombine { HC_YES, HC_NO, HC_AUTO, HC_REPLACE, HC_NEW }; + void changeHeader(const std::string& name, const std::string& value, + HeaderCombine combine); + inline void addHeader(const std::string& name, const std::string& value, + bool append = true) { + changeHeader(name, value, append ? HC_AUTO : HC_NO); + } + inline void setHeader(const std::string& name, const std::string& value, + bool overwrite = true) { + changeHeader(name, value, overwrite ? HC_REPLACE : HC_NEW); + } + // Returns count of erased headers + size_t clearHeader(const std::string& name); + // Returns iterator to next header + iterator clearHeader(iterator header); + + // keep in mind, this may not do what you want in the face of multiple headers + bool hasHeader(const std::string& name, std::string* value) const; + + inline const_iterator begin() const { + return headers_.begin(); + } + inline const_iterator end() const { + return headers_.end(); + } + inline iterator begin() { + return headers_.begin(); + } + inline iterator end() { + return headers_.end(); + } + inline const_iterator begin(const std::string& name) const { + return headers_.lower_bound(name); + } + inline const_iterator end(const std::string& name) const { + return headers_.upper_bound(name); + } + inline iterator begin(const std::string& name) { + return headers_.lower_bound(name); + } + inline iterator end(const std::string& name) { + return headers_.upper_bound(name); + } + + // Convenience methods using HttpHeader + inline void changeHeader(HttpHeader header, const std::string& value, + HeaderCombine combine) { + changeHeader(ToString(header), value, combine); + } + inline void addHeader(HttpHeader header, const std::string& value, + bool append = true) { + addHeader(ToString(header), value, append); + } + inline void setHeader(HttpHeader header, const std::string& value, + bool overwrite = true) { + setHeader(ToString(header), value, overwrite); + } + inline void clearHeader(HttpHeader header) { + clearHeader(ToString(header)); + } + inline bool hasHeader(HttpHeader header, std::string* value) const { + return hasHeader(ToString(header), value); + } + inline const_iterator begin(HttpHeader header) const { + return headers_.lower_bound(ToString(header)); + } + inline const_iterator end(HttpHeader header) const { + return headers_.upper_bound(ToString(header)); + } + inline iterator begin(HttpHeader header) { + return headers_.lower_bound(ToString(header)); + } + inline iterator end(HttpHeader header) { + return headers_.upper_bound(ToString(header)); + } + + void setContent(const std::string& content_type, StreamInterface* document); + void setDocumentAndLength(StreamInterface* document); + + virtual size_t formatLeader(char* buffer, size_t size) const = 0; + virtual HttpError parseLeader(const char* line, size_t len) = 0; + +protected: + virtual ~HttpData() { } + void clear(bool release_document); + void copy(const HttpData& src); + +private: + HeaderMap headers_; +}; + +struct HttpRequestData : public HttpData { + HttpVerb verb; + std::string path; + + HttpRequestData() : verb(HV_GET) { } + + void clear(bool release_document); + void copy(const HttpRequestData& src); + + virtual size_t formatLeader(char* buffer, size_t size) const; + virtual HttpError parseLeader(const char* line, size_t len); + + bool getAbsoluteUri(std::string* uri) const; + bool getRelativeUri(std::string* host, std::string* path) const; +}; + +struct HttpResponseData : public HttpData { + uint32 scode; + std::string message; + + HttpResponseData() : scode(HC_INTERNAL_SERVER_ERROR) { } + void clear(bool release_document); + void copy(const HttpResponseData& src); + + // Convenience methods + void set_success(uint32 scode = HC_OK); + void set_success(const std::string& content_type, StreamInterface* document, + uint32 scode = HC_OK); + void set_redirect(const std::string& location, + uint32 scode = HC_MOVED_TEMPORARILY); + void set_error(uint32 scode); + + virtual size_t formatLeader(char* buffer, size_t size) const; + virtual HttpError parseLeader(const char* line, size_t len); +}; + +struct HttpTransaction { + HttpRequestData request; + HttpResponseData response; +}; + +////////////////////////////////////////////////////////////////////// +// Http Authentication +////////////////////////////////////////////////////////////////////// + +struct HttpAuthContext { + std::string auth_method; + HttpAuthContext(const std::string& auth) : auth_method(auth) { } + virtual ~HttpAuthContext() { } +}; + +enum HttpAuthResult { HAR_RESPONSE, HAR_IGNORE, HAR_CREDENTIALS, HAR_ERROR }; + +// 'context' is used by this function to record information between calls. +// Start by passing a null pointer, then pass the same pointer each additional +// call. When the authentication attempt is finished, delete the context. +HttpAuthResult HttpAuthenticate( + const char * challenge, size_t len, + const SocketAddress& server, + const std::string& method, const std::string& uri, + const std::string& username, const CryptString& password, + HttpAuthContext *& context, std::string& response, std::string& auth_method); + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPCOMMON_H__ diff --git a/webrtc/base/httpcommon_unittest.cc b/webrtc/base/httpcommon_unittest.cc new file mode 100644 index 000000000..10e378987 --- /dev/null +++ b/webrtc/base/httpcommon_unittest.cc @@ -0,0 +1,165 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/httpcommon.h" + +namespace rtc { + +#define TEST_PROTOCOL "http://" +#define TEST_HOST "www.google.com" +#define TEST_PATH "/folder/file.html" +#define TEST_QUERY "?query=x&attr=y" +#define TEST_URL TEST_PROTOCOL TEST_HOST TEST_PATH TEST_QUERY + +TEST(Url, DecomposesUrls) { + Url url(TEST_URL); + EXPECT_TRUE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ(TEST_HOST, url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ(TEST_PATH, url.path().c_str()); + EXPECT_STREQ(TEST_QUERY, url.query().c_str()); + EXPECT_STREQ(TEST_HOST, url.address().c_str()); + EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str()); + EXPECT_STREQ(TEST_URL, url.url().c_str()); +} + +TEST(Url, ComposesUrls) { + // Set in constructor + Url url(TEST_PATH TEST_QUERY, TEST_HOST, 80); + EXPECT_TRUE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ(TEST_HOST, url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ(TEST_PATH, url.path().c_str()); + EXPECT_STREQ(TEST_QUERY, url.query().c_str()); + EXPECT_STREQ(TEST_HOST, url.address().c_str()); + EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str()); + EXPECT_STREQ(TEST_URL, url.url().c_str()); + + url.clear(); + EXPECT_FALSE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ("", url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ("/", url.path().c_str()); + EXPECT_STREQ("", url.query().c_str()); + + // Set component-wise + url.set_host(TEST_HOST); + url.set_port(80); + url.set_path(TEST_PATH); + url.set_query(TEST_QUERY); + EXPECT_TRUE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ(TEST_HOST, url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ(TEST_PATH, url.path().c_str()); + EXPECT_STREQ(TEST_QUERY, url.query().c_str()); + EXPECT_STREQ(TEST_HOST, url.address().c_str()); + EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str()); + EXPECT_STREQ(TEST_URL, url.url().c_str()); +} + +TEST(Url, EnsuresNonEmptyPath) { + Url url(TEST_PROTOCOL TEST_HOST); + EXPECT_TRUE(url.valid()); + EXPECT_STREQ("/", url.path().c_str()); + + url.clear(); + EXPECT_STREQ("/", url.path().c_str()); + url.set_path(""); + EXPECT_STREQ("/", url.path().c_str()); + + url.clear(); + EXPECT_STREQ("/", url.path().c_str()); + url.set_full_path(""); + EXPECT_STREQ("/", url.path().c_str()); +} + +TEST(Url, GetQueryAttributes) { + Url url(TEST_URL); + std::string value; + EXPECT_TRUE(url.get_attribute("query", &value)); + EXPECT_STREQ("x", value.c_str()); + value.clear(); + EXPECT_TRUE(url.get_attribute("attr", &value)); + EXPECT_STREQ("y", value.c_str()); + value.clear(); + EXPECT_FALSE(url.get_attribute("Query", &value)); + EXPECT_TRUE(value.empty()); +} + +TEST(Url, SkipsUserAndPassword) { + Url url("https://mail.google.com:pwd@badsite.com:12345/asdf"); + EXPECT_TRUE(url.valid()); + EXPECT_TRUE(url.secure()); + EXPECT_STREQ("badsite.com", url.host().c_str()); + EXPECT_EQ(12345, url.port()); + EXPECT_STREQ("/asdf", url.path().c_str()); + EXPECT_STREQ("badsite.com:12345", url.address().c_str()); +} + +TEST(Url, SkipsUser) { + Url url("https://mail.google.com@badsite.com:12345/asdf"); + EXPECT_TRUE(url.valid()); + EXPECT_TRUE(url.secure()); + EXPECT_STREQ("badsite.com", url.host().c_str()); + EXPECT_EQ(12345, url.port()); + EXPECT_STREQ("/asdf", url.path().c_str()); + EXPECT_STREQ("badsite.com:12345", url.address().c_str()); +} + +TEST(HttpResponseData, parseLeaderHttp1_0) { + static const char kResponseString[] = "HTTP/1.0 200 OK"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_1_0, response.version); + EXPECT_EQ(200U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttp1_1) { + static const char kResponseString[] = "HTTP/1.1 200 OK"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_1_1, response.version); + EXPECT_EQ(200U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttpUnknown) { + static const char kResponseString[] = "HTTP 200 OK"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_UNKNOWN, response.version); + EXPECT_EQ(200U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttpFailure) { + static const char kResponseString[] = "HTTP/1.1 503 Service Unavailable"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_1_1, response.version); + EXPECT_EQ(503U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttpInvalid) { + static const char kResponseString[] = "Durrrrr, what's HTTP?"; + HttpResponseData response; + EXPECT_EQ(HE_PROTOCOL, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); +} + +} // namespace rtc diff --git a/webrtc/base/httprequest.cc b/webrtc/base/httprequest.cc new file mode 100644 index 000000000..9ce2377e8 --- /dev/null +++ b/webrtc/base/httprequest.cc @@ -0,0 +1,110 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/httprequest.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/firewallsocketserver.h" +#include "webrtc/base/httpclient.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/socketpool.h" +#include "webrtc/base/ssladapter.h" + +using namespace rtc; + +/////////////////////////////////////////////////////////////////////////////// +// HttpMonitor +/////////////////////////////////////////////////////////////////////////////// + +HttpMonitor::HttpMonitor(SocketServer *ss) { + ASSERT(Thread::Current() != NULL); + ss_ = ss; + reset(); +} + +void HttpMonitor::Connect(HttpClient *http) { + http->SignalHttpClientComplete.connect(this, + &HttpMonitor::OnHttpClientComplete); +} + +void HttpMonitor::OnHttpClientComplete(HttpClient * http, HttpErrorType error) { + complete_ = true; + error_ = error; + ss_->WakeUp(); +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpRequest +/////////////////////////////////////////////////////////////////////////////// + +const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec + +HttpRequest::HttpRequest(const std::string &user_agent) + : firewall_(0), port_(80), secure_(false), + timeout_(kDefaultHTTPTimeout), fail_redirect_(false), + client_(user_agent.c_str(), NULL), error_(HE_NONE) { +} + +void HttpRequest::Send() { + // TODO: Rewrite this to use the thread's native socket server, and a more + // natural flow? + + PhysicalSocketServer physical; + SocketServer * ss = &physical; + if (firewall_) { + ss = new FirewallSocketServer(ss, firewall_); + } + + SslSocketFactory factory(ss, client_.agent()); + factory.SetProxy(proxy_); + if (secure_) + factory.UseSSL(host_.c_str()); + + //factory.SetLogging("HttpRequest"); + + ReuseSocketPool pool(&factory); + client_.set_pool(&pool); + + bool transparent_proxy = (port_ == 80) && ((proxy_.type == PROXY_HTTPS) || + (proxy_.type == PROXY_UNKNOWN)); + + if (transparent_proxy) { + client_.set_proxy(proxy_); + } + client_.set_fail_redirect(fail_redirect_); + + SocketAddress server(host_, port_); + client_.set_server(server); + + LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path; + + HttpMonitor monitor(ss); + monitor.Connect(&client_); + client_.start(); + ss->Wait(timeout_, true); + if (!monitor.done()) { + LOG(LS_INFO) << "HttpRequest request timed out"; + client_.reset(); + return; + } + + set_error(monitor.error()); + if (error_) { + LOG(LS_INFO) << "HttpRequest request error: " << error_; + return; + } + + std::string value; + if (client_.response().hasHeader(HH_LOCATION, &value)) { + response_redirect_ = value.c_str(); + } +} diff --git a/webrtc/base/httprequest.h b/webrtc/base/httprequest.h new file mode 100644 index 000000000..37983324c --- /dev/null +++ b/webrtc/base/httprequest.h @@ -0,0 +1,115 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef _HTTPREQUEST_H_ +#define _HTTPREQUEST_H_ + +#include "webrtc/base/httpclient.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/sslsocketfactory.h" // Deprecated include + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// HttpRequest +/////////////////////////////////////////////////////////////////////////////// + +class FirewallManager; +class MemoryStream; + +class HttpRequest { +public: + HttpRequest(const std::string &user_agent); + + void Send(); + + void set_proxy(const ProxyInfo& proxy) { + proxy_ = proxy; + } + void set_firewall(FirewallManager * firewall) { + firewall_ = firewall; + } + + // The DNS name of the host to connect to. + const std::string& host() { return host_; } + void set_host(const std::string& host) { host_ = host; } + + // The port to connect to on the target host. + int port() { return port_; } + void set_port(int port) { port_ = port; } + + // Whether the request should use SSL. + bool secure() { return secure_; } + void set_secure(bool secure) { secure_ = secure; } + + // Returns the redirect when redirection occurs + const std::string& response_redirect() { return response_redirect_; } + + // Time to wait on the download, in ms. Default is 5000 (5s) + int timeout() { return timeout_; } + void set_timeout(int timeout) { timeout_ = timeout; } + + // Fail redirects to allow analysis of redirect urls, etc. + bool fail_redirect() const { return fail_redirect_; } + void set_fail_redirect(bool fail_redirect) { fail_redirect_ = fail_redirect; } + + HttpRequestData& request() { return client_.request(); } + HttpResponseData& response() { return client_.response(); } + HttpErrorType error() { return error_; } + +protected: + void set_error(HttpErrorType error) { error_ = error; } + +private: + ProxyInfo proxy_; + FirewallManager * firewall_; + std::string host_; + int port_; + bool secure_; + int timeout_; + bool fail_redirect_; + HttpClient client_; + HttpErrorType error_; + std::string response_redirect_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// HttpMonitor +/////////////////////////////////////////////////////////////////////////////// + +class HttpMonitor : public sigslot::has_slots<> { +public: + HttpMonitor(SocketServer *ss); + + void reset() { + complete_ = false; + error_ = HE_DEFAULT; + } + + bool done() const { return complete_; } + HttpErrorType error() const { return error_; } + + void Connect(HttpClient* http); + void OnHttpClientComplete(HttpClient * http, HttpErrorType error); + +private: + bool complete_; + HttpErrorType error_; + SocketServer *ss_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc_ + +#endif // _HTTPREQUEST_H_ diff --git a/webrtc/base/httpserver.cc b/webrtc/base/httpserver.cc new file mode 100644 index 000000000..caa58efc2 --- /dev/null +++ b/webrtc/base/httpserver.cc @@ -0,0 +1,288 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/httpcommon-inl.h" + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// HttpServer +/////////////////////////////////////////////////////////////////////////////// + +HttpServer::HttpServer() : next_connection_id_(1), closing_(false) { +} + +HttpServer::~HttpServer() { + if (closing_) { + LOG(LS_WARNING) << "HttpServer::CloseAll has not completed"; + } + for (ConnectionMap::iterator it = connections_.begin(); + it != connections_.end(); + ++it) { + StreamInterface* stream = it->second->EndProcess(); + delete stream; + delete it->second; + } +} + +int +HttpServer::HandleConnection(StreamInterface* stream) { + int connection_id = next_connection_id_++; + ASSERT(connection_id != HTTP_INVALID_CONNECTION_ID); + Connection* connection = new Connection(connection_id, this); + connections_.insert(ConnectionMap::value_type(connection_id, connection)); + connection->BeginProcess(stream); + return connection_id; +} + +void +HttpServer::Respond(HttpServerTransaction* transaction) { + int connection_id = transaction->connection_id(); + if (Connection* connection = Find(connection_id)) { + connection->Respond(transaction); + } else { + delete transaction; + // We may be tempted to SignalHttpComplete, but that implies that a + // connection still exists. + } +} + +void +HttpServer::Close(int connection_id, bool force) { + if (Connection* connection = Find(connection_id)) { + connection->InitiateClose(force); + } +} + +void +HttpServer::CloseAll(bool force) { + if (connections_.empty()) { + SignalCloseAllComplete(this); + return; + } + closing_ = true; + std::list connections; + for (ConnectionMap::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + connections.push_back(it->second); + } + for (std::list::const_iterator it = connections.begin(); + it != connections.end(); ++it) { + (*it)->InitiateClose(force); + } +} + +HttpServer::Connection* +HttpServer::Find(int connection_id) { + ConnectionMap::iterator it = connections_.find(connection_id); + if (it == connections_.end()) + return NULL; + return it->second; +} + +void +HttpServer::Remove(int connection_id) { + ConnectionMap::iterator it = connections_.find(connection_id); + if (it == connections_.end()) { + ASSERT(false); + return; + } + Connection* connection = it->second; + connections_.erase(it); + SignalConnectionClosed(this, connection_id, connection->EndProcess()); + delete connection; + if (closing_ && connections_.empty()) { + closing_ = false; + SignalCloseAllComplete(this); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpServer::Connection +/////////////////////////////////////////////////////////////////////////////// + +HttpServer::Connection::Connection(int connection_id, HttpServer* server) + : connection_id_(connection_id), server_(server), + current_(NULL), signalling_(false), close_(false) { +} + +HttpServer::Connection::~Connection() { + // It's possible that an object hosted inside this transaction signalled + // an event which caused the connection to close. + Thread::Current()->Dispose(current_); +} + +void +HttpServer::Connection::BeginProcess(StreamInterface* stream) { + base_.notify(this); + base_.attach(stream); + current_ = new HttpServerTransaction(connection_id_); + if (base_.mode() != HM_CONNECT) + base_.recv(¤t_->request); +} + +StreamInterface* +HttpServer::Connection::EndProcess() { + base_.notify(NULL); + base_.abort(HE_DISCONNECTED); + return base_.detach(); +} + +void +HttpServer::Connection::Respond(HttpServerTransaction* transaction) { + ASSERT(current_ == NULL); + current_ = transaction; + if (current_->response.begin() == current_->response.end()) { + current_->response.set_error(HC_INTERNAL_SERVER_ERROR); + } + bool keep_alive = HttpShouldKeepAlive(current_->request); + current_->response.setHeader(HH_CONNECTION, + keep_alive ? "Keep-Alive" : "Close", + false); + close_ = !HttpShouldKeepAlive(current_->response); + base_.send(¤t_->response); +} + +void +HttpServer::Connection::InitiateClose(bool force) { + bool request_in_progress = (HM_SEND == base_.mode()) || (NULL == current_); + if (!signalling_ && (force || !request_in_progress)) { + server_->Remove(connection_id_); + } else { + close_ = true; + } +} + +// +// IHttpNotify Implementation +// + +HttpError +HttpServer::Connection::onHttpHeaderComplete(bool chunked, size_t& data_size) { + if (data_size == SIZE_UNKNOWN) { + data_size = 0; + } + ASSERT(current_ != NULL); + bool custom_document = false; + server_->SignalHttpRequestHeader(server_, current_, &custom_document); + if (!custom_document) { + current_->request.document.reset(new MemoryStream); + } + return HE_NONE; +} + +void +HttpServer::Connection::onHttpComplete(HttpMode mode, HttpError err) { + if (mode == HM_SEND) { + ASSERT(current_ != NULL); + signalling_ = true; + server_->SignalHttpRequestComplete(server_, current_, err); + signalling_ = false; + if (close_) { + // Force a close + err = HE_DISCONNECTED; + } + } + if (err != HE_NONE) { + server_->Remove(connection_id_); + } else if (mode == HM_CONNECT) { + base_.recv(¤t_->request); + } else if (mode == HM_RECV) { + ASSERT(current_ != NULL); + // TODO: do we need this? + //request_.document_->rewind(); + HttpServerTransaction* transaction = current_; + current_ = NULL; + server_->SignalHttpRequest(server_, transaction); + } else if (mode == HM_SEND) { + Thread::Current()->Dispose(current_->response.document.release()); + current_->request.clear(true); + current_->response.clear(true); + base_.recv(¤t_->request); + } else { + ASSERT(false); + } +} + +void +HttpServer::Connection::onHttpClosed(HttpError err) { + UNUSED(err); + server_->Remove(connection_id_); +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpListenServer +/////////////////////////////////////////////////////////////////////////////// + +HttpListenServer::HttpListenServer() { + SignalConnectionClosed.connect(this, &HttpListenServer::OnConnectionClosed); +} + +HttpListenServer::~HttpListenServer() { +} + +int HttpListenServer::Listen(const SocketAddress& address) { + AsyncSocket* sock = + Thread::Current()->socketserver()->CreateAsyncSocket(address.family(), + SOCK_STREAM); + if (!sock) { + return SOCKET_ERROR; + } + listener_.reset(sock); + listener_->SignalReadEvent.connect(this, &HttpListenServer::OnReadEvent); + if ((listener_->Bind(address) != SOCKET_ERROR) && + (listener_->Listen(5) != SOCKET_ERROR)) + return 0; + return listener_->GetError(); +} + +bool HttpListenServer::GetAddress(SocketAddress* address) const { + if (!listener_) { + return false; + } + *address = listener_->GetLocalAddress(); + return !address->IsNil(); +} + +void HttpListenServer::StopListening() { + if (listener_) { + listener_->Close(); + } +} + +void HttpListenServer::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == listener_.get()); + ASSERT(listener_); + AsyncSocket* incoming = listener_->Accept(NULL); + if (incoming) { + StreamInterface* stream = new SocketStream(incoming); + //stream = new LoggingAdapter(stream, LS_VERBOSE, "HttpServer", false); + HandleConnection(stream); + } +} + +void HttpListenServer::OnConnectionClosed(HttpServer* server, + int connection_id, + StreamInterface* stream) { + Thread::Current()->Dispose(stream); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/httpserver.h b/webrtc/base/httpserver.h new file mode 100644 index 000000000..77de615fe --- /dev/null +++ b/webrtc/base/httpserver.h @@ -0,0 +1,137 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_HTTPSERVER_H__ +#define WEBRTC_BASE_HTTPSERVER_H__ + +#include +#include "webrtc/base/httpbase.h" + +namespace rtc { + +class AsyncSocket; +class HttpServer; +class SocketAddress; + +////////////////////////////////////////////////////////////////////// +// HttpServer +////////////////////////////////////////////////////////////////////// + +const int HTTP_INVALID_CONNECTION_ID = 0; + +struct HttpServerTransaction : public HttpTransaction { +public: + HttpServerTransaction(int id) : connection_id_(id) { } + int connection_id() const { return connection_id_; } + +private: + int connection_id_; +}; + +class HttpServer { +public: + HttpServer(); + virtual ~HttpServer(); + + int HandleConnection(StreamInterface* stream); + // Due to sigslot issues, we can't destroy some streams at an arbitrary time. + sigslot::signal3 SignalConnectionClosed; + + // This signal occurs when the HTTP request headers have been received, but + // before the request body is written to the request document. By default, + // the request document is a MemoryStream. By handling this signal, the + // document can be overridden, in which case the third signal argument should + // be set to true. In the case where the request body should be ignored, + // the document can be set to NULL. Note that the transaction object is still + // owened by the HttpServer at this point. + sigslot::signal3 + SignalHttpRequestHeader; + + // An HTTP request has been made, and is available in the transaction object. + // Populate the transaction's response, and then return the object via the + // Respond method. Note that during this time, ownership of the transaction + // object is transferred, so it may be passed between threads, although + // respond must be called on the server's active thread. + sigslot::signal2 SignalHttpRequest; + void Respond(HttpServerTransaction* transaction); + + // If you want to know when a request completes, listen to this event. + sigslot::signal3 + SignalHttpRequestComplete; + + // Stop processing the connection indicated by connection_id. + // Unless force is true, the server will complete sending a response that is + // in progress. + void Close(int connection_id, bool force); + void CloseAll(bool force); + + // After calling CloseAll, this event is signalled to indicate that all + // outstanding connections have closed. + sigslot::signal1 SignalCloseAllComplete; + +private: + class Connection : private IHttpNotify { + public: + Connection(int connection_id, HttpServer* server); + virtual ~Connection(); + + void BeginProcess(StreamInterface* stream); + StreamInterface* EndProcess(); + + void Respond(HttpServerTransaction* transaction); + void InitiateClose(bool force); + + // IHttpNotify Interface + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size); + virtual void onHttpComplete(HttpMode mode, HttpError err); + virtual void onHttpClosed(HttpError err); + + int connection_id_; + HttpServer* server_; + HttpBase base_; + HttpServerTransaction* current_; + bool signalling_, close_; + }; + + Connection* Find(int connection_id); + void Remove(int connection_id); + + friend class Connection; + typedef std::map ConnectionMap; + + ConnectionMap connections_; + int next_connection_id_; + bool closing_; +}; + +////////////////////////////////////////////////////////////////////// + +class HttpListenServer : public HttpServer, public sigslot::has_slots<> { +public: + HttpListenServer(); + virtual ~HttpListenServer(); + + int Listen(const SocketAddress& address); + bool GetAddress(SocketAddress* address) const; + void StopListening(); + +private: + void OnReadEvent(AsyncSocket* socket); + void OnConnectionClosed(HttpServer* server, int connection_id, + StreamInterface* stream); + + scoped_ptr listener_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPSERVER_H__ diff --git a/webrtc/base/httpserver_unittest.cc b/webrtc/base/httpserver_unittest.cc new file mode 100644 index 000000000..0c653cbb9 --- /dev/null +++ b/webrtc/base/httpserver_unittest.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/testutils.h" + +using namespace testing; + +namespace rtc { + +namespace { + const char* const kRequest = + "GET /index.html HTTP/1.1\r\n" + "Host: localhost\r\n" + "\r\n"; + + struct HttpServerMonitor : public sigslot::has_slots<> { + HttpServerTransaction* transaction; + bool server_closed, connection_closed; + + HttpServerMonitor(HttpServer* server) + : transaction(NULL), server_closed(false), connection_closed(false) { + server->SignalCloseAllComplete.connect(this, + &HttpServerMonitor::OnClosed); + server->SignalHttpRequest.connect(this, &HttpServerMonitor::OnRequest); + server->SignalHttpRequestComplete.connect(this, + &HttpServerMonitor::OnRequestComplete); + server->SignalConnectionClosed.connect(this, + &HttpServerMonitor::OnConnectionClosed); + } + void OnRequest(HttpServer*, HttpServerTransaction* t) { + ASSERT_FALSE(transaction); + transaction = t; + transaction->response.set_success(); + transaction->response.setHeader(HH_CONNECTION, "Close"); + } + void OnRequestComplete(HttpServer*, HttpServerTransaction* t, int) { + ASSERT_EQ(transaction, t); + transaction = NULL; + } + void OnClosed(HttpServer*) { + server_closed = true; + } + void OnConnectionClosed(HttpServer*, int, StreamInterface* stream) { + connection_closed = true; + delete stream; + } + }; + + void CreateClientConnection(HttpServer& server, + HttpServerMonitor& monitor, + bool send_request) { + StreamSource* client = new StreamSource; + client->SetState(SS_OPEN); + server.HandleConnection(client); + EXPECT_FALSE(monitor.server_closed); + EXPECT_FALSE(monitor.transaction); + + if (send_request) { + // Simulate a request + client->QueueString(kRequest); + EXPECT_FALSE(monitor.server_closed); + } + } +} // anonymous namespace + +TEST(HttpServer, DoesNotSignalCloseUnlessCloseAllIsCalled) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an active client connection + CreateClientConnection(server, monitor, true); + // Simulate a response + ASSERT_TRUE(NULL != monitor.transaction); + server.Respond(monitor.transaction); + EXPECT_FALSE(monitor.transaction); + // Connection has closed, but no server close signal + EXPECT_FALSE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +TEST(HttpServer, SignalsCloseWhenNoConnectionsAreActive) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an idle client connection + CreateClientConnection(server, monitor, false); + // Perform graceful close + server.CloseAll(false); + // Connections have all closed + EXPECT_TRUE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +TEST(HttpServer, SignalsCloseAfterGracefulCloseAll) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an active client connection + CreateClientConnection(server, monitor, true); + // Initiate a graceful close + server.CloseAll(false); + EXPECT_FALSE(monitor.server_closed); + // Simulate a response + ASSERT_TRUE(NULL != monitor.transaction); + server.Respond(monitor.transaction); + EXPECT_FALSE(monitor.transaction); + // Connections have all closed + EXPECT_TRUE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +TEST(HttpServer, SignalsCloseAfterForcedCloseAll) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an active client connection + CreateClientConnection(server, monitor, true); + // Initiate a forceful close + server.CloseAll(true); + // Connections have all closed + EXPECT_TRUE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +} // namespace rtc diff --git a/webrtc/base/ifaddrs-android.cc b/webrtc/base/ifaddrs-android.cc new file mode 100644 index 000000000..5d022a79a --- /dev/null +++ b/webrtc/base/ifaddrs-android.cc @@ -0,0 +1,217 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_ANDROID) +#include "webrtc/base/ifaddrs-android.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct netlinkrequest { + nlmsghdr header; + ifaddrmsg msg; +}; + +namespace { +const int kMaxReadSize = 4096; +}; + +int set_ifname(struct ifaddrs* ifaddr, int interface) { + char buf[IFNAMSIZ] = {0}; + char* name = if_indextoname(interface, buf); + if (name == NULL) { + return -1; + } + ifaddr->ifa_name = new char[strlen(name) + 1]; + strncpy(ifaddr->ifa_name, name, strlen(name) + 1); + return 0; +} + +int set_flags(struct ifaddrs* ifaddr) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + return -1; + } + ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifaddr->ifa_name, IFNAMSIZ - 1); + int rc = ioctl(fd, SIOCGIFFLAGS, &ifr); + close(fd); + if (rc == -1) { + return -1; + } + ifaddr->ifa_flags = ifr.ifr_flags; + return 0; +} + +int set_addresses(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* data, + size_t len) { + if (msg->ifa_family == AF_INET) { + sockaddr_in* sa = new sockaddr_in; + sa->sin_family = AF_INET; + memcpy(&sa->sin_addr, data, len); + ifaddr->ifa_addr = reinterpret_cast(sa); + } else if (msg->ifa_family == AF_INET6) { + sockaddr_in6* sa = new sockaddr_in6; + sa->sin6_family = AF_INET6; + sa->sin6_scope_id = msg->ifa_index; + memcpy(&sa->sin6_addr, data, len); + ifaddr->ifa_addr = reinterpret_cast(sa); + } else { + return -1; + } + return 0; +} + +int make_prefixes(struct ifaddrs* ifaddr, int family, int prefixlen) { + char* prefix = NULL; + if (family == AF_INET) { + sockaddr_in* mask = new sockaddr_in; + mask->sin_family = AF_INET; + memset(&mask->sin_addr, 0, sizeof(in_addr)); + ifaddr->ifa_netmask = reinterpret_cast(mask); + if (prefixlen > 32) { + prefixlen = 32; + } + prefix = reinterpret_cast(&mask->sin_addr); + } else if (family == AF_INET6) { + sockaddr_in6* mask = new sockaddr_in6; + mask->sin6_family = AF_INET6; + memset(&mask->sin6_addr, 0, sizeof(in6_addr)); + ifaddr->ifa_netmask = reinterpret_cast(mask); + if (prefixlen > 128) { + prefixlen = 128; + } + prefix = reinterpret_cast(&mask->sin6_addr); + } else { + return -1; + } + for (int i = 0; i < (prefixlen / 8); i++) { + *prefix++ = 0xFF; + } + char remainder = 0xff; + remainder <<= (8 - prefixlen % 8); + *prefix = remainder; + return 0; +} + +int populate_ifaddrs(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* bytes, + size_t len) { + if (set_ifname(ifaddr, msg->ifa_index) != 0) { + return -1; + } + if (set_flags(ifaddr) != 0) { + return -1; + } + if (set_addresses(ifaddr, msg, bytes, len) != 0) { + return -1; + } + if (make_prefixes(ifaddr, msg->ifa_family, msg->ifa_prefixlen) != 0) { + return -1; + } + return 0; +} + +int getifaddrs(struct ifaddrs** result) { + int fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + return -1; + } + + netlinkrequest ifaddr_request; + memset(&ifaddr_request, 0, sizeof(ifaddr_request)); + ifaddr_request.header.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST; + ifaddr_request.header.nlmsg_type = RTM_GETADDR; + ifaddr_request.header.nlmsg_len = NLMSG_LENGTH(sizeof(ifaddrmsg)); + + ssize_t count = send(fd, &ifaddr_request, ifaddr_request.header.nlmsg_len, 0); + if (static_cast(count) != ifaddr_request.header.nlmsg_len) { + close(fd); + return -1; + } + struct ifaddrs* start = NULL; + struct ifaddrs* current = NULL; + char buf[kMaxReadSize]; + ssize_t amount_read = recv(fd, &buf, kMaxReadSize, 0); + while (amount_read > 0) { + nlmsghdr* header = reinterpret_cast(&buf[0]); + size_t header_size = static_cast(amount_read); + for ( ; NLMSG_OK(header, header_size); + header = NLMSG_NEXT(header, header_size)) { + switch (header->nlmsg_type) { + case NLMSG_DONE: + // Success. Return. + *result = start; + close(fd); + return 0; + case NLMSG_ERROR: + close(fd); + freeifaddrs(start); + return -1; + case RTM_NEWADDR: { + ifaddrmsg* address_msg = + reinterpret_cast(NLMSG_DATA(header)); + rtattr* rta = IFA_RTA(address_msg); + ssize_t payload_len = IFA_PAYLOAD(header); + while (RTA_OK(rta, payload_len)) { + if (rta->rta_type == IFA_ADDRESS) { + int family = address_msg->ifa_family; + if (family == AF_INET || family == AF_INET6) { + ifaddrs* newest = new ifaddrs; + memset(newest, 0, sizeof(ifaddrs)); + if (current) { + current->ifa_next = newest; + } else { + start = newest; + } + if (populate_ifaddrs(newest, address_msg, RTA_DATA(rta), + RTA_PAYLOAD(rta)) != 0) { + freeifaddrs(start); + *result = NULL; + return -1; + } + current = newest; + } + } + rta = RTA_NEXT(rta, payload_len); + } + break; + } + } + } + amount_read = recv(fd, &buf, kMaxReadSize, 0); + } + close(fd); + freeifaddrs(start); + return -1; +} + +void freeifaddrs(struct ifaddrs* addrs) { + struct ifaddrs* last = NULL; + struct ifaddrs* cursor = addrs; + while (cursor) { + delete[] cursor->ifa_name; + delete cursor->ifa_addr; + delete cursor->ifa_netmask; + last = cursor; + cursor = cursor->ifa_next; + delete last; + } +} +#endif // defined(WEBRTC_ANDROID) diff --git a/webrtc/base/ifaddrs-android.h b/webrtc/base/ifaddrs-android.h new file mode 100644 index 000000000..c8671e08f --- /dev/null +++ b/webrtc/base/ifaddrs-android.h @@ -0,0 +1,33 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_IFADDRS_ANDROID_H_ +#define WEBRTC_BASE_IFADDRS_ANDROID_H_ + +#include +#include +// Implementation of getifaddrs for Android. +// Fills out a list of ifaddr structs (see below) which contain information +// about every network interface available on the host. +// See 'man getifaddrs' on Linux or OS X (nb: it is not a POSIX function). +struct ifaddrs { + struct ifaddrs* ifa_next; + char* ifa_name; + unsigned int ifa_flags; + struct sockaddr* ifa_addr; + struct sockaddr* ifa_netmask; + // Real ifaddrs has broadcast, point to point and data members. + // We don't need them (yet?). +}; + +int getifaddrs(struct ifaddrs** result); +void freeifaddrs(struct ifaddrs* addrs); + +#endif // WEBRTC_BASE_IFADDRS_ANDROID_H_ diff --git a/webrtc/base/iosfilesystem.mm b/webrtc/base/iosfilesystem.mm new file mode 100644 index 000000000..eb4bbecd5 --- /dev/null +++ b/webrtc/base/iosfilesystem.mm @@ -0,0 +1,53 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This file only exists because various iOS system APIs are only +// available from Objective-C. See unixfilesystem.cc for the only use +// (enforced by a lack of a header file). + +#import +#import +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/pathutils.h" + +// Return a new[]'d |char*| copy of the UTF8 representation of |s|. +// Caller owns the returned memory and must use delete[] on it. +static char* copyString(NSString* s) { + const char* utf8 = [s UTF8String]; + size_t len = strlen(utf8) + 1; + char* copy = new char[len]; + // This uses a new[] + strcpy (instead of strdup) because the + // receiver expects to be able to delete[] the returned pointer + // (instead of free()ing it). + strcpy(copy, utf8); + return copy; +} + +// Return a (leaked) copy of a directory name suitable for application data. +char* IOSDataDirectory() { + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, NSUserDomainMask, YES); + ASSERT([paths count] == 1); + return copyString([paths objectAtIndex:0]); +} + +// Return a (leaked) copy of a directory name suitable for use as a $TEMP. +char* IOSTempDirectory() { + return copyString(NSTemporaryDirectory()); +} + +// Return the binary's path. +void IOSAppName(rtc::Pathname* path) { + NSProcessInfo *pInfo = [NSProcessInfo processInfo]; + NSString* argv0 = [[pInfo arguments] objectAtIndex:0]; + path->SetPathname([argv0 UTF8String]); +} diff --git a/webrtc/base/ipaddress.cc b/webrtc/base/ipaddress.cc new file mode 100644 index 000000000..4441e16f6 --- /dev/null +++ b/webrtc/base/ipaddress.cc @@ -0,0 +1,449 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#ifdef OPENBSD +#include +#endif +#ifndef __native_client__ +#include +#endif +#include +#include +#include +#endif + +#include + +#include "webrtc/base/ipaddress.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/win32.h" + +namespace rtc { + +// Prefixes used for categorizing IPv6 addresses. +static const in6_addr kV4MappedPrefix = {{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0xFF, 0xFF, 0}}}; +static const in6_addr k6To4Prefix = {{{0x20, 0x02, 0}}}; +static const in6_addr kTeredoPrefix = {{{0x20, 0x01, 0x00, 0x00}}}; +static const in6_addr kV4CompatibilityPrefix = {{{0}}}; +static const in6_addr k6BonePrefix = {{{0x3f, 0xfe, 0}}}; + +bool IPAddress::strip_sensitive_ = false; + +static bool IsPrivateV4(uint32 ip); +static in_addr ExtractMappedAddress(const in6_addr& addr); + +uint32 IPAddress::v4AddressAsHostOrderInteger() const { + if (family_ == AF_INET) { + return NetworkToHost32(u_.ip4.s_addr); + } else { + return 0; + } +} + +size_t IPAddress::Size() const { + switch (family_) { + case AF_INET: + return sizeof(in_addr); + case AF_INET6: + return sizeof(in6_addr); + } + return 0; +} + + +bool IPAddress::operator==(const IPAddress &other) const { + if (family_ != other.family_) { + return false; + } + if (family_ == AF_INET) { + return memcmp(&u_.ip4, &other.u_.ip4, sizeof(u_.ip4)) == 0; + } + if (family_ == AF_INET6) { + return memcmp(&u_.ip6, &other.u_.ip6, sizeof(u_.ip6)) == 0; + } + return family_ == AF_UNSPEC; +} + +bool IPAddress::operator!=(const IPAddress &other) const { + return !((*this) == other); +} + +bool IPAddress::operator >(const IPAddress &other) const { + return (*this) != other && !((*this) < other); +} + +bool IPAddress::operator <(const IPAddress &other) const { + // IPv4 is 'less than' IPv6 + if (family_ != other.family_) { + if (family_ == AF_UNSPEC) { + return true; + } + if (family_ == AF_INET && other.family_ == AF_INET6) { + return true; + } + return false; + } + // Comparing addresses of the same family. + switch (family_) { + case AF_INET: { + return NetworkToHost32(u_.ip4.s_addr) < + NetworkToHost32(other.u_.ip4.s_addr); + } + case AF_INET6: { + return memcmp(&u_.ip6.s6_addr, &other.u_.ip6.s6_addr, 16) < 0; + } + } + // Catches AF_UNSPEC and invalid addresses. + return false; +} + +std::ostream& operator<<(std::ostream& os, const IPAddress& ip) { + os << ip.ToString(); + return os; +} + +in6_addr IPAddress::ipv6_address() const { + return u_.ip6; +} + +in_addr IPAddress::ipv4_address() const { + return u_.ip4; +} + +std::string IPAddress::ToString() const { + if (family_ != AF_INET && family_ != AF_INET6) { + return std::string(); + } + char buf[INET6_ADDRSTRLEN] = {0}; + const void* src = &u_.ip4; + if (family_ == AF_INET6) { + src = &u_.ip6; + } + if (!rtc::inet_ntop(family_, src, buf, sizeof(buf))) { + return std::string(); + } + return std::string(buf); +} + +std::string IPAddress::ToSensitiveString() const { + if (!strip_sensitive_) + return ToString(); + + switch (family_) { + case AF_INET: { + std::string address = ToString(); + size_t find_pos = address.rfind('.'); + if (find_pos == std::string::npos) + return std::string(); + address.resize(find_pos); + address += ".x"; + return address; + } + case AF_INET6: { + // TODO(grunell): Return a string of format 1:2:3:x:x:x:x:x or such + // instead of zeroing out. + return TruncateIP(*this, 128 - 80).ToString(); + } + } + return std::string(); +} + +IPAddress IPAddress::Normalized() const { + if (family_ != AF_INET6) { + return *this; + } + if (!IPIsV4Mapped(*this)) { + return *this; + } + in_addr addr = ExtractMappedAddress(u_.ip6); + return IPAddress(addr); +} + +IPAddress IPAddress::AsIPv6Address() const { + if (family_ != AF_INET) { + return *this; + } + in6_addr v6addr = kV4MappedPrefix; + ::memcpy(&v6addr.s6_addr[12], &u_.ip4.s_addr, sizeof(u_.ip4.s_addr)); + return IPAddress(v6addr); +} + +void IPAddress::set_strip_sensitive(bool enable) { + strip_sensitive_ = enable; +} + + +bool IsPrivateV4(uint32 ip_in_host_order) { + return ((ip_in_host_order >> 24) == 127) || + ((ip_in_host_order >> 24) == 10) || + ((ip_in_host_order >> 20) == ((172 << 4) | 1)) || + ((ip_in_host_order >> 16) == ((192 << 8) | 168)) || + ((ip_in_host_order >> 16) == ((169 << 8) | 254)); +} + +in_addr ExtractMappedAddress(const in6_addr& in6) { + in_addr ipv4; + ::memcpy(&ipv4.s_addr, &in6.s6_addr[12], sizeof(ipv4.s_addr)); + return ipv4; +} + +bool IPFromAddrInfo(struct addrinfo* info, IPAddress* out) { + if (!info || !info->ai_addr) { + return false; + } + if (info->ai_addr->sa_family == AF_INET) { + sockaddr_in* addr = reinterpret_cast(info->ai_addr); + *out = IPAddress(addr->sin_addr); + return true; + } else if (info->ai_addr->sa_family == AF_INET6) { + sockaddr_in6* addr = reinterpret_cast(info->ai_addr); + *out = IPAddress(addr->sin6_addr); + return true; + } + return false; +} + +bool IPFromString(const std::string& str, IPAddress* out) { + if (!out) { + return false; + } + in_addr addr; + if (rtc::inet_pton(AF_INET, str.c_str(), &addr) == 0) { + in6_addr addr6; + if (rtc::inet_pton(AF_INET6, str.c_str(), &addr6) == 0) { + *out = IPAddress(); + return false; + } + *out = IPAddress(addr6); + } else { + *out = IPAddress(addr); + } + return true; +} + +bool IPIsAny(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: + return ip == IPAddress(INADDR_ANY); + case AF_INET6: + return ip == IPAddress(in6addr_any); + case AF_UNSPEC: + return false; + } + return false; +} + +bool IPIsLoopback(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: { + return ip == IPAddress(INADDR_LOOPBACK); + } + case AF_INET6: { + return ip == IPAddress(in6addr_loopback); + } + } + return false; +} + +bool IPIsPrivate(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: { + return IsPrivateV4(ip.v4AddressAsHostOrderInteger()); + } + case AF_INET6: { + in6_addr v6 = ip.ipv6_address(); + return (v6.s6_addr[0] == 0xFE && v6.s6_addr[1] == 0x80) || + IPIsLoopback(ip); + } + } + return false; +} + +bool IPIsUnspec(const IPAddress& ip) { + return ip.family() == AF_UNSPEC; +} + +size_t HashIP(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: { + return ip.ipv4_address().s_addr; + } + case AF_INET6: { + in6_addr v6addr = ip.ipv6_address(); + const uint32* v6_as_ints = + reinterpret_cast(&v6addr.s6_addr); + return v6_as_ints[0] ^ v6_as_ints[1] ^ v6_as_ints[2] ^ v6_as_ints[3]; + } + } + return 0; +} + +IPAddress TruncateIP(const IPAddress& ip, int length) { + if (length < 0) { + return IPAddress(); + } + if (ip.family() == AF_INET) { + if (length > 31) { + return ip; + } + if (length == 0) { + return IPAddress(INADDR_ANY); + } + int mask = (0xFFFFFFFF << (32 - length)); + uint32 host_order_ip = NetworkToHost32(ip.ipv4_address().s_addr); + in_addr masked; + masked.s_addr = HostToNetwork32(host_order_ip & mask); + return IPAddress(masked); + } else if (ip.family() == AF_INET6) { + if (length > 127) { + return ip; + } + if (length == 0) { + return IPAddress(in6addr_any); + } + in6_addr v6addr = ip.ipv6_address(); + int position = length / 32; + int inner_length = 32 - (length - (position * 32)); + // Note: 64bit mask constant needed to allow possible 32-bit left shift. + uint32 inner_mask = 0xFFFFFFFFLL << inner_length; + uint32* v6_as_ints = + reinterpret_cast(&v6addr.s6_addr); + for (int i = 0; i < 4; ++i) { + if (i == position) { + uint32 host_order_inner = NetworkToHost32(v6_as_ints[i]); + v6_as_ints[i] = HostToNetwork32(host_order_inner & inner_mask); + } else if (i > position) { + v6_as_ints[i] = 0; + } + } + return IPAddress(v6addr); + } + return IPAddress(); +} + +int CountIPMaskBits(IPAddress mask) { + uint32 word_to_count = 0; + int bits = 0; + switch (mask.family()) { + case AF_INET: { + word_to_count = NetworkToHost32(mask.ipv4_address().s_addr); + break; + } + case AF_INET6: { + in6_addr v6addr = mask.ipv6_address(); + const uint32* v6_as_ints = + reinterpret_cast(&v6addr.s6_addr); + int i = 0; + for (; i < 4; ++i) { + if (v6_as_ints[i] != 0xFFFFFFFF) { + break; + } + } + if (i < 4) { + word_to_count = NetworkToHost32(v6_as_ints[i]); + } + bits = (i * 32); + break; + } + default: { + return 0; + } + } + if (word_to_count == 0) { + return bits; + } + + // Public domain bit-twiddling hack from: + // http://graphics.stanford.edu/~seander/bithacks.html + // Counts the trailing 0s in the word. + unsigned int zeroes = 32; + word_to_count &= -static_cast(word_to_count); + if (word_to_count) zeroes--; + if (word_to_count & 0x0000FFFF) zeroes -= 16; + if (word_to_count & 0x00FF00FF) zeroes -= 8; + if (word_to_count & 0x0F0F0F0F) zeroes -= 4; + if (word_to_count & 0x33333333) zeroes -= 2; + if (word_to_count & 0x55555555) zeroes -= 1; + + return bits + (32 - zeroes); +} + +bool IPIsHelper(const IPAddress& ip, const in6_addr& tomatch, int length) { + // Helper method for checking IP prefix matches (but only on whole byte + // lengths). Length is in bits. + in6_addr addr = ip.ipv6_address(); + return ::memcmp(&addr, &tomatch, (length >> 3)) == 0; +} + +bool IPIs6Bone(const IPAddress& ip) { + return IPIsHelper(ip, k6BonePrefix, 16); +} + +bool IPIs6To4(const IPAddress& ip) { + return IPIsHelper(ip, k6To4Prefix, 16); +} + +bool IPIsSiteLocal(const IPAddress& ip) { + // Can't use the helper because the prefix is 10 bits. + in6_addr addr = ip.ipv6_address(); + return addr.s6_addr[0] == 0xFE && (addr.s6_addr[1] & 0xC0) == 0xC0; +} + +bool IPIsULA(const IPAddress& ip) { + // Can't use the helper because the prefix is 7 bits. + in6_addr addr = ip.ipv6_address(); + return (addr.s6_addr[0] & 0xFE) == 0xFC; +} + +bool IPIsTeredo(const IPAddress& ip) { + return IPIsHelper(ip, kTeredoPrefix, 32); +} + +bool IPIsV4Compatibility(const IPAddress& ip) { + return IPIsHelper(ip, kV4CompatibilityPrefix, 96); +} + +bool IPIsV4Mapped(const IPAddress& ip) { + return IPIsHelper(ip, kV4MappedPrefix, 96); +} + +int IPAddressPrecedence(const IPAddress& ip) { + // Precedence values from RFC 3484-bis. Prefers native v4 over 6to4/Teredo. + if (ip.family() == AF_INET) { + return 30; + } else if (ip.family() == AF_INET6) { + if (IPIsLoopback(ip)) { + return 60; + } else if (IPIsULA(ip)) { + return 50; + } else if (IPIsV4Mapped(ip)) { + return 30; + } else if (IPIs6To4(ip)) { + return 20; + } else if (IPIsTeredo(ip)) { + return 10; + } else if (IPIsV4Compatibility(ip) || IPIsSiteLocal(ip) || IPIs6Bone(ip)) { + return 1; + } else { + // A 'normal' IPv6 address. + return 40; + } + } + return 0; +} + +} // Namespace talk base diff --git a/webrtc/base/ipaddress.h b/webrtc/base/ipaddress.h new file mode 100644 index 000000000..e7d649acb --- /dev/null +++ b/webrtc/base/ipaddress.h @@ -0,0 +1,141 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_IPADDRESS_H_ +#define WEBRTC_BASE_IPADDRESS_H_ + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#include +#endif +#if defined(WEBRTC_WIN) +#include +#include +#endif +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +// Version-agnostic IP address class, wraps a union of in_addr and in6_addr. +class IPAddress { + public: + IPAddress() : family_(AF_UNSPEC) { + ::memset(&u_, 0, sizeof(u_)); + } + + explicit IPAddress(const in_addr &ip4) : family_(AF_INET) { + memset(&u_, 0, sizeof(u_)); + u_.ip4 = ip4; + } + + explicit IPAddress(const in6_addr &ip6) : family_(AF_INET6) { + u_.ip6 = ip6; + } + + explicit IPAddress(uint32 ip_in_host_byte_order) : family_(AF_INET) { + memset(&u_, 0, sizeof(u_)); + u_.ip4.s_addr = HostToNetwork32(ip_in_host_byte_order); + } + + IPAddress(const IPAddress &other) : family_(other.family_) { + ::memcpy(&u_, &other.u_, sizeof(u_)); + } + + ~IPAddress() {} + + const IPAddress & operator=(const IPAddress &other) { + family_ = other.family_; + ::memcpy(&u_, &other.u_, sizeof(u_)); + return *this; + } + + bool operator==(const IPAddress &other) const; + bool operator!=(const IPAddress &other) const; + bool operator <(const IPAddress &other) const; + bool operator >(const IPAddress &other) const; + friend std::ostream& operator<<(std::ostream& os, const IPAddress& addr); + + int family() const { return family_; } + in_addr ipv4_address() const; + in6_addr ipv6_address() const; + + // Returns the number of bytes needed to store the raw address. + size_t Size() const; + + // Wraps inet_ntop. + std::string ToString() const; + + // Same as ToString but anonymizes it by hiding the last part. + std::string ToSensitiveString() const; + + // Returns an unmapped address from a possibly-mapped address. + // Returns the same address if this isn't a mapped address. + IPAddress Normalized() const; + + // Returns this address as an IPv6 address. + // Maps v4 addresses (as ::ffff:a.b.c.d), returns v6 addresses unchanged. + IPAddress AsIPv6Address() const; + + // For socketaddress' benefit. Returns the IP in host byte order. + uint32 v4AddressAsHostOrderInteger() const; + + static void set_strip_sensitive(bool enable); + + private: + int family_; + union { + in_addr ip4; + in6_addr ip6; + } u_; + + static bool strip_sensitive_; +}; + +bool IPFromAddrInfo(struct addrinfo* info, IPAddress* out); +bool IPFromString(const std::string& str, IPAddress* out); +bool IPIsAny(const IPAddress& ip); +bool IPIsLoopback(const IPAddress& ip); +bool IPIsPrivate(const IPAddress& ip); +bool IPIsUnspec(const IPAddress& ip); +size_t HashIP(const IPAddress& ip); + +// These are only really applicable for IPv6 addresses. +bool IPIs6Bone(const IPAddress& ip); +bool IPIs6To4(const IPAddress& ip); +bool IPIsSiteLocal(const IPAddress& ip); +bool IPIsTeredo(const IPAddress& ip); +bool IPIsULA(const IPAddress& ip); +bool IPIsV4Compatibility(const IPAddress& ip); +bool IPIsV4Mapped(const IPAddress& ip); + +// Returns the precedence value for this IP as given in RFC3484. +int IPAddressPrecedence(const IPAddress& ip); + +// Returns 'ip' truncated to be 'length' bits long. +IPAddress TruncateIP(const IPAddress& ip, int length); + +// Returns the number of contiguously set bits, counting from the MSB in network +// byte order, in this IPAddress. Bits after the first 0 encountered are not +// counted. +int CountIPMaskBits(IPAddress mask); + +} // namespace rtc + +#endif // WEBRTC_BASE_IPADDRESS_H_ diff --git a/webrtc/base/ipaddress_unittest.cc b/webrtc/base/ipaddress_unittest.cc new file mode 100644 index 000000000..657595f68 --- /dev/null +++ b/webrtc/base/ipaddress_unittest.cc @@ -0,0 +1,859 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/ipaddress.h" + +namespace rtc { + +static const unsigned int kIPv4AddrSize = 4; +static const unsigned int kIPv6AddrSize = 16; +static const unsigned int kIPv4RFC1918Addr = 0xC0A80701; +static const unsigned int kIPv4PublicAddr = 0x01020304; +static const in6_addr kIPv6LinkLocalAddr = {{{0xfe, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xbe, 0x30, 0x5b, 0xff, + 0xfe, 0xe5, 0x00, 0xc3}}}; +static const in6_addr kIPv6PublicAddr = {{{0x24, 0x01, 0xfa, 0x00, + 0x00, 0x04, 0x10, 0x00, + 0xbe, 0x30, 0x5b, 0xff, + 0xfe, 0xe5, 0x00, 0xc3}}}; +static const in6_addr kIPv4MappedAnyAddr = {{{0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00}}}; +static const in6_addr kIPv4MappedRFC1918Addr = {{{0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0xc0, 0xa8, 0x07, 0x01}}}; +static const in6_addr kIPv4MappedPublicAddr = {{{0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0x01, 0x02, 0x03, 0x04}}}; + +static const std::string kIPv4AnyAddrString = "0.0.0.0"; +static const std::string kIPv4LoopbackAddrString = "127.0.0.1"; +static const std::string kIPv4RFC1918AddrString = "192.168.7.1"; +static const std::string kIPv4PublicAddrString = "1.2.3.4"; +static const std::string kIPv4PublicAddrAnonymizedString = "1.2.3.x"; +static const std::string kIPv6AnyAddrString = "::"; +static const std::string kIPv6LoopbackAddrString = "::1"; +static const std::string kIPv6LinkLocalAddrString = "fe80::be30:5bff:fee5:c3"; +static const std::string kIPv6PublicAddrString = + "2401:fa00:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6PublicAddrAnonymizedString = "2401:fa00:4::"; +static const std::string kIPv4MappedAnyAddrString = "::ffff:0:0"; +static const std::string kIPv4MappedRFC1918AddrString = "::ffff:c0a8:701"; +static const std::string kIPv4MappedLoopbackAddrString = "::ffff:7f00:1"; +static const std::string kIPv4MappedPublicAddrString = "::ffff:102:0304"; +static const std::string kIPv4MappedV4StyleAddrString = "::ffff:192.168.7.1"; + +static const std::string kIPv4BrokenString1 = "192.168.7."; +static const std::string kIPv4BrokenString2 = "192.168.7.1.1"; +static const std::string kIPv4BrokenString3 = "192.168.7.1:80"; +static const std::string kIPv4BrokenString4 = "192.168.7.ONE"; +static const std::string kIPv4BrokenString5 = "-192.168.7.1"; +static const std::string kIPv4BrokenString6 = "256.168.7.1"; +static const std::string kIPv6BrokenString1 = "2401:fa00:4:1000:be30"; +static const std::string kIPv6BrokenString2 = + "2401:fa00:4:1000:be30:5bff:fee5:c3:1"; +static const std::string kIPv6BrokenString3 = + "[2401:fa00:4:1000:be30:5bff:fee5:c3]:1"; +static const std::string kIPv6BrokenString4 = + "2401::4::be30"; +static const std::string kIPv6BrokenString5 = + "2401:::4:fee5:be30"; +static const std::string kIPv6BrokenString6 = + "2401f:fa00:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString7 = + "2401:ga00:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString8 = + "2401:fa000:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString9 = + "2401:fal0:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString10 = + "::ffff:192.168.7."; +static const std::string kIPv6BrokenString11 = + "::ffff:192.168.7.1.1.1"; +static const std::string kIPv6BrokenString12 = + "::fffe:192.168.7.1"; +static const std::string kIPv6BrokenString13 = + "::ffff:192.168.7.ff"; +static const std::string kIPv6BrokenString14 = + "0x2401:fa00:4:1000:be30:5bff:fee5:c3"; + +bool AreEqual(const IPAddress& addr, + const IPAddress& addr2) { + if ((IPIsAny(addr) != IPIsAny(addr2)) || + (IPIsLoopback(addr) != IPIsLoopback(addr2)) || + (IPIsPrivate(addr) != IPIsPrivate(addr2)) || + (HashIP(addr) != HashIP(addr2)) || + (addr.Size() != addr2.Size()) || + (addr.family() != addr2.family()) || + (addr.ToString() != addr2.ToString())) { + return false; + } + in_addr v4addr, v4addr2; + v4addr = addr.ipv4_address(); + v4addr2 = addr2.ipv4_address(); + if (0 != memcmp(&v4addr, &v4addr2, sizeof(v4addr))) { + return false; + } + in6_addr v6addr, v6addr2; + v6addr = addr.ipv6_address(); + v6addr2 = addr2.ipv6_address(); + if (0 != memcmp(&v6addr, &v6addr2, sizeof(v6addr))) { + return false; + } + return true; +} + +bool BrokenIPStringFails(const std::string& broken) { + IPAddress addr(0); // Intentionally make it v4. + if (IPFromString(kIPv4BrokenString1, &addr)) { + return false; + } + return addr.family() == AF_UNSPEC; +} + +bool CheckMaskCount(const std::string& mask, int expected_length) { + IPAddress addr; + return IPFromString(mask, &addr) && + (expected_length == CountIPMaskBits(addr)); +} + +bool TryInvalidMaskCount(const std::string& mask) { + // We don't care about the result at all, but we do want to know if + // CountIPMaskBits is going to crash or infinite loop or something. + IPAddress addr; + if (!IPFromString(mask, &addr)) { + return false; + } + CountIPMaskBits(addr); + return true; +} + +bool CheckTruncateIP(const std::string& initial, int truncate_length, + const std::string& expected_result) { + IPAddress addr, expected; + IPFromString(initial, &addr); + IPFromString(expected_result, &expected); + IPAddress truncated = TruncateIP(addr, truncate_length); + return truncated == expected; +} + +TEST(IPAddressTest, TestDefaultCtor) { + IPAddress addr; + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + + EXPECT_EQ(0U, addr.Size()); + EXPECT_EQ(AF_UNSPEC, addr.family()); + EXPECT_EQ("", addr.ToString()); +} + +TEST(IPAddressTest, TestInAddrCtor) { + in_addr v4addr; + + // Test V4 Any address. + v4addr.s_addr = INADDR_ANY; + IPAddress addr(v4addr); + EXPECT_TRUE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4AnyAddrString, addr.ToString()); + + // Test a V4 loopback address. + v4addr.s_addr = htonl(INADDR_LOOPBACK); + addr = IPAddress(v4addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_TRUE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString()); + + // Test an RFC1918 address. + v4addr.s_addr = htonl(kIPv4RFC1918Addr); + addr = IPAddress(v4addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString()); + + // Test a 'normal' v4 address. + v4addr.s_addr = htonl(kIPv4PublicAddr); + addr = IPAddress(v4addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4PublicAddrString, addr.ToString()); +} + +TEST(IPAddressTest, TestInAddr6Ctor) { + // Test v6 empty. + IPAddress addr(in6addr_any); + EXPECT_TRUE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6AnyAddrString, addr.ToString()); + + // Test v6 loopback. + addr = IPAddress(in6addr_loopback); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_TRUE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6LoopbackAddrString, addr.ToString()); + + // Test v6 link-local. + addr = IPAddress(kIPv6LinkLocalAddr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6LinkLocalAddrString, addr.ToString()); + + // Test v6 global address. + addr = IPAddress(kIPv6PublicAddr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6PublicAddrString, addr.ToString()); +} + +TEST(IPAddressTest, TestUint32Ctor) { + // Test V4 Any address. + IPAddress addr(0); + EXPECT_TRUE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4AnyAddrString, addr.ToString()); + + // Test a V4 loopback address. + addr = IPAddress(INADDR_LOOPBACK); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_TRUE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString()); + + // Test an RFC1918 address. + addr = IPAddress(kIPv4RFC1918Addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString()); + + // Test a 'normal' v4 address. + addr = IPAddress(kIPv4PublicAddr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4PublicAddrString, addr.ToString()); +} + +TEST(IPAddressTest, TestCopyCtor) { + in_addr v4addr; + v4addr.s_addr = htonl(kIPv4PublicAddr); + IPAddress addr(v4addr); + IPAddress addr2(addr); + + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(INADDR_ANY); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(INADDR_LOOPBACK); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv4PublicAddr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv4RFC1918Addr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(in6addr_any); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(in6addr_loopback); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv6LinkLocalAddr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv6PublicAddr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); +} + +TEST(IPAddressTest, TestEquality) { + // Check v4 equality + in_addr v4addr, v4addr2; + v4addr.s_addr = htonl(kIPv4PublicAddr); + v4addr2.s_addr = htonl(kIPv4PublicAddr + 1); + IPAddress addr(v4addr); + IPAddress addr2(v4addr2); + IPAddress addr3(v4addr); + + EXPECT_TRUE(addr == addr); + EXPECT_TRUE(addr2 == addr2); + EXPECT_TRUE(addr3 == addr3); + EXPECT_TRUE(addr == addr3); + EXPECT_TRUE(addr3 == addr); + EXPECT_FALSE(addr2 == addr); + EXPECT_FALSE(addr2 == addr3); + EXPECT_FALSE(addr == addr2); + EXPECT_FALSE(addr3 == addr2); + + // Check v6 equality + IPAddress addr4(kIPv6PublicAddr); + IPAddress addr5(kIPv6LinkLocalAddr); + IPAddress addr6(kIPv6PublicAddr); + + EXPECT_TRUE(addr4 == addr4); + EXPECT_TRUE(addr5 == addr5); + EXPECT_TRUE(addr4 == addr6); + EXPECT_TRUE(addr6 == addr4); + EXPECT_FALSE(addr4 == addr5); + EXPECT_FALSE(addr5 == addr4); + EXPECT_FALSE(addr6 == addr5); + EXPECT_FALSE(addr5 == addr6); + + // Check v4/v6 cross-equality + EXPECT_FALSE(addr == addr4); + EXPECT_FALSE(addr == addr5); + EXPECT_FALSE(addr == addr6); + EXPECT_FALSE(addr4 == addr); + EXPECT_FALSE(addr5 == addr); + EXPECT_FALSE(addr6 == addr); + EXPECT_FALSE(addr2 == addr4); + EXPECT_FALSE(addr2 == addr5); + EXPECT_FALSE(addr2 == addr6); + EXPECT_FALSE(addr4 == addr2); + EXPECT_FALSE(addr5 == addr2); + EXPECT_FALSE(addr6 == addr2); + EXPECT_FALSE(addr3 == addr4); + EXPECT_FALSE(addr3 == addr5); + EXPECT_FALSE(addr3 == addr6); + EXPECT_FALSE(addr4 == addr3); + EXPECT_FALSE(addr5 == addr3); + EXPECT_FALSE(addr6 == addr3); + + // Special cases: loopback and any. + // They're special but they're still not equal. + IPAddress v4loopback(htonl(INADDR_LOOPBACK)); + IPAddress v6loopback(in6addr_loopback); + EXPECT_FALSE(v4loopback == v6loopback); + + IPAddress v4any(0); + IPAddress v6any(in6addr_any); + EXPECT_FALSE(v4any == v6any); +} + +TEST(IPAddressTest, TestComparison) { + // Defined in 'ascending' order. + // v6 > v4, and intra-family sorting is purely numerical + IPAddress addr0; // AF_UNSPEC + IPAddress addr1(INADDR_ANY); // 0.0.0.0 + IPAddress addr2(kIPv4PublicAddr); // 1.2.3.4 + IPAddress addr3(INADDR_LOOPBACK); // 127.0.0.1 + IPAddress addr4(kIPv4RFC1918Addr); // 192.168.7.1. + IPAddress addr5(in6addr_any); // :: + IPAddress addr6(in6addr_loopback); // ::1 + IPAddress addr7(kIPv6PublicAddr); // 2401.... + IPAddress addr8(kIPv6LinkLocalAddr); // fe80.... + + EXPECT_TRUE(addr0 < addr1); + EXPECT_TRUE(addr1 < addr2); + EXPECT_TRUE(addr2 < addr3); + EXPECT_TRUE(addr3 < addr4); + EXPECT_TRUE(addr4 < addr5); + EXPECT_TRUE(addr5 < addr6); + EXPECT_TRUE(addr6 < addr7); + EXPECT_TRUE(addr7 < addr8); + + EXPECT_FALSE(addr0 > addr1); + EXPECT_FALSE(addr1 > addr2); + EXPECT_FALSE(addr2 > addr3); + EXPECT_FALSE(addr3 > addr4); + EXPECT_FALSE(addr4 > addr5); + EXPECT_FALSE(addr5 > addr6); + EXPECT_FALSE(addr6 > addr7); + EXPECT_FALSE(addr7 > addr8); + + EXPECT_FALSE(addr0 > addr0); + EXPECT_FALSE(addr1 > addr1); + EXPECT_FALSE(addr2 > addr2); + EXPECT_FALSE(addr3 > addr3); + EXPECT_FALSE(addr4 > addr4); + EXPECT_FALSE(addr5 > addr5); + EXPECT_FALSE(addr6 > addr6); + EXPECT_FALSE(addr7 > addr7); + EXPECT_FALSE(addr8 > addr8); + + EXPECT_FALSE(addr0 < addr0); + EXPECT_FALSE(addr1 < addr1); + EXPECT_FALSE(addr2 < addr2); + EXPECT_FALSE(addr3 < addr3); + EXPECT_FALSE(addr4 < addr4); + EXPECT_FALSE(addr5 < addr5); + EXPECT_FALSE(addr6 < addr6); + EXPECT_FALSE(addr7 < addr7); + EXPECT_FALSE(addr8 < addr8); +} + +TEST(IPAddressTest, TestFromString) { + IPAddress addr; + IPAddress addr2; + addr2 = IPAddress(INADDR_ANY); + + EXPECT_TRUE(IPFromString(kIPv4AnyAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4AnyAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(INADDR_LOOPBACK); + EXPECT_TRUE(IPFromString(kIPv4LoopbackAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4LoopbackAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv4RFC1918Addr); + EXPECT_TRUE(IPFromString(kIPv4RFC1918AddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4RFC1918AddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv4PublicAddr); + EXPECT_TRUE(IPFromString(kIPv4PublicAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4PublicAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(in6addr_any); + EXPECT_TRUE(IPFromString(kIPv6AnyAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6AnyAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(in6addr_loopback); + EXPECT_TRUE(IPFromString(kIPv6LoopbackAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6LoopbackAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv6LinkLocalAddr); + EXPECT_TRUE(IPFromString(kIPv6LinkLocalAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6LinkLocalAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv6PublicAddr); + EXPECT_TRUE(IPFromString(kIPv6PublicAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6PublicAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv4MappedRFC1918Addr); + EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr)); + EXPECT_PRED2(AreEqual, addr, addr2); + + // Broken cases, should set addr to AF_UNSPEC. + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString1); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString2); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString3); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString4); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString5); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString6); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString1); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString2); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString3); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString4); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString5); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString6); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString7); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString8); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString9); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString10); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString11); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString12); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString13); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString14); +} + +TEST(IPAddressTest, TestIPFromAddrInfo) { + struct sockaddr_in expected4; + struct sockaddr_in6 expected6; + struct addrinfo test_info; + struct addrinfo next_info; + memset(&next_info, 'A', sizeof(next_info)); + test_info.ai_next = &next_info; + // Check that we can get an IPv4 address out. + test_info.ai_addr = reinterpret_cast(&expected4); + expected4.sin_addr.s_addr = HostToNetwork32(kIPv4PublicAddr); + expected4.sin_family = AF_INET; + IPAddress expected(kIPv4PublicAddr); + IPAddress addr; + EXPECT_TRUE(IPFromAddrInfo(&test_info, &addr)); + EXPECT_EQ(expected, addr); + // Check that we can get an IPv6 address out. + expected6.sin6_addr = kIPv6PublicAddr; + expected6.sin6_family = AF_INET6; + expected = IPAddress(kIPv6PublicAddr); + test_info.ai_addr = reinterpret_cast(&expected6); + EXPECT_TRUE(IPFromAddrInfo(&test_info, &addr)); + EXPECT_EQ(expected, addr); + // Check that unspec fails. + expected6.sin6_family = AF_UNSPEC; + EXPECT_FALSE(IPFromAddrInfo(&test_info, &addr)); + // Check a zeroed out addrinfo doesn't crash us. + memset(&next_info, 0, sizeof(next_info)); + EXPECT_FALSE(IPFromAddrInfo(&next_info, &addr)); +} + +TEST(IPAddressTest, TestIsPrivate) { + EXPECT_FALSE(IPIsPrivate(IPAddress(INADDR_ANY))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4PublicAddr))); + EXPECT_FALSE(IPIsPrivate(IPAddress(in6addr_any))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv6PublicAddr))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedAnyAddr))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedPublicAddr))); + + EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv4RFC1918Addr))); + EXPECT_TRUE(IPIsPrivate(IPAddress(INADDR_LOOPBACK))); + EXPECT_TRUE(IPIsPrivate(IPAddress(in6addr_loopback))); + EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv6LinkLocalAddr))); +} + +TEST(IPAddressTest, TestIsLoopback) { + EXPECT_FALSE(IPIsLoopback(IPAddress(INADDR_ANY))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4PublicAddr))); + EXPECT_FALSE(IPIsLoopback(IPAddress(in6addr_any))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv6PublicAddr))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedAnyAddr))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedPublicAddr))); + + EXPECT_TRUE(IPIsLoopback(IPAddress(INADDR_LOOPBACK))); + EXPECT_TRUE(IPIsLoopback(IPAddress(in6addr_loopback))); +} + +TEST(IPAddressTest, TestNormalized) { + // Check normalizing a ::ffff:a.b.c.d address. + IPAddress addr; + EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr)); + IPAddress addr2(kIPv4RFC1918Addr); + addr = addr.Normalized(); + EXPECT_EQ(addr2, addr); + + // Check normalizing a ::ffff:aabb:ccdd address. + addr = IPAddress(kIPv4MappedPublicAddr); + addr2 = IPAddress(kIPv4PublicAddr); + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + + // Check that a non-mapped v6 addresses isn't altered. + addr = IPAddress(kIPv6PublicAddr); + addr2 = IPAddress(kIPv6PublicAddr); + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + + // Check that addresses that look a bit like mapped addresses aren't altered + EXPECT_TRUE(IPFromString("fe80::ffff:0102:0304", &addr)); + addr2 = addr; + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + EXPECT_TRUE(IPFromString("::0102:0304", &addr)); + addr2 = addr; + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + // This string should 'work' as an IP address but is not a mapped address, + // so it shouldn't change on normalization. + EXPECT_TRUE(IPFromString("::192.168.7.1", &addr)); + addr2 = addr; + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + + // Check that v4 addresses aren't altered. + addr = IPAddress(htonl(kIPv4PublicAddr)); + addr2 = IPAddress(htonl(kIPv4PublicAddr)); + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); +} + +TEST(IPAddressTest, TestAsIPv6Address) { + IPAddress addr(kIPv4PublicAddr); + IPAddress addr2(kIPv4MappedPublicAddr); + addr = addr.AsIPv6Address(); + EXPECT_EQ(addr, addr2); + + addr = IPAddress(kIPv4MappedPublicAddr); + addr2 = IPAddress(kIPv4MappedPublicAddr); + addr = addr.AsIPv6Address(); + EXPECT_EQ(addr, addr2); + + addr = IPAddress(kIPv6PublicAddr); + addr2 = IPAddress(kIPv6PublicAddr); + addr = addr.AsIPv6Address(); + EXPECT_EQ(addr, addr2); +} + +TEST(IPAddressTest, TestCountIPMaskBits) { + IPAddress mask; + // IPv4 on byte boundaries + EXPECT_PRED2(CheckMaskCount, "255.255.255.255", 32); + EXPECT_PRED2(CheckMaskCount, "255.255.255.0", 24); + EXPECT_PRED2(CheckMaskCount, "255.255.0.0", 16); + EXPECT_PRED2(CheckMaskCount, "255.0.0.0", 8); + EXPECT_PRED2(CheckMaskCount, "0.0.0.0", 0); + + // IPv4 not on byte boundaries + EXPECT_PRED2(CheckMaskCount, "128.0.0.0", 1); + EXPECT_PRED2(CheckMaskCount, "224.0.0.0", 3); + EXPECT_PRED2(CheckMaskCount, "255.248.0.0", 13); + EXPECT_PRED2(CheckMaskCount, "255.255.224.0", 19); + EXPECT_PRED2(CheckMaskCount, "255.255.255.252", 30); + + // V6 on byte boundaries + EXPECT_PRED2(CheckMaskCount, "::", 0); + EXPECT_PRED2(CheckMaskCount, "ff00::", 8); + EXPECT_PRED2(CheckMaskCount, "ffff::", 16); + EXPECT_PRED2(CheckMaskCount, "ffff:ff00::", 24); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff::", 32); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00::", 40); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::", 48); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00::", 56); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff::", 64); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff00::", 72); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff::", 80); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff00::", 88); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff::", 96); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0000", 104); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0000", 112); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128); + + // V6 not on byte boundaries. + EXPECT_PRED2(CheckMaskCount, "8000::", 1); + EXPECT_PRED2(CheckMaskCount, "ff80::", 9); + EXPECT_PRED2(CheckMaskCount, "ffff:fe00::", 23); + EXPECT_PRED2(CheckMaskCount, "ffff:fffe::", 31); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:e000::", 35); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffe0::", 43); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:f800::", 53); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:fff8::", 61); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fc00::", 70); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fffc::", 78); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:8000::", 81); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff80::", 89); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fe00::", 103); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0000", 111); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00", 118); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc", 126); + + // Non-contiguous ranges. These are invalid but lets test them + // to make sure they don't crash anything or infinite loop or something. + EXPECT_PRED1(TryInvalidMaskCount, "217.0.0.0"); + EXPECT_PRED1(TryInvalidMaskCount, "255.185.0.0"); + EXPECT_PRED1(TryInvalidMaskCount, "255.255.251.0"); + EXPECT_PRED1(TryInvalidMaskCount, "255.255.251.255"); + EXPECT_PRED1(TryInvalidMaskCount, "255.255.254.201"); + EXPECT_PRED1(TryInvalidMaskCount, "::1"); + EXPECT_PRED1(TryInvalidMaskCount, "fe80::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ff80::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ff00:1::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff::ffff:1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ff00:1::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff::ff00"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ff00:1234::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:0012::ffff"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ff01::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:7f00::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ff7a::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:7f00:0000"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff70:0000"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0211"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff7f"); +} + +TEST(IPAddressTest, TestTruncateIP) { + EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 24, "255.255.255.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 16, "255.255.0.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 8, "255.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "202.67.7.255", 24, "202.67.7.0"); + EXPECT_PRED3(CheckTruncateIP, "202.129.65.205", 16, "202.129.0.0"); + EXPECT_PRED3(CheckTruncateIP, "55.25.2.77", 8, "55.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "74.128.99.254", 1, "0.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "106.55.99.254", 3, "96.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "172.167.53.222", 13, "172.160.0.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.224.0", 18, "255.255.192.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.255.252", 28, "255.255.255.240"); + + EXPECT_PRED3(CheckTruncateIP, "fe80:1111:2222:3333:4444:5555:6666:7777", 1, + "8000::"); + EXPECT_PRED3(CheckTruncateIP, "fff0:1111:2222:3333:4444:5555:6666:7777", 9, + "ff80::"); + EXPECT_PRED3(CheckTruncateIP, "ffff:ff80:1111:2222:3333:4444:5555:6666", 23, + "ffff:fe00::"); + EXPECT_PRED3(CheckTruncateIP, "ffff:ff80:1111:2222:3333:4444:5555:6666", 32, + "ffff:ff80::"); + EXPECT_PRED3(CheckTruncateIP, "2400:f9af:e456:1111:2222:3333:4444:5555", 35, + "2400:f9af:e000::"); + EXPECT_PRED3(CheckTruncateIP, "9999:1111:2233:4444:5555:6666:7777:8888", 53, + "9999:1111:2233:4000::"); + EXPECT_PRED3(CheckTruncateIP, "9999:1111:2233:4567:5555:6666:7777:8888", 64, + "9999:1111:2233:4567::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 68, + "1111:2222:3333:4444:5000::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 92, + "1111:2222:3333:4444:5555:6660::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 96, + "1111:2222:3333:4444:5555:6666::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 105, + "1111:2222:3333:4444:5555:6666:7700::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 124, + "1111:2222:3333:4444:5555:6666:7777:8880"); + + // Slightly degenerate cases + EXPECT_PRED3(CheckTruncateIP, "202.165.33.127", 32, "202.165.33.127"); + EXPECT_PRED3(CheckTruncateIP, "235.105.77.12", 0, "0.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 128, + "1111:2222:3333:4444:5555:6666:7777:8888"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 0, + "::"); +} + +TEST(IPAddressTest, TestCategorizeIPv6) { + // Test determining if an IPAddress is 6Bone/6To4/Teredo/etc. + // IPv4 address, should be none of these (not even v4compat/v4mapped). + IPAddress v4_addr(kIPv4PublicAddr); + EXPECT_FALSE(IPIs6Bone(v4_addr)); + EXPECT_FALSE(IPIs6To4(v4_addr)); + EXPECT_FALSE(IPIsSiteLocal(v4_addr)); + EXPECT_FALSE(IPIsTeredo(v4_addr)); + EXPECT_FALSE(IPIsULA(v4_addr)); + EXPECT_FALSE(IPIsV4Compatibility(v4_addr)); + EXPECT_FALSE(IPIsV4Mapped(v4_addr)); + // Linklocal (fe80::/16) adddress; should be none of these. + IPAddress linklocal_addr(kIPv6LinkLocalAddr); + EXPECT_FALSE(IPIs6Bone(linklocal_addr)); + EXPECT_FALSE(IPIs6To4(linklocal_addr)); + EXPECT_FALSE(IPIsSiteLocal(linklocal_addr)); + EXPECT_FALSE(IPIsTeredo(linklocal_addr)); + EXPECT_FALSE(IPIsULA(linklocal_addr)); + EXPECT_FALSE(IPIsV4Compatibility(linklocal_addr)); + EXPECT_FALSE(IPIsV4Mapped(linklocal_addr)); + // 'Normal' IPv6 address, should also be none of these. + IPAddress normal_addr(kIPv6PublicAddr); + EXPECT_FALSE(IPIs6Bone(normal_addr)); + EXPECT_FALSE(IPIs6To4(normal_addr)); + EXPECT_FALSE(IPIsSiteLocal(normal_addr)); + EXPECT_FALSE(IPIsTeredo(normal_addr)); + EXPECT_FALSE(IPIsULA(normal_addr)); + EXPECT_FALSE(IPIsV4Compatibility(normal_addr)); + EXPECT_FALSE(IPIsV4Mapped(normal_addr)); + // IPv4 mapped address (::ffff:123.123.123.123) + IPAddress v4mapped_addr(kIPv4MappedPublicAddr); + EXPECT_TRUE(IPIsV4Mapped(v4mapped_addr)); + EXPECT_FALSE(IPIsV4Compatibility(v4mapped_addr)); + EXPECT_FALSE(IPIs6Bone(v4mapped_addr)); + EXPECT_FALSE(IPIs6To4(v4mapped_addr)); + EXPECT_FALSE(IPIsSiteLocal(v4mapped_addr)); + EXPECT_FALSE(IPIsTeredo(v4mapped_addr)); + EXPECT_FALSE(IPIsULA(v4mapped_addr)); + // IPv4 compatibility address (::123.123.123.123) + IPAddress v4compat_addr; + IPFromString("::192.168.7.1", &v4compat_addr); + EXPECT_TRUE(IPIsV4Compatibility(v4compat_addr)); + EXPECT_FALSE(IPIs6Bone(v4compat_addr)); + EXPECT_FALSE(IPIs6To4(v4compat_addr)); + EXPECT_FALSE(IPIsSiteLocal(v4compat_addr)); + EXPECT_FALSE(IPIsTeredo(v4compat_addr)); + EXPECT_FALSE(IPIsULA(v4compat_addr)); + EXPECT_FALSE(IPIsV4Mapped(v4compat_addr)); + // 6Bone address (3FFE::/16) + IPAddress sixbone_addr; + IPFromString("3FFE:123:456::789:123", &sixbone_addr); + EXPECT_TRUE(IPIs6Bone(sixbone_addr)); + EXPECT_FALSE(IPIs6To4(sixbone_addr)); + EXPECT_FALSE(IPIsSiteLocal(sixbone_addr)); + EXPECT_FALSE(IPIsTeredo(sixbone_addr)); + EXPECT_FALSE(IPIsULA(sixbone_addr)); + EXPECT_FALSE(IPIsV4Mapped(sixbone_addr)); + EXPECT_FALSE(IPIsV4Compatibility(sixbone_addr)); + // Unique Local Address (FC::/7) + IPAddress ula_addr; + IPFromString("FC00:123:456::789:123", &ula_addr); + EXPECT_TRUE(IPIsULA(ula_addr)); + EXPECT_FALSE(IPIs6Bone(ula_addr)); + EXPECT_FALSE(IPIs6To4(ula_addr)); + EXPECT_FALSE(IPIsSiteLocal(ula_addr)); + EXPECT_FALSE(IPIsTeredo(ula_addr)); + EXPECT_FALSE(IPIsV4Mapped(ula_addr)); + EXPECT_FALSE(IPIsV4Compatibility(ula_addr)); + // 6To4 Address (2002::/16) + IPAddress sixtofour_addr; + IPFromString("2002:123:456::789:123", &sixtofour_addr); + EXPECT_TRUE(IPIs6To4(sixtofour_addr)); + EXPECT_FALSE(IPIs6Bone(sixtofour_addr)); + EXPECT_FALSE(IPIsSiteLocal(sixtofour_addr)); + EXPECT_FALSE(IPIsTeredo(sixtofour_addr)); + EXPECT_FALSE(IPIsULA(sixtofour_addr)); + EXPECT_FALSE(IPIsV4Compatibility(sixtofour_addr)); + EXPECT_FALSE(IPIsV4Mapped(sixtofour_addr)); + // Site Local address (FEC0::/10) + IPAddress sitelocal_addr; + IPFromString("FEC0:123:456::789:123", &sitelocal_addr); + EXPECT_TRUE(IPIsSiteLocal(sitelocal_addr)); + EXPECT_FALSE(IPIs6Bone(sitelocal_addr)); + EXPECT_FALSE(IPIs6To4(sitelocal_addr)); + EXPECT_FALSE(IPIsTeredo(sitelocal_addr)); + EXPECT_FALSE(IPIsULA(sitelocal_addr)); + EXPECT_FALSE(IPIsV4Compatibility(sitelocal_addr)); + EXPECT_FALSE(IPIsV4Mapped(sitelocal_addr)); + // Teredo Address (2001:0000::/32) + IPAddress teredo_addr; + IPFromString("2001:0000:123:456::789:123", &teredo_addr); + EXPECT_TRUE(IPIsTeredo(teredo_addr)); + EXPECT_FALSE(IPIsSiteLocal(teredo_addr)); + EXPECT_FALSE(IPIs6Bone(teredo_addr)); + EXPECT_FALSE(IPIs6To4(teredo_addr)); + EXPECT_FALSE(IPIsULA(teredo_addr)); + EXPECT_FALSE(IPIsV4Compatibility(teredo_addr)); + EXPECT_FALSE(IPIsV4Mapped(teredo_addr)); +} + +TEST(IPAddressTest, TestToSensitiveString) { + IPAddress addr_v4 = IPAddress(kIPv4PublicAddr); + EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToString()); + EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToString()); + EXPECT_EQ(kIPv4PublicAddrAnonymizedString, addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); + + IPAddress addr_v6 = IPAddress(kIPv6PublicAddr); + EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToString()); + EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToString()); + EXPECT_EQ(kIPv6PublicAddrAnonymizedString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); +} + +} // namespace rtc diff --git a/webrtc/base/json.cc b/webrtc/base/json.cc new file mode 100644 index 000000000..49a051c01 --- /dev/null +++ b/webrtc/base/json.cc @@ -0,0 +1,296 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/json.h" + +#include +#include +#include + +#include + +bool GetStringFromJson(const Json::Value& in, std::string* out) { + if (!in.isString()) { + std::ostringstream s; + if (in.isBool()) { + s << std::boolalpha << in.asBool(); + } else if (in.isInt()) { + s << in.asInt(); + } else if (in.isUInt()) { + s << in.asUInt(); + } else if (in.isDouble()) { + s << in.asDouble(); + } else { + return false; + } + *out = s.str(); + } else { + *out = in.asString(); + } + return true; +} + +bool GetIntFromJson(const Json::Value& in, int* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::intValue); + if (ret) { + *out = in.asInt(); + } + } else { + long val; // NOLINT + const char* c_str = in.asCString(); + char* end_ptr; + errno = 0; + val = strtol(c_str, &end_ptr, 10); // NOLINT + ret = (end_ptr != c_str && *end_ptr == '\0' && !errno && + val >= INT_MIN && val <= INT_MAX); + *out = val; + } + return ret; +} + +bool GetUIntFromJson(const Json::Value& in, unsigned int* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::uintValue); + if (ret) { + *out = in.asUInt(); + } + } else { + unsigned long val; // NOLINT + const char* c_str = in.asCString(); + char* end_ptr; + errno = 0; + val = strtoul(c_str, &end_ptr, 10); // NOLINT + ret = (end_ptr != c_str && *end_ptr == '\0' && !errno && + val <= UINT_MAX); + *out = val; + } + return ret; +} + +bool GetBoolFromJson(const Json::Value& in, bool* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::booleanValue); + if (ret) { + *out = in.asBool(); + } + } else { + if (in.asString() == "true") { + *out = true; + ret = true; + } else if (in.asString() == "false") { + *out = false; + ret = true; + } else { + ret = false; + } + } + return ret; +} + +bool GetDoubleFromJson(const Json::Value& in, double* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::realValue); + if (ret) { + *out = in.asDouble(); + } + } else { + double val; + const char* c_str = in.asCString(); + char* end_ptr; + errno = 0; + val = strtod(c_str, &end_ptr); + ret = (end_ptr != c_str && *end_ptr == '\0' && !errno); + *out = val; + } + return ret; +} + +namespace { +template +bool JsonArrayToVector(const Json::Value& value, + bool (*getter)(const Json::Value& in, T* out), + std::vector *vec) { + vec->clear(); + if (!value.isArray()) { + return false; + } + + for (Json::Value::ArrayIndex i = 0; i < value.size(); ++i) { + T val; + if (!getter(value[i], &val)) { + return false; + } + vec->push_back(val); + } + + return true; +} +// Trivial getter helper +bool GetValueFromJson(const Json::Value& in, Json::Value* out) { + *out = in; + return true; +} +} // unnamed namespace + +bool JsonArrayToValueVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetValueFromJson, out); +} + +bool JsonArrayToIntVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetIntFromJson, out); +} + +bool JsonArrayToUIntVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetUIntFromJson, out); +} + +bool JsonArrayToStringVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetStringFromJson, out); +} + +bool JsonArrayToBoolVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetBoolFromJson, out); +} + +bool JsonArrayToDoubleVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetDoubleFromJson, out); +} + +namespace { +template +Json::Value VectorToJsonArray(const std::vector& vec) { + Json::Value result(Json::arrayValue); + for (size_t i = 0; i < vec.size(); ++i) { + result.append(Json::Value(vec[i])); + } + return result; +} +} // unnamed namespace + +Json::Value ValueVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value IntVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value UIntVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value StringVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value BoolVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value DoubleVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +bool GetValueFromJsonArray(const Json::Value& in, size_t n, + Json::Value* out) { + if (!in.isArray() || !in.isValidIndex(static_cast(n))) { + return false; + } + + *out = in[static_cast(n)]; + return true; +} + +bool GetIntFromJsonArray(const Json::Value& in, size_t n, + int* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetIntFromJson(x, out); +} + +bool GetUIntFromJsonArray(const Json::Value& in, size_t n, + unsigned int* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetUIntFromJson(x, out); +} + +bool GetStringFromJsonArray(const Json::Value& in, size_t n, + std::string* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetStringFromJson(x, out); +} + +bool GetBoolFromJsonArray(const Json::Value& in, size_t n, + bool* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetBoolFromJson(x, out); +} + +bool GetDoubleFromJsonArray(const Json::Value& in, size_t n, + double* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetDoubleFromJson(x, out); +} + +bool GetValueFromJsonObject(const Json::Value& in, const std::string& k, + Json::Value* out) { + if (!in.isObject() || !in.isMember(k)) { + return false; + } + + *out = in[k]; + return true; +} + +bool GetIntFromJsonObject(const Json::Value& in, const std::string& k, + int* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetIntFromJson(x, out); +} + +bool GetUIntFromJsonObject(const Json::Value& in, const std::string& k, + unsigned int* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetUIntFromJson(x, out); +} + +bool GetStringFromJsonObject(const Json::Value& in, const std::string& k, + std::string* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetStringFromJson(x, out); +} + +bool GetBoolFromJsonObject(const Json::Value& in, const std::string& k, + bool* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetBoolFromJson(x, out); +} + +bool GetDoubleFromJsonObject(const Json::Value& in, const std::string& k, + double* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetDoubleFromJson(x, out); +} + +std::string JsonValueToString(const Json::Value& json) { + Json::FastWriter w; + std::string value = w.write(json); + return value.substr(0, value.size() - 1); // trim trailing newline +} diff --git a/webrtc/base/json.h b/webrtc/base/json.h new file mode 100644 index 000000000..fe41ac805 --- /dev/null +++ b/webrtc/base/json.h @@ -0,0 +1,89 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_JSON_H_ +#define WEBRTC_BASE_JSON_H_ + +#include +#include + +#if !defined(WEBRTC_CHROMIUM_BUILD) +#include "json/json.h" +#else +#include "third_party/jsoncpp/json.h" +#endif + +// TODO: Move to rtc namespace + +/////////////////////////////////////////////////////////////////////////////// +// JSON Helpers +/////////////////////////////////////////////////////////////////////////////// + +// Robust conversion operators, better than the ones in JsonCpp. +bool GetIntFromJson(const Json::Value& in, int* out); +bool GetUIntFromJson(const Json::Value& in, unsigned int* out); +bool GetStringFromJson(const Json::Value& in, std::string* out); +bool GetBoolFromJson(const Json::Value& in, bool* out); +bool GetDoubleFromJson(const Json::Value& in, double* out); + +// Pull values out of a JSON array. +bool GetValueFromJsonArray(const Json::Value& in, size_t n, + Json::Value* out); +bool GetIntFromJsonArray(const Json::Value& in, size_t n, + int* out); +bool GetUIntFromJsonArray(const Json::Value& in, size_t n, + unsigned int* out); +bool GetStringFromJsonArray(const Json::Value& in, size_t n, + std::string* out); +bool GetBoolFromJsonArray(const Json::Value& in, size_t n, + bool* out); +bool GetDoubleFromJsonArray(const Json::Value& in, size_t n, + double* out); + +// Convert json arrays to std::vector +bool JsonArrayToValueVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToIntVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToUIntVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToStringVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToBoolVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToDoubleVector(const Json::Value& in, + std::vector* out); + +// Convert std::vector to json array +Json::Value ValueVectorToJsonArray(const std::vector& in); +Json::Value IntVectorToJsonArray(const std::vector& in); +Json::Value UIntVectorToJsonArray(const std::vector& in); +Json::Value StringVectorToJsonArray(const std::vector& in); +Json::Value BoolVectorToJsonArray(const std::vector& in); +Json::Value DoubleVectorToJsonArray(const std::vector& in); + +// Pull values out of a JSON object. +bool GetValueFromJsonObject(const Json::Value& in, const std::string& k, + Json::Value* out); +bool GetIntFromJsonObject(const Json::Value& in, const std::string& k, + int* out); +bool GetUIntFromJsonObject(const Json::Value& in, const std::string& k, + unsigned int* out); +bool GetStringFromJsonObject(const Json::Value& in, const std::string& k, + std::string* out); +bool GetBoolFromJsonObject(const Json::Value& in, const std::string& k, + bool* out); +bool GetDoubleFromJsonObject(const Json::Value& in, const std::string& k, + double* out); + +// Writes out a Json value as a string. +std::string JsonValueToString(const Json::Value& json); + +#endif // WEBRTC_BASE_JSON_H_ diff --git a/webrtc/base/json_unittest.cc b/webrtc/base/json_unittest.cc new file mode 100644 index 000000000..e7e58227b --- /dev/null +++ b/webrtc/base/json_unittest.cc @@ -0,0 +1,277 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "webrtc/base/gunit.h" +#include "webrtc/base/json.h" + +static Json::Value in_s("foo"); +static Json::Value in_sn("99"); +static Json::Value in_si("-99"); +static Json::Value in_sb("true"); +static Json::Value in_sd("1.2"); +static Json::Value in_n(12); +static Json::Value in_i(-12); +static Json::Value in_u(34U); +static Json::Value in_b(true); +static Json::Value in_d(1.2); +static Json::Value big_sn("12345678901234567890"); +static Json::Value big_si("-12345678901234567890"); +static Json::Value big_u(0xFFFFFFFF); +static Json::Value bad_a(Json::arrayValue); +static Json::Value bad_o(Json::objectValue); + +TEST(JsonTest, GetString) { + std::string out; + EXPECT_TRUE(GetStringFromJson(in_s, &out)); + EXPECT_EQ("foo", out); + EXPECT_TRUE(GetStringFromJson(in_sn, &out)); + EXPECT_EQ("99", out); + EXPECT_TRUE(GetStringFromJson(in_si, &out)); + EXPECT_EQ("-99", out); + EXPECT_TRUE(GetStringFromJson(in_i, &out)); + EXPECT_EQ("-12", out); + EXPECT_TRUE(GetStringFromJson(in_n, &out)); + EXPECT_EQ("12", out); + EXPECT_TRUE(GetStringFromJson(in_u, &out)); + EXPECT_EQ("34", out); + EXPECT_TRUE(GetStringFromJson(in_b, &out)); + EXPECT_EQ("true", out); + // Not supported here yet. + EXPECT_FALSE(GetStringFromJson(bad_a, &out)); + EXPECT_FALSE(GetStringFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetInt) { + int out; + EXPECT_TRUE(GetIntFromJson(in_sn, &out)); + EXPECT_EQ(99, out); + EXPECT_TRUE(GetIntFromJson(in_si, &out)); + EXPECT_EQ(-99, out); + EXPECT_TRUE(GetIntFromJson(in_n, &out)); + EXPECT_EQ(12, out); + EXPECT_TRUE(GetIntFromJson(in_i, &out)); + EXPECT_EQ(-12, out); + EXPECT_TRUE(GetIntFromJson(in_u, &out)); + EXPECT_EQ(34, out); + EXPECT_TRUE(GetIntFromJson(in_b, &out)); + EXPECT_EQ(1, out); + EXPECT_FALSE(GetIntFromJson(in_s, &out)); + EXPECT_FALSE(GetIntFromJson(big_sn, &out)); + EXPECT_FALSE(GetIntFromJson(big_si, &out)); + EXPECT_FALSE(GetIntFromJson(big_u, &out)); + EXPECT_FALSE(GetIntFromJson(bad_a, &out)); + EXPECT_FALSE(GetIntFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetUInt) { + unsigned int out; + EXPECT_TRUE(GetUIntFromJson(in_sn, &out)); + EXPECT_EQ(99U, out); + EXPECT_TRUE(GetUIntFromJson(in_n, &out)); + EXPECT_EQ(12U, out); + EXPECT_TRUE(GetUIntFromJson(in_u, &out)); + EXPECT_EQ(34U, out); + EXPECT_TRUE(GetUIntFromJson(in_b, &out)); + EXPECT_EQ(1U, out); + EXPECT_TRUE(GetUIntFromJson(big_u, &out)); + EXPECT_EQ(0xFFFFFFFFU, out); + EXPECT_FALSE(GetUIntFromJson(in_s, &out)); + // TODO: Fail reading negative strings. + // EXPECT_FALSE(GetUIntFromJson(in_si, &out)); + EXPECT_FALSE(GetUIntFromJson(in_i, &out)); + EXPECT_FALSE(GetUIntFromJson(big_sn, &out)); + EXPECT_FALSE(GetUIntFromJson(big_si, &out)); + EXPECT_FALSE(GetUIntFromJson(bad_a, &out)); + EXPECT_FALSE(GetUIntFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetBool) { + bool out; + EXPECT_TRUE(GetBoolFromJson(in_sb, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_n, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_i, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_u, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_b, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(big_u, &out)); + EXPECT_EQ(true, out); + EXPECT_FALSE(GetBoolFromJson(in_s, &out)); + EXPECT_FALSE(GetBoolFromJson(in_sn, &out)); + EXPECT_FALSE(GetBoolFromJson(in_si, &out)); + EXPECT_FALSE(GetBoolFromJson(big_sn, &out)); + EXPECT_FALSE(GetBoolFromJson(big_si, &out)); + EXPECT_FALSE(GetBoolFromJson(bad_a, &out)); + EXPECT_FALSE(GetBoolFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetDouble) { + double out; + EXPECT_TRUE(GetDoubleFromJson(in_sn, &out)); + EXPECT_EQ(99, out); + EXPECT_TRUE(GetDoubleFromJson(in_si, &out)); + EXPECT_EQ(-99, out); + EXPECT_TRUE(GetDoubleFromJson(in_sd, &out)); + EXPECT_EQ(1.2, out); + EXPECT_TRUE(GetDoubleFromJson(in_n, &out)); + EXPECT_EQ(12, out); + EXPECT_TRUE(GetDoubleFromJson(in_i, &out)); + EXPECT_EQ(-12, out); + EXPECT_TRUE(GetDoubleFromJson(in_u, &out)); + EXPECT_EQ(34, out); + EXPECT_TRUE(GetDoubleFromJson(in_b, &out)); + EXPECT_EQ(1, out); + EXPECT_TRUE(GetDoubleFromJson(in_d, &out)); + EXPECT_EQ(1.2, out); + EXPECT_FALSE(GetDoubleFromJson(in_s, &out)); +} + +TEST(JsonTest, GetFromArray) { + Json::Value a, out; + a.append(in_s); + a.append(in_i); + a.append(in_u); + a.append(in_b); + EXPECT_TRUE(GetValueFromJsonArray(a, 0, &out)); + EXPECT_TRUE(GetValueFromJsonArray(a, 3, &out)); + EXPECT_FALSE(GetValueFromJsonArray(a, 99, &out)); + EXPECT_FALSE(GetValueFromJsonArray(a, 0xFFFFFFFF, &out)); +} + +TEST(JsonTest, GetFromObject) { + Json::Value o, out; + o["string"] = in_s; + o["int"] = in_i; + o["uint"] = in_u; + o["bool"] = in_b; + EXPECT_TRUE(GetValueFromJsonObject(o, "int", &out)); + EXPECT_TRUE(GetValueFromJsonObject(o, "bool", &out)); + EXPECT_FALSE(GetValueFromJsonObject(o, "foo", &out)); + EXPECT_FALSE(GetValueFromJsonObject(o, "", &out)); +} + +namespace { +template +std::vector VecOf3(const T& a, const T& b, const T& c) { + std::vector in; + in.push_back(a); + in.push_back(b); + in.push_back(c); + return in; +} +template +Json::Value JsonVecOf3(const T& a, const T& b, const T& c) { + Json::Value in(Json::arrayValue); + in.append(a); + in.append(b); + in.append(c); + return in; +} +} // unnamed namespace + +TEST(JsonTest, ValueVectorToFromArray) { + std::vector in = VecOf3("a", "b", "c"); + Json::Value out = ValueVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i].asString(), out[i].asString()); + } + Json::Value inj = JsonVecOf3("a", "b", "c"); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToValueVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, IntVectorToFromArray) { + std::vector in = VecOf3(1, 2, 3); + Json::Value out = IntVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asInt()); + } + Json::Value inj = JsonVecOf3(1, 2, 3); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToIntVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, UIntVectorToFromArray) { + std::vector in = VecOf3(1, 2, 3); + Json::Value out = UIntVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asUInt()); + } + Json::Value inj = JsonVecOf3(1, 2, 3); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToUIntVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, StringVectorToFromArray) { + std::vector in = VecOf3("a", "b", "c"); + Json::Value out = StringVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asString()); + } + Json::Value inj = JsonVecOf3("a", "b", "c"); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToStringVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, BoolVectorToFromArray) { + std::vector in = VecOf3(false, true, false); + Json::Value out = BoolVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asBool()); + } + Json::Value inj = JsonVecOf3(false, true, false); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToBoolVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, DoubleVectorToFromArray) { + std::vector in = VecOf3(1.0, 2.0, 3.0); + Json::Value out = DoubleVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asDouble()); + } + Json::Value inj = JsonVecOf3(1.0, 2.0, 3.0); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToDoubleVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} diff --git a/webrtc/base/latebindingsymboltable.cc b/webrtc/base/latebindingsymboltable.cc new file mode 100644 index 000000000..1896bd0f9 --- /dev/null +++ b/webrtc/base/latebindingsymboltable.cc @@ -0,0 +1,156 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/latebindingsymboltable.h" + +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/logging.h" + +namespace rtc { + +#if defined(WEBRTC_POSIX) +static const DllHandle kInvalidDllHandle = NULL; +#else +#error Not implemented +#endif + +static const char *GetDllError() { +#if defined(WEBRTC_POSIX) + const char *err = dlerror(); + if (err) { + return err; + } else { + return "No error"; + } +#else +#error Not implemented +#endif +} + +static bool LoadSymbol(DllHandle handle, + const char *symbol_name, + void **symbol) { +#if defined(WEBRTC_POSIX) + *symbol = dlsym(handle, symbol_name); + const char *err = dlerror(); + if (err) { + LOG(LS_ERROR) << "Error loading symbol " << symbol_name << ": " << err; + return false; + } else if (!*symbol) { + // ELF allows for symbols to be NULL, but that should never happen for our + // usage. + LOG(LS_ERROR) << "Symbol " << symbol_name << " is NULL"; + return false; + } + return true; +#else +#error Not implemented +#endif +} + +LateBindingSymbolTable::LateBindingSymbolTable(const TableInfo *info, + void **table) + : info_(info), + table_(table), + handle_(kInvalidDllHandle), + undefined_symbols_(false) { + ClearSymbols(); +} + +LateBindingSymbolTable::~LateBindingSymbolTable() { + Unload(); +} + +bool LateBindingSymbolTable::IsLoaded() const { + return handle_ != kInvalidDllHandle; +} + +bool LateBindingSymbolTable::Load() { + ASSERT(info_->dll_name != NULL); + return LoadFromPath(info_->dll_name); +} + +bool LateBindingSymbolTable::LoadFromPath(const char *dll_path) { + if (IsLoaded()) { + return true; + } + if (undefined_symbols_) { + // We do not attempt to load again because repeated attempts are not + // likely to succeed and DLL loading is costly. + LOG(LS_ERROR) << "We know there are undefined symbols"; + return false; + } + +#if defined(WEBRTC_POSIX) + handle_ = dlopen(dll_path, + // RTLD_NOW front-loads symbol resolution so that errors are + // caught early instead of causing a process abort later. + // RTLD_LOCAL prevents other modules from automatically + // seeing symbol definitions in the newly-loaded tree. This + // is necessary for same-named symbols in different ABI + // versions of the same library to not explode. + RTLD_NOW|RTLD_LOCAL +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // RTLD_DEEPBIND makes symbol dependencies in the + // newly-loaded tree prefer to resolve to definitions within + // that tree (the default on OS X). This is necessary for + // same-named symbols in different ABI versions of the same + // library to not explode. + |RTLD_DEEPBIND +#endif + ); // NOLINT +#else +#error Not implemented +#endif + + if (handle_ == kInvalidDllHandle) { + LOG(LS_WARNING) << "Can't load " << dll_path << ": " + << GetDllError(); + return false; + } +#if defined(WEBRTC_POSIX) + // Clear any old errors. + dlerror(); +#endif + for (int i = 0; i < info_->num_symbols; ++i) { + if (!LoadSymbol(handle_, info_->symbol_names[i], &table_[i])) { + undefined_symbols_ = true; + Unload(); + return false; + } + } + return true; +} + +void LateBindingSymbolTable::Unload() { + if (!IsLoaded()) { + return; + } + +#if defined(WEBRTC_POSIX) + if (dlclose(handle_) != 0) { + LOG(LS_ERROR) << GetDllError(); + } +#else +#error Not implemented +#endif + + handle_ = kInvalidDllHandle; + ClearSymbols(); +} + +void LateBindingSymbolTable::ClearSymbols() { + memset(table_, 0, sizeof(void *) * info_->num_symbols); +} + +} // namespace rtc diff --git a/webrtc/base/latebindingsymboltable.cc.def b/webrtc/base/latebindingsymboltable.cc.def new file mode 100644 index 000000000..6ddb2ae62 --- /dev/null +++ b/webrtc/base/latebindingsymboltable.cc.def @@ -0,0 +1,69 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This file is a supermacro +// (see http://wanderinghorse.net/computing/papers/supermacros_cpp.html) to +// expand a definition of a late-binding symbol table class. +// +// Arguments: +// LATE_BINDING_SYMBOL_TABLE_CLASS_NAME: Name of the class to generate. +// LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST: List of symbols to load from the DLL, +// as an X-Macro list (see http://www.drdobbs.com/blogs/cpp/228700289). +// LATE_BINDING_SYMBOL_TABLE_DLL_NAME: String literal for the DLL file name to +// load. +// +// From a .cc file, include the header file containing your call to the .h.def +// supermacro, and then call this supermacro (optionally from inside the +// namespace for the class to generate, if any). Example: +// +// #include "myclassname.h" +// +// namespace foo { +// +// #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME MY_CLASS_NAME +// #define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST MY_SYMBOLS_LIST +// #define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libdll.so.n" +// #include "webrtc/base/latebindingsymboltable.cc.def" +// +// } + +#ifndef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#error You must define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#error You must define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_DLL_NAME +#error You must define LATE_BINDING_SYMBOL_TABLE_DLL_NAME +#endif + +#define X(sym) #sym, +const char* const LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kSymbolNames[] = { + LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +}; +#undef X + +const ::rtc::LateBindingSymbolTable::TableInfo + LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kTableInfo = { + LATE_BINDING_SYMBOL_TABLE_DLL_NAME, + SYMBOL_TABLE_SIZE, + LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kSymbolNames +}; + +LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::LATE_BINDING_SYMBOL_TABLE_CLASS_NAME() + : ::rtc::LateBindingSymbolTable(&kTableInfo, table_) {} + +LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::~LATE_BINDING_SYMBOL_TABLE_CLASS_NAME() {} + +#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#undef LATE_BINDING_SYMBOL_TABLE_DLL_NAME diff --git a/webrtc/base/latebindingsymboltable.h b/webrtc/base/latebindingsymboltable.h new file mode 100644 index 000000000..c1f535cd2 --- /dev/null +++ b/webrtc/base/latebindingsymboltable.h @@ -0,0 +1,69 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_LATEBINDINGSYMBOLTABLE_H_ +#define WEBRTC_BASE_LATEBINDINGSYMBOLTABLE_H_ + +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +#if defined(WEBRTC_POSIX) +typedef void *DllHandle; +#else +#error Not implemented for this platform +#endif + +// This is the base class for "symbol table" classes to simplify the dynamic +// loading of symbols from DLLs. Currently the implementation only supports +// Linux and OS X, and pure C symbols (or extern "C" symbols that wrap C++ +// functions). Sub-classes for specific DLLs are generated via the "supermacro" +// files latebindingsymboltable.h.def and latebindingsymboltable.cc.def. See +// talk/sound/pulseaudiosymboltable.(h|cc) for an example. +class LateBindingSymbolTable { + public: + struct TableInfo { + const char *dll_name; + int num_symbols; + // Array of size num_symbols. + const char *const *symbol_names; + }; + + LateBindingSymbolTable(const TableInfo *info, void **table); + ~LateBindingSymbolTable(); + + bool IsLoaded() const; + // Loads the DLL and the symbol table. Returns true iff the DLL and symbol + // table loaded successfully. + bool Load(); + // Like load, but allows overriding the dll path for when the dll path is + // dynamic. + bool LoadFromPath(const char *dll_path); + void Unload(); + + // Gets the raw OS handle to the DLL. Be careful what you do with it. + DllHandle GetDllHandle() const { return handle_; } + + private: + void ClearSymbols(); + + const TableInfo *info_; + void **table_; + DllHandle handle_; + bool undefined_symbols_; + + DISALLOW_COPY_AND_ASSIGN(LateBindingSymbolTable); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_LATEBINDINGSYMBOLTABLE_H_ diff --git a/webrtc/base/latebindingsymboltable.h.def b/webrtc/base/latebindingsymboltable.h.def new file mode 100644 index 000000000..39b515fbd --- /dev/null +++ b/webrtc/base/latebindingsymboltable.h.def @@ -0,0 +1,83 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This file is a supermacro +// (see http://wanderinghorse.net/computing/papers/supermacros_cpp.html) to +// expand a declaration of a late-binding symbol table class. +// +// Arguments: +// LATE_BINDING_SYMBOL_TABLE_CLASS_NAME: Name of the class to generate. +// LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST: List of symbols to load from the DLL, +// as an X-Macro list (see http://www.drdobbs.com/blogs/cpp/228700289). +// +// From a .h file, include the header(s) for the DLL to late-bind and the +// latebindingsymboltable.h header, and then call this supermacro (optionally +// from inside the namespace for the class to generate, if any). Example: +// +// #include +// +// #include "webrtc/base/latebindingsymboltable.h" +// +// namespace foo { +// +// #define MY_CLASS_NAME DesiredClassName +// #define MY_SYMBOLS_LIST X(acos) X(sin) X(tan) +// +// #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME MY_CLASS_NAME +// #define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST MY_SYMBOLS_LIST +// #include "webrtc/base/latebindingsymboltable.h.def" +// +// } + +#ifndef WEBRTC_BASE_LATEBINDINGSYMBOLTABLE_H_ +#error You must first include latebindingsymboltable.h +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#error You must define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#error You must define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#endif + +class LATE_BINDING_SYMBOL_TABLE_CLASS_NAME : + public ::rtc::LateBindingSymbolTable { + public: + LATE_BINDING_SYMBOL_TABLE_CLASS_NAME(); + ~LATE_BINDING_SYMBOL_TABLE_CLASS_NAME(); + +#define X(sym) \ + typeof(&::sym) sym() const { \ + ASSERT(::rtc::LateBindingSymbolTable::IsLoaded()); \ + return reinterpret_cast(table_[SYMBOL_TABLE_INDEX_##sym]); \ + } +LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#undef X + + private: + enum { +#define X(sym) \ + SYMBOL_TABLE_INDEX_##sym, +LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#undef X + SYMBOL_TABLE_SIZE + }; + + static const ::rtc::LateBindingSymbolTable::TableInfo kTableInfo; + static const char *const kSymbolNames[]; + + void *table_[SYMBOL_TABLE_SIZE]; + + DISALLOW_COPY_AND_ASSIGN(LATE_BINDING_SYMBOL_TABLE_CLASS_NAME); +}; + +#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST diff --git a/webrtc/base/latebindingsymboltable_unittest.cc b/webrtc/base/latebindingsymboltable_unittest.cc new file mode 100644 index 000000000..30ebd17cb --- /dev/null +++ b/webrtc/base/latebindingsymboltable_unittest.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include +#endif + +#include "webrtc/base/gunit.h" +#include "webrtc/base/latebindingsymboltable.h" + +namespace rtc { + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + +#define LIBM_SYMBOLS_CLASS_NAME LibmTestSymbolTable +#define LIBM_SYMBOLS_LIST \ + X(acos) \ + X(sin) \ + X(tan) + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBM_SYMBOLS_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBM_SYMBOLS_LIST +#include "webrtc/base/latebindingsymboltable.h.def" + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBM_SYMBOLS_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBM_SYMBOLS_LIST +#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libm.so.6" +#include "webrtc/base/latebindingsymboltable.cc.def" + +TEST(LateBindingSymbolTable, libm) { + LibmTestSymbolTable table; + EXPECT_FALSE(table.IsLoaded()); + ASSERT_TRUE(table.Load()); + EXPECT_TRUE(table.IsLoaded()); + EXPECT_EQ(table.acos()(0.5), acos(0.5)); + EXPECT_EQ(table.sin()(0.5), sin(0.5)); + EXPECT_EQ(table.tan()(0.5), tan(0.5)); + // It would be nice to check that the addresses are the same, but the nature + // of dynamic linking and relocation makes them actually be different. + table.Unload(); + EXPECT_FALSE(table.IsLoaded()); +} + +#else +#error Not implemented +#endif + +} // namespace rtc diff --git a/webrtc/base/libdbusglibsymboltable.cc b/webrtc/base/libdbusglibsymboltable.cc new file mode 100644 index 000000000..ad51064bc --- /dev/null +++ b/webrtc/base/libdbusglibsymboltable.cc @@ -0,0 +1,24 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifdef HAVE_DBUS_GLIB + +#include "webrtc/base/libdbusglibsymboltable.h" + +namespace rtc { + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBDBUS_GLIB_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBDBUS_GLIB_SYMBOLS_LIST +#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libdbus-glib-1.so.2" +#include "webrtc/base/latebindingsymboltable.cc.def" + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB diff --git a/webrtc/base/libdbusglibsymboltable.h b/webrtc/base/libdbusglibsymboltable.h new file mode 100644 index 000000000..b87b4c174 --- /dev/null +++ b/webrtc/base/libdbusglibsymboltable.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_LIBDBUSGLIBSYMBOLTABLE_H_ +#define WEBRTC_BASE_LIBDBUSGLIBSYMBOLTABLE_H_ + +#ifdef HAVE_DBUS_GLIB + +#include +#include + +#include "webrtc/base/latebindingsymboltable.h" + +namespace rtc { + +#define LIBDBUS_GLIB_CLASS_NAME LibDBusGlibSymbolTable +// The libdbus-glib symbols we need, as an X-Macro list. +// This list must contain precisely every libdbus-glib function that is used in +// dbus.cc. +#define LIBDBUS_GLIB_SYMBOLS_LIST \ + X(dbus_bus_add_match) \ + X(dbus_connection_add_filter) \ + X(dbus_connection_close) \ + X(dbus_connection_remove_filter) \ + X(dbus_connection_set_exit_on_disconnect) \ + X(dbus_g_bus_get) \ + X(dbus_g_bus_get_private) \ + X(dbus_g_connection_get_connection) \ + X(dbus_g_connection_unref) \ + X(dbus_g_thread_init) \ + X(dbus_message_get_interface) \ + X(dbus_message_get_member) \ + X(dbus_message_get_path) \ + X(dbus_message_get_type) \ + X(dbus_message_iter_get_arg_type) \ + X(dbus_message_iter_get_basic) \ + X(dbus_message_iter_init) \ + X(dbus_message_ref) \ + X(dbus_message_unref) + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBDBUS_GLIB_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBDBUS_GLIB_SYMBOLS_LIST +#include "webrtc/base/latebindingsymboltable.h.def" + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB + +#endif // WEBRTC_BASE_LIBDBUSGLIBSYMBOLTABLE_H_ diff --git a/webrtc/base/linked_ptr.h b/webrtc/base/linked_ptr.h new file mode 100644 index 000000000..65e5a00ec --- /dev/null +++ b/webrtc/base/linked_ptr.h @@ -0,0 +1,125 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * linked_ptr - simple reference linked pointer + * (like reference counting, just using a linked list of the references + * instead of their count.) + * + * The implementation stores three pointers for every linked_ptr, but + * does not allocate anything on the free store. + */ + +#ifndef WEBRTC_BASE_LINKED_PTR_H__ +#define WEBRTC_BASE_LINKED_PTR_H__ + +namespace rtc { + +/* For ANSI-challenged compilers, you may want to #define + * NO_MEMBER_TEMPLATES, explicit or mutable */ +#define NO_MEMBER_TEMPLATES + +template class linked_ptr +{ +public: + +#ifndef NO_MEMBER_TEMPLATES +# define TEMPLATE_FUNCTION template + TEMPLATE_FUNCTION friend class linked_ptr; +#else +# define TEMPLATE_FUNCTION + typedef X Y; +#endif + + typedef X element_type; + + explicit linked_ptr(X* p = 0) throw() + : itsPtr(p) {itsPrev = itsNext = this;} + ~linked_ptr() + {release();} + linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } + +#ifndef NO_MEMBER_TEMPLATES + template friend class linked_ptr; + template linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + template linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } +#endif // NO_MEMBER_TEMPLATES + + X& operator*() const throw() {return *itsPtr;} + X* operator->() const throw() {return itsPtr;} + X* get() const throw() {return itsPtr;} + bool unique() const throw() {return itsPrev ? itsPrev==this : true;} + +private: + X* itsPtr; + mutable const linked_ptr* itsPrev; + mutable const linked_ptr* itsNext; + + void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } + +#ifndef NO_MEMBER_TEMPLATES + template void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } +#endif // NO_MEMBER_TEMPLATES + + void release() + { // erase this from the list, delete if unique + if (unique()) delete itsPtr; + else { + itsPrev->itsNext = itsNext; + itsNext->itsPrev = itsPrev; + itsPrev = itsNext = 0; + } + itsPtr = 0; + } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_LINKED_PTR_H__ + diff --git a/webrtc/base/linux.cc b/webrtc/base/linux.cc new file mode 100644 index 000000000..b95854353 --- /dev/null +++ b/webrtc/base/linux.cc @@ -0,0 +1,348 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_LINUX) +#include "webrtc/base/linux.h" + +#include + +#include +#include +#include + +#include +#include + +#include "webrtc/base/stringencode.h" + +namespace rtc { + +static const char kCpuInfoFile[] = "/proc/cpuinfo"; +static const char kCpuMaxFreqFile[] = + "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"; + +ProcCpuInfo::ProcCpuInfo() { +} + +ProcCpuInfo::~ProcCpuInfo() { +} + +bool ProcCpuInfo::LoadFromSystem() { + ConfigParser procfs; + if (!procfs.Open(kCpuInfoFile)) { + return false; + } + return procfs.Parse(§ions_); +}; + +bool ProcCpuInfo::GetSectionCount(size_t* count) { + if (sections_.empty()) { + return false; + } + if (count) { + *count = sections_.size(); + } + return true; +} + +bool ProcCpuInfo::GetNumCpus(int* num) { + if (sections_.empty()) { + return false; + } + int total_cpus = 0; +#if defined(__arm__) + // Count the number of blocks that have a "processor" key defined. On ARM, + // there may be extra blocks of information that aren't per-processor. + size_t section_count = sections_.size(); + for (size_t i = 0; i < section_count; ++i) { + int processor_id; + if (GetSectionIntValue(i, "processor", &processor_id)) { + ++total_cpus; + } + } + // Single core ARM systems don't include "processor" keys at all, so return + // that we have a single core if we didn't find any explicitly above. + if (total_cpus == 0) { + total_cpus = 1; + } +#else + // On X86, there is exactly one info section per processor. + total_cpus = static_cast(sections_.size()); +#endif + if (num) { + *num = total_cpus; + } + return true; +} + +bool ProcCpuInfo::GetNumPhysicalCpus(int* num) { + if (sections_.empty()) { + return false; + } + // TODO: /proc/cpuinfo only reports cores that are currently + // _online_, so this may underreport the number of physical cores. +#if defined(__arm__) + // ARM (currently) has no hyperthreading, so just return the same value + // as GetNumCpus. + return GetNumCpus(num); +#else + int total_cores = 0; + std::set physical_ids; + size_t section_count = sections_.size(); + for (size_t i = 0; i < section_count; ++i) { + int physical_id; + int cores; + // Count the cores for the physical id only if we have not counted the id. + if (GetSectionIntValue(i, "physical id", &physical_id) && + GetSectionIntValue(i, "cpu cores", &cores) && + physical_ids.find(physical_id) == physical_ids.end()) { + physical_ids.insert(physical_id); + total_cores += cores; + } + } + + if (num) { + *num = total_cores; + } + return true; +#endif +} + +bool ProcCpuInfo::GetCpuFamily(int* id) { + int cpu_family = 0; + +#if defined(__arm__) + // On some ARM platforms, there is no 'cpu family' in '/proc/cpuinfo'. But + // there is 'CPU Architecture' which can be used as 'cpu family'. + // See http://en.wikipedia.org/wiki/ARM_architecture for a good list of + // ARM cpu families, architectures, and their mappings. + // There may be multiple sessions that aren't per-processor. We need to scan + // through each session until we find the first 'CPU architecture'. + size_t section_count = sections_.size(); + for (size_t i = 0; i < section_count; ++i) { + if (GetSectionIntValue(i, "CPU architecture", &cpu_family)) { + // We returns the first one (if there are multiple entries). + break; + }; + } +#else + GetSectionIntValue(0, "cpu family", &cpu_family); +#endif + if (id) { + *id = cpu_family; + } + return true; +} + +bool ProcCpuInfo::GetSectionStringValue(size_t section_num, + const std::string& key, + std::string* result) { + if (section_num >= sections_.size()) { + return false; + } + ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key); + if (iter == sections_[section_num].end()) { + return false; + } + *result = iter->second; + return true; +} + +bool ProcCpuInfo::GetSectionIntValue(size_t section_num, + const std::string& key, + int* result) { + if (section_num >= sections_.size()) { + return false; + } + ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key); + if (iter == sections_[section_num].end()) { + return false; + } + return FromString(iter->second, result); +} + +ConfigParser::ConfigParser() {} + +ConfigParser::~ConfigParser() {} + +bool ConfigParser::Open(const std::string& filename) { + FileStream* fs = new FileStream(); + if (!fs->Open(filename, "r", NULL)) { + return false; + } + instream_.reset(fs); + return true; +} + +void ConfigParser::Attach(StreamInterface* stream) { + instream_.reset(stream); +} + +bool ConfigParser::Parse(MapVector* key_val_pairs) { + // Parses the file and places the found key-value pairs into key_val_pairs. + SimpleMap section; + while (ParseSection(§ion)) { + key_val_pairs->push_back(section); + section.clear(); + } + return (!key_val_pairs->empty()); +} + +bool ConfigParser::ParseSection(SimpleMap* key_val_pair) { + // Parses the next section in the filestream and places the found key-value + // pairs into key_val_pair. + std::string key, value; + while (ParseLine(&key, &value)) { + (*key_val_pair)[key] = value; + } + return (!key_val_pair->empty()); +} + +bool ConfigParser::ParseLine(std::string* key, std::string* value) { + // Parses the next line in the filestream and places the found key-value + // pair into key and val. + std::string line; + if ((instream_->ReadLine(&line)) == SR_EOS) { + return false; + } + std::vector tokens; + if (2 != split(line, ':', &tokens)) { + return false; + } + // Removes whitespace at the end of Key name + size_t pos = tokens[0].length() - 1; + while ((pos > 0) && isspace(tokens[0][pos])) { + pos--; + } + tokens[0].erase(pos + 1); + // Removes whitespace at the start of value + pos = 0; + while (pos < tokens[1].length() && isspace(tokens[1][pos])) { + pos++; + } + tokens[1].erase(0, pos); + *key = tokens[0]; + *value = tokens[1]; + return true; +} + +#if !defined(WEBRTC_CHROMIUM_BUILDs) +static bool ExpectLineFromStream(FileStream* stream, + std::string* out) { + StreamResult res = stream->ReadLine(out); + if (res != SR_SUCCESS) { + if (res != SR_EOS) { + LOG(LS_ERROR) << "Error when reading from stream"; + } else { + LOG(LS_ERROR) << "Incorrect number of lines in stream"; + } + return false; + } + return true; +} + +static void ExpectEofFromStream(FileStream* stream) { + std::string unused; + StreamResult res = stream->ReadLine(&unused); + if (res == SR_SUCCESS) { + LOG(LS_WARNING) << "Ignoring unexpected extra lines from stream"; + } else if (res != SR_EOS) { + LOG(LS_WARNING) << "Error when checking for extra lines from stream"; + } +} + +// For caching the lsb_release output (reading it invokes a sub-process and +// hence is somewhat expensive). +static std::string lsb_release_string; +static CriticalSection lsb_release_string_critsec; + +std::string ReadLinuxLsbRelease() { + CritScope cs(&lsb_release_string_critsec); + if (!lsb_release_string.empty()) { + // Have cached result from previous call. + return lsb_release_string; + } + // No cached result. Run lsb_release and parse output. + POpenStream lsb_release_output; + if (!lsb_release_output.Open("lsb_release -idrcs", "r", NULL)) { + LOG_ERR(LS_ERROR) << "Can't run lsb_release"; + return lsb_release_string; // empty + } + // Read in the command's output and build the string. + std::ostringstream sstr; + std::string line; + int wait_status; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << "DISTRIB_ID=" << line; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << " DISTRIB_DESCRIPTION=\"" << line << '"'; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << " DISTRIB_RELEASE=" << line; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << " DISTRIB_CODENAME=" << line; + + // Should not be anything left. + ExpectEofFromStream(&lsb_release_output); + + lsb_release_output.Close(); + wait_status = lsb_release_output.GetWaitStatus(); + if (wait_status == -1 || + !WIFEXITED(wait_status) || + WEXITSTATUS(wait_status) != 0) { + LOG(LS_WARNING) << "Unexpected exit status from lsb_release"; + } + + lsb_release_string = sstr.str(); + + return lsb_release_string; +} +#endif + +std::string ReadLinuxUname() { + struct utsname buf; + if (uname(&buf) < 0) { + LOG_ERR(LS_ERROR) << "Can't call uname()"; + return std::string(); + } + std::ostringstream sstr; + sstr << buf.sysname << " " + << buf.release << " " + << buf.version << " " + << buf.machine; + return sstr.str(); +} + +int ReadCpuMaxFreq() { + FileStream fs; + std::string str; + int freq = -1; + if (!fs.Open(kCpuMaxFreqFile, "r", NULL) || + SR_SUCCESS != fs.ReadLine(&str) || + !FromString(str, &freq)) { + return -1; + } + return freq; +} + +} // namespace rtc + +#endif // defined(WEBRTC_LINUX) diff --git a/webrtc/base/linux.h b/webrtc/base/linux.h new file mode 100644 index 000000000..8f601878f --- /dev/null +++ b/webrtc/base/linux.h @@ -0,0 +1,123 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_LINUX_H_ +#define WEBRTC_BASE_LINUX_H_ + +#if defined(WEBRTC_LINUX) +#include +#include +#include + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////////////// +// ConfigParser parses a FileStream of an ".ini."-type format into a map. +////////////////////////////////////////////////////////////////////////////// + +// Sample Usage: +// ConfigParser parser; +// ConfigParser::MapVector key_val_pairs; +// if (parser.Open(inifile) && parser.Parse(&key_val_pairs)) { +// for (int section_num=0; i < key_val_pairs.size(); ++section_num) { +// std::string val1 = key_val_pairs[section_num][key1]; +// std::string val2 = key_val_pairs[section_num][key2]; +// // Do something with valn; +// } +// } + +class ConfigParser { + public: + typedef std::map SimpleMap; + typedef std::vector MapVector; + + ConfigParser(); + virtual ~ConfigParser(); + + virtual bool Open(const std::string& filename); + virtual void Attach(StreamInterface* stream); + virtual bool Parse(MapVector* key_val_pairs); + virtual bool ParseSection(SimpleMap* key_val_pair); + virtual bool ParseLine(std::string* key, std::string* value); + + private: + scoped_ptr instream_; +}; + +////////////////////////////////////////////////////////////////////////////// +// ProcCpuInfo reads CPU info from the /proc subsystem on any *NIX platform. +////////////////////////////////////////////////////////////////////////////// + +// Sample Usage: +// ProcCpuInfo proc_info; +// int no_of_cpu; +// if (proc_info.LoadFromSystem()) { +// std::string out_str; +// proc_info.GetNumCpus(&no_of_cpu); +// proc_info.GetCpuStringValue(0, "vendor_id", &out_str); +// } +// } + +class ProcCpuInfo { + public: + ProcCpuInfo(); + virtual ~ProcCpuInfo(); + + // Reads the proc subsystem's cpu info into memory. If this fails, this + // returns false; if it succeeds, it returns true. + virtual bool LoadFromSystem(); + + // Obtains the number of logical CPU threads and places the value num. + virtual bool GetNumCpus(int* num); + + // Obtains the number of physical CPU cores and places the value num. + virtual bool GetNumPhysicalCpus(int* num); + + // Obtains the CPU family id. + virtual bool GetCpuFamily(int* id); + + // Obtains the number of sections in /proc/cpuinfo, which may be greater + // than the number of CPUs (e.g. on ARM) + virtual bool GetSectionCount(size_t* count); + + // Looks for the CPU proc item with the given name for the given section + // number and places the string value in result. + virtual bool GetSectionStringValue(size_t section_num, const std::string& key, + std::string* result); + + // Looks for the CPU proc item with the given name for the given section + // number and places the int value in result. + virtual bool GetSectionIntValue(size_t section_num, const std::string& key, + int* result); + + private: + ConfigParser::MapVector sections_; +}; + +#if !defined(WEBRTC_CHROMIUM_BUILDs) +// Builds a string containing the info from lsb_release on a single line. +std::string ReadLinuxLsbRelease(); +#endif + +// Returns the output of "uname". +std::string ReadLinuxUname(); + +// Returns the content (int) of +// /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq +// Returns -1 on error. +int ReadCpuMaxFreq(); + +} // namespace rtc + +#endif // defined(WEBRTC_LINUX) +#endif // WEBRTC_BASE_LINUX_H_ diff --git a/webrtc/base/linux_unittest.cc b/webrtc/base/linux_unittest.cc new file mode 100644 index 000000000..c65ef071c --- /dev/null +++ b/webrtc/base/linux_unittest.cc @@ -0,0 +1,104 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "webrtc/base/linux.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +// These tests running on ARM are fairly specific to the output of the tegra2 +// ARM processor, and so may fail on other ARM-based systems. +TEST(ProcCpuInfo, GetProcInfo) { + ProcCpuInfo proc_info; + EXPECT_TRUE(proc_info.LoadFromSystem()); + + int out_cpus = 0; + EXPECT_TRUE(proc_info.GetNumCpus(&out_cpus)); + LOG(LS_INFO) << "GetNumCpus: " << out_cpus; + EXPECT_GT(out_cpus, 0); + + int out_cpus_phys = 0; + EXPECT_TRUE(proc_info.GetNumPhysicalCpus(&out_cpus_phys)); + LOG(LS_INFO) << "GetNumPhysicalCpus: " << out_cpus_phys; + EXPECT_GT(out_cpus_phys, 0); + EXPECT_LE(out_cpus_phys, out_cpus); + + int out_family = 0; + EXPECT_TRUE(proc_info.GetCpuFamily(&out_family)); + LOG(LS_INFO) << "cpu family: " << out_family; + EXPECT_GE(out_family, 4); + +#if defined(__arm__) + std::string out_processor; + EXPECT_TRUE(proc_info.GetSectionStringValue(0, "Processor", &out_processor)); + LOG(LS_INFO) << "Processor: " << out_processor; + EXPECT_NE(std::string::npos, out_processor.find("ARM")); + + // Most other info, such as model, stepping, vendor, etc. + // is missing on ARM systems. +#else + int out_model = 0; + EXPECT_TRUE(proc_info.GetSectionIntValue(0, "model", &out_model)); + LOG(LS_INFO) << "model: " << out_model; + + int out_stepping = 0; + EXPECT_TRUE(proc_info.GetSectionIntValue(0, "stepping", &out_stepping)); + LOG(LS_INFO) << "stepping: " << out_stepping; + + int out_processor = 0; + EXPECT_TRUE(proc_info.GetSectionIntValue(0, "processor", &out_processor)); + LOG(LS_INFO) << "processor: " << out_processor; + EXPECT_EQ(0, out_processor); + + std::string out_str; + EXPECT_TRUE(proc_info.GetSectionStringValue(0, "vendor_id", &out_str)); + LOG(LS_INFO) << "vendor_id: " << out_str; + EXPECT_FALSE(out_str.empty()); +#endif +} + +TEST(ConfigParser, ParseConfig) { + ConfigParser parser; + MemoryStream *test_stream = new MemoryStream( + "Key1: Value1\n" + "Key2\t: Value2\n" + "Key3:Value3\n" + "\n" + "Key1:Value1\n"); + ConfigParser::MapVector key_val_pairs; + parser.Attach(test_stream); + EXPECT_EQ(true, parser.Parse(&key_val_pairs)); + EXPECT_EQ(2U, key_val_pairs.size()); + EXPECT_EQ("Value1", key_val_pairs[0]["Key1"]); + EXPECT_EQ("Value2", key_val_pairs[0]["Key2"]); + EXPECT_EQ("Value3", key_val_pairs[0]["Key3"]); + EXPECT_EQ("Value1", key_val_pairs[1]["Key1"]); + key_val_pairs.clear(); + EXPECT_EQ(true, parser.Open("/proc/cpuinfo")); + EXPECT_EQ(true, parser.Parse(&key_val_pairs)); +} + +#if !defined(WEBRTC_CHROMIUM_BUILDs) +TEST(ReadLinuxLsbRelease, ReturnsSomething) { + std::string str = ReadLinuxLsbRelease(); + // ChromeOS don't have lsb_release + // EXPECT_FALSE(str.empty()); +} +#endif + +TEST(ReadLinuxUname, ReturnsSomething) { + std::string str = ReadLinuxUname(); + EXPECT_FALSE(str.empty()); +} + +} // namespace rtc diff --git a/webrtc/base/linuxfdwalk.c b/webrtc/base/linuxfdwalk.c new file mode 100644 index 000000000..ae60cc524 --- /dev/null +++ b/webrtc/base/linuxfdwalk.c @@ -0,0 +1,81 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include + +#include "webrtc/base/linuxfdwalk.h" + +// Parses a file descriptor number in base 10, requiring the strict format used +// in /proc/*/fd. Returns the value, or -1 if not a valid string. +static int parse_fd(const char *s) { + if (!*s) { + // Empty string is invalid. + return -1; + } + int val = 0; + do { + if (*s < '0' || *s > '9') { + // Non-numeric characters anywhere are invalid. + return -1; + } + int digit = *s++ - '0'; + val = val * 10 + digit; + } while (*s); + return val; +} + +int fdwalk(void (*func)(void *, int), void *opaque) { + DIR *dir = opendir("/proc/self/fd"); + if (!dir) { + return -1; + } + int opendirfd = dirfd(dir); + int parse_errors = 0; + struct dirent *ent; + // Have to clear errno to distinguish readdir() completion from failure. + while (errno = 0, (ent = readdir(dir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) { + continue; + } + // We avoid atoi or strtol because those are part of libc and they involve + // locale stuff, which is probably not safe from a post-fork context in a + // multi-threaded app. + int fd = parse_fd(ent->d_name); + if (fd < 0) { + parse_errors = 1; + continue; + } + if (fd != opendirfd) { + (*func)(opaque, fd); + } + } + int saved_errno = errno; + if (closedir(dir) < 0) { + if (!saved_errno) { + // Return the closedir error. + return -1; + } + // Else ignore it because we have a more relevant error to return. + } + if (saved_errno) { + errno = saved_errno; + return -1; + } else if (parse_errors) { + errno = EBADF; + return -1; + } else { + return 0; + } +} diff --git a/webrtc/base/linuxfdwalk.h b/webrtc/base/linuxfdwalk.h new file mode 100644 index 000000000..fe5a6977d --- /dev/null +++ b/webrtc/base/linuxfdwalk.h @@ -0,0 +1,34 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_LINUXFDWALK_H_ +#define WEBRTC_BASE_LINUXFDWALK_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Linux port of SunOS's fdwalk(3) call. It loops over all open file descriptors +// and calls func on each one. Additionally, it is safe to use from the child +// of a fork that hasn't exec'ed yet, so you can use it to close all open file +// descriptors prior to exec'ing a daemon. +// The return value is 0 if successful, or else -1 and errno is set. The +// possible errors include any error that can be returned by opendir(), +// readdir(), or closedir(), plus EBADF if there are problems parsing the +// contents of /proc/self/fd. +// The file descriptors that are enumerated will not include the file descriptor +// used for the enumeration itself. +int fdwalk(void (*func)(void *, int), void *opaque); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBRTC_BASE_LINUXFDWALK_H_ diff --git a/webrtc/base/linuxfdwalk_unittest.cc b/webrtc/base/linuxfdwalk_unittest.cc new file mode 100644 index 000000000..bba48e887 --- /dev/null +++ b/webrtc/base/linuxfdwalk_unittest.cc @@ -0,0 +1,75 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/linuxfdwalk.h" + +#include +#include +#include +#include + +static const int kArbitraryLargeFdNumber = 424; + +static void FdCheckVisitor(void *data, int fd) { + std::set *fds = static_cast *>(data); + EXPECT_EQ(1U, fds->erase(fd)); +} + +static void FdEnumVisitor(void *data, int fd) { + std::set *fds = static_cast *>(data); + EXPECT_TRUE(fds->insert(fd).second); +} + +// Checks that the set of open fds is exactly the given list. +static void CheckOpenFdList(std::set fds) { + EXPECT_EQ(0, fdwalk(&FdCheckVisitor, &fds)); + EXPECT_EQ(0U, fds.size()); +} + +static void GetOpenFdList(std::set *fds) { + fds->clear(); + EXPECT_EQ(0, fdwalk(&FdEnumVisitor, fds)); +} + +TEST(LinuxFdWalk, TestFdWalk) { + std::set fds; + GetOpenFdList(&fds); + std::ostringstream str; + // I have observed that the open set when starting a test is [0, 6]. Leaked + // fds would change that, but so can (e.g.) running under a debugger, so we + // can't really do an EXPECT. :( + str << "File descriptors open in test executable:"; + for (std::set::const_iterator i = fds.begin(); i != fds.end(); ++i) { + str << " " << *i; + } + LOG(LS_INFO) << str.str(); + // Open some files. + int fd1 = open("/dev/null", O_RDONLY); + EXPECT_LE(0, fd1); + int fd2 = open("/dev/null", O_WRONLY); + EXPECT_LE(0, fd2); + int fd3 = open("/dev/null", O_RDWR); + EXPECT_LE(0, fd3); + int fd4 = dup2(fd3, kArbitraryLargeFdNumber); + EXPECT_LE(0, fd4); + EXPECT_TRUE(fds.insert(fd1).second); + EXPECT_TRUE(fds.insert(fd2).second); + EXPECT_TRUE(fds.insert(fd3).second); + EXPECT_TRUE(fds.insert(fd4).second); + CheckOpenFdList(fds); + EXPECT_EQ(0, close(fd1)); + EXPECT_EQ(0, close(fd2)); + EXPECT_EQ(0, close(fd3)); + EXPECT_EQ(0, close(fd4)); +} diff --git a/webrtc/base/linuxwindowpicker.cc b/webrtc/base/linuxwindowpicker.cc new file mode 100644 index 000000000..56d565e55 --- /dev/null +++ b/webrtc/base/linuxwindowpicker.cc @@ -0,0 +1,818 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/linuxwindowpicker.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "webrtc/base/logging.h" + +namespace rtc { + +// Convenience wrapper for XGetWindowProperty results. +template +class XWindowProperty { + public: + XWindowProperty(Display* display, Window window, Atom property) + : data_(NULL) { + const int kBitsPerByte = 8; + Atom actual_type; + int actual_format; + unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty + int status = XGetWindowProperty(display, window, property, 0L, ~0L, False, + AnyPropertyType, &actual_type, + &actual_format, &size_, + &bytes_after, &data_); + succeeded_ = (status == Success); + if (!succeeded_) { + data_ = NULL; // Ensure nothing is freed. + } else if (sizeof(PropertyType) * kBitsPerByte != actual_format) { + LOG(LS_WARNING) << "Returned type size differs from " + "requested type size."; + succeeded_ = false; + // We still need to call XFree in this case, so leave data_ alone. + } + if (!succeeded_) { + size_ = 0; + } + } + + ~XWindowProperty() { + if (data_) { + XFree(data_); + } + } + + bool succeeded() const { return succeeded_; } + size_t size() const { return size_; } + const PropertyType* data() const { + return reinterpret_cast(data_); + } + PropertyType* data() { + return reinterpret_cast(data_); + } + + private: + bool succeeded_; + unsigned long size_; // NOLINT: type required by XGetWindowProperty + unsigned char* data_; + + DISALLOW_COPY_AND_ASSIGN(XWindowProperty); +}; + +// Stupid X11. It seems none of the synchronous returns codes from X11 calls +// are meaningful unless an asynchronous error handler is configured. This +// RAII class registers and unregisters an X11 error handler. +class XErrorSuppressor { + public: + explicit XErrorSuppressor(Display* display) + : display_(display), original_error_handler_(NULL) { + SuppressX11Errors(); + } + ~XErrorSuppressor() { + UnsuppressX11Errors(); + } + + private: + static int ErrorHandler(Display* display, XErrorEvent* e) { + char buf[256]; + XGetErrorText(display, e->error_code, buf, sizeof buf); + LOG(LS_WARNING) << "Received X11 error \"" << buf << "\" for request code " + << static_cast(e->request_code); + return 0; + } + + void SuppressX11Errors() { + XFlush(display_); + XSync(display_, False); + original_error_handler_ = XSetErrorHandler(&ErrorHandler); + } + + void UnsuppressX11Errors() { + XFlush(display_); + XSync(display_, False); + XErrorHandler handler = XSetErrorHandler(original_error_handler_); + if (handler != &ErrorHandler) { + LOG(LS_WARNING) << "Unbalanced XSetErrorHandler() calls detected. " + << "Final error handler may not be what you expect!"; + } + original_error_handler_ = NULL; + } + + Display* display_; + XErrorHandler original_error_handler_; + + DISALLOW_COPY_AND_ASSIGN(XErrorSuppressor); +}; + +// Hiding all X11 specifics inside its own class. This to avoid +// conflicts between talk and X11 header declarations. +class XWindowEnumerator { + public: + XWindowEnumerator() + : display_(NULL), + has_composite_extension_(false), + has_render_extension_(false) { + } + + ~XWindowEnumerator() { + if (display_ != NULL) { + XCloseDisplay(display_); + } + } + + bool Init() { + if (display_ != NULL) { + // Already initialized. + return true; + } + display_ = XOpenDisplay(NULL); + if (display_ == NULL) { + LOG(LS_ERROR) << "Failed to open display."; + return false; + } + + XErrorSuppressor error_suppressor(display_); + + wm_state_ = XInternAtom(display_, "WM_STATE", True); + net_wm_icon_ = XInternAtom(display_, "_NET_WM_ICON", False); + + int event_base, error_base, major_version, minor_version; + if (XCompositeQueryExtension(display_, &event_base, &error_base) && + XCompositeQueryVersion(display_, &major_version, &minor_version) && + // XCompositeNameWindowPixmap() requires version 0.2 + (major_version > 0 || minor_version >= 2)) { + has_composite_extension_ = true; + } else { + LOG(LS_INFO) << "Xcomposite extension not available or too old."; + } + + if (XRenderQueryExtension(display_, &event_base, &error_base) && + XRenderQueryVersion(display_, &major_version, &minor_version) && + // XRenderSetPictureTransform() requires version 0.6 + (major_version > 0 || minor_version >= 6)) { + has_render_extension_ = true; + } else { + LOG(LS_INFO) << "Xrender extension not available or too old."; + } + return true; + } + + bool EnumerateWindows(WindowDescriptionList* descriptions) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + int num_screens = XScreenCount(display_); + bool result = false; + for (int i = 0; i < num_screens; ++i) { + if (EnumerateScreenWindows(descriptions, i)) { + // We know we succeded on at least one screen. + result = true; + } + } + return result; + } + + bool EnumerateDesktops(DesktopDescriptionList* descriptions) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + Window default_root_window = XDefaultRootWindow(display_); + int num_screens = XScreenCount(display_); + for (int i = 0; i < num_screens; ++i) { + Window root_window = XRootWindow(display_, i); + DesktopId id(DesktopId(root_window, i)); + // TODO: Figure out an appropriate desktop title. + DesktopDescription desc(id, ""); + desc.set_primary(root_window == default_root_window); + descriptions->push_back(desc); + } + return num_screens > 0; + } + + bool IsVisible(const WindowId& id) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, id.id(), &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + return false; + } + return attr.map_state == IsViewable; + } + + bool MoveToFront(const WindowId& id) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + unsigned int num_children; + Window* children; + Window parent; + Window root; + + // Find root window to pass event to. + int status = XQueryTree(display_, id.id(), &root, &parent, &children, + &num_children); + if (status == 0) { + LOG(LS_WARNING) << "Failed to query for child windows."; + return false; + } + if (children != NULL) { + XFree(children); + } + + // Move the window to front. + XRaiseWindow(display_, id.id()); + + // Some window managers (e.g., metacity in GNOME) consider it illegal to + // raise a window without also giving it input focus with + // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough. + Atom atom = XInternAtom(display_, "_NET_ACTIVE_WINDOW", True); + if (atom != None) { + XEvent xev; + long event_mask; + + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.window = id.id(); + xev.xclient.message_type = atom; + + // The format member is set to 8, 16, or 32 and specifies whether the + // data should be viewed as a list of bytes, shorts, or longs. + xev.xclient.format = 32; + + xev.xclient.data.l[0] = 0; + xev.xclient.data.l[1] = 0; + xev.xclient.data.l[2] = 0; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + event_mask = SubstructureRedirectMask | SubstructureNotifyMask; + + XSendEvent(display_, root, False, event_mask, &xev); + } + XFlush(display_); + return true; + } + + uint8* GetWindowIcon(const WindowId& id, int* width, int* height) { + if (!Init()) { + return NULL; + } + XErrorSuppressor error_suppressor(display_); + Atom ret_type; + int format; + unsigned long length, bytes_after, size; + unsigned char* data = NULL; + + // Find out the size of the icon data. + if (XGetWindowProperty( + display_, id.id(), net_wm_icon_, 0, 0, False, XA_CARDINAL, + &ret_type, &format, &length, &size, &data) == Success && + data) { + XFree(data); + } else { + LOG(LS_ERROR) << "Failed to get size of the icon."; + return NULL; + } + // Get the icon data, the format is one uint32 each for width and height, + // followed by the actual pixel data. + if (size >= 2 && + XGetWindowProperty( + display_, id.id(), net_wm_icon_, 0, size, False, XA_CARDINAL, + &ret_type, &format, &length, &bytes_after, &data) == Success && + data) { + uint32* data_ptr = reinterpret_cast(data); + int w, h; + w = data_ptr[0]; + h = data_ptr[1]; + if (size < static_cast(w * h + 2)) { + XFree(data); + LOG(LS_ERROR) << "Not a vaild icon."; + return NULL; + } + uint8* rgba = + ArgbToRgba(&data_ptr[2], 0, 0, w, h, w, h, true); + XFree(data); + *width = w; + *height = h; + return rgba; + } else { + LOG(LS_ERROR) << "Failed to get window icon data."; + return NULL; + } + } + + uint8* GetWindowThumbnail(const WindowId& id, int width, int height) { + if (!Init()) { + return NULL; + } + + if (!has_composite_extension_) { + // Without the Xcomposite extension we would only get a good thumbnail if + // the whole window is visible on screen and not covered by any + // other window. This is not something we want so instead, just + // bail out. + LOG(LS_INFO) << "No Xcomposite extension detected."; + return NULL; + } + XErrorSuppressor error_suppressor(display_); + + Window root; + int x; + int y; + unsigned int src_width; + unsigned int src_height; + unsigned int border_width; + unsigned int depth; + + // In addition to needing X11 server-side support for Xcomposite, it + // actually needs to be turned on for this window in order to get a good + // thumbnail. If the user has modern hardware/drivers but isn't using a + // compositing window manager, that won't be the case. Here we + // automatically turn it on for shareable windows so that we can get + // thumbnails. We used to avoid it because the transition is visually ugly, + // but recent window managers don't always redirect windows which led to + // no thumbnails at all, which is a worse experience. + + // Redirect drawing to an offscreen buffer (ie, turn on compositing). + // X11 remembers what has requested this and will turn it off for us when + // we exit. + XCompositeRedirectWindow(display_, id.id(), CompositeRedirectAutomatic); + Pixmap src_pixmap = XCompositeNameWindowPixmap(display_, id.id()); + if (!src_pixmap) { + // Even if the backing pixmap doesn't exist, this still should have + // succeeded and returned a valid handle (it just wouldn't be a handle to + // anything). So this is a real error path. + LOG(LS_ERROR) << "XCompositeNameWindowPixmap() failed"; + return NULL; + } + if (!XGetGeometry(display_, src_pixmap, &root, &x, &y, + &src_width, &src_height, &border_width, + &depth)) { + // If the window does not actually have a backing pixmap, this is the path + // that will "fail", so it's a warning rather than an error. + LOG(LS_WARNING) << "XGetGeometry() failed (probably composite is not in " + << "use)"; + XFreePixmap(display_, src_pixmap); + return NULL; + } + + // If we get to here, then composite is in use for this window and it has a + // valid backing pixmap. + + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, id.id(), &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + XFreePixmap(display_, src_pixmap); + return NULL; + } + + uint8* data = GetDrawableThumbnail(src_pixmap, + attr.visual, + src_width, + src_height, + width, + height); + XFreePixmap(display_, src_pixmap); + return data; + } + + int GetNumDesktops() { + if (!Init()) { + return -1; + } + + return XScreenCount(display_); + } + + uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height) { + if (!Init()) { + return NULL; + } + XErrorSuppressor error_suppressor(display_); + + Window root_window = id.id(); + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, root_window, &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + return NULL; + } + + return GetDrawableThumbnail(root_window, + attr.visual, + attr.width, + attr.height, + width, + height); + } + + bool GetDesktopDimensions(const DesktopId& id, int* width, int* height) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, id.id(), &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + return false; + } + *width = attr.width; + *height = attr.height; + return true; + } + + private: + uint8* GetDrawableThumbnail(Drawable src_drawable, + Visual* visual, + int src_width, + int src_height, + int dst_width, + int dst_height) { + if (!has_render_extension_) { + // Without the Xrender extension we would have to read the full window and + // scale it down in our process. Xrender is over a decade old so we aren't + // going to expend effort to support that situation. We still need to + // check though because probably some virtual VNC displays are in this + // category. + LOG(LS_INFO) << "No Xrender extension detected."; + return NULL; + } + + XRenderPictFormat* format = XRenderFindVisualFormat(display_, + visual); + if (!format) { + LOG(LS_ERROR) << "XRenderFindVisualFormat() failed"; + return NULL; + } + + // Create a picture to reference the window pixmap. + XRenderPictureAttributes pa; + pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets + Picture src = XRenderCreatePicture(display_, + src_drawable, + format, + CPSubwindowMode, + &pa); + if (!src) { + LOG(LS_ERROR) << "XRenderCreatePicture() failed"; + return NULL; + } + + // Create a picture to reference the destination pixmap. + Pixmap dst_pixmap = XCreatePixmap(display_, + src_drawable, + dst_width, + dst_height, + format->depth); + if (!dst_pixmap) { + LOG(LS_ERROR) << "XCreatePixmap() failed"; + XRenderFreePicture(display_, src); + return NULL; + } + + Picture dst = XRenderCreatePicture(display_, dst_pixmap, format, 0, NULL); + if (!dst) { + LOG(LS_ERROR) << "XRenderCreatePicture() failed"; + XFreePixmap(display_, dst_pixmap); + XRenderFreePicture(display_, src); + return NULL; + } + + // Clear the background. + XRenderColor transparent = {0}; + XRenderFillRectangle(display_, + PictOpSrc, + dst, + &transparent, + 0, + 0, + dst_width, + dst_height); + + // Calculate how much we need to scale the image. + double scale_x = static_cast(dst_width) / + static_cast(src_width); + double scale_y = static_cast(dst_height) / + static_cast(src_height); + double scale = rtc::_min(scale_y, scale_x); + + int scaled_width = round(src_width * scale); + int scaled_height = round(src_height * scale); + + // Render the thumbnail centered on both axis. + int centered_x = (dst_width - scaled_width) / 2; + int centered_y = (dst_height - scaled_height) / 2; + + // Scaling matrix + XTransform xform = { { + { XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) }, + { XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) }, + { XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(scale) } + } }; + XRenderSetPictureTransform(display_, src, &xform); + + // Apply filter to smooth out the image. + XRenderSetPictureFilter(display_, src, FilterBest, NULL, 0); + + // Render the image to the destination picture. + XRenderComposite(display_, + PictOpSrc, + src, + None, + dst, + 0, + 0, + 0, + 0, + centered_x, + centered_y, + scaled_width, + scaled_height); + + // Get the pixel data from the X server. TODO: XGetImage + // might be slow here, compare with ShmGetImage. + XImage* image = XGetImage(display_, + dst_pixmap, + 0, + 0, + dst_width, + dst_height, + AllPlanes, ZPixmap); + uint8* data = ArgbToRgba(reinterpret_cast(image->data), + centered_x, + centered_y, + scaled_width, + scaled_height, + dst_width, + dst_height, + false); + XDestroyImage(image); + XRenderFreePicture(display_, dst); + XFreePixmap(display_, dst_pixmap); + XRenderFreePicture(display_, src); + return data; + } + + uint8* ArgbToRgba(uint32* argb_data, int x, int y, int w, int h, + int stride_x, int stride_y, bool has_alpha) { + uint8* p; + int len = stride_x * stride_y * 4; + uint8* data = new uint8[len]; + memset(data, 0, len); + p = data + 4 * (y * stride_x + x); + for (int i = 0; i < h; ++i) { + for (int j = 0; j < w; ++j) { + uint32 argb; + uint32 rgba; + argb = argb_data[stride_x * (y + i) + x + j]; + rgba = (argb << 8) | (argb >> 24); + *p = rgba >> 24; + ++p; + *p = (rgba >> 16) & 0xff; + ++p; + *p = (rgba >> 8) & 0xff; + ++p; + *p = has_alpha ? rgba & 0xFF : 0xFF; + ++p; + } + p += (stride_x - w) * 4; + } + return data; + } + + bool EnumerateScreenWindows(WindowDescriptionList* descriptions, int screen) { + Window parent; + Window *children; + int status; + unsigned int num_children; + Window root_window = XRootWindow(display_, screen); + status = XQueryTree(display_, root_window, &root_window, &parent, &children, + &num_children); + if (status == 0) { + LOG(LS_ERROR) << "Failed to query for child windows."; + return false; + } + for (unsigned int i = 0; i < num_children; ++i) { + // Iterate in reverse order to display windows from front to back. +#ifdef CHROMEOS + // TODO(jhorwich): Short-term fix for crbug.com/120229: Don't need to + // filter, just return all windows and let the picker scan through them. + Window app_window = children[num_children - 1 - i]; +#else + Window app_window = GetApplicationWindow(children[num_children - 1 - i]); +#endif + if (app_window && + !LinuxWindowPicker::IsDesktopElement(display_, app_window)) { + std::string title; + if (GetWindowTitle(app_window, &title)) { + WindowId id(app_window); + WindowDescription desc(id, title); + descriptions->push_back(desc); + } + } + } + if (children != NULL) { + XFree(children); + } + return true; + } + + bool GetWindowTitle(Window window, std::string* title) { + int status; + bool result = false; + XTextProperty window_name; + window_name.value = NULL; + if (window) { + status = XGetWMName(display_, window, &window_name); + if (status && window_name.value && window_name.nitems) { + int cnt; + char **list = NULL; + status = Xutf8TextPropertyToTextList(display_, &window_name, &list, + &cnt); + if (status >= Success && cnt && *list) { + if (cnt > 1) { + LOG(LS_INFO) << "Window has " << cnt + << " text properties, only using the first one."; + } + *title = *list; + result = true; + } + if (list != NULL) { + XFreeStringList(list); + } + } + if (window_name.value != NULL) { + XFree(window_name.value); + } + } + return result; + } + + Window GetApplicationWindow(Window window) { + Window root, parent; + Window app_window = 0; + Window *children; + unsigned int num_children; + Atom type = None; + int format; + unsigned long nitems, after; + unsigned char *data; + + int ret = XGetWindowProperty(display_, window, + wm_state_, 0L, 2, + False, wm_state_, &type, &format, + &nitems, &after, &data); + if (ret != Success) { + LOG(LS_ERROR) << "XGetWindowProperty failed with return code " << ret + << " for window " << window << "."; + return 0; + } + if (type != None) { + int64 state = static_cast(*data); + XFree(data); + return state == NormalState ? window : 0; + } + XFree(data); + if (!XQueryTree(display_, window, &root, &parent, &children, + &num_children)) { + LOG(LS_ERROR) << "Failed to query for child windows although window" + << "does not have a valid WM_STATE."; + return 0; + } + for (unsigned int i = 0; i < num_children; ++i) { + app_window = GetApplicationWindow(children[i]); + if (app_window) { + break; + } + } + if (children != NULL) { + XFree(children); + } + return app_window; + } + + Atom wm_state_; + Atom net_wm_icon_; + Display* display_; + bool has_composite_extension_; + bool has_render_extension_; +}; + +LinuxWindowPicker::LinuxWindowPicker() : enumerator_(new XWindowEnumerator()) { +} + +LinuxWindowPicker::~LinuxWindowPicker() { +} + +bool LinuxWindowPicker::IsDesktopElement(_XDisplay* display, Window window) { + if (window == 0) { + LOG(LS_WARNING) << "Zero is never a valid window."; + return false; + } + + // First look for _NET_WM_WINDOW_TYPE. The standard + // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306) + // says this hint *should* be present on all windows, and we use the existence + // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not + // a desktop element (that is, only "normal" windows should be shareable). + Atom window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", True); + XWindowProperty window_type(display, window, window_type_atom); + if (window_type.succeeded() && window_type.size() > 0) { + Atom normal_window_type_atom = XInternAtom( + display, "_NET_WM_WINDOW_TYPE_NORMAL", True); + uint32_t* end = window_type.data() + window_type.size(); + bool is_normal = (end != std::find( + window_type.data(), end, normal_window_type_atom)); + return !is_normal; + } + + // Fall back on using the hint. + XClassHint class_hint; + Status s = XGetClassHint(display, window, &class_hint); + bool result = false; + if (s == 0) { + // No hints, assume this is a normal application window. + return result; + } + static const std::string gnome_panel("gnome-panel"); + static const std::string desktop_window("desktop_window"); + + if (gnome_panel.compare(class_hint.res_name) == 0 || + desktop_window.compare(class_hint.res_name) == 0) { + result = true; + } + XFree(class_hint.res_name); + XFree(class_hint.res_class); + return result; +} + +bool LinuxWindowPicker::Init() { + return enumerator_->Init(); +} + +bool LinuxWindowPicker::GetWindowList(WindowDescriptionList* descriptions) { + return enumerator_->EnumerateWindows(descriptions); +} + +bool LinuxWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { + return enumerator_->EnumerateDesktops(descriptions); +} + +bool LinuxWindowPicker::IsVisible(const WindowId& id) { + return enumerator_->IsVisible(id); +} + +bool LinuxWindowPicker::MoveToFront(const WindowId& id) { + return enumerator_->MoveToFront(id); +} + + +uint8* LinuxWindowPicker::GetWindowIcon(const WindowId& id, int* width, + int* height) { + return enumerator_->GetWindowIcon(id, width, height); +} + +uint8* LinuxWindowPicker::GetWindowThumbnail(const WindowId& id, int width, + int height) { + return enumerator_->GetWindowThumbnail(id, width, height); +} + +int LinuxWindowPicker::GetNumDesktops() { + return enumerator_->GetNumDesktops(); +} + +uint8* LinuxWindowPicker::GetDesktopThumbnail(const DesktopId& id, + int width, + int height) { + return enumerator_->GetDesktopThumbnail(id, width, height); +} + +bool LinuxWindowPicker::GetDesktopDimensions(const DesktopId& id, int* width, + int* height) { + return enumerator_->GetDesktopDimensions(id, width, height); +} + +} // namespace rtc diff --git a/webrtc/base/linuxwindowpicker.h b/webrtc/base/linuxwindowpicker.h new file mode 100644 index 000000000..f87b15081 --- /dev/null +++ b/webrtc/base/linuxwindowpicker.h @@ -0,0 +1,51 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_LINUXWINDOWPICKER_H_ +#define WEBRTC_BASE_LINUXWINDOWPICKER_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/windowpicker.h" + +// Avoid include . +struct _XDisplay; +typedef unsigned long Window; + +namespace rtc { + +class XWindowEnumerator; + +class LinuxWindowPicker : public WindowPicker { + public: + LinuxWindowPicker(); + ~LinuxWindowPicker(); + + static bool IsDesktopElement(_XDisplay* display, Window window); + + virtual bool Init(); + virtual bool IsVisible(const WindowId& id); + virtual bool MoveToFront(const WindowId& id); + virtual bool GetWindowList(WindowDescriptionList* descriptions); + virtual bool GetDesktopList(DesktopDescriptionList* descriptions); + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height); + uint8* GetWindowIcon(const WindowId& id, int* width, int* height); + uint8* GetWindowThumbnail(const WindowId& id, int width, int height); + int GetNumDesktops(); + uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height); + + private: + scoped_ptr enumerator_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_LINUXWINDOWPICKER_H_ diff --git a/webrtc/base/linuxwindowpicker_unittest.cc b/webrtc/base/linuxwindowpicker_unittest.cc new file mode 100644 index 000000000..c2276ccd6 --- /dev/null +++ b/webrtc/base/linuxwindowpicker_unittest.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/linuxwindowpicker.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/windowpicker.h" + +#if !defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID) +#error Only for Linux +#endif + +namespace rtc { + +TEST(LinuxWindowPickerTest, TestGetWindowList) { + MAYBE_SKIP_SCREENCAST_TEST(); + LinuxWindowPicker window_picker; + WindowDescriptionList descriptions; + window_picker.Init(); + window_picker.GetWindowList(&descriptions); +} + +TEST(LinuxWindowPickerTest, TestGetDesktopList) { + MAYBE_SKIP_SCREENCAST_TEST(); + LinuxWindowPicker window_picker; + DesktopDescriptionList descriptions; + EXPECT_TRUE(window_picker.Init()); + EXPECT_TRUE(window_picker.GetDesktopList(&descriptions)); + EXPECT_TRUE(descriptions.size() > 0); +} + +} // namespace rtc diff --git a/webrtc/base/logging.cc b/webrtc/base/logging.cc new file mode 100644 index 000000000..a417ed6c2 --- /dev/null +++ b/webrtc/base/logging.cc @@ -0,0 +1,618 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#define snprintf _snprintf +#undef ERROR // wingdi.h +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#elif defined(WEBRTC_ANDROID) +#include +static const char kLibjingle[] = "libjingle"; +// Android has a 1024 limit on log inputs. We use 60 chars as an +// approx for the header/tag portion. +// See android/system/core/liblog/logd_write.c +static const int kMaxLogLineSize = 1024 - 60; +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) || WEBRTC_ANDROID + +#include + +#include +#include +#include +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +///////////////////////////////////////////////////////////////////////////// +// Constant Labels +///////////////////////////////////////////////////////////////////////////// + +const char * FindLabel(int value, const ConstantLabel entries[]) { + for (int i = 0; entries[i].label; ++i) { + if (value == entries[i].value) { + return entries[i].label; + } + } + return 0; +} + +std::string ErrorName(int err, const ConstantLabel * err_table) { + if (err == 0) + return "No error"; + + if (err_table != 0) { + if (const char * value = FindLabel(err, err_table)) + return value; + } + + char buffer[16]; + snprintf(buffer, sizeof(buffer), "0x%08x", err); + return buffer; +} + +///////////////////////////////////////////////////////////////////////////// +// LogMessage +///////////////////////////////////////////////////////////////////////////// + +const int LogMessage::NO_LOGGING = LS_ERROR + 1; + +#if _DEBUG +static const int LOG_DEFAULT = LS_INFO; +#else // !_DEBUG +static const int LOG_DEFAULT = LogMessage::NO_LOGGING; +#endif // !_DEBUG + +// Global lock for log subsystem, only needed to serialize access to streams_. +CriticalSection LogMessage::crit_; + +// By default, release builds don't log, debug builds at info level +int LogMessage::min_sev_ = LOG_DEFAULT; +int LogMessage::dbg_sev_ = LOG_DEFAULT; + +// Don't bother printing context for the ubiquitous INFO log messages +int LogMessage::ctx_sev_ = LS_WARNING; + +// The list of logging streams currently configured. +// Note: we explicitly do not clean this up, because of the uncertain ordering +// of destructors at program exit. Let the person who sets the stream trigger +// cleanup by setting to NULL, or let it leak (safe at program exit). +LogMessage::StreamList LogMessage::streams_; + +// Boolean options default to false (0) +bool LogMessage::thread_, LogMessage::timestamp_; + +// If we're in diagnostic mode, we'll be explicitly set that way; default=false. +bool LogMessage::is_diagnostic_mode_ = false; + +LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev, + LogErrorContext err_ctx, int err, const char* module) + : severity_(sev), + warn_slow_logs_delay_(WARN_SLOW_LOGS_DELAY) { + if (timestamp_) { + uint32 time = TimeSince(LogStartTime()); + // Also ensure WallClockStartTime is initialized, so that it matches + // LogStartTime. + WallClockStartTime(); + print_stream_ << "[" << std::setfill('0') << std::setw(3) << (time / 1000) + << ":" << std::setw(3) << (time % 1000) << std::setfill(' ') + << "] "; + } + + if (thread_) { +#if defined(WEBRTC_WIN) + DWORD id = GetCurrentThreadId(); + print_stream_ << "[" << std::hex << id << std::dec << "] "; +#endif // WEBRTC_WIN + } + + if (severity_ >= ctx_sev_) { + print_stream_ << Describe(sev) << "(" << DescribeFile(file) + << ":" << line << "): "; + } + + if (err_ctx != ERRCTX_NONE) { + std::ostringstream tmp; + tmp << "[0x" << std::setfill('0') << std::hex << std::setw(8) << err << "]"; + switch (err_ctx) { + case ERRCTX_ERRNO: + tmp << " " << strerror(err); + break; +#if WEBRTC_WIN + case ERRCTX_HRESULT: { + char msgbuf[256]; + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM; + HMODULE hmod = GetModuleHandleA(module); + if (hmod) + flags |= FORMAT_MESSAGE_FROM_HMODULE; + if (DWORD len = FormatMessageA( + flags, hmod, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), NULL)) { + while ((len > 0) && + isspace(static_cast(msgbuf[len-1]))) { + msgbuf[--len] = 0; + } + tmp << " " << msgbuf; + } + break; + } +#endif // WEBRTC_WIN +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + case ERRCTX_OSSTATUS: { + tmp << " " << nonnull(GetMacOSStatusErrorString(err), "Unknown error"); + if (const char* desc = GetMacOSStatusCommentString(err)) { + tmp << ": " << desc; + } + break; + } +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + default: + break; + } + extra_ = tmp.str(); + } +} + +LogMessage::~LogMessage() { + if (!extra_.empty()) + print_stream_ << " : " << extra_; + print_stream_ << std::endl; + + const std::string& str = print_stream_.str(); + if (severity_ >= dbg_sev_) { + OutputToDebug(str, severity_); + } + + uint32 before = Time(); + // Must lock streams_ before accessing + CritScope cs(&crit_); + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + if (severity_ >= it->second) { + OutputToStream(it->first, str); + } + } + uint32 delay = TimeSince(before); + if (delay >= warn_slow_logs_delay_) { + LogMessage slow_log_warning = + rtc::LogMessage(__FILE__, __LINE__, LS_WARNING); + // If our warning is slow, we don't want to warn about it, because + // that would lead to inifinite recursion. So, give a really big + // number for the delay threshold. + slow_log_warning.warn_slow_logs_delay_ = UINT_MAX; + slow_log_warning.stream() << "Slow log: took " << delay << "ms to write " + << str.size() << " bytes."; + } +} + +uint32 LogMessage::LogStartTime() { + static const uint32 g_start = Time(); + return g_start; +} + +uint32 LogMessage::WallClockStartTime() { + static const uint32 g_start_wallclock = time(NULL); + return g_start_wallclock; +} + +void LogMessage::LogContext(int min_sev) { + ctx_sev_ = min_sev; +} + +void LogMessage::LogThreads(bool on) { + thread_ = on; +} + +void LogMessage::LogTimestamps(bool on) { + timestamp_ = on; +} + +void LogMessage::LogToDebug(int min_sev) { + dbg_sev_ = min_sev; + UpdateMinLogSeverity(); +} + +void LogMessage::LogToStream(StreamInterface* stream, int min_sev) { + CritScope cs(&crit_); + // Discard and delete all previously installed streams + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + delete it->first; + } + streams_.clear(); + // Install the new stream, if specified + if (stream) { + AddLogToStream(stream, min_sev); + } +} + +int LogMessage::GetLogToStream(StreamInterface* stream) { + CritScope cs(&crit_); + int sev = NO_LOGGING; + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + if (!stream || stream == it->first) { + sev = _min(sev, it->second); + } + } + return sev; +} + +void LogMessage::AddLogToStream(StreamInterface* stream, int min_sev) { + CritScope cs(&crit_); + streams_.push_back(std::make_pair(stream, min_sev)); + UpdateMinLogSeverity(); +} + +void LogMessage::RemoveLogToStream(StreamInterface* stream) { + CritScope cs(&crit_); + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + if (stream == it->first) { + streams_.erase(it); + break; + } + } + UpdateMinLogSeverity(); +} + +void LogMessage::ConfigureLogging(const char* params, const char* filename) { + int current_level = LS_VERBOSE; + int debug_level = GetLogToDebug(); + int file_level = GetLogToStream(); + + std::vector tokens; + tokenize(params, ' ', &tokens); + + for (size_t i = 0; i < tokens.size(); ++i) { + if (tokens[i].empty()) + continue; + + // Logging features + if (tokens[i] == "tstamp") { + LogTimestamps(); + } else if (tokens[i] == "thread") { + LogThreads(); + + // Logging levels + } else if (tokens[i] == "sensitive") { + current_level = LS_SENSITIVE; + } else if (tokens[i] == "verbose") { + current_level = LS_VERBOSE; + } else if (tokens[i] == "info") { + current_level = LS_INFO; + } else if (tokens[i] == "warning") { + current_level = LS_WARNING; + } else if (tokens[i] == "error") { + current_level = LS_ERROR; + } else if (tokens[i] == "none") { + current_level = NO_LOGGING; + + // Logging targets + } else if (tokens[i] == "file") { + file_level = current_level; + } else if (tokens[i] == "debug") { + debug_level = current_level; + } + } + +#if defined(WEBRTC_WIN) + if ((NO_LOGGING != debug_level) && !::IsDebuggerPresent()) { + // First, attempt to attach to our parent's console... so if you invoke + // from the command line, we'll see the output there. Otherwise, create + // our own console window. + // Note: These methods fail if a console already exists, which is fine. + bool success = false; + typedef BOOL (WINAPI* PFN_AttachConsole)(DWORD); + if (HINSTANCE kernel32 = ::LoadLibrary(L"kernel32.dll")) { + // AttachConsole is defined on WinXP+. + if (PFN_AttachConsole attach_console = reinterpret_cast + (::GetProcAddress(kernel32, "AttachConsole"))) { + success = (FALSE != attach_console(ATTACH_PARENT_PROCESS)); + } + ::FreeLibrary(kernel32); + } + if (!success) { + ::AllocConsole(); + } + } +#endif // WEBRTC_WIN + + LogToDebug(debug_level); + +#if !defined(__native_client__) // No logging to file in NaCl. + scoped_ptr stream; + if (NO_LOGGING != file_level) { + stream.reset(new FileStream); + if (!stream->Open(filename, "wb", NULL) || !stream->DisableBuffering()) { + stream.reset(); + } + } + + LogToStream(stream.release(), file_level); +#endif +} + +int LogMessage::ParseLogSeverity(const std::string& value) { + int level = NO_LOGGING; + if (value == "LS_SENSITIVE") { + level = LS_SENSITIVE; + } else if (value == "LS_VERBOSE") { + level = LS_VERBOSE; + } else if (value == "LS_INFO") { + level = LS_INFO; + } else if (value == "LS_WARNING") { + level = LS_WARNING; + } else if (value == "LS_ERROR") { + level = LS_ERROR; + } else if (isdigit(value[0])) { + level = atoi(value.c_str()); // NOLINT + } + return level; +} + +void LogMessage::UpdateMinLogSeverity() { + int min_sev = dbg_sev_; + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + min_sev = _min(dbg_sev_, it->second); + } + min_sev_ = min_sev; +} + +const char* LogMessage::Describe(LoggingSeverity sev) { + switch (sev) { + case LS_SENSITIVE: return "Sensitive"; + case LS_VERBOSE: return "Verbose"; + case LS_INFO: return "Info"; + case LS_WARNING: return "Warning"; + case LS_ERROR: return "Error"; + default: return ""; + } +} + +const char* LogMessage::DescribeFile(const char* file) { + const char* end1 = ::strrchr(file, '/'); + const char* end2 = ::strrchr(file, '\\'); + if (!end1 && !end2) + return file; + else + return (end1 > end2) ? end1 + 1 : end2 + 1; +} + +void LogMessage::OutputToDebug(const std::string& str, + LoggingSeverity severity) { + bool log_to_stderr = true; +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) && (!defined(DEBUG) || defined(NDEBUG)) + // On the Mac, all stderr output goes to the Console log and causes clutter. + // So in opt builds, don't log to stderr unless the user specifically sets + // a preference to do so. + CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault, + "logToStdErr", + kCFStringEncodingUTF8); + CFStringRef domain = CFBundleGetIdentifier(CFBundleGetMainBundle()); + if (key != NULL && domain != NULL) { + Boolean exists_and_is_valid; + Boolean should_log = + CFPreferencesGetAppBooleanValue(key, domain, &exists_and_is_valid); + // If the key doesn't exist or is invalid or is false, we will not log to + // stderr. + log_to_stderr = exists_and_is_valid && should_log; + } + if (key != NULL) { + CFRelease(key); + } +#endif +#if defined(WEBRTC_WIN) + // Always log to the debugger. + // Perhaps stderr should be controlled by a preference, as on Mac? + OutputDebugStringA(str.c_str()); + if (log_to_stderr) { + // This handles dynamically allocated consoles, too. + if (HANDLE error_handle = ::GetStdHandle(STD_ERROR_HANDLE)) { + log_to_stderr = false; + DWORD written = 0; + ::WriteFile(error_handle, str.data(), static_cast(str.size()), + &written, 0); + } + } +#endif // WEBRTC_WIN +#if defined(WEBRTC_ANDROID) + // Android's logging facility uses severity to log messages but we + // need to map libjingle's severity levels to Android ones first. + // Also write to stderr which maybe available to executable started + // from the shell. + int prio; + switch (severity) { + case LS_SENSITIVE: + __android_log_write(ANDROID_LOG_INFO, kLibjingle, "SENSITIVE"); + if (log_to_stderr) { + fprintf(stderr, "SENSITIVE"); + fflush(stderr); + } + return; + case LS_VERBOSE: + prio = ANDROID_LOG_VERBOSE; + break; + case LS_INFO: + prio = ANDROID_LOG_INFO; + break; + case LS_WARNING: + prio = ANDROID_LOG_WARN; + break; + case LS_ERROR: + prio = ANDROID_LOG_ERROR; + break; + default: + prio = ANDROID_LOG_UNKNOWN; + } + + int size = str.size(); + int line = 0; + int idx = 0; + const int max_lines = size / kMaxLogLineSize + 1; + if (max_lines == 1) { + __android_log_print(prio, kLibjingle, "%.*s", size, str.c_str()); + } else { + while (size > 0) { + const int len = std::min(size, kMaxLogLineSize); + // Use the size of the string in the format (str may have \0 in the + // middle). + __android_log_print(prio, kLibjingle, "[%d/%d] %.*s", + line + 1, max_lines, + len, str.c_str() + idx); + idx += len; + size -= len; + ++line; + } + } +#endif // WEBRTC_ANDROID + if (log_to_stderr) { + fprintf(stderr, "%s", str.c_str()); + fflush(stderr); + } +} + +void LogMessage::OutputToStream(StreamInterface* stream, + const std::string& str) { + // If write isn't fully successful, what are we going to do, log it? :) + stream->WriteAll(str.data(), str.size(), NULL, NULL); +} + +////////////////////////////////////////////////////////////////////// +// Logging Helpers +////////////////////////////////////////////////////////////////////// + +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state) { + if (!LOG_CHECK_LEVEL_V(level)) + return; + + const char * direction = (input ? " << " : " >> "); + + // NULL data means to flush our count of unprintable characters. + if (!data) { + if (state && state->unprintable_count_[input]) { + LOG_V(level) << label << direction << "## " + << state->unprintable_count_[input] + << " consecutive unprintable ##"; + state->unprintable_count_[input] = 0; + } + return; + } + + // The ctype classification functions want unsigned chars. + const unsigned char* udata = static_cast(data); + + if (hex_mode) { + const size_t LINE_SIZE = 24; + char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1]; + while (len > 0) { + memset(asc_line, ' ', sizeof(asc_line)); + memset(hex_line, ' ', sizeof(hex_line)); + size_t line_len = _min(len, LINE_SIZE); + for (size_t i = 0; i < line_len; ++i) { + unsigned char ch = udata[i]; + asc_line[i] = isprint(ch) ? ch : '.'; + hex_line[i*2 + i/4] = hex_encode(ch >> 4); + hex_line[i*2 + i/4 + 1] = hex_encode(ch & 0xf); + } + asc_line[sizeof(asc_line)-1] = 0; + hex_line[sizeof(hex_line)-1] = 0; + LOG_V(level) << label << direction + << asc_line << " " << hex_line << " "; + udata += line_len; + len -= line_len; + } + return; + } + + size_t consecutive_unprintable = state ? state->unprintable_count_[input] : 0; + + const unsigned char* end = udata + len; + while (udata < end) { + const unsigned char* line = udata; + const unsigned char* end_of_line = strchrn(udata, + end - udata, + '\n'); + if (!end_of_line) { + udata = end_of_line = end; + } else { + udata = end_of_line + 1; + } + + bool is_printable = true; + + // If we are in unprintable mode, we need to see a line of at least + // kMinPrintableLine characters before we'll switch back. + const ptrdiff_t kMinPrintableLine = 4; + if (consecutive_unprintable && ((end_of_line - line) < kMinPrintableLine)) { + is_printable = false; + } else { + // Determine if the line contains only whitespace and printable + // characters. + bool is_entirely_whitespace = true; + for (const unsigned char* pos = line; pos < end_of_line; ++pos) { + if (isspace(*pos)) + continue; + is_entirely_whitespace = false; + if (!isprint(*pos)) { + is_printable = false; + break; + } + } + // Treat an empty line following unprintable data as unprintable. + if (consecutive_unprintable && is_entirely_whitespace) { + is_printable = false; + } + } + if (!is_printable) { + consecutive_unprintable += (udata - line); + continue; + } + // Print out the current line, but prefix with a count of prior unprintable + // characters. + if (consecutive_unprintable) { + LOG_V(level) << label << direction << "## " << consecutive_unprintable + << " consecutive unprintable ##"; + consecutive_unprintable = 0; + } + // Strip off trailing whitespace. + while ((end_of_line > line) && isspace(*(end_of_line-1))) { + --end_of_line; + } + // Filter out any private data + std::string substr(reinterpret_cast(line), end_of_line - line); + std::string::size_type pos_private = substr.find("Email"); + if (pos_private == std::string::npos) { + pos_private = substr.find("Passwd"); + } + if (pos_private == std::string::npos) { + LOG_V(level) << label << direction << substr; + } else { + LOG_V(level) << label << direction << "## omitted for privacy ##"; + } + } + + if (state) { + state->unprintable_count_[input] = consecutive_unprintable; + } +} + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/logging.h b/webrtc/base/logging.h new file mode 100644 index 000000000..91d61b3e9 --- /dev/null +++ b/webrtc/base/logging.h @@ -0,0 +1,387 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// LOG(...) an ostream target that can be used to send formatted +// output to a variety of logging targets, such as debugger console, stderr, +// file, or any StreamInterface. +// The severity level passed as the first argument to the LOGging +// functions is used as a filter, to limit the verbosity of the logging. +// Static members of LogMessage documented below are used to control the +// verbosity and target of the output. +// There are several variations on the LOG macro which facilitate logging +// of common error conditions, detailed below. + +// LOG(sev) logs the given stream at severity "sev", which must be a +// compile-time constant of the LoggingSeverity type, without the namespace +// prefix. +// LOG_V(sev) Like LOG(), but sev is a run-time variable of the LoggingSeverity +// type (basically, it just doesn't prepend the namespace). +// LOG_F(sev) Like LOG(), but includes the name of the current function. +// LOG_T(sev) Like LOG(), but includes the this pointer. +// LOG_T_F(sev) Like LOG_F(), but includes the this pointer. +// LOG_GLE(M)(sev [, mod]) attempt to add a string description of the +// HRESULT returned by GetLastError. The "M" variant allows searching of a +// DLL's string table for the error description. +// LOG_ERRNO(sev) attempts to add a string description of an errno-derived +// error. errno and associated facilities exist on both Windows and POSIX, +// but on Windows they only apply to the C/C++ runtime. +// LOG_ERR(sev) is an alias for the platform's normal error system, i.e. _GLE on +// Windows and _ERRNO on POSIX. +// (The above three also all have _EX versions that let you specify the error +// code, rather than using the last one.) +// LOG_E(sev, ctx, err, ...) logs a detailed error interpreted using the +// specified context. +// LOG_CHECK_LEVEL(sev) (and LOG_CHECK_LEVEL_V(sev)) can be used as a test +// before performing expensive or sensitive operations whose sole purpose is +// to output logging data at the desired level. +// Lastly, PLOG(sev, err) is an alias for LOG_ERR_EX. + +#ifndef WEBRTC_BASE_LOGGING_H_ +#define WEBRTC_BASE_LOGGING_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" // NOLINT +#endif + +#include +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// ConstantLabel can be used to easily generate string names from constant +// values. This can be useful for logging descriptive names of error messages. +// Usage: +// const ConstantLabel LIBRARY_ERRORS[] = { +// KLABEL(SOME_ERROR), +// KLABEL(SOME_OTHER_ERROR), +// ... +// LASTLABEL +// } +// +// int err = LibraryFunc(); +// LOG(LS_ERROR) << "LibraryFunc returned: " +// << ErrorName(err, LIBRARY_ERRORS); + +struct ConstantLabel { int value; const char * label; }; +#define KLABEL(x) { x, #x } +#define TLABEL(x, y) { x, y } +#define LASTLABEL { 0, 0 } + +const char * FindLabel(int value, const ConstantLabel entries[]); +std::string ErrorName(int err, const ConstantLabel* err_table); + +////////////////////////////////////////////////////////////////////// + +// Note that the non-standard LoggingSeverity aliases exist because they are +// still in broad use. The meanings of the levels are: +// LS_SENSITIVE: Information which should only be logged with the consent +// of the user, due to privacy concerns. +// LS_VERBOSE: This level is for data which we do not want to appear in the +// normal debug log, but should appear in diagnostic logs. +// LS_INFO: Chatty level used in debugging for all sorts of things, the default +// in debug builds. +// LS_WARNING: Something that may warrant investigation. +// LS_ERROR: Something that should not have occurred. +enum LoggingSeverity { LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR, + INFO = LS_INFO, + WARNING = LS_WARNING, + LERROR = LS_ERROR }; + +// LogErrorContext assists in interpreting the meaning of an error value. +enum LogErrorContext { + ERRCTX_NONE, + ERRCTX_ERRNO, // System-local errno + ERRCTX_HRESULT, // Windows HRESULT + ERRCTX_OSSTATUS, // MacOS OSStatus + + // Abbreviations for LOG_E macro + ERRCTX_EN = ERRCTX_ERRNO, // LOG_E(sev, EN, x) + ERRCTX_HR = ERRCTX_HRESULT, // LOG_E(sev, HR, x) + ERRCTX_OS = ERRCTX_OSSTATUS, // LOG_E(sev, OS, x) +}; + +class LogMessage { + public: + static const int NO_LOGGING; + static const uint32 WARN_SLOW_LOGS_DELAY = 50; // ms + + LogMessage(const char* file, int line, LoggingSeverity sev, + LogErrorContext err_ctx = ERRCTX_NONE, int err = 0, + const char* module = NULL); + ~LogMessage(); + + static inline bool Loggable(LoggingSeverity sev) { return (sev >= min_sev_); } + std::ostream& stream() { return print_stream_; } + + // Returns the time at which this function was called for the first time. + // The time will be used as the logging start time. + // If this is not called externally, the LogMessage ctor also calls it, in + // which case the logging start time will be the time of the first LogMessage + // instance is created. + static uint32 LogStartTime(); + + // Returns the wall clock equivalent of |LogStartTime|, in seconds from the + // epoch. + static uint32 WallClockStartTime(); + + // These are attributes which apply to all logging channels + // LogContext: Display the file and line number of the message + static void LogContext(int min_sev); + // LogThreads: Display the thread identifier of the current thread + static void LogThreads(bool on = true); + // LogTimestamps: Display the elapsed time of the program + static void LogTimestamps(bool on = true); + + // These are the available logging channels + // Debug: Debug console on Windows, otherwise stderr + static void LogToDebug(int min_sev); + static int GetLogToDebug() { return dbg_sev_; } + + // Stream: Any non-blocking stream interface. LogMessage takes ownership of + // the stream. Multiple streams may be specified by using AddLogToStream. + // LogToStream is retained for backwards compatibility; when invoked, it + // will discard any previously set streams and install the specified stream. + // GetLogToStream gets the severity for the specified stream, of if none + // is specified, the minimum stream severity. + // RemoveLogToStream removes the specified stream, without destroying it. + static void LogToStream(StreamInterface* stream, int min_sev); + static int GetLogToStream(StreamInterface* stream = NULL); + static void AddLogToStream(StreamInterface* stream, int min_sev); + static void RemoveLogToStream(StreamInterface* stream); + + // Testing against MinLogSeverity allows code to avoid potentially expensive + // logging operations by pre-checking the logging level. + static int GetMinLogSeverity() { return min_sev_; } + + static void SetDiagnosticMode(bool f) { is_diagnostic_mode_ = f; } + static bool IsDiagnosticMode() { return is_diagnostic_mode_; } + + // Parses the provided parameter stream to configure the options above. + // Useful for configuring logging from the command line. If file logging + // is enabled, it is output to the specified filename. + static void ConfigureLogging(const char* params, const char* filename); + + // Convert the string to a LS_ value; also accept numeric values. + static int ParseLogSeverity(const std::string& value); + + private: + typedef std::list > StreamList; + + // Updates min_sev_ appropriately when debug sinks change. + static void UpdateMinLogSeverity(); + + // These assist in formatting some parts of the debug output. + static const char* Describe(LoggingSeverity sev); + static const char* DescribeFile(const char* file); + + // These write out the actual log messages. + static void OutputToDebug(const std::string& msg, LoggingSeverity severity_); + static void OutputToStream(StreamInterface* stream, const std::string& msg); + + // The ostream that buffers the formatted message before output + std::ostringstream print_stream_; + + // The severity level of this message + LoggingSeverity severity_; + + // String data generated in the constructor, that should be appended to + // the message before output. + std::string extra_; + + // If time it takes to write to stream is more than this, log one + // additional warning about it. + uint32 warn_slow_logs_delay_; + + // Global lock for the logging subsystem + static CriticalSection crit_; + + // dbg_sev_ is the thresholds for those output targets + // min_sev_ is the minimum (most verbose) of those levels, and is used + // as a short-circuit in the logging macros to identify messages that won't + // be logged. + // ctx_sev_ is the minimum level at which file context is displayed + static int min_sev_, dbg_sev_, ctx_sev_; + + // The output streams and their associated severities + static StreamList streams_; + + // Flags for formatting options + static bool thread_, timestamp_; + + // are we in diagnostic mode (as defined by the app)? + static bool is_diagnostic_mode_; + + DISALLOW_EVIL_CONSTRUCTORS(LogMessage); +}; + +////////////////////////////////////////////////////////////////////// +// Logging Helpers +////////////////////////////////////////////////////////////////////// + +class LogMultilineState { + public: + size_t unprintable_count_[2]; + LogMultilineState() { + unprintable_count_[0] = unprintable_count_[1] = 0; + } +}; + +// When possible, pass optional state variable to track various data across +// multiple calls to LogMultiline. Otherwise, pass NULL. +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state); + +////////////////////////////////////////////////////////////////////// +// Macros which automatically disable logging when LOGGING == 0 +////////////////////////////////////////////////////////////////////// + +// If LOGGING is not explicitly defined, default to enabled in debug mode +#if !defined(LOGGING) +#if defined(_DEBUG) && !defined(NDEBUG) +#define LOGGING 1 +#else +#define LOGGING 0 +#endif +#endif // !defined(LOGGING) + +#ifndef LOG +#if LOGGING + +// The following non-obvious technique for implementation of a +// conditional log stream was stolen from google3/base/logging.h. + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". + +class LogMessageVoidify { + public: + LogMessageVoidify() { } + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream&) { } +}; + +#define LOG_SEVERITY_PRECONDITION(sev) \ + !(rtc::LogMessage::Loggable(sev)) \ + ? (void) 0 \ + : rtc::LogMessageVoidify() & + +#define LOG(sev) \ + LOG_SEVERITY_PRECONDITION(rtc::sev) \ + rtc::LogMessage(__FILE__, __LINE__, rtc::sev).stream() + +// The _V version is for when a variable is passed in. It doesn't do the +// namespace concatination. +#define LOG_V(sev) \ + LOG_SEVERITY_PRECONDITION(sev) \ + rtc::LogMessage(__FILE__, __LINE__, sev).stream() + +// The _F version prefixes the message with the current function name. +#if (defined(__GNUC__) && defined(_DEBUG)) || defined(WANT_PRETTY_LOG_F) +#define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": " +#define LOG_T_F(sev) LOG(sev) << this << ": " << __PRETTY_FUNCTION__ << ": " +#else +#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": " +#define LOG_T_F(sev) LOG(sev) << this << ": " << __FUNCTION__ << ": " +#endif + +#define LOG_CHECK_LEVEL(sev) \ + rtc::LogCheckLevel(rtc::sev) +#define LOG_CHECK_LEVEL_V(sev) \ + rtc::LogCheckLevel(sev) +inline bool LogCheckLevel(LoggingSeverity sev) { + return (LogMessage::GetMinLogSeverity() <= sev); +} + +#define LOG_E(sev, ctx, err, ...) \ + LOG_SEVERITY_PRECONDITION(rtc::sev) \ + rtc::LogMessage(__FILE__, __LINE__, rtc::sev, \ + rtc::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \ + .stream() + +#define LOG_T(sev) LOG(sev) << this << ": " + +#else // !LOGGING + +// Hopefully, the compiler will optimize away some of this code. +// Note: syntax of "1 ? (void)0 : LogMessage" was causing errors in g++, +// converted to "while (false)" +#define LOG(sev) \ + while (false)rtc:: LogMessage(NULL, 0, rtc::sev).stream() +#define LOG_V(sev) \ + while (false) rtc::LogMessage(NULL, 0, sev).stream() +#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": " +#define LOG_CHECK_LEVEL(sev) \ + false +#define LOG_CHECK_LEVEL_V(sev) \ + false + +#define LOG_E(sev, ctx, err, ...) \ + while (false) rtc::LogMessage(__FILE__, __LINE__, rtc::sev, \ + rtc::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \ + .stream() + +#define LOG_T(sev) LOG(sev) << this << ": " +#define LOG_T_F(sev) LOG(sev) << this << ": " << __FUNCTION__ << +#endif // !LOGGING + +#define LOG_ERRNO_EX(sev, err) \ + LOG_E(sev, ERRNO, err) +#define LOG_ERRNO(sev) \ + LOG_ERRNO_EX(sev, errno) + +#if defined(WEBRTC_WIN) +#define LOG_GLE_EX(sev, err) \ + LOG_E(sev, HRESULT, err) +#define LOG_GLE(sev) \ + LOG_GLE_EX(sev, GetLastError()) +#define LOG_GLEM(sev, mod) \ + LOG_E(sev, HRESULT, GetLastError(), mod) +#define LOG_ERR_EX(sev, err) \ + LOG_GLE_EX(sev, err) +#define LOG_ERR(sev) \ + LOG_GLE(sev) +#define LAST_SYSTEM_ERROR \ + (::GetLastError()) +#elif __native_client__ +#define LOG_ERR_EX(sev, err) \ + LOG(sev) +#define LOG_ERR(sev) \ + LOG(sev) +#define LAST_SYSTEM_ERROR \ + (0) +#elif defined(WEBRTC_POSIX) +#define LOG_ERR_EX(sev, err) \ + LOG_ERRNO_EX(sev, err) +#define LOG_ERR(sev) \ + LOG_ERRNO(sev) +#define LAST_SYSTEM_ERROR \ + (errno) +#endif // WEBRTC_WIN + +#define PLOG(sev, err) \ + LOG_ERR_EX(sev, err) + +// TODO(?): Add an "assert" wrapper that logs in the same manner. + +#endif // LOG + +} // namespace rtc + +#endif // WEBRTC_BASE_LOGGING_H_ diff --git a/webrtc/base/logging_unittest.cc b/webrtc/base/logging_unittest.cc new file mode 100644 index 000000000..59630d746 --- /dev/null +++ b/webrtc/base/logging_unittest.cc @@ -0,0 +1,138 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Test basic logging operation. We should get the INFO log but not the VERBOSE. +// We should restore the correct global state at the end. +TEST(LogTest, SingleStream) { + int sev = LogMessage::GetLogToStream(NULL); + + std::string str; + StringStream stream(str); + LogMessage::AddLogToStream(&stream, LS_INFO); + EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream)); + + LOG(LS_INFO) << "INFO"; + LOG(LS_VERBOSE) << "VERBOSE"; + EXPECT_NE(std::string::npos, str.find("INFO")); + EXPECT_EQ(std::string::npos, str.find("VERBOSE")); + + LogMessage::RemoveLogToStream(&stream); + EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream)); + + EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL)); +} + +// Test using multiple log streams. The INFO stream should get the INFO message, +// the VERBOSE stream should get the INFO and the VERBOSE. +// We should restore the correct global state at the end. +TEST(LogTest, MultipleStreams) { + int sev = LogMessage::GetLogToStream(NULL); + + std::string str1, str2; + StringStream stream1(str1), stream2(str2); + LogMessage::AddLogToStream(&stream1, LS_INFO); + LogMessage::AddLogToStream(&stream2, LS_VERBOSE); + EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream1)); + EXPECT_EQ(LS_VERBOSE, LogMessage::GetLogToStream(&stream2)); + + LOG(LS_INFO) << "INFO"; + LOG(LS_VERBOSE) << "VERBOSE"; + + EXPECT_NE(std::string::npos, str1.find("INFO")); + EXPECT_EQ(std::string::npos, str1.find("VERBOSE")); + EXPECT_NE(std::string::npos, str2.find("INFO")); + EXPECT_NE(std::string::npos, str2.find("VERBOSE")); + + LogMessage::RemoveLogToStream(&stream2); + LogMessage::RemoveLogToStream(&stream1); + EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream2)); + EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream1)); + + EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL)); +} + +// Ensure we don't crash when adding/removing streams while threads are going. +// We should restore the correct global state at the end. +class LogThread : public Thread { + public: + virtual ~LogThread() { + Stop(); + } + + private: + void Run() { + // LS_SENSITIVE to avoid cluttering up any real logging going on + LOG(LS_SENSITIVE) << "LOG"; + } +}; + +TEST(LogTest, MultipleThreads) { + int sev = LogMessage::GetLogToStream(NULL); + + LogThread thread1, thread2, thread3; + thread1.Start(); + thread2.Start(); + thread3.Start(); + + NullStream stream1, stream2, stream3; + for (int i = 0; i < 1000; ++i) { + LogMessage::AddLogToStream(&stream1, LS_INFO); + LogMessage::AddLogToStream(&stream2, LS_VERBOSE); + LogMessage::AddLogToStream(&stream3, LS_SENSITIVE); + LogMessage::RemoveLogToStream(&stream1); + LogMessage::RemoveLogToStream(&stream2); + LogMessage::RemoveLogToStream(&stream3); + } + + EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL)); +} + + +TEST(LogTest, WallClockStartTime) { + uint32 time = LogMessage::WallClockStartTime(); + // Expect the time to be in a sensible range, e.g. > 2012-01-01. + EXPECT_GT(time, 1325376000u); +} + +// Test the time required to write 1000 80-character logs to an unbuffered file. +TEST(LogTest, Perf) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetPathname(Filesystem::TempFilename(path, "ut")); + + FileStream stream; + EXPECT_TRUE(stream.Open(path.pathname(), "wb", NULL)); + stream.DisableBuffering(); + LogMessage::AddLogToStream(&stream, LS_SENSITIVE); + + uint32 start = Time(), finish; + std::string message('X', 80); + for (int i = 0; i < 1000; ++i) { + LOG(LS_SENSITIVE) << message; + } + finish = Time(); + + LogMessage::RemoveLogToStream(&stream); + stream.Close(); + Filesystem::DeleteFile(path); + + LOG(LS_INFO) << "Average log time: " << TimeDiff(finish, start) << " us"; +} + +} // namespace rtc diff --git a/webrtc/base/macasyncsocket.cc b/webrtc/base/macasyncsocket.cc new file mode 100644 index 000000000..ee982ffff --- /dev/null +++ b/webrtc/base/macasyncsocket.cc @@ -0,0 +1,477 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +// +// MacAsyncSocket is a kind of AsyncSocket. It does not support the SOCK_DGRAM +// type (yet). It works asynchronously, which means that users of this socket +// should connect to the various events declared in asyncsocket.h to receive +// notifications about this socket. It uses CFSockets for signals, but prefers +// the basic bsd socket operations rather than their CFSocket wrappers when +// possible. + +#include +#include + +#include "webrtc/base/macasyncsocket.h" + +#include "webrtc/base/logging.h" +#include "webrtc/base/macsocketserver.h" + +namespace rtc { + +static const int kCallbackFlags = kCFSocketReadCallBack | + kCFSocketConnectCallBack | + kCFSocketWriteCallBack; + +MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family) + : ss_(ss), + socket_(NULL), + native_socket_(INVALID_SOCKET), + source_(NULL), + current_callbacks_(0), + disabled_(false), + error_(0), + state_(CS_CLOSED), + resolver_(NULL) { + Initialize(family); +} + +MacAsyncSocket::~MacAsyncSocket() { + Close(); +} + +// Returns the address to which the socket is bound. If the socket is not +// bound, then the any-address is returned. +SocketAddress MacAsyncSocket::GetLocalAddress() const { + SocketAddress address; + + // The CFSocket doesn't pick up on implicit binds from the connect call. + // Calling bind in before connect explicitly causes errors, so just query + // the underlying bsd socket. + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(native_socket_, + reinterpret_cast(&addr), &addrlen); + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } + return address; +} + +// Returns the address to which the socket is connected. If the socket is not +// connected, then the any-address is returned. +SocketAddress MacAsyncSocket::GetRemoteAddress() const { + SocketAddress address; + + // Use native_socket for consistency with GetLocalAddress. + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(native_socket_, + reinterpret_cast(&addr), &addrlen); + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } + return address; +} + +// Bind the socket to a local address. +int MacAsyncSocket::Bind(const SocketAddress& address) { + sockaddr_storage saddr = {0}; + size_t len = address.ToSockAddrStorage(&saddr); + int err = ::bind(native_socket_, reinterpret_cast(&saddr), len); + if (err == SOCKET_ERROR) error_ = errno; + return err; +} + +void MacAsyncSocket::OnResolveResult(SignalThread* thread) { + if (thread != resolver_) { + return; + } + int error = resolver_->GetError(); + if (error == 0) { + error = DoConnect(resolver_->address()); + } else { + Close(); + } + if (error) { + error_ = error; + SignalCloseEvent(this, error_); + } +} + +// Connect to a remote address. +int MacAsyncSocket::Connect(const SocketAddress& addr) { + // TODO(djw): Consolidate all the connect->resolve->doconnect implementations. + if (state_ != CS_CLOSED) { + SetError(EALREADY); + return SOCKET_ERROR; + } + if (addr.IsUnresolved()) { + LOG(LS_VERBOSE) << "Resolving addr in MacAsyncSocket::Connect"; + resolver_ = new AsyncResolver(); + resolver_->SignalWorkDone.connect(this, + &MacAsyncSocket::OnResolveResult); + resolver_->Start(addr); + state_ = CS_CONNECTING; + return 0; + } + return DoConnect(addr); +} + +int MacAsyncSocket::DoConnect(const SocketAddress& addr) { + if (!valid()) { + Initialize(addr.family()); + if (!valid()) + return SOCKET_ERROR; + } + + sockaddr_storage saddr; + size_t len = addr.ToSockAddrStorage(&saddr); + int result = ::connect(native_socket_, reinterpret_cast(&saddr), + len); + + if (result != SOCKET_ERROR) { + state_ = CS_CONNECTED; + } else { + error_ = errno; + if (error_ == EINPROGRESS) { + state_ = CS_CONNECTING; + result = 0; + } + } + return result; +} + +// Send to the remote end we're connected to. +int MacAsyncSocket::Send(const void* buffer, size_t length) { + if (!valid()) { + return SOCKET_ERROR; + } + + int sent = ::send(native_socket_, buffer, length, 0); + + if (sent == SOCKET_ERROR) { + error_ = errno; + + if (IsBlocking()) { + // Reenable the writable callback (once), since we are flow controlled. + CFSocketEnableCallBacks(socket_, kCallbackFlags); + current_callbacks_ = kCallbackFlags; + } + } + return sent; +} + +// Send to the given address. We may or may not be connected to anyone. +int MacAsyncSocket::SendTo(const void* buffer, size_t length, + const SocketAddress& address) { + if (!valid()) { + return SOCKET_ERROR; + } + + sockaddr_storage saddr; + size_t len = address.ToSockAddrStorage(&saddr); + int sent = ::sendto(native_socket_, buffer, length, 0, + reinterpret_cast(&saddr), len); + + if (sent == SOCKET_ERROR) { + error_ = errno; + } + + return sent; +} + +// Read data received from the remote end we're connected to. +int MacAsyncSocket::Recv(void* buffer, size_t length) { + int received = ::recv(native_socket_, reinterpret_cast(buffer), + length, 0); + if (received == SOCKET_ERROR) error_ = errno; + + // Recv should only be called when there is data to read + ASSERT((received != 0) || (length == 0)); + return received; +} + +// Read data received from any remote party +int MacAsyncSocket::RecvFrom(void* buffer, size_t length, + SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + int received = ::recvfrom(native_socket_, reinterpret_cast(buffer), + length, 0, reinterpret_cast(&saddr), + &addr_len); + if (received >= 0 && out_addr != NULL) { + SocketAddressFromSockAddrStorage(saddr, out_addr); + } else if (received == SOCKET_ERROR) { + error_ = errno; + } + return received; +} + +int MacAsyncSocket::Listen(int backlog) { + if (!valid()) { + return SOCKET_ERROR; + } + + int res = ::listen(native_socket_, backlog); + if (res != SOCKET_ERROR) + state_ = CS_CONNECTING; + else + error_ = errno; + + return res; +} + +MacAsyncSocket* MacAsyncSocket::Accept(SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + + int socket_fd = ::accept(native_socket_, reinterpret_cast(&saddr), + &addr_len); + if (socket_fd == INVALID_SOCKET) { + error_ = errno; + return NULL; + } + + MacAsyncSocket* s = new MacAsyncSocket(ss_, saddr.ss_family, socket_fd); + if (s && s->valid()) { + s->state_ = CS_CONNECTED; + if (out_addr) + SocketAddressFromSockAddrStorage(saddr, out_addr); + } else { + delete s; + s = NULL; + } + return s; +} + +int MacAsyncSocket::Close() { + if (source_ != NULL) { + CFRunLoopSourceInvalidate(source_); + CFRelease(source_); + if (ss_) ss_->UnregisterSocket(this); + source_ = NULL; + } + + if (socket_ != NULL) { + CFSocketInvalidate(socket_); + CFRelease(socket_); + socket_ = NULL; + } + + if (resolver_) { + resolver_->Destroy(false); + resolver_ = NULL; + } + + native_socket_ = INVALID_SOCKET; // invalidates the socket + error_ = 0; + state_ = CS_CLOSED; + return 0; +} + +int MacAsyncSocket::EstimateMTU(uint16* mtu) { + ASSERT(false && "NYI"); + return -1; +} + +int MacAsyncSocket::GetError() const { + return error_; +} + +void MacAsyncSocket::SetError(int error) { + error_ = error; +} + +Socket::ConnState MacAsyncSocket::GetState() const { + return state_; +} + +int MacAsyncSocket::GetOption(Option opt, int* value) { + ASSERT(false && "NYI"); + return -1; +} + +int MacAsyncSocket::SetOption(Option opt, int value) { + ASSERT(false && "NYI"); + return -1; +} + +void MacAsyncSocket::EnableCallbacks() { + if (valid()) { + disabled_ = false; + CFSocketEnableCallBacks(socket_, current_callbacks_); + } +} + +void MacAsyncSocket::DisableCallbacks() { + if (valid()) { + disabled_ = true; + CFSocketDisableCallBacks(socket_, kCallbackFlags); + } +} + +MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family, + int native_socket) + : ss_(ss), + socket_(NULL), + native_socket_(native_socket), + source_(NULL), + current_callbacks_(0), + disabled_(false), + error_(0), + state_(CS_CLOSED), + resolver_(NULL) { + Initialize(family); +} + +// Create a new socket, wrapping the native socket if provided or creating one +// otherwise. In case of any failure, consume the native socket. We assume the +// wrapped socket is in the closed state. If this is not the case you must +// update the state_ field for this socket yourself. +void MacAsyncSocket::Initialize(int family) { + CFSocketContext ctx = { 0 }; + ctx.info = this; + + // First create the CFSocket + CFSocketRef cf_socket = NULL; + bool res = false; + if (native_socket_ == INVALID_SOCKET) { + cf_socket = CFSocketCreate(kCFAllocatorDefault, + family, SOCK_STREAM, IPPROTO_TCP, + kCallbackFlags, MacAsyncSocketCallBack, &ctx); + } else { + cf_socket = CFSocketCreateWithNative(kCFAllocatorDefault, + native_socket_, kCallbackFlags, + MacAsyncSocketCallBack, &ctx); + } + + if (cf_socket) { + res = true; + socket_ = cf_socket; + native_socket_ = CFSocketGetNative(cf_socket); + current_callbacks_ = kCallbackFlags; + } + + if (res) { + // Make the underlying socket asynchronous + res = (-1 != ::fcntl(native_socket_, F_SETFL, + ::fcntl(native_socket_, F_GETFL, 0) | O_NONBLOCK)); + } + + if (res) { + // Add this socket to the run loop, at priority 1 so that it will be + // queued behind any pending signals. + source_ = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 1); + res = (source_ != NULL); + if (!res) errno = EINVAL; + } + + if (res) { + if (ss_) ss_->RegisterSocket(this); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopCommonModes); + } + + if (!res) { + int error = errno; + Close(); // Clears error_. + error_ = error; + } +} + +// Call CFRelease on the result when done using it +CFDataRef MacAsyncSocket::CopyCFAddress(const SocketAddress& address) { + sockaddr_storage saddr; + size_t len = address.ToSockAddrStorage(&saddr); + + const UInt8* bytes = reinterpret_cast(&saddr); + + CFDataRef cf_address = CFDataCreate(kCFAllocatorDefault, + bytes, len); + + ASSERT(cf_address != NULL); + return cf_address; +} + +void MacAsyncSocket::MacAsyncSocketCallBack(CFSocketRef s, + CFSocketCallBackType callbackType, + CFDataRef address, + const void* data, + void* info) { + MacAsyncSocket* this_socket = + reinterpret_cast(info); + ASSERT(this_socket != NULL && this_socket->socket_ == s); + + // Don't signal any socket messages if the socketserver is not listening on + // them. When we are reenabled they will be requeued and will fire again. + if (this_socket->disabled_) + return; + + switch (callbackType) { + case kCFSocketReadCallBack: + // This callback is invoked in one of 3 situations: + // 1. A new connection is waiting to be accepted. + // 2. The remote end closed the connection (a recv will return 0). + // 3. Data is available to read. + // 4. The connection closed unhappily (recv will return -1). + if (this_socket->state_ == CS_CONNECTING) { + // Case 1. + this_socket->SignalReadEvent(this_socket); + } else { + char ch, amt; + amt = ::recv(this_socket->native_socket_, &ch, 1, MSG_PEEK); + if (amt == 0) { + // Case 2. + this_socket->state_ = CS_CLOSED; + + // Disable additional callbacks or we will signal close twice. + CFSocketDisableCallBacks(this_socket->socket_, kCFSocketReadCallBack); + this_socket->current_callbacks_ &= ~kCFSocketReadCallBack; + this_socket->SignalCloseEvent(this_socket, 0); + } else if (amt > 0) { + // Case 3. + this_socket->SignalReadEvent(this_socket); + } else { + // Case 4. + int error = errno; + if (error == EAGAIN) { + // Observed in practice. Let's hope it's a spurious or out of date + // signal, since we just eat it. + } else { + this_socket->error_ = error; + this_socket->SignalCloseEvent(this_socket, error); + } + } + } + break; + + case kCFSocketConnectCallBack: + if (data != NULL) { + // An error occured in the background while connecting + this_socket->error_ = errno; + this_socket->state_ = CS_CLOSED; + this_socket->SignalCloseEvent(this_socket, this_socket->error_); + } else { + this_socket->state_ = CS_CONNECTED; + this_socket->SignalConnectEvent(this_socket); + } + break; + + case kCFSocketWriteCallBack: + // Update our callback tracking. Write doesn't reenable, so it's off now. + this_socket->current_callbacks_ &= ~kCFSocketWriteCallBack; + this_socket->SignalWriteEvent(this_socket); + break; + + default: + ASSERT(false && "Invalid callback type for socket"); + } +} + +} // namespace rtc diff --git a/webrtc/base/macasyncsocket.h b/webrtc/base/macasyncsocket.h new file mode 100644 index 000000000..bf8386546 --- /dev/null +++ b/webrtc/base/macasyncsocket.h @@ -0,0 +1,97 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +// MacAsyncSocket is a kind of AsyncSocket. It only creates sockets +// of the TCP type, and does not (yet) support listen and accept. It works +// asynchronously, which means that users of this socket should connect to +// the various events declared in asyncsocket.h to receive notifications about +// this socket. + +#ifndef WEBRTC_BASE_MACASYNCSOCKET_H__ +#define WEBRTC_BASE_MACASYNCSOCKET_H__ + +#include + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/nethelpers.h" + +namespace rtc { + +class MacBaseSocketServer; + +class MacAsyncSocket : public AsyncSocket, public sigslot::has_slots<> { + public: + MacAsyncSocket(MacBaseSocketServer* ss, int family); + virtual ~MacAsyncSocket(); + + bool valid() const { return source_ != NULL; } + + // Socket interface + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Bind(const SocketAddress& addr); + virtual int Connect(const SocketAddress& addr); + virtual int Send(const void* buffer, size_t length); + virtual int SendTo(const void* buffer, size_t length, + const SocketAddress& addr); + virtual int Recv(void* buffer, size_t length); + virtual int RecvFrom(void* buffer, size_t length, SocketAddress* out_addr); + virtual int Listen(int backlog); + virtual MacAsyncSocket* Accept(SocketAddress* out_addr); + virtual int Close(); + virtual int GetError() const; + virtual void SetError(int error); + virtual ConnState GetState() const; + virtual int EstimateMTU(uint16* mtu); + virtual int GetOption(Option opt, int* value); + virtual int SetOption(Option opt, int value); + + // For the MacBaseSocketServer to disable callbacks when process_io is false. + void EnableCallbacks(); + void DisableCallbacks(); + + protected: + void OnResolveResult(SignalThread* thread); + int DoConnect(const SocketAddress& addr); + + private: + // Creates an async socket from an existing bsd socket + MacAsyncSocket(MacBaseSocketServer* ss, int family, int native_socket); + + // Attaches the socket to the CFRunloop and sets the wrapped bsd socket + // to async mode + void Initialize(int family); + + // Translate the SocketAddress into a CFDataRef to pass to CF socket + // functions. Caller must call CFRelease on the result when done. + static CFDataRef CopyCFAddress(const SocketAddress& address); + + // Callback for the underlying CFSocketRef. + static void MacAsyncSocketCallBack(CFSocketRef s, + CFSocketCallBackType callbackType, + CFDataRef address, + const void* data, + void* info); + + MacBaseSocketServer* ss_; + CFSocketRef socket_; + int native_socket_; + CFRunLoopSourceRef source_; + int current_callbacks_; + bool disabled_; + int error_; + ConnState state_; + AsyncResolver* resolver_; + + DISALLOW_EVIL_CONSTRUCTORS(MacAsyncSocket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MACASYNCSOCKET_H__ diff --git a/webrtc/base/maccocoasocketserver.h b/webrtc/base/maccocoasocketserver.h new file mode 100644 index 000000000..d5deac153 --- /dev/null +++ b/webrtc/base/maccocoasocketserver.h @@ -0,0 +1,48 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// A libjingle compatible SocketServer for OSX/iOS/Cocoa. + +#ifndef WEBRTC_BASE_MACCOCOASOCKETSERVER_H_ +#define WEBRTC_BASE_MACCOCOASOCKETSERVER_H_ + +#include "webrtc/base/macsocketserver.h" + +#ifdef __OBJC__ +@class NSTimer, MacCocoaSocketServerHelperRtc; +#else +class NSTimer; +class MacCocoaSocketServerHelperRtc; +#endif + +namespace rtc { + +// A socketserver implementation that wraps the main cocoa +// application loop accessed through [NSApp run]. +class MacCocoaSocketServer : public MacBaseSocketServer { + public: + explicit MacCocoaSocketServer(); + virtual ~MacCocoaSocketServer(); + + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + private: + MacCocoaSocketServerHelperRtc* helper_; + NSTimer* timer_; // Weak. + // The count of how many times we're inside the NSApplication main loop. + int run_count_; + + DISALLOW_EVIL_CONSTRUCTORS(MacCocoaSocketServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MACCOCOASOCKETSERVER_H_ diff --git a/webrtc/base/maccocoasocketserver.mm b/webrtc/base/maccocoasocketserver.mm new file mode 100644 index 000000000..123ffdc52 --- /dev/null +++ b/webrtc/base/maccocoasocketserver.mm @@ -0,0 +1,140 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#import "webrtc/base/maccocoasocketserver.h" + +#import +#import +#include + +#include "webrtc/base/scoped_autorelease_pool.h" + +// MacCocoaSocketServerHelperRtc serves as a delegate to NSMachPort or a target for +// a timeout. +@interface MacCocoaSocketServerHelperRtc : NSObject { + // This is a weak reference. This works fine since the + // rtc::MacCocoaSocketServer owns this object. + rtc::MacCocoaSocketServer* socketServer_; // Weak. +} +@end + +@implementation MacCocoaSocketServerHelperRtc +- (id)initWithSocketServer:(rtc::MacCocoaSocketServer*)ss { + self = [super init]; + if (self) { + socketServer_ = ss; + } + return self; +} + +- (void)timerFired:(NSTimer*)timer { + socketServer_->WakeUp(); +} + +- (void)breakMainloop { + [NSApp stop:self]; + // NSApp stop only exits after finishing processing of the + // current event. Since we're potentially in a timer callback + // and not an NSEvent handler, we need to trigger a dummy one + // and turn the loop over. We may be able to skip this if we're + // on the ss' thread and not inside the app loop already. + NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:event atStart:NO]; +} +@end + +namespace rtc { + +MacCocoaSocketServer::MacCocoaSocketServer() { + helper_ = [[MacCocoaSocketServerHelperRtc alloc] initWithSocketServer:this]; + timer_ = nil; + run_count_ = 0; + + // Initialize the shared NSApplication + [NSApplication sharedApplication]; +} + +MacCocoaSocketServer::~MacCocoaSocketServer() { + [timer_ invalidate]; + [timer_ release]; + [helper_ release]; +} + +// ::Wait is reentrant, for example when blocking on another thread while +// responding to I/O. Calls to [NSApp] MUST be made from the main thread +// only! +bool MacCocoaSocketServer::Wait(int cms, bool process_io) { + rtc::ScopedAutoreleasePool pool; + if (!process_io && cms == 0) { + // No op. + return true; + } + if ([NSApp isRunning]) { + // Only allow reentrant waiting if we're in a blocking send. + ASSERT(!process_io && cms == kForever); + } + + if (!process_io) { + // No way to listen to common modes and not get socket events, unless + // we disable each one's callbacks. + EnableSocketCallbacks(false); + } + + if (kForever != cms) { + // Install a timer that fires wakeup after cms has elapsed. + timer_ = + [NSTimer scheduledTimerWithTimeInterval:cms / 1000.0 + target:helper_ + selector:@selector(timerFired:) + userInfo:nil + repeats:NO]; + [timer_ retain]; + } + + // Run until WakeUp is called, which will call stop and exit this loop. + run_count_++; + [NSApp run]; + run_count_--; + + if (!process_io) { + // Reenable them. Hopefully this won't cause spurious callbacks or + // missing ones while they were disabled. + EnableSocketCallbacks(true); + } + + return true; +} + +// Can be called from any thread. Post a message back to the main thread to +// break out of the NSApp loop. +void MacCocoaSocketServer::WakeUp() { + if (timer_ != nil) { + [timer_ invalidate]; + [timer_ release]; + timer_ = nil; + } + + // [NSApp isRunning] returns unexpected results when called from another + // thread. Maintain our own count of how many times to break the main loop. + if (run_count_ > 0) { + [helper_ performSelectorOnMainThread:@selector(breakMainloop) + withObject:nil + waitUntilDone:false]; + } +} + +} // namespace rtc diff --git a/webrtc/base/maccocoasocketserver_unittest.mm b/webrtc/base/maccocoasocketserver_unittest.mm new file mode 100644 index 000000000..932b4a14f --- /dev/null +++ b/webrtc/base/maccocoasocketserver_unittest.mm @@ -0,0 +1,50 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/maccocoasocketserver.h" + +namespace rtc { + +class WakeThread : public Thread { + public: + WakeThread(SocketServer* ss) : ss_(ss) { + } + virtual ~WakeThread() { + Stop(); + } + void Run() { + ss_->WakeUp(); + } + private: + SocketServer* ss_; +}; + +// Test that MacCocoaSocketServer::Wait works as expected. +TEST(MacCocoaSocketServer, TestWait) { + MacCocoaSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCocoaSocketServer::Wakeup works as expected. +TEST(MacCocoaSocketServer, TestWakeup) { + MacCFSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +} // namespace rtc diff --git a/webrtc/base/maccocoathreadhelper.h b/webrtc/base/maccocoathreadhelper.h new file mode 100644 index 000000000..255d081ce --- /dev/null +++ b/webrtc/base/maccocoathreadhelper.h @@ -0,0 +1,27 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Helper function for using Cocoa with Posix threads. This header should be +// included from C/C++ files that want to use some Cocoa functionality without +// using the .mm extension (mostly for files that are compiled on multiple +// platforms). + +#ifndef WEBRTC_BASE_MACCOCOATHREADHELPER_H__ +#define WEBRTC_BASE_MACCOCOATHREADHELPER_H__ + +namespace rtc { + +// Cocoa must be "put into multithreading mode" before Cocoa functionality can +// be used on POSIX threads. This function does that. +void InitCocoaMultiThreading(); + +} // namespace rtc + +#endif // WEBRTC_BASE_MACCOCOATHREADHELPER_H__ diff --git a/webrtc/base/maccocoathreadhelper.mm b/webrtc/base/maccocoathreadhelper.mm new file mode 100644 index 000000000..7bf9e9206 --- /dev/null +++ b/webrtc/base/maccocoathreadhelper.mm @@ -0,0 +1,44 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +// Helper function for using Cocoa with Posix threading. + +#import +#import + +#import "webrtc/base/maccocoathreadhelper.h" + +namespace rtc { + +// Cocoa must be "put into multithreading mode" before Cocoa functionality can +// be used on POSIX threads. The way to do that is to spawn one thread that may +// immediately exit. +void InitCocoaMultiThreading() { + if ([NSThread isMultiThreaded] == NO) { + // The sole purpose of this autorelease pool is to avoid a console + // message on Leopard that tells us we're autoreleasing the thread + // with no autorelease pool in place; we can't set up an autorelease + // pool before this, because this is executed from an initializer, + // which is run before main. This means we leak an autorelease pool, + // and one thread, and if other objects are set up in initializers after + // this they'll be silently added to this pool and never released. + + // Doing NSAutoreleasePool* hack = [[NSAutoreleasePool alloc] init]; + // causes unused variable error. + NSAutoreleasePool* hack; + hack = [[NSAutoreleasePool alloc] init]; + [NSThread detachNewThreadSelector:@selector(class) + toTarget:[NSObject class] + withObject:nil]; + } + + assert([NSThread isMultiThreaded]); +} + +} // namespace rtc diff --git a/webrtc/base/macconversion.cc b/webrtc/base/macconversion.cc new file mode 100644 index 000000000..75d11a803 --- /dev/null +++ b/webrtc/base/macconversion.cc @@ -0,0 +1,159 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/macconversion.h" + +bool p_convertHostCFStringRefToCPPString( + const CFStringRef cfstr, std::string& cppstr) { + bool result = false; + + // First this must be non-null, + if (NULL != cfstr) { + // it must actually *be* a CFString, and not something just masquerading + // as one, + if (CFGetTypeID(cfstr) == CFStringGetTypeID()) { + // and we must be able to get the characters out of it. + // (The cfstr owns this buffer; it came from somewhere else, + // so someone else gets to take care of getting rid of the cfstr, + // and then this buffer will go away automatically.) + unsigned length = CFStringGetLength(cfstr); + char* buf = new char[1 + length]; + if (CFStringGetCString(cfstr, buf, 1 + length, kCFStringEncodingASCII)) { + if (strlen(buf) == length) { + cppstr.assign(buf); + result = true; + } + } + delete [] buf; + } + } + + return result; +} + +bool p_convertCFNumberToInt(CFNumberRef cfn, int* i) { + bool converted = false; + + // It must not be null. + if (NULL != cfn) { + // It must actually *be* a CFNumber and not something just masquerading + // as one. + if (CFGetTypeID(cfn) == CFNumberGetTypeID()) { + CFNumberType ntype = CFNumberGetType(cfn); + switch (ntype) { + case kCFNumberSInt8Type: + SInt8 sint8; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint8)); + if (converted) *i = static_cast(sint8); + break; + case kCFNumberSInt16Type: + SInt16 sint16; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint16)); + if (converted) *i = static_cast(sint16); + break; + case kCFNumberSInt32Type: + SInt32 sint32; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint32)); + if (converted) *i = static_cast(sint32); + break; + case kCFNumberSInt64Type: + SInt64 sint64; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint64)); + if (converted) *i = static_cast(sint64); + break; + case kCFNumberFloat32Type: + Float32 float32; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&float32)); + if (converted) *i = static_cast(float32); + break; + case kCFNumberFloat64Type: + Float64 float64; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&float64)); + if (converted) *i = static_cast(float64); + break; + case kCFNumberCharType: + char charvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&charvalue)); + if (converted) *i = static_cast(charvalue); + break; + case kCFNumberShortType: + short shortvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&shortvalue)); + if (converted) *i = static_cast(shortvalue); + break; + case kCFNumberIntType: + int intvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&intvalue)); + if (converted) *i = static_cast(intvalue); + break; + case kCFNumberLongType: + long longvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&longvalue)); + if (converted) *i = static_cast(longvalue); + break; + case kCFNumberLongLongType: + long long llvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&llvalue)); + if (converted) *i = static_cast(llvalue); + break; + case kCFNumberFloatType: + float floatvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&floatvalue)); + if (converted) *i = static_cast(floatvalue); + break; + case kCFNumberDoubleType: + double doublevalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&doublevalue)); + if (converted) *i = static_cast(doublevalue); + break; + case kCFNumberCFIndexType: + CFIndex cfindex; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&cfindex)); + if (converted) *i = static_cast(cfindex); + break; + default: + LOG(LS_ERROR) << "got unknown type."; + break; + } + } + } + + return converted; +} + +bool p_isCFNumberTrue(CFNumberRef cfn) { + // We assume it's false until proven otherwise. + bool result = false; + int asInt; + bool converted = p_convertCFNumberToInt(cfn, &asInt); + + if (converted && (0 != asInt)) { + result = true; + } + + return result; +} + +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) diff --git a/webrtc/base/macconversion.h b/webrtc/base/macconversion.h new file mode 100644 index 000000000..a96ed2298 --- /dev/null +++ b/webrtc/base/macconversion.h @@ -0,0 +1,39 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MACCONVERSION_H_ +#define WEBRTC_BASE_MACCONVERSION_H_ + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + +#include + +#include + +// given a CFStringRef, attempt to convert it to a C++ string. +// returns true if it succeeds, false otherwise. +// We can safely assume, given our context, that the string is +// going to be in ASCII, because it will either be an IP address, +// or a domain name, which is guaranteed to be ASCII-representable. +bool p_convertHostCFStringRefToCPPString(const CFStringRef cfstr, + std::string& cppstr); + +// Convert the CFNumber to an integer, putting the integer in the location +// given, and returhing true, if the conversion succeeds. +// If given a NULL or a non-CFNumber, returns false. +// This is pretty aggresive about trying to convert to int. +bool p_convertCFNumberToInt(CFNumberRef cfn, int* i); + +// given a CFNumberRef, determine if it represents a true value. +bool p_isCFNumberTrue(CFNumberRef cfn); + +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +#endif // WEBRTC_BASE_MACCONVERSION_H_ diff --git a/webrtc/base/macsocketserver.cc b/webrtc/base/macsocketserver.cc new file mode 100644 index 000000000..c7ab6e44d --- /dev/null +++ b/webrtc/base/macsocketserver.cc @@ -0,0 +1,378 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#include "webrtc/base/macsocketserver.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/macasyncsocket.h" +#include "webrtc/base/macutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MacBaseSocketServer +/////////////////////////////////////////////////////////////////////////////// + +MacBaseSocketServer::MacBaseSocketServer() { +} + +MacBaseSocketServer::~MacBaseSocketServer() { +} + +AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int family, int type) { + if (SOCK_STREAM != type) + return NULL; + + MacAsyncSocket* socket = new MacAsyncSocket(this, family); + if (!socket->valid()) { + delete socket; + return NULL; + } + return socket; +} + +void MacBaseSocketServer::RegisterSocket(MacAsyncSocket* s) { + sockets_.insert(s); +} + +void MacBaseSocketServer::UnregisterSocket(MacAsyncSocket* s) { + VERIFY(1 == sockets_.erase(s)); // found 1 +} + +bool MacBaseSocketServer::SetPosixSignalHandler(int signum, + void (*handler)(int)) { + Dispatcher* dispatcher = signal_dispatcher(); + if (!PhysicalSocketServer::SetPosixSignalHandler(signum, handler)) { + return false; + } + + // Only register the FD once, when the first custom handler is installed. + if (!dispatcher && (dispatcher = signal_dispatcher())) { + CFFileDescriptorContext ctx = { 0 }; + ctx.info = this; + + CFFileDescriptorRef desc = CFFileDescriptorCreate( + kCFAllocatorDefault, + dispatcher->GetDescriptor(), + false, + &MacBaseSocketServer::FileDescriptorCallback, + &ctx); + if (!desc) { + return false; + } + + CFFileDescriptorEnableCallBacks(desc, kCFFileDescriptorReadCallBack); + CFRunLoopSourceRef ref = + CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, desc, 0); + + if (!ref) { + CFRelease(desc); + return false; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), ref, kCFRunLoopCommonModes); + CFRelease(desc); + CFRelease(ref); + } + + return true; +} + +// Used to disable socket events from waking our message queue when +// process_io is false. Does not disable signal event handling though. +void MacBaseSocketServer::EnableSocketCallbacks(bool enable) { + for (std::set::iterator it = sockets().begin(); + it != sockets().end(); ++it) { + if (enable) { + (*it)->EnableCallbacks(); + } else { + (*it)->DisableCallbacks(); + } + } +} + +void MacBaseSocketServer::FileDescriptorCallback(CFFileDescriptorRef fd, + CFOptionFlags flags, + void* context) { + MacBaseSocketServer* this_ss = + reinterpret_cast(context); + ASSERT(this_ss); + Dispatcher* signal_dispatcher = this_ss->signal_dispatcher(); + ASSERT(signal_dispatcher); + + signal_dispatcher->OnPreEvent(DE_READ); + signal_dispatcher->OnEvent(DE_READ, 0); + CFFileDescriptorEnableCallBacks(fd, kCFFileDescriptorReadCallBack); +} + + +/////////////////////////////////////////////////////////////////////////////// +// MacCFSocketServer +/////////////////////////////////////////////////////////////////////////////// + +void WakeUpCallback(void* info) { + MacCFSocketServer* server = static_cast(info); + ASSERT(NULL != server); + server->OnWakeUpCallback(); +} + +MacCFSocketServer::MacCFSocketServer() + : run_loop_(CFRunLoopGetCurrent()), + wake_up_(NULL) { + CFRunLoopSourceContext ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.info = this; + ctx.perform = &WakeUpCallback; + wake_up_ = CFRunLoopSourceCreate(NULL, 0, &ctx); + ASSERT(NULL != wake_up_); + if (wake_up_) { + CFRunLoopAddSource(run_loop_, wake_up_, kCFRunLoopCommonModes); + } +} + +MacCFSocketServer::~MacCFSocketServer() { + if (wake_up_) { + CFRunLoopSourceInvalidate(wake_up_); + CFRelease(wake_up_); + } +} + +bool MacCFSocketServer::Wait(int cms, bool process_io) { + ASSERT(CFRunLoopGetCurrent() == run_loop_); + + if (!process_io && cms == 0) { + // No op. + return true; + } + + if (!process_io) { + // No way to listen to common modes and not get socket events, unless + // we disable each one's callbacks. + EnableSocketCallbacks(false); + } + + SInt32 result; + if (kForever == cms) { + do { + // Would prefer to run in a custom mode that only listens to wake_up, + // but we have qtkit sending work to the main thread which is effectively + // blocked here, causing deadlock. Thus listen to the common modes. + // TODO: If QTKit becomes thread safe, do the above. + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000000, false); + } while (result != kCFRunLoopRunFinished && result != kCFRunLoopRunStopped); + } else { + // TODO: In the case of 0ms wait, this will only process one event, so we + // may want to loop until it returns TimedOut. + CFTimeInterval seconds = cms / 1000.0; + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, false); + } + + if (!process_io) { + // Reenable them. Hopefully this won't cause spurious callbacks or + // missing ones while they were disabled. + EnableSocketCallbacks(true); + } + + if (kCFRunLoopRunFinished == result) { + return false; + } + return true; +} + +void MacCFSocketServer::WakeUp() { + if (wake_up_) { + CFRunLoopSourceSignal(wake_up_); + CFRunLoopWakeUp(run_loop_); + } +} + +void MacCFSocketServer::OnWakeUpCallback() { + ASSERT(run_loop_ == CFRunLoopGetCurrent()); + CFRunLoopStop(run_loop_); +} + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonSocketServer +/////////////////////////////////////////////////////////////////////////////// +#ifndef CARBON_DEPRECATED + +const UInt32 kEventClassSocketServer = 'MCSS'; +const UInt32 kEventWakeUp = 'WAKE'; +const EventTypeSpec kEventWakeUpSpec[] = { + { kEventClassSocketServer, kEventWakeUp } +}; + +std::string DecodeEvent(EventRef event) { + std::string str; + DecodeFourChar(::GetEventClass(event), &str); + str.push_back(':'); + DecodeFourChar(::GetEventKind(event), &str); + return str; +} + +MacCarbonSocketServer::MacCarbonSocketServer() + : event_queue_(GetCurrentEventQueue()), wake_up_(NULL) { + VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0, + kEventAttributeUserEvent, &wake_up_)); +} + +MacCarbonSocketServer::~MacCarbonSocketServer() { + if (wake_up_) { + ReleaseEvent(wake_up_); + } +} + +bool MacCarbonSocketServer::Wait(int cms, bool process_io) { + ASSERT(GetCurrentEventQueue() == event_queue_); + + // Listen to all events if we're processing I/O. + // Only listen for our wakeup event if we're not. + UInt32 num_types = 0; + const EventTypeSpec* events = NULL; + if (!process_io) { + num_types = GetEventTypeCount(kEventWakeUpSpec); + events = kEventWakeUpSpec; + } + + EventTargetRef target = GetEventDispatcherTarget(); + EventTimeout timeout = + (kForever == cms) ? kEventDurationForever : cms / 1000.0; + EventTimeout end_time = GetCurrentEventTime() + timeout; + + bool done = false; + while (!done) { + EventRef event; + OSStatus result = ReceiveNextEvent(num_types, events, timeout, true, + &event); + if (noErr == result) { + if (wake_up_ != event) { + LOG_F(LS_VERBOSE) << "Dispatching event: " << DecodeEvent(event); + result = SendEventToEventTarget(event, target); + if ((noErr != result) && (eventNotHandledErr != result)) { + LOG_E(LS_ERROR, OS, result) << "SendEventToEventTarget"; + } + } else { + done = true; + } + ReleaseEvent(event); + } else if (eventLoopTimedOutErr == result) { + ASSERT(cms != kForever); + done = true; + } else if (eventLoopQuitErr == result) { + // Ignore this... we get spurious quits for a variety of reasons. + LOG_E(LS_VERBOSE, OS, result) << "ReceiveNextEvent"; + } else { + // Some strange error occurred. Log it. + LOG_E(LS_WARNING, OS, result) << "ReceiveNextEvent"; + return false; + } + if (kForever != cms) { + timeout = end_time - GetCurrentEventTime(); + } + } + return true; +} + +void MacCarbonSocketServer::WakeUp() { + if (!IsEventInQueue(event_queue_, wake_up_)) { + RetainEvent(wake_up_); + OSStatus result = PostEventToQueue(event_queue_, wake_up_, + kEventPriorityStandard); + if (noErr != result) { + LOG_E(LS_ERROR, OS, result) << "PostEventToQueue"; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonAppSocketServer +/////////////////////////////////////////////////////////////////////////////// + +MacCarbonAppSocketServer::MacCarbonAppSocketServer() + : event_queue_(GetCurrentEventQueue()) { + // Install event handler + VERIFY(noErr == InstallApplicationEventHandler( + NewEventHandlerUPP(WakeUpEventHandler), 1, kEventWakeUpSpec, this, + &event_handler_)); + + // Install a timer and set it idle to begin with. + VERIFY(noErr == InstallEventLoopTimer(GetMainEventLoop(), + kEventDurationForever, + kEventDurationForever, + NewEventLoopTimerUPP(TimerHandler), + this, + &timer_)); +} + +MacCarbonAppSocketServer::~MacCarbonAppSocketServer() { + RemoveEventLoopTimer(timer_); + RemoveEventHandler(event_handler_); +} + +OSStatus MacCarbonAppSocketServer::WakeUpEventHandler( + EventHandlerCallRef next, EventRef event, void *data) { + QuitApplicationEventLoop(); + return noErr; +} + +void MacCarbonAppSocketServer::TimerHandler( + EventLoopTimerRef timer, void *data) { + QuitApplicationEventLoop(); +} + +bool MacCarbonAppSocketServer::Wait(int cms, bool process_io) { + if (!process_io && cms == 0) { + // No op. + return true; + } + if (kForever != cms) { + // Start a timer. + OSStatus error = + SetEventLoopTimerNextFireTime(timer_, cms / 1000.0); + if (error != noErr) { + LOG(LS_ERROR) << "Failed setting next fire time."; + } + } + if (!process_io) { + // No way to listen to common modes and not get socket events, unless + // we disable each one's callbacks. + EnableSocketCallbacks(false); + } + RunApplicationEventLoop(); + if (!process_io) { + // Reenable them. Hopefully this won't cause spurious callbacks or + // missing ones while they were disabled. + EnableSocketCallbacks(true); + } + return true; +} + +void MacCarbonAppSocketServer::WakeUp() { + // TODO: No-op if there's already a WakeUp in flight. + EventRef wake_up; + VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0, + kEventAttributeUserEvent, &wake_up)); + OSStatus result = PostEventToQueue(event_queue_, wake_up, + kEventPriorityStandard); + if (noErr != result) { + LOG_E(LS_ERROR, OS, result) << "PostEventToQueue"; + } + ReleaseEvent(wake_up); +} + +#endif +} // namespace rtc diff --git a/webrtc/base/macsocketserver.h b/webrtc/base/macsocketserver.h new file mode 100644 index 000000000..8eebac6c6 --- /dev/null +++ b/webrtc/base/macsocketserver.h @@ -0,0 +1,136 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef WEBRTC_BASE_MACSOCKETSERVER_H__ +#define WEBRTC_BASE_MACSOCKETSERVER_H__ + +#include +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) // Invalid on IOS +#include +#endif +#include "webrtc/base/physicalsocketserver.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MacBaseSocketServer +/////////////////////////////////////////////////////////////////////////////// +class MacAsyncSocket; + +class MacBaseSocketServer : public PhysicalSocketServer { + public: + MacBaseSocketServer(); + virtual ~MacBaseSocketServer(); + + // SocketServer Interface + virtual Socket* CreateSocket(int type) { return NULL; } + virtual Socket* CreateSocket(int family, int type) { return NULL; } + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual bool Wait(int cms, bool process_io) = 0; + virtual void WakeUp() = 0; + + void RegisterSocket(MacAsyncSocket* socket); + void UnregisterSocket(MacAsyncSocket* socket); + + // PhysicalSocketServer Overrides + virtual bool SetPosixSignalHandler(int signum, void (*handler)(int)); + + protected: + void EnableSocketCallbacks(bool enable); + const std::set& sockets() { + return sockets_; + } + + private: + static void FileDescriptorCallback(CFFileDescriptorRef ref, + CFOptionFlags flags, + void* context); + + std::set sockets_; +}; + +// Core Foundation implementation of the socket server. While idle it +// will run the current CF run loop. When the socket server has work +// to do the run loop will be paused. Does not support Carbon or Cocoa +// UI interaction. +class MacCFSocketServer : public MacBaseSocketServer { + public: + MacCFSocketServer(); + virtual ~MacCFSocketServer(); + + // SocketServer Interface + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + void OnWakeUpCallback(); + + private: + CFRunLoopRef run_loop_; + CFRunLoopSourceRef wake_up_; +}; + +#ifndef CARBON_DEPRECATED + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonSocketServer +/////////////////////////////////////////////////////////////////////////////// + +// Interacts with the Carbon event queue. While idle it will block, +// waiting for events. When the socket server has work to do, it will +// post a 'wake up' event to the queue, causing the thread to exit the +// event loop until the next call to Wait. Other events are dispatched +// to their target. Supports Carbon and Cocoa UI interaction. +class MacCarbonSocketServer : public MacBaseSocketServer { + public: + MacCarbonSocketServer(); + virtual ~MacCarbonSocketServer(); + + // SocketServer Interface + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + private: + EventQueueRef event_queue_; + EventRef wake_up_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonAppSocketServer +/////////////////////////////////////////////////////////////////////////////// + +// Runs the Carbon application event loop on the current thread while +// idle. When the socket server has work to do, it will post an event +// to the queue, causing the thread to exit the event loop until the +// next call to Wait. Other events are automatically dispatched to +// their target. +class MacCarbonAppSocketServer : public MacBaseSocketServer { + public: + MacCarbonAppSocketServer(); + virtual ~MacCarbonAppSocketServer(); + + // SocketServer Interface + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + private: + static OSStatus WakeUpEventHandler(EventHandlerCallRef next, EventRef event, + void *data); + static void TimerHandler(EventLoopTimerRef timer, void *data); + + EventQueueRef event_queue_; + EventHandlerRef event_handler_; + EventLoopTimerRef timer_; +}; + +#endif +} // namespace rtc + +#endif // WEBRTC_BASE_MACSOCKETSERVER_H__ diff --git a/webrtc/base/macsocketserver_unittest.cc b/webrtc/base/macsocketserver_unittest.cc new file mode 100644 index 000000000..e98be918c --- /dev/null +++ b/webrtc/base/macsocketserver_unittest.cc @@ -0,0 +1,237 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socket_unittest.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/macsocketserver.h" + +namespace rtc { + +class WakeThread : public Thread { + public: + WakeThread(SocketServer* ss) : ss_(ss) { + } + virtual ~WakeThread() { + Stop(); + } + void Run() { + ss_->WakeUp(); + } + private: + SocketServer* ss_; +}; + +#ifndef CARBON_DEPRECATED + +// Test that MacCFSocketServer::Wait works as expected. +TEST(MacCFSocketServerTest, TestWait) { + MacCFSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCFSocketServer::Wakeup works as expected. +TEST(MacCFSocketServerTest, TestWakeup) { + MacCFSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +// Test that MacCarbonSocketServer::Wait works as expected. +TEST(MacCarbonSocketServerTest, TestWait) { + MacCarbonSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCarbonSocketServer::Wakeup works as expected. +TEST(MacCarbonSocketServerTest, TestWakeup) { + MacCarbonSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +// Test that MacCarbonAppSocketServer::Wait works as expected. +TEST(MacCarbonAppSocketServerTest, TestWait) { + MacCarbonAppSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCarbonAppSocketServer::Wakeup works as expected. +TEST(MacCarbonAppSocketServerTest, TestWakeup) { + MacCarbonAppSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +#endif + +// Test that MacAsyncSocket passes all the generic Socket tests. +class MacAsyncSocketTest : public SocketTest { + protected: + MacAsyncSocketTest() + : server_(CreateSocketServer()), + scope_(server_.get()) {} + // Override for other implementations of MacBaseSocketServer. + virtual MacBaseSocketServer* CreateSocketServer() { + return new MacCFSocketServer(); + }; + rtc::scoped_ptr server_; + SocketServerScope scope_; +}; + +TEST_F(MacAsyncSocketTest, TestConnectIPv4) { + SocketTest::TestConnectIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectIPv6) { + SocketTest::TestConnectIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookupIPv4) { + SocketTest::TestConnectWithDnsLookupIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookupIPv6) { + SocketTest::TestConnectWithDnsLookupIPv6(); +} + +// BUG=https://code.google.com/p/webrtc/issues/detail?id=2272 +TEST_F(MacAsyncSocketTest, DISABLED_TestConnectFailIPv4) { + SocketTest::TestConnectFailIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectFailIPv6) { + SocketTest::TestConnectFailIPv6(); +} + +// Reenable once we have mac async dns +TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFailIPv4) { + SocketTest::TestConnectWithDnsLookupFailIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFailIPv6) { + SocketTest::TestConnectWithDnsLookupFailIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocketIPv4) { + SocketTest::TestConnectWithClosedSocketIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocketIPv6) { + SocketTest::TestConnectWithClosedSocketIPv6(); +} + +// Flaky at the moment (10% failure rate). Seems the client doesn't get +// signalled in a timely manner... +TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnectIPv4) { + SocketTest::TestServerCloseDuringConnectIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnectIPv6) { + SocketTest::TestServerCloseDuringConnectIPv6(); +} +// Flaky at the moment (0.5% failure rate). Seems the client doesn't get +// signalled in a timely manner... +TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnectIPv4) { + SocketTest::TestClientCloseDuringConnectIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnectIPv6) { + SocketTest::TestClientCloseDuringConnectIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestServerCloseIPv4) { + SocketTest::TestServerCloseIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestServerCloseIPv6) { + SocketTest::TestServerCloseIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestCloseInClosedCallbackIPv4) { + SocketTest::TestCloseInClosedCallbackIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestCloseInClosedCallbackIPv6) { + SocketTest::TestCloseInClosedCallbackIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestTcpIPv4) { + SocketTest::TestTcpIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestTcpIPv6) { + SocketTest::TestTcpIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallbackIPv4) { + SocketTest::TestSingleFlowControlCallbackIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallbackIPv6) { + SocketTest::TestSingleFlowControlCallbackIPv6(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestUdpIPv4) { + SocketTest::TestUdpIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestUdpIPv6) { + SocketTest::TestUdpIPv6(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptionsIPv4) { + SocketTest::TestGetSetOptionsIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptionsIPv6) { + SocketTest::TestGetSetOptionsIPv6(); +} + +#ifndef CARBON_DEPRECATED +class MacCarbonAppAsyncSocketTest : public MacAsyncSocketTest { + virtual MacBaseSocketServer* CreateSocketServer() { + return new MacCarbonAppSocketServer(); + }; +}; + +TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} +#endif +} // namespace rtc diff --git a/webrtc/base/macutils.cc b/webrtc/base/macutils.cc new file mode 100644 index 000000000..6e436d4a8 --- /dev/null +++ b/webrtc/base/macutils.cc @@ -0,0 +1,221 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/macutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +bool ToUtf8(const CFStringRef str16, std::string* str8) { + if ((NULL == str16) || (NULL == str8)) { + return false; + } + size_t maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str16), + kCFStringEncodingUTF8) + 1; + scoped_ptr buffer(new char[maxlen]); + if (!buffer || !CFStringGetCString(str16, buffer.get(), maxlen, + kCFStringEncodingUTF8)) { + return false; + } + str8->assign(buffer.get()); + return true; +} + +bool ToUtf16(const std::string& str8, CFStringRef* str16) { + if (NULL == str16) { + return false; + } + *str16 = CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast(str8.data()), + str8.length(), kCFStringEncodingUTF8, + false); + return NULL != *str16; +} + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +void DecodeFourChar(UInt32 fc, std::string* out) { + std::stringstream ss; + ss << '\''; + bool printable = true; + for (int i = 3; i >= 0; --i) { + char ch = (fc >> (8 * i)) & 0xFF; + if (isprint(static_cast(ch))) { + ss << ch; + } else { + printable = false; + break; + } + } + if (printable) { + ss << '\''; + } else { + ss.str(""); + ss << "0x" << std::hex << fc; + } + out->append(ss.str()); +} + +static bool GetGestalt(OSType ostype, int* value) { + ASSERT(NULL != value); + SInt32 native_value; + OSStatus result = Gestalt(ostype, &native_value); + if (noErr == result) { + *value = native_value; + return true; + } + std::string str; + DecodeFourChar(ostype, &str); + LOG_E(LS_ERROR, OS, result) << "Gestalt(" << str << ")"; + return false; +} + +bool GetOSVersion(int* major, int* minor, int* bugfix) { + ASSERT(major && minor && bugfix); + if (!GetGestalt(gestaltSystemVersion, major)) { + return false; + } + if (*major < 0x1040) { + *bugfix = *major & 0xF; + *minor = (*major >> 4) & 0xF; + *major = (*major >> 8); + return true; + } + return GetGestalt(gestaltSystemVersionMajor, major) && + GetGestalt(gestaltSystemVersionMinor, minor) && + GetGestalt(gestaltSystemVersionBugFix, bugfix); +} + +MacOSVersionName GetOSVersionName() { + int major = 0, minor = 0, bugfix = 0; + if (!GetOSVersion(&major, &minor, &bugfix)) { + return kMacOSUnknown; + } + if (major > 10) { + return kMacOSNewer; + } + if ((major < 10) || (minor < 3)) { + return kMacOSOlder; + } + switch (minor) { + case 3: + return kMacOSPanther; + case 4: + return kMacOSTiger; + case 5: + return kMacOSLeopard; + case 6: + return kMacOSSnowLeopard; + case 7: + return kMacOSLion; + case 8: + return kMacOSMountainLion; + case 9: + return kMacOSMavericks; + } + return kMacOSNewer; +} + +bool GetQuickTimeVersion(std::string* out) { + int ver; + if (!GetGestalt(gestaltQuickTimeVersion, &ver)) { + return false; + } + + std::stringstream ss; + ss << std::hex << ver; + *out = ss.str(); + return true; +} + +bool RunAppleScript(const std::string& script) { + // TODO(thaloun): Add a .mm file that contains something like this: + // NSString source from script + // NSAppleScript* appleScript = [[NSAppleScript alloc] initWithSource:&source] + // if (appleScript != nil) { + // [appleScript executeAndReturnError:nil] + // [appleScript release] +#ifndef CARBON_DEPRECATED + ComponentInstance component = NULL; + AEDesc script_desc; + AEDesc result_data; + OSStatus err; + OSAID script_id, result_id; + + AECreateDesc(typeNull, NULL, 0, &script_desc); + AECreateDesc(typeNull, NULL, 0, &result_data); + script_id = kOSANullScript; + result_id = kOSANullScript; + + component = OpenDefaultComponent(kOSAComponentType, typeAppleScript); + if (component == NULL) { + LOG(LS_ERROR) << "Failed opening Apple Script component"; + return false; + } + err = AECreateDesc(typeUTF8Text, script.data(), script.size(), &script_desc); + if (err != noErr) { + CloseComponent(component); + LOG(LS_ERROR) << "Failed creating Apple Script description"; + return false; + } + + err = OSACompile(component, &script_desc, kOSAModeCanInteract, &script_id); + if (err != noErr) { + AEDisposeDesc(&script_desc); + if (script_id != kOSANullScript) { + OSADispose(component, script_id); + } + CloseComponent(component); + LOG(LS_ERROR) << "Error compiling Apple Script"; + return false; + } + + err = OSAExecute(component, script_id, kOSANullScript, kOSAModeCanInteract, + &result_id); + + if (err == errOSAScriptError) { + LOG(LS_ERROR) << "Error when executing Apple Script: " << script; + AECreateDesc(typeNull, NULL, 0, &result_data); + OSAScriptError(component, kOSAErrorMessage, typeChar, &result_data); + int len = AEGetDescDataSize(&result_data); + char* data = (char*) malloc(len); + if (data != NULL) { + err = AEGetDescData(&result_data, data, len); + LOG(LS_ERROR) << "Script error: " << data; + } + AEDisposeDesc(&script_desc); + AEDisposeDesc(&result_data); + return false; + } + AEDisposeDesc(&script_desc); + if (script_id != kOSANullScript) { + OSADispose(component, script_id); + } + if (result_id != kOSANullScript) { + OSADispose(component, result_id); + } + CloseComponent(component); + return true; +#else + // TODO(thaloun): Support applescripts with the NSAppleScript API. + return false; +#endif // CARBON_DEPRECATED +} +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/macutils.h b/webrtc/base/macutils.h new file mode 100644 index 000000000..35c3d1870 --- /dev/null +++ b/webrtc/base/macutils.h @@ -0,0 +1,59 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MACUTILS_H__ +#define WEBRTC_BASE_MACUTILS_H__ + +#include +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#endif +#include + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +// Note that some of these functions work for both iOS and Mac OS X. The ones +// that are specific to Mac are #ifdef'ed as such. + +bool ToUtf8(const CFStringRef str16, std::string* str8); +bool ToUtf16(const std::string& str8, CFStringRef* str16); + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +void DecodeFourChar(UInt32 fc, std::string* out); + +enum MacOSVersionName { + kMacOSUnknown, // ??? + kMacOSOlder, // 10.2- + kMacOSPanther, // 10.3 + kMacOSTiger, // 10.4 + kMacOSLeopard, // 10.5 + kMacOSSnowLeopard, // 10.6 + kMacOSLion, // 10.7 + kMacOSMountainLion, // 10.8 + kMacOSMavericks, // 10.9 + kMacOSNewer, // 10.10+ +}; + +bool GetOSVersion(int* major, int* minor, int* bugfix); +MacOSVersionName GetOSVersionName(); +bool GetQuickTimeVersion(std::string* version); + +// Runs the given apple script. Only supports scripts that does not +// require user interaction. +bool RunAppleScript(const std::string& script); +#endif + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_MACUTILS_H__ diff --git a/webrtc/base/macutils_unittest.cc b/webrtc/base/macutils_unittest.cc new file mode 100644 index 000000000..7150bf355 --- /dev/null +++ b/webrtc/base/macutils_unittest.cc @@ -0,0 +1,43 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/macutils.h" + +TEST(MacUtilsTest, GetOsVersionName) { + rtc::MacOSVersionName ver = rtc::GetOSVersionName(); + LOG(LS_INFO) << "GetOsVersionName " << ver; + EXPECT_NE(rtc::kMacOSUnknown, ver); +} + +TEST(MacUtilsTest, GetQuickTimeVersion) { + std::string version; + EXPECT_TRUE(rtc::GetQuickTimeVersion(&version)); + LOG(LS_INFO) << "GetQuickTimeVersion " << version; +} + +TEST(MacUtilsTest, RunAppleScriptCompileError) { + std::string script("set value to to 5"); + EXPECT_FALSE(rtc::RunAppleScript(script)); +} + +TEST(MacUtilsTest, RunAppleScriptRuntimeError) { + std::string script("set value to 5 / 0"); + EXPECT_FALSE(rtc::RunAppleScript(script)); +} + +#ifdef CARBON_DEPRECATED +TEST(MacUtilsTest, DISABLED_RunAppleScriptSuccess) { +#else +TEST(MacUtilsTest, RunAppleScriptSuccess) { +#endif + std::string script("set value to 5"); + EXPECT_TRUE(rtc::RunAppleScript(script)); +} diff --git a/webrtc/base/macwindowpicker.cc b/webrtc/base/macwindowpicker.cc new file mode 100644 index 000000000..bb97d20f1 --- /dev/null +++ b/webrtc/base/macwindowpicker.cc @@ -0,0 +1,256 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "webrtc/base/macwindowpicker.h" + +#include +#include +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/macutils.h" + +namespace rtc { + +static const char* kCoreGraphicsName = + "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" + "CoreGraphics.framework/CoreGraphics"; + +static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo"; +static const char* kWindowListCreateDescriptionFromArray = + "CGWindowListCreateDescriptionFromArray"; + +// Function pointer for holding the CGWindowListCopyWindowInfo function. +typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption, + CGWindowID); + +// Function pointer for holding the CGWindowListCreateDescriptionFromArray +// function. +typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef); + +MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL), + get_window_list_desc_(NULL) { +} + +MacWindowPicker::~MacWindowPicker() { + if (lib_handle_ != NULL) { + dlclose(lib_handle_); + } +} + +bool MacWindowPicker::Init() { + // TODO: If this class grows to use more dynamically functions + // from the CoreGraphics framework, consider using + // webrtc/base/latebindingsymboltable.h. + lib_handle_ = dlopen(kCoreGraphicsName, RTLD_NOW); + if (lib_handle_ == NULL) { + LOG(LS_ERROR) << "Could not load CoreGraphics"; + return false; + } + + get_window_list_ = dlsym(lib_handle_, kWindowListCopyWindowInfo); + get_window_list_desc_ = + dlsym(lib_handle_, kWindowListCreateDescriptionFromArray); + if (get_window_list_ == NULL || get_window_list_desc_ == NULL) { + // The CGWindowListCopyWindowInfo and the + // CGWindowListCreateDescriptionFromArray functions was introduced + // in Leopard(10.5) so this is a normal failure on Tiger. + LOG(LS_INFO) << "Failed to load Core Graphics symbols"; + dlclose(lib_handle_); + lib_handle_ = NULL; + return false; + } + + return true; +} + +bool MacWindowPicker::IsVisible(const WindowId& id) { + // Init if we're not already inited. + if (get_window_list_desc_ == NULL && !Init()) { + return false; + } + CGWindowID ids[1]; + ids[0] = id.id(); + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); + + CFArrayRef window_array = + reinterpret_cast( + get_window_list_desc_)(window_id_array); + if (window_array == NULL || 0 == CFArrayGetCount(window_array)) { + // Could not find the window. It might have been closed. + LOG(LS_INFO) << "Window not found"; + CFRelease(window_id_array); + return false; + } + + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(window_array, 0)); + CFBooleanRef is_visible = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowIsOnscreen)); + + // Check that the window is visible. If not we might crash. + bool visible = false; + if (is_visible != NULL) { + visible = CFBooleanGetValue(is_visible); + } + CFRelease(window_id_array); + CFRelease(window_array); + return visible; +} + +bool MacWindowPicker::MoveToFront(const WindowId& id) { + // Init if we're not already initialized. + if (get_window_list_desc_ == NULL && !Init()) { + return false; + } + CGWindowID ids[1]; + ids[0] = id.id(); + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); + + CFArrayRef window_array = + reinterpret_cast( + get_window_list_desc_)(window_id_array); + if (window_array == NULL || 0 == CFArrayGetCount(window_array)) { + // Could not find the window. It might have been closed. + LOG(LS_INFO) << "Window not found"; + CFRelease(window_id_array); + return false; + } + + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(window_array, 0)); + CFStringRef window_name_ref = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef application_pid = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowOwnerPID)); + + int pid_val; + CFNumberGetValue(application_pid, kCFNumberIntType, &pid_val); + std::string window_name; + ToUtf8(window_name_ref, &window_name); + + // Build an applescript that sets the selected window to front + // within the application. Then set the application to front. + bool result = true; + std::stringstream ss; + ss << "tell application \"System Events\"\n" + << "set proc to the first item of (every process whose unix id is " + << pid_val + << ")\n" + << "tell proc to perform action \"AXRaise\" of window \"" + << window_name + << "\"\n" + << "set the frontmost of proc to true\n" + << "end tell"; + if (!RunAppleScript(ss.str())) { + // This might happen to for example X applications where the X + // server spawns of processes with their own PID but the X server + // is still registered as owner to the application windows. As a + // workaround, we put the X server process to front, meaning that + // all X applications will show up. The drawback with this + // workaround is that the application that we really wanted to set + // to front might be behind another X application. + ProcessSerialNumber psn; + pid_t pid = pid_val; + int res = GetProcessForPID(pid, &psn); + if (res != 0) { + LOG(LS_ERROR) << "Failed getting process for pid"; + result = false; + } + res = SetFrontProcess(&psn); + if (res != 0) { + LOG(LS_ERROR) << "Failed setting process to front"; + result = false; + } + } + CFRelease(window_id_array); + CFRelease(window_array); + return result; +} + +bool MacWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { + const uint32_t kMaxDisplays = 128; + CGDirectDisplayID active_displays[kMaxDisplays]; + uint32_t display_count = 0; + + CGError err = CGGetActiveDisplayList(kMaxDisplays, + active_displays, + &display_count); + if (err != kCGErrorSuccess) { + LOG_E(LS_ERROR, OS, err) << "Failed to enumerate the active displays."; + return false; + } + for (uint32_t i = 0; i < display_count; ++i) { + DesktopId id(active_displays[i], static_cast(i)); + // TODO: Figure out an appropriate desktop title. + DesktopDescription desc(id, ""); + desc.set_primary(CGDisplayIsMain(id.id())); + descriptions->push_back(desc); + } + return display_count > 0; +} + +bool MacWindowPicker::GetDesktopDimensions(const DesktopId& id, + int* width, + int* height) { + *width = CGDisplayPixelsWide(id.id()); + *height = CGDisplayPixelsHigh(id.id()); + return true; +} + +bool MacWindowPicker::GetWindowList(WindowDescriptionList* descriptions) { + // Init if we're not already inited. + if (get_window_list_ == NULL && !Init()) { + return false; + } + + // Only get onscreen, non-desktop windows. + CFArrayRef window_array = + reinterpret_cast(get_window_list_)( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (window_array == NULL) { + return false; + } + + // Check windows to make sure they have an id, title, and use window layer 0. + CFIndex i; + CFIndex count = CFArrayGetCount(window_array); + for (i = 0; i < count; ++i) { + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(window_array, i)); + CFStringRef window_title = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef window_id = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowNumber)); + CFNumberRef window_layer = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowLayer)); + if (window_title != NULL && window_id != NULL && window_layer != NULL) { + std::string title_str; + int id_val, layer_val; + ToUtf8(window_title, &title_str); + CFNumberGetValue(window_id, kCFNumberIntType, &id_val); + CFNumberGetValue(window_layer, kCFNumberIntType, &layer_val); + + // Discard windows without a title. + if (layer_val == 0 && title_str.length() > 0) { + WindowId id(static_cast(id_val)); + WindowDescription desc(id, title_str); + descriptions->push_back(desc); + } + } + } + + CFRelease(window_array); + return true; +} + +} // namespace rtc diff --git a/webrtc/base/macwindowpicker.h b/webrtc/base/macwindowpicker.h new file mode 100644 index 000000000..9a44747d2 --- /dev/null +++ b/webrtc/base/macwindowpicker.h @@ -0,0 +1,37 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef WEBRTC_BASE_MACWINDOWPICKER_H_ +#define WEBRTC_BASE_MACWINDOWPICKER_H_ + +#include "webrtc/base/windowpicker.h" + +namespace rtc { + +class MacWindowPicker : public WindowPicker { + public: + MacWindowPicker(); + ~MacWindowPicker(); + virtual bool Init(); + virtual bool IsVisible(const WindowId& id); + virtual bool MoveToFront(const WindowId& id); + virtual bool GetWindowList(WindowDescriptionList* descriptions); + virtual bool GetDesktopList(DesktopDescriptionList* descriptions); + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height); + + private: + void* lib_handle_; + void* get_window_list_; + void* get_window_list_desc_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MACWINDOWPICKER_H_ diff --git a/webrtc/base/macwindowpicker_unittest.cc b/webrtc/base/macwindowpicker_unittest.cc new file mode 100644 index 000000000..7140f0231 --- /dev/null +++ b/webrtc/base/macwindowpicker_unittest.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/macutils.h" +#include "webrtc/base/macwindowpicker.h" +#include "webrtc/base/windowpicker.h" + +#if !defined(WEBRTC_MAC) || defined(WEBRTC_IOS) +#error Only for WEBRTC_MAC && !WEBRTC_IOS +#endif + +namespace rtc { + +bool IsLeopardOrLater() { + return GetOSVersionName() >= kMacOSLeopard; +} + +// Test that this works on new versions and fails acceptably on old versions. +TEST(MacWindowPickerTest, TestGetWindowList) { + MacWindowPicker picker, picker2; + WindowDescriptionList descriptions; + if (IsLeopardOrLater()) { + EXPECT_TRUE(picker.Init()); + EXPECT_TRUE(picker.GetWindowList(&descriptions)); + EXPECT_TRUE(picker2.GetWindowList(&descriptions)); // Init is optional + } else { + EXPECT_FALSE(picker.Init()); + EXPECT_FALSE(picker.GetWindowList(&descriptions)); + EXPECT_FALSE(picker2.GetWindowList(&descriptions)); + } +} + +// TODO: Add verification of the actual parsing, ie, add +// functionality to inject a fake get_window_array function which +// provide a pre-constructed list of windows. + +} // namespace rtc diff --git a/webrtc/base/mathutils.h b/webrtc/base/mathutils.h new file mode 100644 index 000000000..e2b21261d --- /dev/null +++ b/webrtc/base/mathutils.h @@ -0,0 +1,20 @@ +/* + * Copyright 2005 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MATHUTILS_H_ +#define WEBRTC_BASE_MATHUTILS_H_ + +#include + +#ifndef M_PI +#define M_PI 3.14159265359f +#endif + +#endif // WEBRTC_BASE_MATHUTILS_H_ diff --git a/webrtc/base/md5.cc b/webrtc/base/md5.cc new file mode 100644 index 000000000..a4f28d730 --- /dev/null +++ b/webrtc/base/md5.cc @@ -0,0 +1,218 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +// Changes from original C code: +// Ported to C++, type casting, Google code style. + +#include "webrtc/base/md5.h" + +// TODO: Avoid memcmpy - hash directly from memory. +#include // for memcpy(). + +#include "webrtc/base/byteorder.h" // for ARCH_CPU_LITTLE_ENDIAN. + +#ifdef ARCH_CPU_LITTLE_ENDIAN +#define ByteReverse(buf, len) // Nothing. +#else // ARCH_CPU_BIG_ENDIAN +static void ByteReverse(uint32* buf, int len) { + for (int i = 0; i < len; ++i) { + buf[i] = rtc::GetLE32(&buf[i]); + } +} +#endif + +// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious +// initialization constants. +void MD5Init(MD5Context* ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +// Update context to reflect the concatenation of another buffer full of bytes. +void MD5Update(MD5Context* ctx, const uint8* buf, size_t len) { + // Update bitcount. + uint32 t = ctx->bits[0]; + if ((ctx->bits[0] = t + (static_cast(len) << 3)) < t) { + ctx->bits[1]++; // Carry from low to high. + } + ctx->bits[1] += static_cast(len >> 29); + t = (t >> 3) & 0x3f; // Bytes already in shsInfo->data. + + // Handle any leading odd-sized chunks. + if (t) { + uint8* p = reinterpret_cast(ctx->in) + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + ByteReverse(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + } + + // Process data in 64-byte chunks. + while (len >= 64) { + memcpy(ctx->in, buf, 64); + ByteReverse(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + // Handle any remaining bytes of data. + memcpy(ctx->in, buf, len); +} + +// Final wrapup - pad to 64-byte boundary with the bit pattern. +// 1 0* (64-bit count of bits processed, MSB-first) +void MD5Final(MD5Context* ctx, uint8 digest[16]) { + // Compute number of bytes mod 64. + uint32 count = (ctx->bits[0] >> 3) & 0x3F; + + // Set the first char of padding to 0x80. This is safe since there is + // always at least one byte free. + uint8* p = reinterpret_cast(ctx->in) + count; + *p++ = 0x80; + + // Bytes of padding needed to make 64 bytes. + count = 64 - 1 - count; + + // Pad out to 56 mod 64. + if (count < 8) { + // Two lots of padding: Pad the first block to 64 bytes. + memset(p, 0, count); + ByteReverse(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + + // Now fill the next block with 56 bytes. + memset(ctx->in, 0, 56); + } else { + // Pad block to 56 bytes. + memset(p, 0, count - 8); + } + ByteReverse(ctx->in, 14); + + // Append length in bits and transform. + ctx->in[14] = ctx->bits[0]; + ctx->in[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, ctx->in); + ByteReverse(ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); // In case it's sensitive. +} + +// The four core functions - F1 is optimized somewhat. +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +// The core of the MD5 algorithm, this alters an existing MD5 hash to +// reflect the addition of 16 longwords of new data. MD5Update blocks +// the data and converts bytes into longwords for this routine. +void MD5Transform(uint32 buf[4], const uint32 in[16]) { + uint32 a = buf[0]; + uint32 b = buf[1]; + uint32 c = buf[2]; + uint32 d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9] + 0xeb86d391, 21); + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} diff --git a/webrtc/base/md5.h b/webrtc/base/md5.h new file mode 100644 index 000000000..fc563b764 --- /dev/null +++ b/webrtc/base/md5.h @@ -0,0 +1,40 @@ +/* + * This is the header file for the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + */ + +// Changes(fbarchard): Ported to C++ and Google style guide. +// Made context first parameter in MD5Final for consistency with Sha1. + +#ifndef WEBRTC_BASE_MD5_H_ +#define WEBRTC_BASE_MD5_H_ + +#include "webrtc/base/basictypes.h" + +// Canonical name for a MD5 context structure, used in many crypto libs. +typedef struct MD5Context MD5_CTX; + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + uint32 in[16]; +}; + +void MD5Init(MD5Context* context); +void MD5Update(MD5Context* context, const uint8* data, size_t len); +void MD5Final(MD5Context* context, uint8 digest[16]); +void MD5Transform(uint32 buf[4], const uint32 in[16]); + +#endif // WEBRTC_BASE_MD5_H_ diff --git a/webrtc/base/md5digest.h b/webrtc/base/md5digest.h new file mode 100644 index 000000000..5e8580222 --- /dev/null +++ b/webrtc/base/md5digest.h @@ -0,0 +1,46 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MD5DIGEST_H_ +#define WEBRTC_BASE_MD5DIGEST_H_ + +#include "webrtc/base/md5.h" +#include "webrtc/base/messagedigest.h" + +namespace rtc { + +// A simple wrapper for our MD5 implementation. +class Md5Digest : public MessageDigest { + public: + enum { kSize = 16 }; + Md5Digest() { + MD5Init(&ctx_); + } + virtual size_t Size() const { + return kSize; + } + virtual void Update(const void* buf, size_t len) { + MD5Update(&ctx_, static_cast(buf), len); + } + virtual size_t Finish(void* buf, size_t len) { + if (len < kSize) { + return 0; + } + MD5Final(&ctx_, static_cast(buf)); + MD5Init(&ctx_); // Reset for next use. + return kSize; + } + private: + MD5_CTX ctx_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MD5DIGEST_H_ diff --git a/webrtc/base/md5digest_unittest.cc b/webrtc/base/md5digest_unittest.cc new file mode 100644 index 000000000..67c62db62 --- /dev/null +++ b/webrtc/base/md5digest_unittest.cc @@ -0,0 +1,79 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/md5digest.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +std::string Md5(const std::string& input) { + Md5Digest md5; + return ComputeDigest(&md5, input); +} + +TEST(Md5DigestTest, TestSize) { + Md5Digest md5; + EXPECT_EQ(16, static_cast(Md5Digest::kSize)); + EXPECT_EQ(16U, md5.Size()); +} + +TEST(Md5DigestTest, TestBasic) { + // These are the standard MD5 test vectors from RFC 1321. + EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", Md5("")); + EXPECT_EQ("0cc175b9c0f1b6a831c399e269772661", Md5("a")); + EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", Md5("abc")); + EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", Md5("message digest")); + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", + Md5("abcdefghijklmnopqrstuvwxyz")); +} + +TEST(Md5DigestTest, TestMultipleUpdates) { + Md5Digest md5; + std::string input = "abcdefghijklmnopqrstuvwxyz"; + char output[Md5Digest::kSize]; + for (size_t i = 0; i < input.size(); ++i) { + md5.Update(&input[i], 1); + } + EXPECT_EQ(md5.Size(), md5.Finish(output, sizeof(output))); + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", + hex_encode(output, sizeof(output))); +} + +TEST(Md5DigestTest, TestReuse) { + Md5Digest md5; + std::string input = "message digest"; + EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", ComputeDigest(&md5, input)); + input = "abcdefghijklmnopqrstuvwxyz"; + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", ComputeDigest(&md5, input)); +} + +TEST(Md5DigestTest, TestBufferTooSmall) { + Md5Digest md5; + std::string input = "abcdefghijklmnopqrstuvwxyz"; + char output[Md5Digest::kSize - 1]; + md5.Update(input.c_str(), input.size()); + EXPECT_EQ(0U, md5.Finish(output, sizeof(output))); +} + +TEST(Md5DigestTest, TestBufferConst) { + Md5Digest md5; + const int kLongSize = 1000000; + std::string input(kLongSize, '\0'); + for (int i = 0; i < kLongSize; ++i) { + input[i] = static_cast(i); + } + md5.Update(input.c_str(), input.size()); + for (int i = 0; i < kLongSize; ++i) { + EXPECT_EQ(static_cast(i), input[i]); + } +} + +} // namespace rtc diff --git a/webrtc/base/messagedigest.cc b/webrtc/base/messagedigest.cc new file mode 100644 index 000000000..dc3e1006a --- /dev/null +++ b/webrtc/base/messagedigest.cc @@ -0,0 +1,180 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/messagedigest.h" + +#include + +#include "webrtc/base/sslconfig.h" +#if SSL_USE_OPENSSL +#include "webrtc/base/openssldigest.h" +#else +#include "webrtc/base/md5digest.h" +#include "webrtc/base/sha1digest.h" +#endif +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +// From RFC 4572. +const char DIGEST_MD5[] = "md5"; +const char DIGEST_SHA_1[] = "sha-1"; +const char DIGEST_SHA_224[] = "sha-224"; +const char DIGEST_SHA_256[] = "sha-256"; +const char DIGEST_SHA_384[] = "sha-384"; +const char DIGEST_SHA_512[] = "sha-512"; + +static const size_t kBlockSize = 64; // valid for SHA-256 and down + +MessageDigest* MessageDigestFactory::Create(const std::string& alg) { +#if SSL_USE_OPENSSL + MessageDigest* digest = new OpenSSLDigest(alg); + if (digest->Size() == 0) { // invalid algorithm + delete digest; + digest = NULL; + } + return digest; +#else + MessageDigest* digest = NULL; + if (alg == DIGEST_MD5) { + digest = new Md5Digest(); + } else if (alg == DIGEST_SHA_1) { + digest = new Sha1Digest(); + } + return digest; +#endif +} + +bool IsFips180DigestAlgorithm(const std::string& alg) { + // These are the FIPS 180 algorithms. According to RFC 4572 Section 5, + // "Self-signed certificates (for which legacy certificates are not a + // consideration) MUST use one of the FIPS 180 algorithms (SHA-1, + // SHA-224, SHA-256, SHA-384, or SHA-512) as their signature algorithm, + // and thus also MUST use it to calculate certificate fingerprints." + return alg == DIGEST_SHA_1 || + alg == DIGEST_SHA_224 || + alg == DIGEST_SHA_256 || + alg == DIGEST_SHA_384 || + alg == DIGEST_SHA_512; +} + +size_t ComputeDigest(MessageDigest* digest, const void* input, size_t in_len, + void* output, size_t out_len) { + digest->Update(input, in_len); + return digest->Finish(output, out_len); +} + +size_t ComputeDigest(const std::string& alg, const void* input, size_t in_len, + void* output, size_t out_len) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + return (digest) ? + ComputeDigest(digest.get(), input, in_len, output, out_len) : + 0; +} + +std::string ComputeDigest(MessageDigest* digest, const std::string& input) { + scoped_ptr output(new char[digest->Size()]); + ComputeDigest(digest, input.data(), input.size(), + output.get(), digest->Size()); + return hex_encode(output.get(), digest->Size()); +} + +bool ComputeDigest(const std::string& alg, const std::string& input, + std::string* output) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + if (!digest) { + return false; + } + *output = ComputeDigest(digest.get(), input); + return true; +} + +std::string ComputeDigest(const std::string& alg, const std::string& input) { + std::string output; + ComputeDigest(alg, input, &output); + return output; +} + +// Compute a RFC 2104 HMAC: H(K XOR opad, H(K XOR ipad, text)) +size_t ComputeHmac(MessageDigest* digest, + const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len) { + // We only handle algorithms with a 64-byte blocksize. + // TODO: Add BlockSize() method to MessageDigest. + size_t block_len = kBlockSize; + if (digest->Size() > 32) { + return 0; + } + // Copy the key to a block-sized buffer to simplify padding. + // If the key is longer than a block, hash it and use the result instead. + scoped_ptr new_key(new uint8[block_len]); + if (key_len > block_len) { + ComputeDigest(digest, key, key_len, new_key.get(), block_len); + memset(new_key.get() + digest->Size(), 0, block_len - digest->Size()); + } else { + memcpy(new_key.get(), key, key_len); + memset(new_key.get() + key_len, 0, block_len - key_len); + } + // Set up the padding from the key, salting appropriately for each padding. + scoped_ptr o_pad(new uint8[block_len]), i_pad(new uint8[block_len]); + for (size_t i = 0; i < block_len; ++i) { + o_pad[i] = 0x5c ^ new_key[i]; + i_pad[i] = 0x36 ^ new_key[i]; + } + // Inner hash; hash the inner padding, and then the input buffer. + scoped_ptr inner(new uint8[digest->Size()]); + digest->Update(i_pad.get(), block_len); + digest->Update(input, in_len); + digest->Finish(inner.get(), digest->Size()); + // Outer hash; hash the outer padding, and then the result of the inner hash. + digest->Update(o_pad.get(), block_len); + digest->Update(inner.get(), digest->Size()); + return digest->Finish(output, out_len); +} + +size_t ComputeHmac(const std::string& alg, const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + if (!digest) { + return 0; + } + return ComputeHmac(digest.get(), key, key_len, + input, in_len, output, out_len); +} + +std::string ComputeHmac(MessageDigest* digest, const std::string& key, + const std::string& input) { + scoped_ptr output(new char[digest->Size()]); + ComputeHmac(digest, key.data(), key.size(), + input.data(), input.size(), output.get(), digest->Size()); + return hex_encode(output.get(), digest->Size()); +} + +bool ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input, std::string* output) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + if (!digest) { + return false; + } + *output = ComputeHmac(digest.get(), key, input); + return true; +} + +std::string ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input) { + std::string output; + ComputeHmac(alg, key, input, &output); + return output; +} + +} // namespace rtc diff --git a/webrtc/base/messagedigest.h b/webrtc/base/messagedigest.h new file mode 100644 index 000000000..5cfcb4772 --- /dev/null +++ b/webrtc/base/messagedigest.h @@ -0,0 +1,109 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MESSAGEDIGEST_H_ +#define WEBRTC_BASE_MESSAGEDIGEST_H_ + +#include + +namespace rtc { + +// Definitions for the digest algorithms. +extern const char DIGEST_MD5[]; +extern const char DIGEST_SHA_1[]; +extern const char DIGEST_SHA_224[]; +extern const char DIGEST_SHA_256[]; +extern const char DIGEST_SHA_384[]; +extern const char DIGEST_SHA_512[]; + +// A general class for computing hashes. +class MessageDigest { + public: + enum { kMaxSize = 64 }; // Maximum known size (SHA-512) + virtual ~MessageDigest() {} + // Returns the digest output size (e.g. 16 bytes for MD5). + virtual size_t Size() const = 0; + // Updates the digest with |len| bytes from |buf|. + virtual void Update(const void* buf, size_t len) = 0; + // Outputs the digest value to |buf| with length |len|. + // Returns the number of bytes written, i.e., Size(). + virtual size_t Finish(void* buf, size_t len) = 0; +}; + +// A factory class for creating digest objects. +class MessageDigestFactory { + public: + static MessageDigest* Create(const std::string& alg); +}; + +// A whitelist of approved digest algorithms from RFC 4572 (FIPS 180). +bool IsFips180DigestAlgorithm(const std::string& alg); + +// Functions to create hashes. + +// Computes the hash of |in_len| bytes of |input|, using the |digest| hash +// implementation, and outputs the hash to the buffer |output|, which is +// |out_len| bytes long. Returns the number of bytes written to |output| if +// successful, or 0 if |out_len| was too small. +size_t ComputeDigest(MessageDigest* digest, const void* input, size_t in_len, + void* output, size_t out_len); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns 0 if there is no +// digest with the given name. +size_t ComputeDigest(const std::string& alg, const void* input, size_t in_len, + void* output, size_t out_len); +// Computes the hash of |input| using the |digest| hash implementation, and +// returns it as a hex-encoded string. +std::string ComputeDigest(MessageDigest* digest, const std::string& input); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns empty string if +// there is no digest with the given name. +std::string ComputeDigest(const std::string& alg, const std::string& input); +// Like the previous function, but returns an explicit result code. +bool ComputeDigest(const std::string& alg, const std::string& input, + std::string* output); + +// Shorthand way to compute a hex-encoded hash using MD5. +inline std::string MD5(const std::string& input) { + return ComputeDigest(DIGEST_MD5, input); +} + +// Functions to compute RFC 2104 HMACs. + +// Computes the HMAC of |in_len| bytes of |input|, using the |digest| hash +// implementation and |key_len| bytes of |key| to key the HMAC, and outputs +// the HMAC to the buffer |output|, which is |out_len| bytes long. Returns the +// number of bytes written to |output| if successful, or 0 if |out_len| was too +// small. +size_t ComputeHmac(MessageDigest* digest, const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns 0 if there is no +// digest with the given name. +size_t ComputeHmac(const std::string& alg, const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len); +// Computes the HMAC of |input| using the |digest| hash implementation and |key| +// to key the HMAC, and returns it as a hex-encoded string. +std::string ComputeHmac(MessageDigest* digest, const std::string& key, + const std::string& input); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns empty string if +// there is no digest with the given name. +std::string ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input); +// Like the previous function, but returns an explicit result code. +bool ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input, std::string* output); + +} // namespace rtc + +#endif // WEBRTC_BASE_MESSAGEDIGEST_H_ diff --git a/webrtc/base/messagedigest_unittest.cc b/webrtc/base/messagedigest_unittest.cc new file mode 100644 index 000000000..86cf688ce --- /dev/null +++ b/webrtc/base/messagedigest_unittest.cc @@ -0,0 +1,151 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +// Test vectors from RFC 1321. +TEST(MessageDigestTest, TestMd5Digest) { + // Test the string versions of the APIs. + EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", + ComputeDigest(DIGEST_MD5, "")); + EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", + ComputeDigest(DIGEST_MD5, "abc")); + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", + ComputeDigest(DIGEST_MD5, "abcdefghijklmnopqrstuvwxyz")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + char output[16]; + EXPECT_EQ(sizeof(output), + ComputeDigest(DIGEST_MD5, "abc", 3, output, sizeof(output))); + EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeDigest(DIGEST_MD5, "abc", 3, output, sizeof(output) - 1)); +} + +// Test vectors from RFC 3174. +TEST(MessageDigestTest, TestSha1Digest) { + // Test the string versions of the APIs. + EXPECT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709", + ComputeDigest(DIGEST_SHA_1, "")); + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", + ComputeDigest(DIGEST_SHA_1, "abc")); + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + ComputeDigest(DIGEST_SHA_1, + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + char output[20]; + EXPECT_EQ(sizeof(output), + ComputeDigest(DIGEST_SHA_1, "abc", 3, output, sizeof(output))); + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeDigest(DIGEST_SHA_1, "abc", 3, output, sizeof(output) - 1)); +} + +// Test that we fail properly if a bad digest algorithm is specified. +TEST(MessageDigestTest, TestBadDigest) { + std::string output; + EXPECT_FALSE(ComputeDigest("sha-9000", "abc", &output)); + EXPECT_EQ("", ComputeDigest("sha-9000", "abc")); +} + +// Test vectors from RFC 2202. +TEST(MessageDigestTest, TestMd5Hmac) { + // Test the string versions of the APIs. + EXPECT_EQ("9294727a3638bb1c13f48ef8158bfc9d", + ComputeHmac(DIGEST_MD5, std::string(16, '\x0b'), "Hi There")); + EXPECT_EQ("750c783e6ab0b503eaa86e310a5db738", + ComputeHmac(DIGEST_MD5, "Jefe", "what do ya want for nothing?")); + EXPECT_EQ("56be34521d144c88dbb8c733f0e8b3f6", + ComputeHmac(DIGEST_MD5, std::string(16, '\xaa'), + std::string(50, '\xdd'))); + EXPECT_EQ("697eaf0aca3a3aea3a75164746ffaa79", + ComputeHmac(DIGEST_MD5, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", + std::string(50, '\xcd'))); + EXPECT_EQ("56461ef2342edc00f9bab995690efd4c", + ComputeHmac(DIGEST_MD5, std::string(16, '\x0c'), + "Test With Truncation")); + EXPECT_EQ("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd", + ComputeHmac(DIGEST_MD5, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key - Hash Key First")); + EXPECT_EQ("6f630fad67cda0ee1fb1f562db3aa53e", + ComputeHmac(DIGEST_MD5, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key and Larger " + "Than One Block-Size Data")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + std::string key(16, '\x0b'); + std::string input("Hi There"); + char output[16]; + EXPECT_EQ(sizeof(output), + ComputeHmac(DIGEST_MD5, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output))); + EXPECT_EQ("9294727a3638bb1c13f48ef8158bfc9d", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeHmac(DIGEST_MD5, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output) - 1)); +} + +// Test vectors from RFC 2202. +TEST(MessageDigestTest, TestSha1Hmac) { + // Test the string versions of the APIs. + EXPECT_EQ("b617318655057264e28bc0b6fb378c8ef146be00", + ComputeHmac(DIGEST_SHA_1, std::string(20, '\x0b'), "Hi There")); + EXPECT_EQ("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", + ComputeHmac(DIGEST_SHA_1, "Jefe", "what do ya want for nothing?")); + EXPECT_EQ("125d7342b9ac11cd91a39af48aa17b4f63f175d3", + ComputeHmac(DIGEST_SHA_1, std::string(20, '\xaa'), + std::string(50, '\xdd'))); + EXPECT_EQ("4c9007f4026250c6bc8414f9bf50c86c2d7235da", + ComputeHmac(DIGEST_SHA_1, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", + std::string(50, '\xcd'))); + EXPECT_EQ("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04", + ComputeHmac(DIGEST_SHA_1, std::string(20, '\x0c'), + "Test With Truncation")); + EXPECT_EQ("aa4ae5e15272d00e95705637ce8a3b55ed402112", + ComputeHmac(DIGEST_SHA_1, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key - Hash Key First")); + EXPECT_EQ("e8e99d0f45237d786d6bbaa7965c7808bbff1a91", + ComputeHmac(DIGEST_SHA_1, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key and Larger " + "Than One Block-Size Data")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + std::string key(20, '\x0b'); + std::string input("Hi There"); + char output[20]; + EXPECT_EQ(sizeof(output), + ComputeHmac(DIGEST_SHA_1, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output))); + EXPECT_EQ("b617318655057264e28bc0b6fb378c8ef146be00", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeHmac(DIGEST_SHA_1, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output) - 1)); +} + +TEST(MessageDigestTest, TestBadHmac) { + std::string output; + EXPECT_FALSE(ComputeHmac("sha-9000", "key", "abc", &output)); + EXPECT_EQ("", ComputeHmac("sha-9000", "key", "abc")); +} + +} // namespace rtc diff --git a/webrtc/base/messagehandler.cc b/webrtc/base/messagehandler.cc new file mode 100644 index 000000000..be5bb7f8f --- /dev/null +++ b/webrtc/base/messagehandler.cc @@ -0,0 +1,20 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" + +namespace rtc { + +MessageHandler::~MessageHandler() { + MessageQueueManager::Clear(this); +} + +} // namespace rtc diff --git a/webrtc/base/messagehandler.h b/webrtc/base/messagehandler.h new file mode 100644 index 000000000..123c85097 --- /dev/null +++ b/webrtc/base/messagehandler.h @@ -0,0 +1,68 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MESSAGEHANDLER_H_ +#define WEBRTC_BASE_MESSAGEHANDLER_H_ + +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +struct Message; + +// Messages get dispatched to a MessageHandler + +class MessageHandler { + public: + virtual ~MessageHandler(); + virtual void OnMessage(Message* msg) = 0; + + protected: + MessageHandler() {} + + private: + DISALLOW_COPY_AND_ASSIGN(MessageHandler); +}; + +// Helper class to facilitate executing a functor on a thread. +template +class FunctorMessageHandler : public MessageHandler { + public: + explicit FunctorMessageHandler(const FunctorT& functor) + : functor_(functor) {} + virtual void OnMessage(Message* msg) { + result_ = functor_(); + } + const ReturnT& result() const { return result_; } + + private: + FunctorT functor_; + ReturnT result_; +}; + +// Specialization for ReturnT of void. +template +class FunctorMessageHandler : public MessageHandler { + public: + explicit FunctorMessageHandler(const FunctorT& functor) + : functor_(functor) {} + virtual void OnMessage(Message* msg) { + functor_(); + } + void result() const {} + + private: + FunctorT functor_; +}; + + +} // namespace rtc + +#endif // WEBRTC_BASE_MESSAGEHANDLER_H_ diff --git a/webrtc/base/messagequeue.cc b/webrtc/base/messagequeue.cc new file mode 100644 index 000000000..bf31a0596 --- /dev/null +++ b/webrtc/base/messagequeue.cc @@ -0,0 +1,395 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagequeue.h" +#if defined(__native_client__) +#include "webrtc/base/nullsocketserver.h" +typedef rtc::NullSocketServer DefaultSocketServer; +#else +#include "webrtc/base/physicalsocketserver.h" +typedef rtc::PhysicalSocketServer DefaultSocketServer; +#endif + +namespace rtc { + +const uint32 kMaxMsgLatency = 150; // 150 ms + +//------------------------------------------------------------------ +// MessageQueueManager + +MessageQueueManager* MessageQueueManager::instance_ = NULL; + +MessageQueueManager* MessageQueueManager::Instance() { + // Note: This is not thread safe, but it is first called before threads are + // spawned. + if (!instance_) + instance_ = new MessageQueueManager; + return instance_; +} + +bool MessageQueueManager::IsInitialized() { + return instance_ != NULL; +} + +MessageQueueManager::MessageQueueManager() { +} + +MessageQueueManager::~MessageQueueManager() { +} + +void MessageQueueManager::Add(MessageQueue *message_queue) { + return Instance()->AddInternal(message_queue); +} +void MessageQueueManager::AddInternal(MessageQueue *message_queue) { + // MessageQueueManager methods should be non-reentrant, so we + // ASSERT that is the case. If any of these ASSERT, please + // contact bpm or jbeda. + ASSERT(!crit_.CurrentThreadIsOwner()); + CritScope cs(&crit_); + message_queues_.push_back(message_queue); +} + +void MessageQueueManager::Remove(MessageQueue *message_queue) { + // If there isn't a message queue manager instance, then there isn't a queue + // to remove. + if (!instance_) return; + return Instance()->RemoveInternal(message_queue); +} +void MessageQueueManager::RemoveInternal(MessageQueue *message_queue) { + ASSERT(!crit_.CurrentThreadIsOwner()); // See note above. + // If this is the last MessageQueue, destroy the manager as well so that + // we don't leak this object at program shutdown. As mentioned above, this is + // not thread-safe, but this should only happen at program termination (when + // the ThreadManager is destroyed, and threads are no longer active). + bool destroy = false; + { + CritScope cs(&crit_); + std::vector::iterator iter; + iter = std::find(message_queues_.begin(), message_queues_.end(), + message_queue); + if (iter != message_queues_.end()) { + message_queues_.erase(iter); + } + destroy = message_queues_.empty(); + } + if (destroy) { + instance_ = NULL; + delete this; + } +} + +void MessageQueueManager::Clear(MessageHandler *handler) { + // If there isn't a message queue manager instance, then there aren't any + // queues to remove this handler from. + if (!instance_) return; + return Instance()->ClearInternal(handler); +} +void MessageQueueManager::ClearInternal(MessageHandler *handler) { + ASSERT(!crit_.CurrentThreadIsOwner()); // See note above. + CritScope cs(&crit_); + std::vector::iterator iter; + for (iter = message_queues_.begin(); iter != message_queues_.end(); iter++) + (*iter)->Clear(handler); +} + +//------------------------------------------------------------------ +// MessageQueue + +MessageQueue::MessageQueue(SocketServer* ss) + : ss_(ss), fStop_(false), fPeekKeep_(false), active_(false), + dmsgq_next_num_(0) { + if (!ss_) { + // Currently, MessageQueue holds a socket server, and is the base class for + // Thread. It seems like it makes more sense for Thread to hold the socket + // server, and provide it to the MessageQueue, since the Thread controls + // the I/O model, and MQ is agnostic to those details. Anyway, this causes + // messagequeue_unittest to depend on network libraries... yuck. + default_ss_.reset(new DefaultSocketServer()); + ss_ = default_ss_.get(); + } + ss_->SetMessageQueue(this); +} + +MessageQueue::~MessageQueue() { + // The signal is done from here to ensure + // that it always gets called when the queue + // is going away. + SignalQueueDestroyed(); + if (active_) { + MessageQueueManager::Remove(this); + Clear(NULL); + } + if (ss_) { + ss_->SetMessageQueue(NULL); + } +} + +void MessageQueue::set_socketserver(SocketServer* ss) { + ss_ = ss ? ss : default_ss_.get(); + ss_->SetMessageQueue(this); +} + +void MessageQueue::Quit() { + fStop_ = true; + ss_->WakeUp(); +} + +bool MessageQueue::IsQuitting() { + return fStop_; +} + +void MessageQueue::Restart() { + fStop_ = false; +} + +bool MessageQueue::Peek(Message *pmsg, int cmsWait) { + if (fPeekKeep_) { + *pmsg = msgPeek_; + return true; + } + if (!Get(pmsg, cmsWait)) + return false; + msgPeek_ = *pmsg; + fPeekKeep_ = true; + return true; +} + +bool MessageQueue::Get(Message *pmsg, int cmsWait, bool process_io) { + // Return and clear peek if present + // Always return the peek if it exists so there is Peek/Get symmetry + + if (fPeekKeep_) { + *pmsg = msgPeek_; + fPeekKeep_ = false; + return true; + } + + // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch + + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = Time(); + uint32 msCurrent = msStart; + while (true) { + // Check for sent messages + ReceiveSends(); + + // Check for posted events + int cmsDelayNext = kForever; + bool first_pass = true; + while (true) { + // All queue operations need to be locked, but nothing else in this loop + // (specifically handling disposed message) can happen inside the crit. + // Otherwise, disposed MessageHandlers will cause deadlocks. + { + CritScope cs(&crit_); + // On the first pass, check for delayed messages that have been + // triggered and calculate the next trigger time. + if (first_pass) { + first_pass = false; + while (!dmsgq_.empty()) { + if (TimeIsLater(msCurrent, dmsgq_.top().msTrigger_)) { + cmsDelayNext = TimeDiff(dmsgq_.top().msTrigger_, msCurrent); + break; + } + msgq_.push_back(dmsgq_.top().msg_); + dmsgq_.pop(); + } + } + // Pull a message off the message queue, if available. + if (msgq_.empty()) { + break; + } else { + *pmsg = msgq_.front(); + msgq_.pop_front(); + } + } // crit_ is released here. + + // Log a warning for time-sensitive messages that we're late to deliver. + if (pmsg->ts_sensitive) { + int32 delay = TimeDiff(msCurrent, pmsg->ts_sensitive); + if (delay > 0) { + LOG_F(LS_WARNING) << "id: " << pmsg->message_id << " delay: " + << (delay + kMaxMsgLatency) << "ms"; + } + } + // If this was a dispose message, delete it and skip it. + if (MQID_DISPOSE == pmsg->message_id) { + ASSERT(NULL == pmsg->phandler); + delete pmsg->pdata; + *pmsg = Message(); + continue; + } + return true; + } + + if (fStop_) + break; + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == kForever) { + cmsNext = cmsDelayNext; + } else { + cmsNext = _max(0, cmsTotal - cmsElapsed); + if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext)) + cmsNext = cmsDelayNext; + } + + // Wait and multiplex in the meantime + if (!ss_->Wait(cmsNext, process_io)) + return false; + + // If the specified timeout expired, return + + msCurrent = Time(); + cmsElapsed = TimeDiff(msCurrent, msStart); + if (cmsWait != kForever) { + if (cmsElapsed >= cmsWait) + return false; + } + } + return false; +} + +void MessageQueue::ReceiveSends() { +} + +void MessageQueue::Post(MessageHandler *phandler, uint32 id, + MessageData *pdata, bool time_sensitive) { + if (fStop_) + return; + + // Keep thread safe + // Add the message to the end of the queue + // Signal for the multiplexer to return + + CritScope cs(&crit_); + EnsureActive(); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + if (time_sensitive) { + msg.ts_sensitive = Time() + kMaxMsgLatency; + } + msgq_.push_back(msg); + ss_->WakeUp(); +} + +void MessageQueue::DoDelayPost(int cmsDelay, uint32 tstamp, + MessageHandler *phandler, uint32 id, MessageData* pdata) { + if (fStop_) + return; + + // Keep thread safe + // Add to the priority queue. Gets sorted soonest first. + // Signal for the multiplexer to return. + + CritScope cs(&crit_); + EnsureActive(); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + DelayedMessage dmsg(cmsDelay, tstamp, dmsgq_next_num_, msg); + dmsgq_.push(dmsg); + // If this message queue processes 1 message every millisecond for 50 days, + // we will wrap this number. Even then, only messages with identical times + // will be misordered, and then only briefly. This is probably ok. + VERIFY(0 != ++dmsgq_next_num_); + ss_->WakeUp(); +} + +int MessageQueue::GetDelay() { + CritScope cs(&crit_); + + if (!msgq_.empty()) + return 0; + + if (!dmsgq_.empty()) { + int delay = TimeUntil(dmsgq_.top().msTrigger_); + if (delay < 0) + delay = 0; + return delay; + } + + return kForever; +} + +void MessageQueue::Clear(MessageHandler *phandler, uint32 id, + MessageList* removed) { + CritScope cs(&crit_); + + // Remove messages with phandler + + if (fPeekKeep_ && msgPeek_.Match(phandler, id)) { + if (removed) { + removed->push_back(msgPeek_); + } else { + delete msgPeek_.pdata; + } + fPeekKeep_ = false; + } + + // Remove from ordered message queue + + for (MessageList::iterator it = msgq_.begin(); it != msgq_.end();) { + if (it->Match(phandler, id)) { + if (removed) { + removed->push_back(*it); + } else { + delete it->pdata; + } + it = msgq_.erase(it); + } else { + ++it; + } + } + + // Remove from priority queue. Not directly iterable, so use this approach + + PriorityQueue::container_type::iterator new_end = dmsgq_.container().begin(); + for (PriorityQueue::container_type::iterator it = new_end; + it != dmsgq_.container().end(); ++it) { + if (it->msg_.Match(phandler, id)) { + if (removed) { + removed->push_back(it->msg_); + } else { + delete it->msg_.pdata; + } + } else { + *new_end++ = *it; + } + } + dmsgq_.container().erase(new_end, dmsgq_.container().end()); + dmsgq_.reheap(); +} + +void MessageQueue::Dispatch(Message *pmsg) { + pmsg->phandler->OnMessage(pmsg); +} + +void MessageQueue::EnsureActive() { + ASSERT(crit_.CurrentThreadIsOwner()); + if (!active_) { + active_ = true; + MessageQueueManager::Add(this); + } +} + +} // namespace rtc diff --git a/webrtc/base/messagequeue.h b/webrtc/base/messagequeue.h new file mode 100644 index 000000000..958a39eb9 --- /dev/null +++ b/webrtc/base/messagequeue.h @@ -0,0 +1,258 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MESSAGEQUEUE_H_ +#define WEBRTC_BASE_MESSAGEQUEUE_H_ + +#include + +#include +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +struct Message; +class MessageQueue; + +// MessageQueueManager does cleanup of of message queues + +class MessageQueueManager { + public: + static void Add(MessageQueue *message_queue); + static void Remove(MessageQueue *message_queue); + static void Clear(MessageHandler *handler); + + // For testing purposes, we expose whether or not the MessageQueueManager + // instance has been initialized. It has no other use relative to the rest of + // the functions of this class, which auto-initialize the underlying + // MessageQueueManager instance when necessary. + static bool IsInitialized(); + + private: + static MessageQueueManager* Instance(); + + MessageQueueManager(); + ~MessageQueueManager(); + + void AddInternal(MessageQueue *message_queue); + void RemoveInternal(MessageQueue *message_queue); + void ClearInternal(MessageHandler *handler); + + static MessageQueueManager* instance_; + // This list contains 'active' MessageQueues. + std::vector message_queues_; + CriticalSection crit_; +}; + +// Derive from this for specialized data +// App manages lifetime, except when messages are purged + +class MessageData { + public: + MessageData() {} + virtual ~MessageData() {} +}; + +template +class TypedMessageData : public MessageData { + public: + explicit TypedMessageData(const T& data) : data_(data) { } + const T& data() const { return data_; } + T& data() { return data_; } + private: + T data_; +}; + +// Like TypedMessageData, but for pointers that require a delete. +template +class ScopedMessageData : public MessageData { + public: + explicit ScopedMessageData(T* data) : data_(data) { } + const scoped_ptr& data() const { return data_; } + scoped_ptr& data() { return data_; } + private: + scoped_ptr data_; +}; + +// Like ScopedMessageData, but for reference counted pointers. +template +class ScopedRefMessageData : public MessageData { + public: + explicit ScopedRefMessageData(T* data) : data_(data) { } + const scoped_refptr& data() const { return data_; } + scoped_refptr& data() { return data_; } + private: + scoped_refptr data_; +}; + +template +inline MessageData* WrapMessageData(const T& data) { + return new TypedMessageData(data); +} + +template +inline const T& UseMessageData(MessageData* data) { + return static_cast< TypedMessageData* >(data)->data(); +} + +template +class DisposeData : public MessageData { + public: + explicit DisposeData(T* data) : data_(data) { } + virtual ~DisposeData() { delete data_; } + private: + T* data_; +}; + +const uint32 MQID_ANY = static_cast(-1); +const uint32 MQID_DISPOSE = static_cast(-2); + +// No destructor + +struct Message { + Message() { + memset(this, 0, sizeof(*this)); + } + inline bool Match(MessageHandler* handler, uint32 id) const { + return (handler == NULL || handler == phandler) + && (id == MQID_ANY || id == message_id); + } + MessageHandler *phandler; + uint32 message_id; + MessageData *pdata; + uint32 ts_sensitive; +}; + +typedef std::list MessageList; + +// DelayedMessage goes into a priority queue, sorted by trigger time. Messages +// with the same trigger time are processed in num_ (FIFO) order. + +class DelayedMessage { + public: + DelayedMessage(int delay, uint32 trigger, uint32 num, const Message& msg) + : cmsDelay_(delay), msTrigger_(trigger), num_(num), msg_(msg) { } + + bool operator< (const DelayedMessage& dmsg) const { + return (dmsg.msTrigger_ < msTrigger_) + || ((dmsg.msTrigger_ == msTrigger_) && (dmsg.num_ < num_)); + } + + int cmsDelay_; // for debugging + uint32 msTrigger_; + uint32 num_; + Message msg_; +}; + +class MessageQueue { + public: + explicit MessageQueue(SocketServer* ss = NULL); + virtual ~MessageQueue(); + + SocketServer* socketserver() { return ss_; } + void set_socketserver(SocketServer* ss); + + // Note: The behavior of MessageQueue has changed. When a MQ is stopped, + // futher Posts and Sends will fail. However, any pending Sends and *ready* + // Posts (as opposed to unexpired delayed Posts) will be delivered before + // Get (or Peek) returns false. By guaranteeing delivery of those messages, + // we eliminate the race condition when an MessageHandler and MessageQueue + // may be destroyed independently of each other. + virtual void Quit(); + virtual bool IsQuitting(); + virtual void Restart(); + + // Get() will process I/O until: + // 1) A message is available (returns true) + // 2) cmsWait seconds have elapsed (returns false) + // 3) Stop() is called (returns false) + virtual bool Get(Message *pmsg, int cmsWait = kForever, + bool process_io = true); + virtual bool Peek(Message *pmsg, int cmsWait = 0); + virtual void Post(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL, bool time_sensitive = false); + virtual void PostDelayed(int cmsDelay, MessageHandler *phandler, + uint32 id = 0, MessageData *pdata = NULL) { + return DoDelayPost(cmsDelay, TimeAfter(cmsDelay), phandler, id, pdata); + } + virtual void PostAt(uint32 tstamp, MessageHandler *phandler, + uint32 id = 0, MessageData *pdata = NULL) { + return DoDelayPost(TimeUntil(tstamp), tstamp, phandler, id, pdata); + } + virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY, + MessageList* removed = NULL); + virtual void Dispatch(Message *pmsg); + virtual void ReceiveSends(); + + // Amount of time until the next message can be retrieved + virtual int GetDelay(); + + bool empty() const { return size() == 0u; } + size_t size() const { + CritScope cs(&crit_); // msgq_.size() is not thread safe. + return msgq_.size() + dmsgq_.size() + (fPeekKeep_ ? 1u : 0u); + } + + // Internally posts a message which causes the doomed object to be deleted + template void Dispose(T* doomed) { + if (doomed) { + Post(NULL, MQID_DISPOSE, new DisposeData(doomed)); + } + } + + // When this signal is sent out, any references to this queue should + // no longer be used. + sigslot::signal0<> SignalQueueDestroyed; + + protected: + class PriorityQueue : public std::priority_queue { + public: + container_type& container() { return c; } + void reheap() { make_heap(c.begin(), c.end(), comp); } + }; + + void EnsureActive(); + void DoDelayPost(int cmsDelay, uint32 tstamp, MessageHandler *phandler, + uint32 id, MessageData* pdata); + + // The SocketServer is not owned by MessageQueue. + SocketServer* ss_; + // If a server isn't supplied in the constructor, use this one. + scoped_ptr default_ss_; + bool fStop_; + bool fPeekKeep_; + Message msgPeek_; + // A message queue is active if it has ever had a message posted to it. + // This also corresponds to being in MessageQueueManager's global list. + bool active_; + MessageList msgq_; + PriorityQueue dmsgq_; + uint32 dmsgq_next_num_; + mutable CriticalSection crit_; + + private: + DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MESSAGEQUEUE_H_ diff --git a/webrtc/base/messagequeue_unittest.cc b/webrtc/base/messagequeue_unittest.cc new file mode 100644 index 000000000..78024e0b2 --- /dev/null +++ b/webrtc/base/messagequeue_unittest.cc @@ -0,0 +1,140 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/messagequeue.h" + +#include "webrtc/base/bind.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/nullsocketserver.h" + +using namespace rtc; + +class MessageQueueTest: public testing::Test, public MessageQueue { + public: + bool IsLocked_Worker() { + if (!crit_.TryEnter()) { + return true; + } + crit_.Leave(); + return false; + } + bool IsLocked() { + // We have to do this on a worker thread, or else the TryEnter will + // succeed, since our critical sections are reentrant. + Thread worker; + worker.Start(); + return worker.Invoke( + rtc::Bind(&MessageQueueTest::IsLocked_Worker, this)); + } +}; + +struct DeletedLockChecker { + DeletedLockChecker(MessageQueueTest* test, bool* was_locked, bool* deleted) + : test(test), was_locked(was_locked), deleted(deleted) { } + ~DeletedLockChecker() { + *deleted = true; + *was_locked = test->IsLocked(); + } + MessageQueueTest* test; + bool* was_locked; + bool* deleted; +}; + +static void DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder( + MessageQueue* q) { + EXPECT_TRUE(q != NULL); + TimeStamp now = Time(); + q->PostAt(now, NULL, 3); + q->PostAt(now - 2, NULL, 0); + q->PostAt(now - 1, NULL, 1); + q->PostAt(now, NULL, 4); + q->PostAt(now - 1, NULL, 2); + + Message msg; + for (size_t i=0; i<5; ++i) { + memset(&msg, 0, sizeof(msg)); + EXPECT_TRUE(q->Get(&msg, 0)); + EXPECT_EQ(i, msg.message_id); + } + + EXPECT_FALSE(q->Get(&msg, 0)); // No more messages +} + +TEST_F(MessageQueueTest, + DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder) { + MessageQueue q; + DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(&q); + NullSocketServer nullss; + MessageQueue q_nullss(&nullss); + DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(&q_nullss); +} + +TEST_F(MessageQueueTest, DisposeNotLocked) { + bool was_locked = true; + bool deleted = false; + DeletedLockChecker* d = new DeletedLockChecker(this, &was_locked, &deleted); + Dispose(d); + Message msg; + EXPECT_FALSE(Get(&msg, 0)); + EXPECT_TRUE(deleted); + EXPECT_FALSE(was_locked); +} + +class DeletedMessageHandler : public MessageHandler { + public: + explicit DeletedMessageHandler(bool* deleted) : deleted_(deleted) { } + ~DeletedMessageHandler() { + *deleted_ = true; + } + void OnMessage(Message* msg) { } + private: + bool* deleted_; +}; + +TEST_F(MessageQueueTest, DiposeHandlerWithPostedMessagePending) { + bool deleted = false; + DeletedMessageHandler *handler = new DeletedMessageHandler(&deleted); + // First, post a dispose. + Dispose(handler); + // Now, post a message, which should *not* be returned by Get(). + Post(handler, 1); + Message msg; + EXPECT_FALSE(Get(&msg, 0)); + EXPECT_TRUE(deleted); +} + +struct UnwrapMainThreadScope { + UnwrapMainThreadScope() : rewrap_(Thread::Current() != NULL) { + if (rewrap_) ThreadManager::Instance()->UnwrapCurrentThread(); + } + ~UnwrapMainThreadScope() { + if (rewrap_) ThreadManager::Instance()->WrapCurrentThread(); + } + private: + bool rewrap_; +}; + +TEST(MessageQueueManager, Clear) { + UnwrapMainThreadScope s; + if (MessageQueueManager::IsInitialized()) { + LOG(LS_INFO) << "Unable to run MessageQueueManager::Clear test, since the " + << "MessageQueueManager was already initialized by some " + << "other test in this run."; + return; + } + bool deleted = false; + DeletedMessageHandler* handler = new DeletedMessageHandler(&deleted); + delete handler; + EXPECT_TRUE(deleted); + EXPECT_FALSE(MessageQueueManager::IsInitialized()); +} diff --git a/webrtc/base/move.h b/webrtc/base/move.h new file mode 100644 index 000000000..6d59cc583 --- /dev/null +++ b/webrtc/base/move.h @@ -0,0 +1,213 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THIRD_PARTY_WEBRTC_FILES_WEBRTC_BASE_MOVE_H_ +#define THIRD_PARTY_WEBRTC_FILES_WEBRTC_BASE_MOVE_H_ + +// Macro with the boilerplate that makes a type move-only in C++03. +// +// USAGE +// +// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create +// a "move-only" type. Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be +// the first line in a class declaration. +// +// A class using this macro must call .Pass() (or somehow be an r-value already) +// before it can be: +// +// * Passed as a function argument +// * Used as the right-hand side of an assignment +// * Returned from a function +// +// Each class will still need to define their own "move constructor" and "move +// operator=" to make this useful. Here's an example of the macro, the move +// constructor, and the move operator= from the scoped_ptr class: +// +// template +// class scoped_ptr { +// TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) +// public: +// scoped_ptr(RValue& other) : ptr_(other.release()) { } +// scoped_ptr& operator=(RValue& other) { +// swap(other); +// return *this; +// } +// }; +// +// Note that the constructor must NOT be marked explicit. +// +// For consistency, the second parameter to the macro should always be RValue +// unless you have a strong reason to do otherwise. It is only exposed as a +// macro parameter so that the move constructor and move operator= don't look +// like they're using a phantom type. +// +// +// HOW THIS WORKS +// +// For a thorough explanation of this technique, see: +// +// http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor +// +// The summary is that we take advantage of 2 properties: +// +// 1) non-const references will not bind to r-values. +// 2) C++ can apply one user-defined conversion when initializing a +// variable. +// +// The first lets us disable the copy constructor and assignment operator +// by declaring private version of them with a non-const reference parameter. +// +// For l-values, direct initialization still fails like in +// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment +// operators are private. +// +// For r-values, the situation is different. The copy constructor and +// assignment operator are not viable due to (1), so we are trying to call +// a non-existent constructor and non-existing operator= rather than a private +// one. Since we have not committed an error quite yet, we can provide an +// alternate conversion sequence and a constructor. We add +// +// * a private struct named "RValue" +// * a user-defined conversion "operator RValue()" +// * a "move constructor" and "move operator=" that take the RValue& as +// their sole parameter. +// +// Only r-values will trigger this sequence and execute our "move constructor" +// or "move operator=." L-values will match the private copy constructor and +// operator= first giving a "private in this context" error. This combination +// gives us a move-only type. +// +// For signaling a destructive transfer of data from an l-value, we provide a +// method named Pass() which creates an r-value for the current instance +// triggering the move constructor or move operator=. +// +// Other ways to get r-values is to use the result of an expression like a +// function call. +// +// Here's an example with comments explaining what gets triggered where: +// +// class Foo { +// TALK_MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue); +// +// public: +// ... API ... +// Foo(RValue other); // Move constructor. +// Foo& operator=(RValue rhs); // Move operator= +// }; +// +// Foo MakeFoo(); // Function that returns a Foo. +// +// Foo f; +// Foo f_copy(f); // ERROR: Foo(Foo&) is private in this context. +// Foo f_assign; +// f_assign = f; // ERROR: operator=(Foo&) is private in this context. +// +// +// Foo f(MakeFoo()); // R-value so alternate conversion executed. +// Foo f_copy(f.Pass()); // R-value so alternate conversion executed. +// f = f_copy.Pass(); // R-value so alternate conversion executed. +// +// +// IMPLEMENTATION SUBTLETIES WITH RValue +// +// The RValue struct is just a container for a pointer back to the original +// object. It should only ever be created as a temporary, and no external +// class should ever declare it or use it in a parameter. +// +// It is tempting to want to use the RValue type in function parameters, but +// excluding the limited usage here for the move constructor and move +// operator=, doing so would mean that the function could take both r-values +// and l-values equially which is unexpected. See COMPARED To Boost.Move for +// more details. +// +// An alternate, and incorrect, implementation of the RValue class used by +// Boost.Move makes RValue a fieldless child of the move-only type. RValue& +// is then used in place of RValue in the various operators. The RValue& is +// "created" by doing *reinterpret_cast(this). This has the appeal +// of never creating a temporary RValue struct even with optimizations +// disabled. Also, by virtue of inheritance you can treat the RValue +// reference as if it were the move-only type itself. Unfortunately, +// using the result of this reinterpret_cast<> is actually undefined behavior +// due to C++98 5.2.10.7. In certain compilers (e.g., NaCl) the optimizer +// will generate non-working code. +// +// In optimized builds, both implementations generate the same assembly so we +// choose the one that adheres to the standard. +// +// +// COMPARED TO C++11 +// +// In C++11, you would implement this functionality using an r-value reference +// and our .Pass() method would be replaced with a call to std::move(). +// +// This emulation also has a deficiency where it uses up the single +// user-defined conversion allowed by C++ during initialization. This can +// cause problems in some API edge cases. For instance, in scoped_ptr, it is +// impossible to make a function "void Foo(scoped_ptr p)" accept a +// value of type scoped_ptr even if you add a constructor to +// scoped_ptr<> that would make it look like it should work. C++11 does not +// have this deficiency. +// +// +// COMPARED TO Boost.Move +// +// Our implementation similar to Boost.Move, but we keep the RValue struct +// private to the move-only type, and we don't use the reinterpret_cast<> hack. +// +// In Boost.Move, RValue is the boost::rv<> template. This type can be used +// when writing APIs like: +// +// void MyFunc(boost::rv& f) +// +// that can take advantage of rv<> to avoid extra copies of a type. However you +// would still be able to call this version of MyFunc with an l-value: +// +// Foo f; +// MyFunc(f); // Uh oh, we probably just destroyed |f| w/o calling Pass(). +// +// unless someone is very careful to also declare a parallel override like: +// +// void MyFunc(const Foo& f) +// +// that would catch the l-values first. This was declared unsafe in C++11 and +// a C++11 compiler will explicitly fail MyFunc(f). Unfortunately, we cannot +// ensure this in C++03. +// +// Since we have no need for writing such APIs yet, our implementation keeps +// RValue private and uses a .Pass() method to do the conversion instead of +// trying to write a version of "std::move()." Writing an API like std::move() +// would require the RValue struct to be public. +// +// +// CAVEATS +// +// If you include a move-only type as a field inside a class that does not +// explicitly declare a copy constructor, the containing class's implicit +// copy constructor will change from Containing(const Containing&) to +// Containing(Containing&). This can cause some unexpected errors. +// +// http://llvm.org/bugs/show_bug.cgi?id=11528 +// +// The workaround is to explicitly declare your copy constructor. +// +#define TALK_MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \ + private: \ + struct rvalue_type { \ + explicit rvalue_type(type* object) : object(object) {} \ + type* object; \ + }; \ + type(type&); \ + void operator=(type&); \ + public: \ + operator rvalue_type() { return rvalue_type(this); } \ + type Pass() { return type(rvalue_type(this)); } \ + private: + +#endif // THIRD_PARTY_WEBRTC_FILES_WEBRTC_BASE_MOVE_H_ diff --git a/webrtc/base/multipart.cc b/webrtc/base/multipart.cc new file mode 100644 index 000000000..0d73880e4 --- /dev/null +++ b/webrtc/base/multipart.cc @@ -0,0 +1,253 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#include "webrtc/base/common.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/multipart.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MultipartStream +/////////////////////////////////////////////////////////////////////////////// + +MultipartStream::MultipartStream(const std::string& type, + const std::string& boundary) + : type_(type), + boundary_(boundary), + adding_(true), + current_(0), + position_(0) { + // The content type should be multipart/*. + ASSERT(0 == strncmp(type_.c_str(), "multipart/", 10)); +} + +MultipartStream::~MultipartStream() { + Close(); +} + +void MultipartStream::GetContentType(std::string* content_type) { + ASSERT(NULL != content_type); + content_type->assign(type_); + content_type->append("; boundary="); + content_type->append(boundary_); +} + +bool MultipartStream::AddPart(StreamInterface* data_stream, + const std::string& content_disposition, + const std::string& content_type) { + if (!AddPart("", content_disposition, content_type)) + return false; + parts_.push_back(data_stream); + data_stream->SignalEvent.connect(this, &MultipartStream::OnEvent); + return true; +} + +bool MultipartStream::AddPart(const std::string& data, + const std::string& content_disposition, + const std::string& content_type) { + ASSERT(adding_); + if (!adding_) + return false; + std::stringstream ss; + if (!parts_.empty()) { + ss << "\r\n"; + } + ss << "--" << boundary_ << "\r\n"; + if (!content_disposition.empty()) { + ss << ToString(HH_CONTENT_DISPOSITION) << ": " + << content_disposition << "\r\n"; + } + if (!content_type.empty()) { + ss << ToString(HH_CONTENT_TYPE) << ": " + << content_type << "\r\n"; + } + ss << "\r\n" << data; + parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size())); + return true; +} + +void MultipartStream::EndParts() { + ASSERT(adding_); + if (!adding_) + return; + + std::stringstream ss; + if (!parts_.empty()) { + ss << "\r\n"; + } + ss << "--" << boundary_ << "--" << "\r\n"; + parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size())); + + ASSERT(0 == current_); + ASSERT(0 == position_); + adding_ = false; + SignalEvent(this, SE_OPEN | SE_READ, 0); +} + +size_t MultipartStream::GetPartSize(const std::string& data, + const std::string& content_disposition, + const std::string& content_type) const { + size_t size = 0; + if (!parts_.empty()) { + size += 2; // for "\r\n"; + } + size += boundary_.size() + 4; // for "--boundary_\r\n"; + if (!content_disposition.empty()) { + // for ToString(HH_CONTENT_DISPOSITION): content_disposition\r\n + size += std::string(ToString(HH_CONTENT_DISPOSITION)).size() + 2 + + content_disposition.size() + 2; + } + if (!content_type.empty()) { + // for ToString(HH_CONTENT_TYPE): content_type\r\n + size += std::string(ToString(HH_CONTENT_TYPE)).size() + 2 + + content_type.size() + 2; + } + size += 2 + data.size(); // for \r\ndata + return size; +} + +size_t MultipartStream::GetEndPartSize() const { + size_t size = 0; + if (!parts_.empty()) { + size += 2; // for "\r\n"; + } + size += boundary_.size() + 6; // for "--boundary_--\r\n"; + return size; +} + +// +// StreamInterface +// + +StreamState MultipartStream::GetState() const { + if (adding_) { + return SS_OPENING; + } + return (current_ < parts_.size()) ? SS_OPEN : SS_CLOSED; +} + +StreamResult MultipartStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (adding_) { + return SR_BLOCK; + } + size_t local_read; + if (!read) read = &local_read; + while (current_ < parts_.size()) { + StreamResult result = parts_[current_]->Read(buffer, buffer_len, read, + error); + if (SR_EOS != result) { + if (SR_SUCCESS == result) { + position_ += *read; + } + return result; + } + ++current_; + } + return SR_EOS; +} + +StreamResult MultipartStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (error) { + *error = -1; + } + return SR_ERROR; +} + +void MultipartStream::Close() { + for (size_t i = 0; i < parts_.size(); ++i) { + delete parts_[i]; + } + parts_.clear(); + adding_ = false; + current_ = 0; + position_ = 0; +} + +bool MultipartStream::SetPosition(size_t position) { + if (adding_) { + return false; + } + size_t part_size, part_offset = 0; + for (size_t i = 0; i < parts_.size(); ++i) { + if (!parts_[i]->GetSize(&part_size)) { + return false; + } + if (part_offset + part_size > position) { + for (size_t j = i+1; j < _min(parts_.size(), current_+1); ++j) { + if (!parts_[j]->Rewind()) { + return false; + } + } + if (!parts_[i]->SetPosition(position - part_offset)) { + return false; + } + current_ = i; + position_ = position; + return true; + } + part_offset += part_size; + } + return false; +} + +bool MultipartStream::GetPosition(size_t* position) const { + if (position) { + *position = position_; + } + return true; +} + +bool MultipartStream::GetSize(size_t* size) const { + size_t part_size, total_size = 0; + for (size_t i = 0; i < parts_.size(); ++i) { + if (!parts_[i]->GetSize(&part_size)) { + return false; + } + total_size += part_size; + } + if (size) { + *size = total_size; + } + return true; +} + +bool MultipartStream::GetAvailable(size_t* size) const { + if (adding_) { + return false; + } + size_t part_size, total_size = 0; + for (size_t i = current_; i < parts_.size(); ++i) { + if (!parts_[i]->GetAvailable(&part_size)) { + return false; + } + total_size += part_size; + } + if (size) { + *size = total_size; + } + return true; +} + +// +// StreamInterface Slots +// + +void MultipartStream::OnEvent(StreamInterface* stream, int events, int error) { + if (adding_ || (current_ >= parts_.size()) || (parts_[current_] != stream)) { + return; + } + SignalEvent(this, events, error); +} + +} // namespace rtc diff --git a/webrtc/base/multipart.h b/webrtc/base/multipart.h new file mode 100644 index 000000000..a41f596ff --- /dev/null +++ b/webrtc/base/multipart.h @@ -0,0 +1,79 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_MULTIPART_H__ +#define WEBRTC_BASE_MULTIPART_H__ + +#include +#include + +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MultipartStream - Implements an RFC2046 multipart stream by concatenating +// the supplied parts together, and adding the correct boundaries. +/////////////////////////////////////////////////////////////////////////////// + +class MultipartStream : public StreamInterface, public sigslot::has_slots<> { + public: + MultipartStream(const std::string& type, const std::string& boundary); + virtual ~MultipartStream(); + + void GetContentType(std::string* content_type); + + // Note: If content_disposition and/or content_type are the empty string, + // they will be omitted. + bool AddPart(StreamInterface* data_stream, + const std::string& content_disposition, + const std::string& content_type); + bool AddPart(const std::string& data, + const std::string& content_disposition, + const std::string& content_type); + void EndParts(); + + // Calculates the size of a part before actually adding the part. + size_t GetPartSize(const std::string& data, + const std::string& content_disposition, + const std::string& content_type) const; + size_t GetEndPartSize() const; + + // StreamInterface + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + + private: + typedef std::vector PartList; + + // StreamInterface Slots + void OnEvent(StreamInterface* stream, int events, int error); + + std::string type_, boundary_; + PartList parts_; + bool adding_; + size_t current_; // The index into parts_ of the current read position. + size_t position_; // The current read position in bytes. + + DISALLOW_COPY_AND_ASSIGN(MultipartStream); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MULTIPART_H__ diff --git a/webrtc/base/multipart_unittest.cc b/webrtc/base/multipart_unittest.cc new file mode 100644 index 000000000..38e111493 --- /dev/null +++ b/webrtc/base/multipart_unittest.cc @@ -0,0 +1,125 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/multipart.h" + +namespace rtc { + +static const std::string kTestMultipartBoundary = "123456789987654321"; +static const std::string kTestContentType = + "multipart/form-data; boundary=123456789987654321"; +static const char kTestData[] = "This is a test."; +static const char kTestStreamContent[] = "This is a test stream."; + +TEST(MultipartTest, TestBasicOperations) { + MultipartStream multipart("multipart/form-data", kTestMultipartBoundary); + std::string content_type; + multipart.GetContentType(&content_type); + EXPECT_EQ(kTestContentType, content_type); + + EXPECT_EQ(rtc::SS_OPENING, multipart.GetState()); + + // The multipart stream contains only --boundary--\r\n + size_t end_part_size = multipart.GetEndPartSize(); + multipart.EndParts(); + EXPECT_EQ(rtc::SS_OPEN, multipart.GetState()); + size_t size; + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(end_part_size, size); + + // Write is not supported. + EXPECT_EQ(rtc::SR_ERROR, + multipart.Write(kTestData, sizeof(kTestData), NULL, NULL)); + + multipart.Close(); + EXPECT_EQ(rtc::SS_CLOSED, multipart.GetState()); + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(0U, size); +} + +TEST(MultipartTest, TestAddAndRead) { + MultipartStream multipart("multipart/form-data", kTestMultipartBoundary); + + size_t part_size = + multipart.GetPartSize(kTestData, "form-data; name=\"text\"", "text"); + EXPECT_TRUE(multipart.AddPart(kTestData, "form-data; name=\"text\"", "text")); + size_t size; + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(part_size, size); + + rtc::scoped_ptr stream( + new rtc::MemoryStream(kTestStreamContent)); + size_t stream_size = 0; + EXPECT_TRUE(stream->GetSize(&stream_size)); + part_size += + multipart.GetPartSize("", "form-data; name=\"stream\"", "stream"); + part_size += stream_size; + + EXPECT_TRUE(multipart.AddPart( + new rtc::MemoryStream(kTestStreamContent), + "form-data; name=\"stream\"", + "stream")); + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(part_size, size); + + // In adding state, block read. + char buffer[1024]; + EXPECT_EQ(rtc::SR_BLOCK, + multipart.Read(buffer, sizeof(buffer), NULL, NULL)); + // Write is not supported. + EXPECT_EQ(rtc::SR_ERROR, + multipart.Write(buffer, sizeof(buffer), NULL, NULL)); + + part_size += multipart.GetEndPartSize(); + multipart.EndParts(); + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(part_size, size); + + // Read the multipart stream into StringStream + std::string str; + rtc::StringStream str_stream(str); + EXPECT_EQ(rtc::SR_SUCCESS, + Flow(&multipart, buffer, sizeof(buffer), &str_stream)); + EXPECT_EQ(size, str.length()); + + // Search three boundaries and two parts in the order. + size_t pos = 0; + pos = str.find(kTestMultipartBoundary); + EXPECT_NE(std::string::npos, pos); + pos += kTestMultipartBoundary.length(); + + pos = str.find(kTestData, pos); + EXPECT_NE(std::string::npos, pos); + pos += sizeof(kTestData); + + pos = str.find(kTestMultipartBoundary, pos); + EXPECT_NE(std::string::npos, pos); + pos += kTestMultipartBoundary.length(); + + pos = str.find(kTestStreamContent, pos); + EXPECT_NE(std::string::npos, pos); + pos += sizeof(kTestStreamContent); + + pos = str.find(kTestMultipartBoundary, pos); + EXPECT_NE(std::string::npos, pos); + pos += kTestMultipartBoundary.length(); + + pos = str.find(kTestMultipartBoundary, pos); + EXPECT_EQ(std::string::npos, pos); +} + +} // namespace rtc diff --git a/webrtc/base/nat_unittest.cc b/webrtc/base/nat_unittest.cc new file mode 100644 index 000000000..8b9d8a150 --- /dev/null +++ b/webrtc/base/nat_unittest.cc @@ -0,0 +1,345 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/natsocketfactory.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/network.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/virtualsocketserver.h" + +using namespace rtc; + +bool CheckReceive( + TestClient* client, bool should_receive, const char* buf, size_t size) { + return (should_receive) ? + client->CheckNextPacket(buf, size, 0) : + client->CheckNoPacket(); +} + +TestClient* CreateTestClient( + SocketFactory* factory, const SocketAddress& local_addr) { + AsyncUDPSocket* socket = AsyncUDPSocket::Create(factory, local_addr); + return new TestClient(socket); +} + +// Tests that when sending from internal_addr to external_addrs through the +// NAT type specified by nat_type, all external addrs receive the sent packet +// and, if exp_same is true, all use the same mapped-address on the NAT. +void TestSend( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4], + NATType nat_type, bool exp_same) { + Thread th_int(internal); + Thread th_ext(external); + + SocketAddress server_addr = internal_addr; + server_addr.SetPort(0); // Auto-select a port + NATServer* nat = new NATServer( + nat_type, internal, server_addr, external, external_addrs[0]); + NATSocketFactory* natsf = new NATSocketFactory(internal, + nat->internal_address()); + + TestClient* in = CreateTestClient(natsf, internal_addr); + TestClient* out[4]; + for (int i = 0; i < 4; i++) + out[i] = CreateTestClient(external, external_addrs[i]); + + th_int.Start(); + th_ext.Start(); + + const char* buf = "filter_test"; + size_t len = strlen(buf); + + in->SendTo(buf, len, out[0]->address()); + SocketAddress trans_addr; + EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr)); + + for (int i = 1; i < 4; i++) { + in->SendTo(buf, len, out[i]->address()); + SocketAddress trans_addr2; + EXPECT_TRUE(out[i]->CheckNextPacket(buf, len, &trans_addr2)); + bool are_same = (trans_addr == trans_addr2); + ASSERT_EQ(are_same, exp_same) << "same translated address"; + ASSERT_NE(AF_UNSPEC, trans_addr.family()); + ASSERT_NE(AF_UNSPEC, trans_addr2.family()); + } + + th_int.Stop(); + th_ext.Stop(); + + delete nat; + delete natsf; + delete in; + for (int i = 0; i < 4; i++) + delete out[i]; +} + +// Tests that when sending from external_addrs to internal_addr, the packet +// is delivered according to the specified filter_ip and filter_port rules. +void TestRecv( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4], + NATType nat_type, bool filter_ip, bool filter_port) { + Thread th_int(internal); + Thread th_ext(external); + + SocketAddress server_addr = internal_addr; + server_addr.SetPort(0); // Auto-select a port + NATServer* nat = new NATServer( + nat_type, internal, server_addr, external, external_addrs[0]); + NATSocketFactory* natsf = new NATSocketFactory(internal, + nat->internal_address()); + + TestClient* in = CreateTestClient(natsf, internal_addr); + TestClient* out[4]; + for (int i = 0; i < 4; i++) + out[i] = CreateTestClient(external, external_addrs[i]); + + th_int.Start(); + th_ext.Start(); + + const char* buf = "filter_test"; + size_t len = strlen(buf); + + in->SendTo(buf, len, out[0]->address()); + SocketAddress trans_addr; + EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr)); + + out[1]->SendTo(buf, len, trans_addr); + EXPECT_TRUE(CheckReceive(in, !filter_ip, buf, len)); + + out[2]->SendTo(buf, len, trans_addr); + EXPECT_TRUE(CheckReceive(in, !filter_port, buf, len)); + + out[3]->SendTo(buf, len, trans_addr); + EXPECT_TRUE(CheckReceive(in, !filter_ip && !filter_port, buf, len)); + + th_int.Stop(); + th_ext.Stop(); + + delete nat; + delete natsf; + delete in; + for (int i = 0; i < 4; i++) + delete out[i]; +} + +// Tests that NATServer allocates bindings properly. +void TestBindings( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4]) { + TestSend(internal, internal_addr, external, external_addrs, + NAT_OPEN_CONE, true); + TestSend(internal, internal_addr, external, external_addrs, + NAT_ADDR_RESTRICTED, true); + TestSend(internal, internal_addr, external, external_addrs, + NAT_PORT_RESTRICTED, true); + TestSend(internal, internal_addr, external, external_addrs, + NAT_SYMMETRIC, false); +} + +// Tests that NATServer filters packets properly. +void TestFilters( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4]) { + TestRecv(internal, internal_addr, external, external_addrs, + NAT_OPEN_CONE, false, false); + TestRecv(internal, internal_addr, external, external_addrs, + NAT_ADDR_RESTRICTED, true, false); + TestRecv(internal, internal_addr, external, external_addrs, + NAT_PORT_RESTRICTED, true, true); + TestRecv(internal, internal_addr, external, external_addrs, + NAT_SYMMETRIC, true, true); +} + +bool TestConnectivity(const SocketAddress& src, const IPAddress& dst) { + // The physical NAT tests require connectivity to the selected ip from the + // internal address used for the NAT. Things like firewalls can break that, so + // check to see if it's worth even trying with this ip. + scoped_ptr pss(new PhysicalSocketServer()); + scoped_ptr client(pss->CreateAsyncSocket(src.family(), + SOCK_DGRAM)); + scoped_ptr server(pss->CreateAsyncSocket(src.family(), + SOCK_DGRAM)); + if (client->Bind(SocketAddress(src.ipaddr(), 0)) != 0 || + server->Bind(SocketAddress(dst, 0)) != 0) { + return false; + } + const char* buf = "hello other socket"; + size_t len = strlen(buf); + int sent = client->SendTo(buf, len, server->GetLocalAddress()); + SocketAddress addr; + const size_t kRecvBufSize = 64; + char recvbuf[kRecvBufSize]; + Thread::Current()->SleepMs(100); + int received = server->RecvFrom(recvbuf, kRecvBufSize, &addr); + return received == sent && ::memcmp(buf, recvbuf, len) == 0; +} + +void TestPhysicalInternal(const SocketAddress& int_addr) { + BasicNetworkManager network_manager; + network_manager.set_ipv6_enabled(true); + network_manager.StartUpdating(); + // Process pending messages so the network list is updated. + Thread::Current()->ProcessMessages(0); + + std::vector networks; + network_manager.GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_WARNING) << "Not enough network adapters for test."; + return; + } + + SocketAddress ext_addr1(int_addr); + SocketAddress ext_addr2; + // Find an available IP with matching family. The test breaks if int_addr + // can't talk to ip, so check for connectivity as well. + for (std::vector::iterator it = networks.begin(); + it != networks.end(); ++it) { + const IPAddress& ip = (*it)->ip(); + if (ip.family() == int_addr.family() && TestConnectivity(int_addr, ip)) { + ext_addr2.SetIP(ip); + break; + } + } + if (ext_addr2.IsNil()) { + LOG(LS_WARNING) << "No available IP of same family as " << int_addr; + return; + } + + LOG(LS_INFO) << "selected ip " << ext_addr2.ipaddr(); + + SocketAddress ext_addrs[4] = { + SocketAddress(ext_addr1), + SocketAddress(ext_addr2), + SocketAddress(ext_addr1), + SocketAddress(ext_addr2) + }; + + scoped_ptr int_pss(new PhysicalSocketServer()); + scoped_ptr ext_pss(new PhysicalSocketServer()); + + TestBindings(int_pss.get(), int_addr, ext_pss.get(), ext_addrs); + TestFilters(int_pss.get(), int_addr, ext_pss.get(), ext_addrs); +} + +TEST(NatTest, TestPhysicalIPv4) { + TestPhysicalInternal(SocketAddress("127.0.0.1", 0)); +} + +TEST(NatTest, TestPhysicalIPv6) { + if (HasIPv6Enabled()) { + TestPhysicalInternal(SocketAddress("::1", 0)); + } else { + LOG(LS_WARNING) << "No IPv6, skipping"; + } +} + +class TestVirtualSocketServer : public VirtualSocketServer { + public: + explicit TestVirtualSocketServer(SocketServer* ss) + : VirtualSocketServer(ss), + ss_(ss) {} + // Expose this publicly + IPAddress GetNextIP(int af) { return VirtualSocketServer::GetNextIP(af); } + + private: + scoped_ptr ss_; +}; + +void TestVirtualInternal(int family) { + scoped_ptr int_vss(new TestVirtualSocketServer( + new PhysicalSocketServer())); + scoped_ptr ext_vss(new TestVirtualSocketServer( + new PhysicalSocketServer())); + + SocketAddress int_addr; + SocketAddress ext_addrs[4]; + int_addr.SetIP(int_vss->GetNextIP(family)); + ext_addrs[0].SetIP(ext_vss->GetNextIP(int_addr.family())); + ext_addrs[1].SetIP(ext_vss->GetNextIP(int_addr.family())); + ext_addrs[2].SetIP(ext_addrs[0].ipaddr()); + ext_addrs[3].SetIP(ext_addrs[1].ipaddr()); + + TestBindings(int_vss.get(), int_addr, ext_vss.get(), ext_addrs); + TestFilters(int_vss.get(), int_addr, ext_vss.get(), ext_addrs); +} + +TEST(NatTest, TestVirtualIPv4) { + TestVirtualInternal(AF_INET); +} + +TEST(NatTest, TestVirtualIPv6) { + if (HasIPv6Enabled()) { + TestVirtualInternal(AF_INET6); + } else { + LOG(LS_WARNING) << "No IPv6, skipping"; + } +} + +// TODO: Finish this test +class NatTcpTest : public testing::Test, public sigslot::has_slots<> { + public: + NatTcpTest() : connected_(false) {} + virtual void SetUp() { + int_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer()); + ext_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer()); + nat_ = new NATServer(NAT_OPEN_CONE, int_vss_, SocketAddress(), + ext_vss_, SocketAddress()); + natsf_ = new NATSocketFactory(int_vss_, nat_->internal_address()); + } + void OnConnectEvent(AsyncSocket* socket) { + connected_ = true; + } + void OnAcceptEvent(AsyncSocket* socket) { + accepted_ = server_->Accept(NULL); + } + void OnCloseEvent(AsyncSocket* socket, int error) { + } + void ConnectEvents() { + server_->SignalReadEvent.connect(this, &NatTcpTest::OnAcceptEvent); + client_->SignalConnectEvent.connect(this, &NatTcpTest::OnConnectEvent); + } + TestVirtualSocketServer* int_vss_; + TestVirtualSocketServer* ext_vss_; + NATServer* nat_; + NATSocketFactory* natsf_; + AsyncSocket* client_; + AsyncSocket* server_; + AsyncSocket* accepted_; + bool connected_; +}; + +TEST_F(NatTcpTest, DISABLED_TestConnectOut) { + server_ = ext_vss_->CreateAsyncSocket(SOCK_STREAM); + server_->Bind(SocketAddress()); + server_->Listen(5); + + client_ = int_vss_->CreateAsyncSocket(SOCK_STREAM); + EXPECT_GE(0, client_->Bind(SocketAddress())); + EXPECT_GE(0, client_->Connect(server_->GetLocalAddress())); + + + ConnectEvents(); + + EXPECT_TRUE_WAIT(connected_, 1000); + EXPECT_EQ(client_->GetRemoteAddress(), server_->GetLocalAddress()); + EXPECT_EQ(client_->GetRemoteAddress(), accepted_->GetLocalAddress()); + EXPECT_EQ(client_->GetLocalAddress(), accepted_->GetRemoteAddress()); + + client_->Close(); +} +//#endif diff --git a/webrtc/base/natserver.cc b/webrtc/base/natserver.cc new file mode 100644 index 000000000..0ce04d70b --- /dev/null +++ b/webrtc/base/natserver.cc @@ -0,0 +1,186 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/natsocketfactory.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +RouteCmp::RouteCmp(NAT* nat) : symmetric(nat->IsSymmetric()) { +} + +size_t RouteCmp::operator()(const SocketAddressPair& r) const { + size_t h = r.source().Hash(); + if (symmetric) + h ^= r.destination().Hash(); + return h; +} + +bool RouteCmp::operator()( + const SocketAddressPair& r1, const SocketAddressPair& r2) const { + if (r1.source() < r2.source()) + return true; + if (r2.source() < r1.source()) + return false; + if (symmetric && (r1.destination() < r2.destination())) + return true; + if (symmetric && (r2.destination() < r1.destination())) + return false; + return false; +} + +AddrCmp::AddrCmp(NAT* nat) + : use_ip(nat->FiltersIP()), use_port(nat->FiltersPort()) { +} + +size_t AddrCmp::operator()(const SocketAddress& a) const { + size_t h = 0; + if (use_ip) + h ^= HashIP(a.ipaddr()); + if (use_port) + h ^= a.port() | (a.port() << 16); + return h; +} + +bool AddrCmp::operator()( + const SocketAddress& a1, const SocketAddress& a2) const { + if (use_ip && (a1.ipaddr() < a2.ipaddr())) + return true; + if (use_ip && (a2.ipaddr() < a1.ipaddr())) + return false; + if (use_port && (a1.port() < a2.port())) + return true; + if (use_port && (a2.port() < a1.port())) + return false; + return false; +} + +NATServer::NATServer( + NATType type, SocketFactory* internal, const SocketAddress& internal_addr, + SocketFactory* external, const SocketAddress& external_ip) + : external_(external), external_ip_(external_ip.ipaddr(), 0) { + nat_ = NAT::Create(type); + + server_socket_ = AsyncUDPSocket::Create(internal, internal_addr); + server_socket_->SignalReadPacket.connect(this, &NATServer::OnInternalPacket); + + int_map_ = new InternalMap(RouteCmp(nat_)); + ext_map_ = new ExternalMap(); +} + +NATServer::~NATServer() { + for (InternalMap::iterator iter = int_map_->begin(); + iter != int_map_->end(); + iter++) + delete iter->second; + + delete nat_; + delete server_socket_; + delete int_map_; + delete ext_map_; +} + +void NATServer::OnInternalPacket( + AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& addr, const PacketTime& packet_time) { + + // Read the intended destination from the wire. + SocketAddress dest_addr; + size_t length = UnpackAddressFromNAT(buf, size, &dest_addr); + + // Find the translation for these addresses (allocating one if necessary). + SocketAddressPair route(addr, dest_addr); + InternalMap::iterator iter = int_map_->find(route); + if (iter == int_map_->end()) { + Translate(route); + iter = int_map_->find(route); + } + ASSERT(iter != int_map_->end()); + + // Allow the destination to send packets back to the source. + iter->second->WhitelistInsert(dest_addr); + + // Send the packet to its intended destination. + rtc::PacketOptions options; + iter->second->socket->SendTo(buf + length, size - length, dest_addr, options); +} + +void NATServer::OnExternalPacket( + AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& remote_addr, const PacketTime& packet_time) { + + SocketAddress local_addr = socket->GetLocalAddress(); + + // Find the translation for this addresses. + ExternalMap::iterator iter = ext_map_->find(local_addr); + ASSERT(iter != ext_map_->end()); + + // Allow the NAT to reject this packet. + if (ShouldFilterOut(iter->second, remote_addr)) { + LOG(LS_INFO) << "Packet from " << remote_addr.ToSensitiveString() + << " was filtered out by the NAT."; + return; + } + + // Forward this packet to the internal address. + // First prepend the address in a quasi-STUN format. + scoped_ptr real_buf(new char[size + kNATEncodedIPv6AddressSize]); + size_t addrlength = PackAddressForNAT(real_buf.get(), + size + kNATEncodedIPv6AddressSize, + remote_addr); + // Copy the data part after the address. + rtc::PacketOptions options; + memcpy(real_buf.get() + addrlength, buf, size); + server_socket_->SendTo(real_buf.get(), size + addrlength, + iter->second->route.source(), options); +} + +void NATServer::Translate(const SocketAddressPair& route) { + AsyncUDPSocket* socket = AsyncUDPSocket::Create(external_, external_ip_); + + if (!socket) { + LOG(LS_ERROR) << "Couldn't find a free port!"; + return; + } + + TransEntry* entry = new TransEntry(route, socket, nat_); + (*int_map_)[route] = entry; + (*ext_map_)[socket->GetLocalAddress()] = entry; + socket->SignalReadPacket.connect(this, &NATServer::OnExternalPacket); +} + +bool NATServer::ShouldFilterOut(TransEntry* entry, + const SocketAddress& ext_addr) { + return entry->WhitelistContains(ext_addr); +} + +NATServer::TransEntry::TransEntry( + const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat) + : route(r), socket(s) { + whitelist = new AddressSet(AddrCmp(nat)); +} + +NATServer::TransEntry::~TransEntry() { + delete whitelist; + delete socket; +} + +void NATServer::TransEntry::WhitelistInsert(const SocketAddress& addr) { + CritScope cs(&crit_); + whitelist->insert(addr); +} + +bool NATServer::TransEntry::WhitelistContains(const SocketAddress& ext_addr) { + CritScope cs(&crit_); + return whitelist->find(ext_addr) == whitelist->end(); +} + +} // namespace rtc diff --git a/webrtc/base/natserver.h b/webrtc/base/natserver.h new file mode 100644 index 000000000..1db77dacf --- /dev/null +++ b/webrtc/base/natserver.h @@ -0,0 +1,110 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NATSERVER_H_ +#define WEBRTC_BASE_NATSERVER_H_ + +#include +#include + +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/socketaddresspair.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/socketfactory.h" +#include "webrtc/base/nattypes.h" + +namespace rtc { + +// Change how routes (socketaddress pairs) are compared based on the type of +// NAT. The NAT server maintains a hashtable of the routes that it knows +// about. So these affect which routes are treated the same. +struct RouteCmp { + explicit RouteCmp(NAT* nat); + size_t operator()(const SocketAddressPair& r) const; + bool operator()( + const SocketAddressPair& r1, const SocketAddressPair& r2) const; + + bool symmetric; +}; + +// Changes how addresses are compared based on the filtering rules of the NAT. +struct AddrCmp { + explicit AddrCmp(NAT* nat); + size_t operator()(const SocketAddress& r) const; + bool operator()(const SocketAddress& r1, const SocketAddress& r2) const; + + bool use_ip; + bool use_port; +}; + +// Implements the NAT device. It listens for packets on the internal network, +// translates them, and sends them out over the external network. + +const int NAT_SERVER_PORT = 4237; + +class NATServer : public sigslot::has_slots<> { + public: + NATServer( + NATType type, SocketFactory* internal, const SocketAddress& internal_addr, + SocketFactory* external, const SocketAddress& external_ip); + ~NATServer(); + + SocketAddress internal_address() const { + return server_socket_->GetLocalAddress(); + } + + // Packets received on one of the networks. + void OnInternalPacket(AsyncPacketSocket* socket, const char* buf, + size_t size, const SocketAddress& addr, + const PacketTime& packet_time); + void OnExternalPacket(AsyncPacketSocket* socket, const char* buf, + size_t size, const SocketAddress& remote_addr, + const PacketTime& packet_time); + + private: + typedef std::set AddressSet; + + /* Records a translation and the associated external socket. */ + struct TransEntry { + TransEntry(const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat); + ~TransEntry(); + + void WhitelistInsert(const SocketAddress& addr); + bool WhitelistContains(const SocketAddress& ext_addr); + + SocketAddressPair route; + AsyncUDPSocket* socket; + AddressSet* whitelist; + CriticalSection crit_; + }; + + typedef std::map InternalMap; + typedef std::map ExternalMap; + + /* Creates a new entry that translates the given route. */ + void Translate(const SocketAddressPair& route); + + /* Determines whether the NAT would filter out a packet from this address. */ + bool ShouldFilterOut(TransEntry* entry, const SocketAddress& ext_addr); + + NAT* nat_; + SocketFactory* internal_; + SocketFactory* external_; + SocketAddress external_ip_; + AsyncUDPSocket* server_socket_; + AsyncSocket* tcp_server_socket_; + InternalMap* int_map_; + ExternalMap* ext_map_; + DISALLOW_EVIL_CONSTRUCTORS(NATServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NATSERVER_H_ diff --git a/webrtc/base/natserver_main.cc b/webrtc/base/natserver_main.cc new file mode 100644 index 000000000..3ad2113a6 --- /dev/null +++ b/webrtc/base/natserver_main.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/natserver.h" +#include "webrtc/base/host.h" +#include "webrtc/base/physicalsocketserver.h" + +using namespace rtc; + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "usage: natserver " << std::endl; + exit(1); + } + + SocketAddress internal = SocketAddress(argv[1]); + SocketAddress external = SocketAddress(argv[2]); + if (internal.EqualIPs(external)) { + std::cerr << "internal and external IPs must differ" << std::endl; + exit(1); + } + + Thread* pthMain = Thread::Current(); + PhysicalSocketServer* ss = new PhysicalSocketServer(); + pthMain->set_socketserver(ss); + NATServer* server = new NATServer(NAT_OPEN_CONE, ss, internal, ss, external); + server = server; + + pthMain->Run(); + return 0; +} diff --git a/webrtc/base/natsocketfactory.cc b/webrtc/base/natsocketfactory.cc new file mode 100644 index 000000000..b5ae67b25 --- /dev/null +++ b/webrtc/base/natsocketfactory.cc @@ -0,0 +1,487 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/natsocketfactory.h" + +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/virtualsocketserver.h" + +namespace rtc { + +// Packs the given socketaddress into the buffer in buf, in the quasi-STUN +// format that the natserver uses. +// Returns 0 if an invalid address is passed. +size_t PackAddressForNAT(char* buf, size_t buf_size, + const SocketAddress& remote_addr) { + const IPAddress& ip = remote_addr.ipaddr(); + int family = ip.family(); + buf[0] = 0; + buf[1] = family; + // Writes the port. + *(reinterpret_cast(&buf[2])) = HostToNetwork16(remote_addr.port()); + if (family == AF_INET) { + ASSERT(buf_size >= kNATEncodedIPv4AddressSize); + in_addr v4addr = ip.ipv4_address(); + memcpy(&buf[4], &v4addr, kNATEncodedIPv4AddressSize - 4); + return kNATEncodedIPv4AddressSize; + } else if (family == AF_INET6) { + ASSERT(buf_size >= kNATEncodedIPv6AddressSize); + in6_addr v6addr = ip.ipv6_address(); + memcpy(&buf[4], &v6addr, kNATEncodedIPv6AddressSize - 4); + return kNATEncodedIPv6AddressSize; + } + return 0U; +} + +// Decodes the remote address from a packet that has been encoded with the nat's +// quasi-STUN format. Returns the length of the address (i.e., the offset into +// data where the original packet starts). +size_t UnpackAddressFromNAT(const char* buf, size_t buf_size, + SocketAddress* remote_addr) { + ASSERT(buf_size >= 8); + ASSERT(buf[0] == 0); + int family = buf[1]; + uint16 port = NetworkToHost16(*(reinterpret_cast(&buf[2]))); + if (family == AF_INET) { + const in_addr* v4addr = reinterpret_cast(&buf[4]); + *remote_addr = SocketAddress(IPAddress(*v4addr), port); + return kNATEncodedIPv4AddressSize; + } else if (family == AF_INET6) { + ASSERT(buf_size >= 20); + const in6_addr* v6addr = reinterpret_cast(&buf[4]); + *remote_addr = SocketAddress(IPAddress(*v6addr), port); + return kNATEncodedIPv6AddressSize; + } + return 0U; +} + + +// NATSocket +class NATSocket : public AsyncSocket, public sigslot::has_slots<> { + public: + explicit NATSocket(NATInternalSocketFactory* sf, int family, int type) + : sf_(sf), family_(family), type_(type), connected_(false), + socket_(NULL), buf_(NULL), size_(0) { + } + + virtual ~NATSocket() { + delete socket_; + delete[] buf_; + } + + virtual SocketAddress GetLocalAddress() const { + return (socket_) ? socket_->GetLocalAddress() : SocketAddress(); + } + + virtual SocketAddress GetRemoteAddress() const { + return remote_addr_; // will be NIL if not connected + } + + virtual int Bind(const SocketAddress& addr) { + if (socket_) { // already bound, bubble up error + return -1; + } + + int result; + socket_ = sf_->CreateInternalSocket(family_, type_, addr, &server_addr_); + result = (socket_) ? socket_->Bind(addr) : -1; + if (result >= 0) { + socket_->SignalConnectEvent.connect(this, &NATSocket::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &NATSocket::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &NATSocket::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &NATSocket::OnCloseEvent); + } else { + server_addr_.Clear(); + delete socket_; + socket_ = NULL; + } + + return result; + } + + virtual int Connect(const SocketAddress& addr) { + if (!socket_) { // socket must be bound, for now + return -1; + } + + int result = 0; + if (type_ == SOCK_STREAM) { + result = socket_->Connect(server_addr_.IsNil() ? addr : server_addr_); + } else { + connected_ = true; + } + + if (result >= 0) { + remote_addr_ = addr; + } + + return result; + } + + virtual int Send(const void* data, size_t size) { + ASSERT(connected_); + return SendTo(data, size, remote_addr_); + } + + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr) { + ASSERT(!connected_ || addr == remote_addr_); + if (server_addr_.IsNil() || type_ == SOCK_STREAM) { + return socket_->SendTo(data, size, addr); + } + // This array will be too large for IPv4 packets, but only by 12 bytes. + scoped_ptr buf(new char[size + kNATEncodedIPv6AddressSize]); + size_t addrlength = PackAddressForNAT(buf.get(), + size + kNATEncodedIPv6AddressSize, + addr); + size_t encoded_size = size + addrlength; + memcpy(buf.get() + addrlength, data, size); + int result = socket_->SendTo(buf.get(), encoded_size, server_addr_); + if (result >= 0) { + ASSERT(result == static_cast(encoded_size)); + result = result - static_cast(addrlength); + } + return result; + } + + virtual int Recv(void* data, size_t size) { + SocketAddress addr; + return RecvFrom(data, size, &addr); + } + + virtual int RecvFrom(void* data, size_t size, SocketAddress *out_addr) { + if (server_addr_.IsNil() || type_ == SOCK_STREAM) { + return socket_->RecvFrom(data, size, out_addr); + } + // Make sure we have enough room to read the requested amount plus the + // largest possible header address. + SocketAddress remote_addr; + Grow(size + kNATEncodedIPv6AddressSize); + + // Read the packet from the socket. + int result = socket_->RecvFrom(buf_, size_, &remote_addr); + if (result >= 0) { + ASSERT(remote_addr == server_addr_); + + // TODO: we need better framing so we know how many bytes we can + // return before we need to read the next address. For UDP, this will be + // fine as long as the reader always reads everything in the packet. + ASSERT((size_t)result < size_); + + // Decode the wire packet into the actual results. + SocketAddress real_remote_addr; + size_t addrlength = + UnpackAddressFromNAT(buf_, result, &real_remote_addr); + memcpy(data, buf_ + addrlength, result - addrlength); + + // Make sure this packet should be delivered before returning it. + if (!connected_ || (real_remote_addr == remote_addr_)) { + if (out_addr) + *out_addr = real_remote_addr; + result = result - static_cast(addrlength); + } else { + LOG(LS_ERROR) << "Dropping packet from unknown remote address: " + << real_remote_addr.ToString(); + result = 0; // Tell the caller we didn't read anything + } + } + + return result; + } + + virtual int Close() { + int result = 0; + if (socket_) { + result = socket_->Close(); + if (result >= 0) { + connected_ = false; + remote_addr_ = SocketAddress(); + delete socket_; + socket_ = NULL; + } + } + return result; + } + + virtual int Listen(int backlog) { + return socket_->Listen(backlog); + } + virtual AsyncSocket* Accept(SocketAddress *paddr) { + return socket_->Accept(paddr); + } + virtual int GetError() const { + return socket_->GetError(); + } + virtual void SetError(int error) { + socket_->SetError(error); + } + virtual ConnState GetState() const { + return connected_ ? CS_CONNECTED : CS_CLOSED; + } + virtual int EstimateMTU(uint16* mtu) { + return socket_->EstimateMTU(mtu); + } + virtual int GetOption(Option opt, int* value) { + return socket_->GetOption(opt, value); + } + virtual int SetOption(Option opt, int value) { + return socket_->SetOption(opt, value); + } + + void OnConnectEvent(AsyncSocket* socket) { + // If we're NATed, we need to send a request with the real addr to use. + ASSERT(socket == socket_); + if (server_addr_.IsNil()) { + connected_ = true; + SignalConnectEvent(this); + } else { + SendConnectRequest(); + } + } + void OnReadEvent(AsyncSocket* socket) { + // If we're NATed, we need to process the connect reply. + ASSERT(socket == socket_); + if (type_ == SOCK_STREAM && !server_addr_.IsNil() && !connected_) { + HandleConnectReply(); + } else { + SignalReadEvent(this); + } + } + void OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalWriteEvent(this); + } + void OnCloseEvent(AsyncSocket* socket, int error) { + ASSERT(socket == socket_); + SignalCloseEvent(this, error); + } + + private: + // Makes sure the buffer is at least the given size. + void Grow(size_t new_size) { + if (size_ < new_size) { + delete[] buf_; + size_ = new_size; + buf_ = new char[size_]; + } + } + + // Sends the destination address to the server to tell it to connect. + void SendConnectRequest() { + char buf[256]; + size_t length = PackAddressForNAT(buf, ARRAY_SIZE(buf), remote_addr_); + socket_->Send(buf, length); + } + + // Handles the byte sent back from the server and fires the appropriate event. + void HandleConnectReply() { + char code; + socket_->Recv(&code, sizeof(code)); + if (code == 0) { + SignalConnectEvent(this); + } else { + Close(); + SignalCloseEvent(this, code); + } + } + + NATInternalSocketFactory* sf_; + int family_; + int type_; + bool connected_; + SocketAddress remote_addr_; + SocketAddress server_addr_; // address of the NAT server + AsyncSocket* socket_; + char* buf_; + size_t size_; +}; + +// NATSocketFactory +NATSocketFactory::NATSocketFactory(SocketFactory* factory, + const SocketAddress& nat_addr) + : factory_(factory), nat_addr_(nat_addr) { +} + +Socket* NATSocketFactory::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* NATSocketFactory::CreateSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketFactory::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* NATSocketFactory::CreateAsyncSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketFactory::CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr) { + *nat_addr = nat_addr_; + return factory_->CreateAsyncSocket(family, type); +} + +// NATSocketServer +NATSocketServer::NATSocketServer(SocketServer* server) + : server_(server), msg_queue_(NULL) { +} + +NATSocketServer::Translator* NATSocketServer::GetTranslator( + const SocketAddress& ext_ip) { + return nats_.Get(ext_ip); +} + +NATSocketServer::Translator* NATSocketServer::AddTranslator( + const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) { + // Fail if a translator already exists with this extternal address. + if (nats_.Get(ext_ip)) + return NULL; + + return nats_.Add(ext_ip, new Translator(this, type, int_ip, server_, ext_ip)); +} + +void NATSocketServer::RemoveTranslator( + const SocketAddress& ext_ip) { + nats_.Remove(ext_ip); +} + +Socket* NATSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* NATSocketServer::CreateSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* NATSocketServer::CreateAsyncSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketServer::CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr) { + AsyncSocket* socket = NULL; + Translator* nat = nats_.FindClient(local_addr); + if (nat) { + socket = nat->internal_factory()->CreateAsyncSocket(family, type); + *nat_addr = (type == SOCK_STREAM) ? + nat->internal_tcp_address() : nat->internal_address(); + } else { + socket = server_->CreateAsyncSocket(family, type); + } + return socket; +} + +// NATSocketServer::Translator +NATSocketServer::Translator::Translator( + NATSocketServer* server, NATType type, const SocketAddress& int_ip, + SocketFactory* ext_factory, const SocketAddress& ext_ip) + : server_(server) { + // Create a new private network, and a NATServer running on the private + // network that bridges to the external network. Also tell the private + // network to use the same message queue as us. + VirtualSocketServer* internal_server = new VirtualSocketServer(server_); + internal_server->SetMessageQueue(server_->queue()); + internal_factory_.reset(internal_server); + nat_server_.reset(new NATServer(type, internal_server, int_ip, + ext_factory, ext_ip)); +} + + +NATSocketServer::Translator* NATSocketServer::Translator::GetTranslator( + const SocketAddress& ext_ip) { + return nats_.Get(ext_ip); +} + +NATSocketServer::Translator* NATSocketServer::Translator::AddTranslator( + const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) { + // Fail if a translator already exists with this extternal address. + if (nats_.Get(ext_ip)) + return NULL; + + AddClient(ext_ip); + return nats_.Add(ext_ip, + new Translator(server_, type, int_ip, server_, ext_ip)); +} +void NATSocketServer::Translator::RemoveTranslator( + const SocketAddress& ext_ip) { + nats_.Remove(ext_ip); + RemoveClient(ext_ip); +} + +bool NATSocketServer::Translator::AddClient( + const SocketAddress& int_ip) { + // Fail if a client already exists with this internal address. + if (clients_.find(int_ip) != clients_.end()) + return false; + + clients_.insert(int_ip); + return true; +} + +void NATSocketServer::Translator::RemoveClient( + const SocketAddress& int_ip) { + std::set::iterator it = clients_.find(int_ip); + if (it != clients_.end()) { + clients_.erase(it); + } +} + +NATSocketServer::Translator* NATSocketServer::Translator::FindClient( + const SocketAddress& int_ip) { + // See if we have the requested IP, or any of our children do. + return (clients_.find(int_ip) != clients_.end()) ? + this : nats_.FindClient(int_ip); +} + +// NATSocketServer::TranslatorMap +NATSocketServer::TranslatorMap::~TranslatorMap() { + for (TranslatorMap::iterator it = begin(); it != end(); ++it) { + delete it->second; + } +} + +NATSocketServer::Translator* NATSocketServer::TranslatorMap::Get( + const SocketAddress& ext_ip) { + TranslatorMap::iterator it = find(ext_ip); + return (it != end()) ? it->second : NULL; +} + +NATSocketServer::Translator* NATSocketServer::TranslatorMap::Add( + const SocketAddress& ext_ip, Translator* nat) { + (*this)[ext_ip] = nat; + return nat; +} + +void NATSocketServer::TranslatorMap::Remove( + const SocketAddress& ext_ip) { + TranslatorMap::iterator it = find(ext_ip); + if (it != end()) { + delete it->second; + erase(it); + } +} + +NATSocketServer::Translator* NATSocketServer::TranslatorMap::FindClient( + const SocketAddress& int_ip) { + Translator* nat = NULL; + for (TranslatorMap::iterator it = begin(); it != end() && !nat; ++it) { + nat = it->second->FindClient(int_ip); + } + return nat; +} + +} // namespace rtc diff --git a/webrtc/base/natsocketfactory.h b/webrtc/base/natsocketfactory.h new file mode 100644 index 000000000..6a8e20fe1 --- /dev/null +++ b/webrtc/base/natsocketfactory.h @@ -0,0 +1,166 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NATSOCKETFACTORY_H_ +#define WEBRTC_BASE_NATSOCKETFACTORY_H_ + +#include +#include +#include + +#include "webrtc/base/natserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/socketserver.h" + +namespace rtc { + +const size_t kNATEncodedIPv4AddressSize = 8U; +const size_t kNATEncodedIPv6AddressSize = 20U; + +// Used by the NAT socket implementation. +class NATInternalSocketFactory { + public: + virtual ~NATInternalSocketFactory() {} + virtual AsyncSocket* CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr) = 0; +}; + +// Creates sockets that will send all traffic through a NAT, using an existing +// NATServer instance running at nat_addr. The actual data is sent using sockets +// from a socket factory, given to the constructor. +class NATSocketFactory : public SocketFactory, public NATInternalSocketFactory { + public: + NATSocketFactory(SocketFactory* factory, const SocketAddress& nat_addr); + + // SocketFactory implementation + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + // NATInternalSocketFactory implementation + virtual AsyncSocket* CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr); + + private: + SocketFactory* factory_; + SocketAddress nat_addr_; + DISALLOW_EVIL_CONSTRUCTORS(NATSocketFactory); +}; + +// Creates sockets that will send traffic through a NAT depending on what +// address they bind to. This can be used to simulate a client on a NAT sending +// to a client that is not behind a NAT. +// Note that the internal addresses of clients must be unique. This is because +// there is only one socketserver per thread, and the Bind() address is used to +// figure out which NAT (if any) the socket should talk to. +// +// Example with 3 NATs (2 cascaded), and 3 clients. +// ss->AddTranslator("1.2.3.4", "192.168.0.1", NAT_ADDR_RESTRICTED); +// ss->AddTranslator("99.99.99.99", "10.0.0.1", NAT_SYMMETRIC)-> +// AddTranslator("10.0.0.2", "192.168.1.1", NAT_OPEN_CONE); +// ss->GetTranslator("1.2.3.4")->AddClient("1.2.3.4", "192.168.0.2"); +// ss->GetTranslator("99.99.99.99")->AddClient("10.0.0.3"); +// ss->GetTranslator("99.99.99.99")->GetTranslator("10.0.0.2")-> +// AddClient("192.168.1.2"); +class NATSocketServer : public SocketServer, public NATInternalSocketFactory { + public: + class Translator; + // holds a list of NATs + class TranslatorMap : private std::map { + public: + ~TranslatorMap(); + Translator* Get(const SocketAddress& ext_ip); + Translator* Add(const SocketAddress& ext_ip, Translator*); + void Remove(const SocketAddress& ext_ip); + Translator* FindClient(const SocketAddress& int_ip); + }; + + // a specific NAT + class Translator { + public: + Translator(NATSocketServer* server, NATType type, + const SocketAddress& int_addr, SocketFactory* ext_factory, + const SocketAddress& ext_addr); + + SocketFactory* internal_factory() { return internal_factory_.get(); } + SocketAddress internal_address() const { + return nat_server_->internal_address(); + } + SocketAddress internal_tcp_address() const { + return SocketAddress(); // nat_server_->internal_tcp_address(); + } + + Translator* GetTranslator(const SocketAddress& ext_ip); + Translator* AddTranslator(const SocketAddress& ext_ip, + const SocketAddress& int_ip, NATType type); + void RemoveTranslator(const SocketAddress& ext_ip); + + bool AddClient(const SocketAddress& int_ip); + void RemoveClient(const SocketAddress& int_ip); + + // Looks for the specified client in this or a child NAT. + Translator* FindClient(const SocketAddress& int_ip); + + private: + NATSocketServer* server_; + scoped_ptr internal_factory_; + scoped_ptr nat_server_; + TranslatorMap nats_; + std::set clients_; + }; + + explicit NATSocketServer(SocketServer* ss); + + SocketServer* socketserver() { return server_; } + MessageQueue* queue() { return msg_queue_; } + + Translator* GetTranslator(const SocketAddress& ext_ip); + Translator* AddTranslator(const SocketAddress& ext_ip, + const SocketAddress& int_ip, NATType type); + void RemoveTranslator(const SocketAddress& ext_ip); + + // SocketServer implementation + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual void SetMessageQueue(MessageQueue* queue) { + msg_queue_ = queue; + server_->SetMessageQueue(queue); + } + virtual bool Wait(int cms, bool process_io) { + return server_->Wait(cms, process_io); + } + virtual void WakeUp() { + server_->WakeUp(); + } + + // NATInternalSocketFactory implementation + virtual AsyncSocket* CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr); + + private: + SocketServer* server_; + MessageQueue* msg_queue_; + TranslatorMap nats_; + DISALLOW_EVIL_CONSTRUCTORS(NATSocketServer); +}; + +// Free-standing NAT helper functions. +size_t PackAddressForNAT(char* buf, size_t buf_size, + const SocketAddress& remote_addr); +size_t UnpackAddressFromNAT(const char* buf, size_t buf_size, + SocketAddress* remote_addr); +} // namespace rtc + +#endif // WEBRTC_BASE_NATSOCKETFACTORY_H_ diff --git a/webrtc/base/nattypes.cc b/webrtc/base/nattypes.cc new file mode 100644 index 000000000..fedb78dc5 --- /dev/null +++ b/webrtc/base/nattypes.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/nattypes.h" + +namespace rtc { + +class SymmetricNAT : public NAT { +public: + bool IsSymmetric() { return true; } + bool FiltersIP() { return true; } + bool FiltersPort() { return true; } +}; + +class OpenConeNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return false; } + bool FiltersPort() { return false; } +}; + +class AddressRestrictedNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return true; } + bool FiltersPort() { return false; } +}; + +class PortRestrictedNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return true; } + bool FiltersPort() { return true; } +}; + +NAT* NAT::Create(NATType type) { + switch (type) { + case NAT_OPEN_CONE: return new OpenConeNAT(); + case NAT_ADDR_RESTRICTED: return new AddressRestrictedNAT(); + case NAT_PORT_RESTRICTED: return new PortRestrictedNAT(); + case NAT_SYMMETRIC: return new SymmetricNAT(); + default: assert(0); return 0; + } +} + +} // namespace rtc diff --git a/webrtc/base/nattypes.h b/webrtc/base/nattypes.h new file mode 100644 index 000000000..27e4b2f45 --- /dev/null +++ b/webrtc/base/nattypes.h @@ -0,0 +1,47 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NATTYPE_H__ +#define WEBRTC_BASE_NATTYPE_H__ + +namespace rtc { + +/* Identifies each type of NAT that can be simulated. */ +enum NATType { + NAT_OPEN_CONE, + NAT_ADDR_RESTRICTED, + NAT_PORT_RESTRICTED, + NAT_SYMMETRIC +}; + +// Implements the rules for each specific type of NAT. +class NAT { +public: + virtual ~NAT() { } + + // Determines whether this NAT uses both source and destination address when + // checking whether a mapping already exists. + virtual bool IsSymmetric() = 0; + + // Determines whether this NAT drops packets received from a different IP + // the one last sent to. + virtual bool FiltersIP() = 0; + + // Determines whether this NAT drops packets received from a different port + // the one last sent to. + virtual bool FiltersPort() = 0; + + // Returns an implementation of the given type of NAT. + static NAT* Create(NATType type); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NATTYPE_H__ diff --git a/webrtc/base/nethelpers.cc b/webrtc/base/nethelpers.cc new file mode 100644 index 000000000..5d4802dfd --- /dev/null +++ b/webrtc/base/nethelpers.cc @@ -0,0 +1,150 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/nethelpers.h" + +#if defined(WEBRTC_WIN) +#include +#include +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/signalthread.h" + +namespace rtc { + +int ResolveHostname(const std::string& hostname, int family, + std::vector* addresses) { +#ifdef __native_client__ + ASSERT(false); + LOG(LS_WARNING) << "ResolveHostname() is not implemented for NaCl"; + return -1; +#else // __native_client__ + if (!addresses) { + return -1; + } + addresses->clear(); + struct addrinfo* result = NULL; + struct addrinfo hints = {0}; + // TODO(djw): For now this is IPv4 only so existing users remain unaffected. + hints.ai_family = AF_INET; + hints.ai_flags = AI_ADDRCONFIG; + int ret = getaddrinfo(hostname.c_str(), NULL, &hints, &result); + if (ret != 0) { + return ret; + } + struct addrinfo* cursor = result; + for (; cursor; cursor = cursor->ai_next) { + if (family == AF_UNSPEC || cursor->ai_family == family) { + IPAddress ip; + if (IPFromAddrInfo(cursor, &ip)) { + addresses->push_back(ip); + } + } + } + freeaddrinfo(result); + return 0; +#endif // !__native_client__ +} + +// AsyncResolver +AsyncResolver::AsyncResolver() : error_(-1) { +} + +void AsyncResolver::Start(const SocketAddress& addr) { + addr_ = addr; + // SignalThred Start will kickoff the resolve process. + SignalThread::Start(); +} + +bool AsyncResolver::GetResolvedAddress(int family, SocketAddress* addr) const { + if (error_ != 0 || addresses_.empty()) + return false; + + *addr = addr_; + for (size_t i = 0; i < addresses_.size(); ++i) { + if (family == addresses_[i].family()) { + addr->SetResolvedIP(addresses_[i]); + return true; + } + } + return false; +} + +void AsyncResolver::DoWork() { + error_ = ResolveHostname(addr_.hostname().c_str(), addr_.family(), + &addresses_); +} + +void AsyncResolver::OnWorkDone() { + SignalDone(this); +} + +const char* inet_ntop(int af, const void *src, char* dst, socklen_t size) { +#if defined(WEBRTC_WIN) + return win32_inet_ntop(af, src, dst, size); +#else + return ::inet_ntop(af, src, dst, size); +#endif +} + +int inet_pton(int af, const char* src, void *dst) { +#if defined(WEBRTC_WIN) + return win32_inet_pton(af, src, dst); +#else + return ::inet_pton(af, src, dst); +#endif +} + +bool HasIPv6Enabled() { +#if !defined(WEBRTC_WIN) + // We only need to check this for Windows XP (so far). + return true; +#else + if (IsWindowsVistaOrLater()) { + return true; + } + if (!IsWindowsXpOrLater()) { + return false; + } + DWORD protbuff_size = 4096; + scoped_ptr protocols; + LPWSAPROTOCOL_INFOW protocol_infos = NULL; + int requested_protocols[2] = {AF_INET6, 0}; + + int err = 0; + int ret = 0; + // Check for protocols in a do-while loop until we provide a buffer large + // enough. (WSCEnumProtocols sets protbuff_size to its desired value). + // It is extremely unlikely that this will loop more than once. + do { + protocols.reset(new char[protbuff_size]); + protocol_infos = reinterpret_cast(protocols.get()); + ret = WSCEnumProtocols(requested_protocols, protocol_infos, + &protbuff_size, &err); + } while (ret == SOCKET_ERROR && err == WSAENOBUFS); + + if (ret == SOCKET_ERROR) { + return false; + } + + // Even if ret is positive, check specifically for IPv6. + // Non-IPv6 enabled WinXP will still return a RAW protocol. + for (int i = 0; i < ret; ++i) { + if (protocol_infos[i].iAddressFamily == AF_INET6) { + return true; + } + } + return false; +#endif +} +} // namespace rtc diff --git a/webrtc/base/nethelpers.h b/webrtc/base/nethelpers.h new file mode 100644 index 000000000..d39400c2f --- /dev/null +++ b/webrtc/base/nethelpers.h @@ -0,0 +1,65 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NETHELPERS_H_ +#define WEBRTC_BASE_NETHELPERS_H_ + +#if defined(WEBRTC_POSIX) +#include +#include +#elif WEBRTC_WIN +#include // NOLINT +#endif + +#include + +#include "webrtc/base/asyncresolverinterface.h" +#include "webrtc/base/signalthread.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +class AsyncResolverTest; + +// AsyncResolver will perform async DNS resolution, signaling the result on +// the SignalDone from AsyncResolverInterface when the operation completes. +class AsyncResolver : public SignalThread, public AsyncResolverInterface { + public: + AsyncResolver(); + virtual ~AsyncResolver() {} + + virtual void Start(const SocketAddress& addr); + virtual bool GetResolvedAddress(int family, SocketAddress* addr) const; + virtual int GetError() const { return error_; } + virtual void Destroy(bool wait) { SignalThread::Destroy(wait); } + + const std::vector& addresses() const { return addresses_; } + void set_error(int error) { error_ = error; } + + protected: + virtual void DoWork(); + virtual void OnWorkDone(); + + private: + SocketAddress addr_; + std::vector addresses_; + int error_; +}; + +// rtc namespaced wrappers for inet_ntop and inet_pton so we can avoid +// the windows-native versions of these. +const char* inet_ntop(int af, const void *src, char* dst, socklen_t size); +int inet_pton(int af, const char* src, void *dst); + +bool HasIPv6Enabled(); +} // namespace rtc + +#endif // WEBRTC_BASE_NETHELPERS_H_ diff --git a/webrtc/base/network.cc b/webrtc/base/network.cc new file mode 100644 index 000000000..d94c69eae --- /dev/null +++ b/webrtc/base/network.cc @@ -0,0 +1,658 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "webrtc/base/network.h" + +#if defined(WEBRTC_POSIX) +// linux/if.h can't be included at the same time as the posix sys/if.h, and +// it's transitively required by linux/route.h, so include that version on +// linux instead of the standard posix one. +#if defined(WEBRTC_LINUX) +#include +#include +#elif !defined(__native_client__) +#include +#endif +#include +#include +#include +#include +#include + +#if defined(WEBRTC_ANDROID) +#include "webrtc/base/ifaddrs-android.h" +#elif !defined(__native_client__) +#include +#endif + +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif + +#include + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socket.h" // includes something that makes windows happy +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/thread.h" + +namespace rtc { +namespace { + +const uint32 kUpdateNetworksMessage = 1; +const uint32 kSignalNetworksMessage = 2; + +// Fetch list of networks every two seconds. +const int kNetworksUpdateIntervalMs = 2000; + +const int kHighestNetworkPreference = 127; + +bool CompareNetworks(const Network* a, const Network* b) { + if (a->prefix_length() == b->prefix_length()) { + if (a->name() == b->name()) { + return a->prefix() < b->prefix(); + } + } + return a->name() < b->name(); +} + +bool SortNetworks(const Network* a, const Network* b) { + // Network types will be preferred above everything else while sorting + // Networks. + + // Networks are sorted first by type. + if (a->type() != b->type()) { + return a->type() < b->type(); + } + + // After type, networks are sorted by IP address precedence values + // from RFC 3484-bis + if (IPAddressPrecedence(a->ip()) != IPAddressPrecedence(b->ip())) { + return IPAddressPrecedence(a->ip()) > IPAddressPrecedence(b->ip()); + } + + // TODO(mallinath) - Add VPN and Link speed conditions while sorting. + + // Networks are sorted last by key. + return a->key() > b->key(); +} + +std::string AdapterTypeToString(AdapterType type) { + switch (type) { + case ADAPTER_TYPE_UNKNOWN: + return "Unknown"; + case ADAPTER_TYPE_ETHERNET: + return "Ethernet"; + case ADAPTER_TYPE_WIFI: + return "Wifi"; + case ADAPTER_TYPE_CELLULAR: + return "Cellular"; + case ADAPTER_TYPE_VPN: + return "VPN"; + default: + ASSERT(false); + return std::string(); + } +} + +} // namespace + +std::string MakeNetworkKey(const std::string& name, const IPAddress& prefix, + int prefix_length) { + std::ostringstream ost; + ost << name << "%" << prefix.ToString() << "/" << prefix_length; + return ost.str(); +} + +NetworkManager::NetworkManager() { +} + +NetworkManager::~NetworkManager() { +} + +NetworkManagerBase::NetworkManagerBase() : ipv6_enabled_(true) { +} + +NetworkManagerBase::~NetworkManagerBase() { + for (NetworkMap::iterator i = networks_map_.begin(); + i != networks_map_.end(); ++i) { + delete i->second; + } +} + +void NetworkManagerBase::GetNetworks(NetworkList* result) const { + *result = networks_; +} + +void NetworkManagerBase::MergeNetworkList(const NetworkList& new_networks, + bool* changed) { + // Sort the list so that we can detect when it changes. + typedef std::pair > address_list; + std::map address_map; + NetworkList list(new_networks); + NetworkList merged_list; + std::sort(list.begin(), list.end(), CompareNetworks); + + *changed = false; + + if (networks_.size() != list.size()) + *changed = true; + + // First, build a set of network-keys to the ipaddresses. + for (uint32 i = 0; i < list.size(); ++i) { + bool might_add_to_merged_list = false; + std::string key = MakeNetworkKey(list[i]->name(), + list[i]->prefix(), + list[i]->prefix_length()); + if (address_map.find(key) == address_map.end()) { + address_map[key] = address_list(list[i], std::vector()); + might_add_to_merged_list = true; + } + const std::vector& addresses = list[i]->GetIPs(); + address_list& current_list = address_map[key]; + for (std::vector::const_iterator it = addresses.begin(); + it != addresses.end(); + ++it) { + current_list.second.push_back(*it); + } + if (!might_add_to_merged_list) { + delete list[i]; + } + } + + // Next, look for existing network objects to re-use. + for (std::map::iterator it = address_map.begin(); + it != address_map.end(); + ++it) { + const std::string& key = it->first; + Network* net = it->second.first; + NetworkMap::iterator existing = networks_map_.find(key); + if (existing == networks_map_.end()) { + // This network is new. Place it in the network map. + merged_list.push_back(net); + networks_map_[key] = net; + *changed = true; + } else { + // This network exists in the map already. Reset its IP addresses. + *changed = existing->second->SetIPs(it->second.second, *changed); + merged_list.push_back(existing->second); + if (existing->second != net) { + delete net; + } + } + } + networks_ = merged_list; + + // If the network lists changes, we resort it. + if (changed) { + std::sort(networks_.begin(), networks_.end(), SortNetworks); + // Now network interfaces are sorted, we should set the preference value + // for each of the interfaces we are planning to use. + // Preference order of network interfaces might have changed from previous + // sorting due to addition of higher preference network interface. + // Since we have already sorted the network interfaces based on our + // requirements, we will just assign a preference value starting with 127, + // in decreasing order. + int pref = kHighestNetworkPreference; + for (NetworkList::const_iterator iter = networks_.begin(); + iter != networks_.end(); ++iter) { + (*iter)->set_preference(pref); + if (pref > 0) { + --pref; + } else { + LOG(LS_ERROR) << "Too many network interfaces to handle!"; + break; + } + } + } +} + +BasicNetworkManager::BasicNetworkManager() + : thread_(NULL), sent_first_update_(false), start_count_(0), + ignore_non_default_routes_(false) { +} + +BasicNetworkManager::~BasicNetworkManager() { +} + +#if defined(__native_client__) + +bool BasicNetworkManager::CreateNetworks(bool include_ignored, + NetworkList* networks) const { + ASSERT(false); + LOG(LS_WARNING) << "BasicNetworkManager doesn't work on NaCl yet"; + return false; +} + +#elif defined(WEBRTC_POSIX) +void BasicNetworkManager::ConvertIfAddrs(struct ifaddrs* interfaces, + bool include_ignored, + NetworkList* networks) const { + NetworkMap current_networks; + for (struct ifaddrs* cursor = interfaces; + cursor != NULL; cursor = cursor->ifa_next) { + IPAddress prefix; + IPAddress mask; + IPAddress ip; + int scope_id = 0; + + // Some interfaces may not have address assigned. + if (!cursor->ifa_addr || !cursor->ifa_netmask) + continue; + + switch (cursor->ifa_addr->sa_family) { + case AF_INET: { + ip = IPAddress( + reinterpret_cast(cursor->ifa_addr)->sin_addr); + mask = IPAddress( + reinterpret_cast(cursor->ifa_netmask)->sin_addr); + break; + } + case AF_INET6: { + if (ipv6_enabled()) { + ip = IPAddress( + reinterpret_cast(cursor->ifa_addr)->sin6_addr); + mask = IPAddress( + reinterpret_cast(cursor->ifa_netmask)->sin6_addr); + scope_id = + reinterpret_cast(cursor->ifa_addr)->sin6_scope_id; + break; + } else { + continue; + } + } + default: { + continue; + } + } + + int prefix_length = CountIPMaskBits(mask); + prefix = TruncateIP(ip, prefix_length); + std::string key = MakeNetworkKey(std::string(cursor->ifa_name), + prefix, prefix_length); + NetworkMap::iterator existing_network = current_networks.find(key); + if (existing_network == current_networks.end()) { + scoped_ptr network(new Network(cursor->ifa_name, + cursor->ifa_name, + prefix, + prefix_length)); + network->set_scope_id(scope_id); + network->AddIP(ip); + bool ignored = ((cursor->ifa_flags & IFF_LOOPBACK) || + IsIgnoredNetwork(*network)); + network->set_ignored(ignored); + if (include_ignored || !network->ignored()) { + networks->push_back(network.release()); + } + } else { + (*existing_network).second->AddIP(ip); + } + } +} + +bool BasicNetworkManager::CreateNetworks(bool include_ignored, + NetworkList* networks) const { + struct ifaddrs* interfaces; + int error = getifaddrs(&interfaces); + if (error != 0) { + LOG_ERR(LERROR) << "getifaddrs failed to gather interface data: " << error; + return false; + } + + ConvertIfAddrs(interfaces, include_ignored, networks); + + freeifaddrs(interfaces); + return true; +} + +#elif defined(WEBRTC_WIN) + +unsigned int GetPrefix(PIP_ADAPTER_PREFIX prefixlist, + const IPAddress& ip, IPAddress* prefix) { + IPAddress current_prefix; + IPAddress best_prefix; + unsigned int best_length = 0; + while (prefixlist) { + // Look for the longest matching prefix in the prefixlist. + if (prefixlist->Address.lpSockaddr == NULL || + prefixlist->Address.lpSockaddr->sa_family != ip.family()) { + prefixlist = prefixlist->Next; + continue; + } + switch (prefixlist->Address.lpSockaddr->sa_family) { + case AF_INET: { + sockaddr_in* v4_addr = + reinterpret_cast(prefixlist->Address.lpSockaddr); + current_prefix = IPAddress(v4_addr->sin_addr); + break; + } + case AF_INET6: { + sockaddr_in6* v6_addr = + reinterpret_cast(prefixlist->Address.lpSockaddr); + current_prefix = IPAddress(v6_addr->sin6_addr); + break; + } + default: { + prefixlist = prefixlist->Next; + continue; + } + } + if (TruncateIP(ip, prefixlist->PrefixLength) == current_prefix && + prefixlist->PrefixLength > best_length) { + best_prefix = current_prefix; + best_length = prefixlist->PrefixLength; + } + prefixlist = prefixlist->Next; + } + *prefix = best_prefix; + return best_length; +} + +bool BasicNetworkManager::CreateNetworks(bool include_ignored, + NetworkList* networks) const { + NetworkMap current_networks; + // MSDN recommends a 15KB buffer for the first try at GetAdaptersAddresses. + size_t buffer_size = 16384; + scoped_ptr adapter_info(new char[buffer_size]); + PIP_ADAPTER_ADDRESSES adapter_addrs = + reinterpret_cast(adapter_info.get()); + int adapter_flags = (GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_PREFIX); + int ret = 0; + do { + adapter_info.reset(new char[buffer_size]); + adapter_addrs = reinterpret_cast(adapter_info.get()); + ret = GetAdaptersAddresses(AF_UNSPEC, adapter_flags, + 0, adapter_addrs, + reinterpret_cast(&buffer_size)); + } while (ret == ERROR_BUFFER_OVERFLOW); + if (ret != ERROR_SUCCESS) { + return false; + } + int count = 0; + while (adapter_addrs) { + if (adapter_addrs->OperStatus == IfOperStatusUp) { + PIP_ADAPTER_UNICAST_ADDRESS address = adapter_addrs->FirstUnicastAddress; + PIP_ADAPTER_PREFIX prefixlist = adapter_addrs->FirstPrefix; + std::string name; + std::string description; +#ifdef _DEBUG + name = ToUtf8(adapter_addrs->FriendlyName, + wcslen(adapter_addrs->FriendlyName)); +#endif + description = ToUtf8(adapter_addrs->Description, + wcslen(adapter_addrs->Description)); + for (; address; address = address->Next) { +#ifndef _DEBUG + name = rtc::ToString(count); +#endif + + IPAddress ip; + int scope_id = 0; + scoped_ptr network; + switch (address->Address.lpSockaddr->sa_family) { + case AF_INET: { + sockaddr_in* v4_addr = + reinterpret_cast(address->Address.lpSockaddr); + ip = IPAddress(v4_addr->sin_addr); + break; + } + case AF_INET6: { + if (ipv6_enabled()) { + sockaddr_in6* v6_addr = + reinterpret_cast(address->Address.lpSockaddr); + scope_id = v6_addr->sin6_scope_id; + ip = IPAddress(v6_addr->sin6_addr); + break; + } else { + continue; + } + } + default: { + continue; + } + } + + IPAddress prefix; + int prefix_length = GetPrefix(prefixlist, ip, &prefix); + std::string key = MakeNetworkKey(name, prefix, prefix_length); + NetworkMap::iterator existing_network = current_networks.find(key); + if (existing_network == current_networks.end()) { + scoped_ptr network(new Network(name, + description, + prefix, + prefix_length)); + network->set_scope_id(scope_id); + network->AddIP(ip); + bool ignore = ((adapter_addrs->IfType == IF_TYPE_SOFTWARE_LOOPBACK) || + IsIgnoredNetwork(*network)); + network->set_ignored(ignore); + if (include_ignored || !network->ignored()) { + networks->push_back(network.release()); + } + } else { + (*existing_network).second->AddIP(ip); + } + } + // Count is per-adapter - all 'Networks' created from the same + // adapter need to have the same name. + ++count; + } + adapter_addrs = adapter_addrs->Next; + } + return true; +} +#endif // WEBRTC_WIN + +#if defined(WEBRTC_LINUX) +bool IsDefaultRoute(const std::string& network_name) { + FileStream fs; + if (!fs.Open("/proc/net/route", "r", NULL)) { + LOG(LS_WARNING) << "Couldn't read /proc/net/route, skipping default " + << "route check (assuming everything is a default route)."; + return true; + } else { + std::string line; + while (fs.ReadLine(&line) == SR_SUCCESS) { + char iface_name[256]; + unsigned int iface_ip, iface_gw, iface_mask, iface_flags; + if (sscanf(line.c_str(), + "%255s %8X %8X %4X %*d %*u %*d %8X", + iface_name, &iface_ip, &iface_gw, + &iface_flags, &iface_mask) == 5 && + network_name == iface_name && + iface_mask == 0 && + (iface_flags & (RTF_UP | RTF_HOST)) == RTF_UP) { + return true; + } + } + } + return false; +} +#endif + +bool BasicNetworkManager::IsIgnoredNetwork(const Network& network) const { + // Ignore networks on the explicit ignore list. + for (size_t i = 0; i < network_ignore_list_.size(); ++i) { + if (network.name() == network_ignore_list_[i]) { + return true; + } + } +#if defined(WEBRTC_POSIX) + // Filter out VMware interfaces, typically named vmnet1 and vmnet8 + if (strncmp(network.name().c_str(), "vmnet", 5) == 0 || + strncmp(network.name().c_str(), "vnic", 4) == 0) { + return true; + } +#if defined(WEBRTC_LINUX) + // Make sure this is a default route, if we're ignoring non-defaults. + if (ignore_non_default_routes_ && !IsDefaultRoute(network.name())) { + return true; + } +#endif +#elif defined(WEBRTC_WIN) + // Ignore any HOST side vmware adapters with a description like: + // VMware Virtual Ethernet Adapter for VMnet1 + // but don't ignore any GUEST side adapters with a description like: + // VMware Accelerated AMD PCNet Adapter #2 + if (strstr(network.description().c_str(), "VMnet") != NULL) { + return true; + } +#endif + + // Ignore any networks with a 0.x.y.z IP + if (network.prefix().family() == AF_INET) { + return (network.prefix().v4AddressAsHostOrderInteger() < 0x01000000); + } + return false; +} + +void BasicNetworkManager::StartUpdating() { + thread_ = Thread::Current(); + if (start_count_) { + // If network interfaces are already discovered and signal is sent, + // we should trigger network signal immediately for the new clients + // to start allocating ports. + if (sent_first_update_) + thread_->Post(this, kSignalNetworksMessage); + } else { + thread_->Post(this, kUpdateNetworksMessage); + } + ++start_count_; +} + +void BasicNetworkManager::StopUpdating() { + ASSERT(Thread::Current() == thread_); + if (!start_count_) + return; + + --start_count_; + if (!start_count_) { + thread_->Clear(this); + sent_first_update_ = false; + } +} + +void BasicNetworkManager::OnMessage(Message* msg) { + switch (msg->message_id) { + case kUpdateNetworksMessage: { + DoUpdateNetworks(); + break; + } + case kSignalNetworksMessage: { + SignalNetworksChanged(); + break; + } + default: + ASSERT(false); + } +} + +void BasicNetworkManager::DoUpdateNetworks() { + if (!start_count_) + return; + + ASSERT(Thread::Current() == thread_); + + NetworkList list; + if (!CreateNetworks(false, &list)) { + SignalError(); + } else { + bool changed; + MergeNetworkList(list, &changed); + if (changed || !sent_first_update_) { + SignalNetworksChanged(); + sent_first_update_ = true; + } + } + + thread_->PostDelayed(kNetworksUpdateIntervalMs, this, kUpdateNetworksMessage); +} + +void BasicNetworkManager::DumpNetworks(bool include_ignored) { + NetworkList list; + CreateNetworks(include_ignored, &list); + LOG(LS_INFO) << "NetworkManager detected " << list.size() << " networks:"; + for (size_t i = 0; i < list.size(); ++i) { + const Network* network = list[i]; + if (!network->ignored() || include_ignored) { + LOG(LS_INFO) << network->ToString() << ": " + << network->description() + << ((network->ignored()) ? ", Ignored" : ""); + } + } + // Release the network list created previously. + // Do this in a seperated for loop for better readability. + for (size_t i = 0; i < list.size(); ++i) { + delete list[i]; + } +} + +Network::Network(const std::string& name, const std::string& desc, + const IPAddress& prefix, int prefix_length) + : name_(name), description_(desc), prefix_(prefix), + prefix_length_(prefix_length), + key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0), + ignored_(false), type_(ADAPTER_TYPE_UNKNOWN), preference_(0) { +} + +Network::Network(const std::string& name, const std::string& desc, + const IPAddress& prefix, int prefix_length, AdapterType type) + : name_(name), description_(desc), prefix_(prefix), + prefix_length_(prefix_length), + key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0), + ignored_(false), type_(type), preference_(0) { +} + +std::string Network::ToString() const { + std::stringstream ss; + // Print out the first space-terminated token of the network desc, plus + // the IP address. + ss << "Net[" << description_.substr(0, description_.find(' ')) + << ":" << prefix_.ToSensitiveString() << "/" << prefix_length_ + << ":" << AdapterTypeToString(type_) << "]"; + return ss.str(); +} + +// Sets the addresses of this network. Returns true if the address set changed. +// Change detection is short circuited if the changed argument is true. +bool Network::SetIPs(const std::vector& ips, bool changed) { + changed = changed || ips.size() != ips_.size(); + // Detect changes with a nested loop; n-squared but we expect on the order + // of 2-3 addresses per network. + for (std::vector::const_iterator it = ips.begin(); + !changed && it != ips.end(); + ++it) { + bool found = false; + for (std::vector::iterator inner_it = ips_.begin(); + !found && inner_it != ips_.end(); + ++inner_it) { + if (*it == *inner_it) { + found = true; + } + } + changed = !found; + } + ips_ = ips; + return changed; +} + +} // namespace rtc diff --git a/webrtc/base/network.h b/webrtc/base/network.h new file mode 100644 index 000000000..855b1b74a --- /dev/null +++ b/webrtc/base/network.h @@ -0,0 +1,245 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NETWORK_H_ +#define WEBRTC_BASE_NETWORK_H_ + +#include +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/ipaddress.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/sigslot.h" + +#if defined(WEBRTC_POSIX) +struct ifaddrs; +#endif // defined(WEBRTC_POSIX) + +namespace rtc { + +class Network; +class Thread; + +enum AdapterType { + // This enum resembles the one in Chromium net::ConnectionType. + ADAPTER_TYPE_UNKNOWN = 0, + ADAPTER_TYPE_ETHERNET = 1, + ADAPTER_TYPE_WIFI = 2, + ADAPTER_TYPE_CELLULAR = 3, + ADAPTER_TYPE_VPN = 4 +}; + +// Makes a string key for this network. Used in the network manager's maps. +// Network objects are keyed on interface name, network prefix and the +// length of that prefix. +std::string MakeNetworkKey(const std::string& name, const IPAddress& prefix, + int prefix_length); + +// Generic network manager interface. It provides list of local +// networks. +class NetworkManager { + public: + typedef std::vector NetworkList; + + NetworkManager(); + virtual ~NetworkManager(); + + // Called when network list is updated. + sigslot::signal0<> SignalNetworksChanged; + + // Indicates a failure when getting list of network interfaces. + sigslot::signal0<> SignalError; + + // Start/Stop monitoring of network interfaces + // list. SignalNetworksChanged or SignalError is emitted immidiately + // after StartUpdating() is called. After that SignalNetworksChanged + // is emitted wheneven list of networks changes. + virtual void StartUpdating() = 0; + virtual void StopUpdating() = 0; + + // Returns the current list of networks available on this machine. + // UpdateNetworks() must be called before this method is called. + // It makes sure that repeated calls return the same object for a + // given network, so that quality is tracked appropriately. Does not + // include ignored networks. + virtual void GetNetworks(NetworkList* networks) const = 0; + + // Dumps a list of networks available to LS_INFO. + virtual void DumpNetworks(bool include_ignored) {} +}; + +// Base class for NetworkManager implementations. +class NetworkManagerBase : public NetworkManager { + public: + NetworkManagerBase(); + virtual ~NetworkManagerBase(); + + virtual void GetNetworks(std::vector* networks) const; + bool ipv6_enabled() const { return ipv6_enabled_; } + void set_ipv6_enabled(bool enabled) { ipv6_enabled_ = enabled; } + + protected: + typedef std::map NetworkMap; + // Updates |networks_| with the networks listed in |list|. If + // |network_map_| already has a Network object for a network listed + // in the |list| then it is reused. Accept ownership of the Network + // objects in the |list|. |changed| will be set to true if there is + // any change in the network list. + void MergeNetworkList(const NetworkList& list, bool* changed); + + private: + friend class NetworkTest; + void DoUpdateNetworks(); + + NetworkList networks_; + NetworkMap networks_map_; + bool ipv6_enabled_; +}; + +// Basic implementation of the NetworkManager interface that gets list +// of networks using OS APIs. +class BasicNetworkManager : public NetworkManagerBase, + public MessageHandler { + public: + BasicNetworkManager(); + virtual ~BasicNetworkManager(); + + virtual void StartUpdating(); + virtual void StopUpdating(); + + // Logs the available networks. + virtual void DumpNetworks(bool include_ignored); + + // MessageHandler interface. + virtual void OnMessage(Message* msg); + bool started() { return start_count_ > 0; } + + // Sets the network ignore list, which is empty by default. Any network on + // the ignore list will be filtered from network enumeration results. + void set_network_ignore_list(const std::vector& list) { + network_ignore_list_ = list; + } +#if defined(WEBRTC_LINUX) + // Sets the flag for ignoring non-default routes. + void set_ignore_non_default_routes(bool value) { + ignore_non_default_routes_ = true; + } +#endif + + protected: +#if defined(WEBRTC_POSIX) + // Separated from CreateNetworks for tests. + void ConvertIfAddrs(ifaddrs* interfaces, + bool include_ignored, + NetworkList* networks) const; +#endif // defined(WEBRTC_POSIX) + + // Creates a network object for each network available on the machine. + bool CreateNetworks(bool include_ignored, NetworkList* networks) const; + + // Determines if a network should be ignored. + bool IsIgnoredNetwork(const Network& network) const; + + private: + friend class NetworkTest; + + void DoUpdateNetworks(); + + Thread* thread_; + bool sent_first_update_; + int start_count_; + std::vector network_ignore_list_; + bool ignore_non_default_routes_; +}; + +// Represents a Unix-type network interface, with a name and single address. +class Network { + public: + Network(const std::string& name, const std::string& description, + const IPAddress& prefix, int prefix_length); + + Network(const std::string& name, const std::string& description, + const IPAddress& prefix, int prefix_length, AdapterType type); + + // Returns the name of the interface this network is associated wtih. + const std::string& name() const { return name_; } + + // Returns the OS-assigned name for this network. This is useful for + // debugging but should not be sent over the wire (for privacy reasons). + const std::string& description() const { return description_; } + + // Returns the prefix for this network. + const IPAddress& prefix() const { return prefix_; } + // Returns the length, in bits, of this network's prefix. + int prefix_length() const { return prefix_length_; } + + // |key_| has unique value per network interface. Used in sorting network + // interfaces. Key is derived from interface name and it's prefix. + std::string key() const { return key_; } + + // Returns the Network's current idea of the 'best' IP it has. + // 'Best' currently means the first one added. + // TODO: We should be preferring temporary addresses. + // Returns an unset IP if this network has no active addresses. + IPAddress ip() const { + if (ips_.size() == 0) { + return IPAddress(); + } + return ips_.at(0); + } + // Adds an active IP address to this network. Does not check for duplicates. + void AddIP(const IPAddress& ip) { ips_.push_back(ip); } + + // Sets the network's IP address list. Returns true if new IP addresses were + // detected. Passing true to already_changed skips this check. + bool SetIPs(const std::vector& ips, bool already_changed); + // Get the list of IP Addresses associated with this network. + const std::vector& GetIPs() { return ips_;} + // Clear the network's list of addresses. + void ClearIPs() { ips_.clear(); } + + // Returns the scope-id of the network's address. + // Should only be relevant for link-local IPv6 addresses. + int scope_id() const { return scope_id_; } + void set_scope_id(int id) { scope_id_ = id; } + + // Indicates whether this network should be ignored, perhaps because + // the IP is 0, or the interface is one we know is invalid. + bool ignored() const { return ignored_; } + void set_ignored(bool ignored) { ignored_ = ignored; } + + AdapterType type() const { return type_; } + int preference() const { return preference_; } + void set_preference(int preference) { preference_ = preference; } + + // Debugging description of this network + std::string ToString() const; + + private: + std::string name_; + std::string description_; + IPAddress prefix_; + int prefix_length_; + std::string key_; + std::vector ips_; + int scope_id_; + bool ignored_; + AdapterType type_; + int preference_; + + friend class NetworkManager; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NETWORK_H_ diff --git a/webrtc/base/network_unittest.cc b/webrtc/base/network_unittest.cc new file mode 100644 index 000000000..431f8b4ea --- /dev/null +++ b/webrtc/base/network_unittest.cc @@ -0,0 +1,617 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/network.h" + +#include +#if defined(WEBRTC_POSIX) +#include +#if !defined(WEBRTC_ANDROID) +#include +#else +#include "webrtc/base/ifaddrs-android.h" +#endif +#endif +#include "webrtc/base/gunit.h" +#if defined(WEBRTC_WIN) +#include "webrtc/base/logging.h" // For LOG_GLE +#endif + +namespace rtc { + +class NetworkTest : public testing::Test, public sigslot::has_slots<> { + public: + NetworkTest() : callback_called_(false) {} + + void OnNetworksChanged() { + callback_called_ = true; + } + + void MergeNetworkList(BasicNetworkManager& network_manager, + const NetworkManager::NetworkList& list, + bool* changed ) { + network_manager.MergeNetworkList(list, changed); + } + + bool IsIgnoredNetwork(BasicNetworkManager& network_manager, + const Network& network) { + return network_manager.IsIgnoredNetwork(network); + } + + NetworkManager::NetworkList GetNetworks( + const BasicNetworkManager& network_manager, bool include_ignored) { + NetworkManager::NetworkList list; + network_manager.CreateNetworks(include_ignored, &list); + return list; + } + +#if defined(WEBRTC_POSIX) + // Separated from CreateNetworks for tests. + static void CallConvertIfAddrs(const BasicNetworkManager& network_manager, + struct ifaddrs* interfaces, + bool include_ignored, + NetworkManager::NetworkList* networks) { + network_manager.ConvertIfAddrs(interfaces, include_ignored, networks); + } +#endif // defined(WEBRTC_POSIX) + + protected: + bool callback_called_; +}; + +// Test that the Network ctor works properly. +TEST_F(NetworkTest, TestNetworkConstruct) { + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + EXPECT_EQ("test_eth0", ipv4_network1.name()); + EXPECT_EQ("Test Network Adapter 1", ipv4_network1.description()); + EXPECT_EQ(IPAddress(0x12345600U), ipv4_network1.prefix()); + EXPECT_EQ(24, ipv4_network1.prefix_length()); + EXPECT_FALSE(ipv4_network1.ignored()); +} + +// Tests that our ignore function works properly. +TEST_F(NetworkTest, TestNetworkIgnore) { + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + Network ipv4_network2("test_eth1", "Test Network Adapter 2", + IPAddress(0x00010000U), 16); + BasicNetworkManager network_manager; + EXPECT_FALSE(IsIgnoredNetwork(network_manager, ipv4_network1)); + EXPECT_TRUE(IsIgnoredNetwork(network_manager, ipv4_network2)); +} + +TEST_F(NetworkTest, TestIgnoreList) { + Network ignore_me("ignore_me", "Ignore me please!", + IPAddress(0x12345600U), 24); + Network include_me("include_me", "Include me please!", + IPAddress(0x12345600U), 24); + BasicNetworkManager network_manager; + EXPECT_FALSE(IsIgnoredNetwork(network_manager, ignore_me)); + EXPECT_FALSE(IsIgnoredNetwork(network_manager, include_me)); + std::vector ignore_list; + ignore_list.push_back("ignore_me"); + network_manager.set_network_ignore_list(ignore_list); + EXPECT_TRUE(IsIgnoredNetwork(network_manager, ignore_me)); + EXPECT_FALSE(IsIgnoredNetwork(network_manager, include_me)); +} + +// Test is failing on Windows opt: b/11288214 +TEST_F(NetworkTest, DISABLED_TestCreateNetworks) { + BasicNetworkManager manager; + NetworkManager::NetworkList result = GetNetworks(manager, true); + // We should be able to bind to any addresses we find. + NetworkManager::NetworkList::iterator it; + for (it = result.begin(); + it != result.end(); + ++it) { + sockaddr_storage storage; + memset(&storage, 0, sizeof(storage)); + IPAddress ip = (*it)->ip(); + SocketAddress bindaddress(ip, 0); + bindaddress.SetScopeID((*it)->scope_id()); + // TODO(thaloun): Use rtc::AsyncSocket once it supports IPv6. + int fd = static_cast(socket(ip.family(), SOCK_STREAM, IPPROTO_TCP)); + if (fd > 0) { + size_t ipsize = bindaddress.ToSockAddrStorage(&storage); + EXPECT_GE(ipsize, 0U); + int success = ::bind(fd, + reinterpret_cast(&storage), + static_cast(ipsize)); +#if defined(WEBRTC_WIN) + if (success) LOG_GLE(LS_ERROR) << "Socket bind failed."; +#endif + EXPECT_EQ(0, success); +#if defined(WEBRTC_WIN) + closesocket(fd); +#else + close(fd); +#endif + } + delete (*it); + } +} + +// Test that UpdateNetworks succeeds. +TEST_F(NetworkTest, TestUpdateNetworks) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + manager.StartUpdating(); + Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(callback_called_); + callback_called_ = false; + // Callback should be triggered immediately when StartUpdating + // is called, after network update signal is already sent. + manager.StartUpdating(); + EXPECT_TRUE(manager.started()); + Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(callback_called_); + manager.StopUpdating(); + EXPECT_TRUE(manager.started()); + manager.StopUpdating(); + EXPECT_FALSE(manager.started()); + manager.StopUpdating(); + EXPECT_FALSE(manager.started()); + callback_called_ = false; + // Callback should be triggered immediately after StartUpdating is called + // when start_count_ is reset to 0. + manager.StartUpdating(); + Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(callback_called_); +} + +// Verify that MergeNetworkList() merges network lists properly. +TEST_F(NetworkTest, TestBasicMergeNetworkList) { + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + Network ipv4_network2("test_eth1", "Test Network Adapter 2", + IPAddress(0x00010000U), 16); + ipv4_network1.AddIP(IPAddress(0x12345678)); + ipv4_network2.AddIP(IPAddress(0x00010004)); + BasicNetworkManager manager; + + // Add ipv4_network1 to the list of networks. + NetworkManager::NetworkList list; + list.push_back(new Network(ipv4_network1)); + bool changed; + MergeNetworkList(manager, list, &changed); + EXPECT_TRUE(changed); + list.clear(); + + manager.GetNetworks(&list); + EXPECT_EQ(1U, list.size()); + EXPECT_EQ(ipv4_network1.ToString(), list[0]->ToString()); + Network* net1 = list[0]; + list.clear(); + + // Replace ipv4_network1 with ipv4_network2. + list.push_back(new Network(ipv4_network2)); + MergeNetworkList(manager, list, &changed); + EXPECT_TRUE(changed); + list.clear(); + + manager.GetNetworks(&list); + EXPECT_EQ(1U, list.size()); + EXPECT_EQ(ipv4_network2.ToString(), list[0]->ToString()); + Network* net2 = list[0]; + list.clear(); + + // Add Network2 back. + list.push_back(new Network(ipv4_network1)); + list.push_back(new Network(ipv4_network2)); + MergeNetworkList(manager, list, &changed); + EXPECT_TRUE(changed); + list.clear(); + + // Verify that we get previous instances of Network objects. + manager.GetNetworks(&list); + EXPECT_EQ(2U, list.size()); + EXPECT_TRUE((net1 == list[0] && net2 == list[1]) || + (net1 == list[1] && net2 == list[0])); + list.clear(); + + // Call MergeNetworkList() again and verify that we don't get update + // notification. + list.push_back(new Network(ipv4_network2)); + list.push_back(new Network(ipv4_network1)); + MergeNetworkList(manager, list, &changed); + EXPECT_FALSE(changed); + list.clear(); + + // Verify that we get previous instances of Network objects. + manager.GetNetworks(&list); + EXPECT_EQ(2U, list.size()); + EXPECT_TRUE((net1 == list[0] && net2 == list[1]) || + (net1 == list[1] && net2 == list[0])); + list.clear(); +} + +// Sets up some test IPv6 networks and appends them to list. +// Four networks are added - public and link local, for two interfaces. +void SetupNetworks(NetworkManager::NetworkList* list) { + IPAddress ip; + IPAddress prefix; + EXPECT_TRUE(IPFromString("fe80::1234:5678:abcd:ef12", &ip)); + EXPECT_TRUE(IPFromString("fe80::", &prefix)); + // First, fake link-locals. + Network ipv6_eth0_linklocalnetwork("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth0_linklocalnetwork.AddIP(ip); + EXPECT_TRUE(IPFromString("fe80::5678:abcd:ef12:3456", &ip)); + Network ipv6_eth1_linklocalnetwork("test_eth1", "Test NetworkAdapter 2", + prefix, 64); + ipv6_eth1_linklocalnetwork.AddIP(ip); + // Public networks: + EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c3", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth0_publicnetwork1_ip1("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth0_publicnetwork1_ip1.AddIP(ip); + EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:abcd:efab:cdef", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth1_publicnetwork1_ip1("test_eth1", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth1_publicnetwork1_ip1.AddIP(ip); + list->push_back(new Network(ipv6_eth0_linklocalnetwork)); + list->push_back(new Network(ipv6_eth1_linklocalnetwork)); + list->push_back(new Network(ipv6_eth0_publicnetwork1_ip1)); + list->push_back(new Network(ipv6_eth1_publicnetwork1_ip1)); +} + +// Test that the basic network merging case works. +TEST_F(NetworkTest, TestIPv6MergeNetworkList) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(original_list.size(), list.size()); + // Verify that the original members are in the merged list. + for (NetworkManager::NetworkList::iterator it = original_list.begin(); + it != original_list.end(); ++it) { + EXPECT_NE(list.end(), std::find(list.begin(), list.end(), *it)); + } +} + +// Tests that when two network lists that describe the same set of networks are +// merged, that the changed callback is not called, and that the original +// objects remain in the result list. +TEST_F(NetworkTest, TestNoChangeMerge) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + // Second list that describes the same networks but with new objects. + NetworkManager::NetworkList second_list; + SetupNetworks(&second_list); + changed = false; + MergeNetworkList(manager, second_list, &changed); + EXPECT_FALSE(changed); + NetworkManager::NetworkList resulting_list; + manager.GetNetworks(&resulting_list); + EXPECT_EQ(original_list.size(), resulting_list.size()); + // Verify that the original members are in the merged list. + for (NetworkManager::NetworkList::iterator it = original_list.begin(); + it != original_list.end(); ++it) { + EXPECT_NE(resulting_list.end(), + std::find(resulting_list.begin(), resulting_list.end(), *it)); + } + // Doublecheck that the new networks aren't in the list. + for (NetworkManager::NetworkList::iterator it = second_list.begin(); + it != second_list.end(); ++it) { + EXPECT_EQ(resulting_list.end(), + std::find(resulting_list.begin(), resulting_list.end(), *it)); + } +} + +// Test that we can merge a network that is the same as another network but with +// a different IP. The original network should remain in the list, but have its +// IP changed. +TEST_F(NetworkTest, MergeWithChangedIP) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + // Make a network that we're going to change. + IPAddress ip; + EXPECT_TRUE(IPFromString("2401:fa01:4:1000:be30:faa:fee:faa", &ip)); + IPAddress prefix = TruncateIP(ip, 64); + Network* network_to_change = new Network("test_eth0", + "Test Network Adapter 1", + prefix, 64); + Network* changed_network = new Network(*network_to_change); + network_to_change->AddIP(ip); + IPAddress changed_ip; + EXPECT_TRUE(IPFromString("2401:fa01:4:1000:be30:f00:f00:f00", &changed_ip)); + changed_network->AddIP(changed_ip); + original_list.push_back(network_to_change); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + NetworkManager::NetworkList second_list; + SetupNetworks(&second_list); + second_list.push_back(changed_network); + changed = false; + MergeNetworkList(manager, second_list, &changed); + EXPECT_TRUE(changed); + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(original_list.size(), list.size()); + // Make sure the original network is still in the merged list. + EXPECT_NE(list.end(), + std::find(list.begin(), list.end(), network_to_change)); + EXPECT_EQ(changed_ip, network_to_change->GetIPs().at(0)); +} + +// Testing a similar case to above, but checking that a network can be updated +// with additional IPs (not just a replacement). +TEST_F(NetworkTest, TestMultipleIPMergeNetworkList) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + IPAddress ip; + IPAddress check_ip; + IPAddress prefix; + // Add a second IP to the public network on eth0 (2401:fa00:4:1000/64). + EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c6", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth0_publicnetwork1_ip2("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + // This is the IP that already existed in the public network on eth0. + EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c3", &check_ip)); + ipv6_eth0_publicnetwork1_ip2.AddIP(ip); + original_list.push_back(new Network(ipv6_eth0_publicnetwork1_ip2)); + changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + // There should still be four networks. + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(4U, list.size()); + // Check the gathered IPs. + int matchcount = 0; + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->ToString() == original_list[2]->ToString()) { + ++matchcount; + EXPECT_EQ(1, matchcount); + // This should be the same network object as before. + EXPECT_EQ((*it), original_list[2]); + // But with two addresses now. + EXPECT_EQ(2U, (*it)->GetIPs().size()); + EXPECT_NE((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + check_ip)); + EXPECT_NE((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + ip)); + } else { + // Check the IP didn't get added anywhere it wasn't supposed to. + EXPECT_EQ((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + ip)); + } + } +} + +// Test that merge correctly distinguishes multiple networks on an interface. +TEST_F(NetworkTest, TestMultiplePublicNetworksOnOneInterfaceMerge) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + IPAddress ip; + IPAddress prefix; + // A second network for eth0. + EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:5bff:fee5:c3", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth0_publicnetwork2_ip1("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth0_publicnetwork2_ip1.AddIP(ip); + original_list.push_back(new Network(ipv6_eth0_publicnetwork2_ip1)); + changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + // There should be five networks now. + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(5U, list.size()); + // Check the resulting addresses. + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->prefix() == ipv6_eth0_publicnetwork2_ip1.prefix() && + (*it)->name() == ipv6_eth0_publicnetwork2_ip1.name()) { + // Check the new network has 1 IP and that it's the correct one. + EXPECT_EQ(1U, (*it)->GetIPs().size()); + EXPECT_EQ(ip, (*it)->GetIPs().at(0)); + } else { + // Check the IP didn't get added anywhere it wasn't supposed to. + EXPECT_EQ((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + ip)); + } + } +} + +// Test that DumpNetworks works. +TEST_F(NetworkTest, TestDumpNetworks) { + BasicNetworkManager manager; + manager.DumpNetworks(true); +} + +// Test that we can toggle IPv6 on and off. +TEST_F(NetworkTest, TestIPv6Toggle) { + BasicNetworkManager manager; + bool ipv6_found = false; + NetworkManager::NetworkList list; +#if !defined(WEBRTC_WIN) + // There should be at least one IPv6 network (fe80::/64 should be in there). + // TODO(thaloun): Disabling this test on windows for the moment as the test + // machines don't seem to have IPv6 installed on them at all. + manager.set_ipv6_enabled(true); + list = GetNetworks(manager, true); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->prefix().family() == AF_INET6) { + ipv6_found = true; + break; + } + } + EXPECT_TRUE(ipv6_found); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } +#endif + ipv6_found = false; + manager.set_ipv6_enabled(false); + list = GetNetworks(manager, true); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->prefix().family() == AF_INET6) { + ipv6_found = true; + break; + } + } + EXPECT_FALSE(ipv6_found); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } +} + +TEST_F(NetworkTest, TestNetworkListSorting) { + BasicNetworkManager manager; + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + ipv4_network1.AddIP(IPAddress(0x12345600U)); + + IPAddress ip; + IPAddress prefix; + EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:abcd:efab:cdef", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth1_publicnetwork1_ip1("test_eth1", "Test NetworkAdapter 2", + prefix, 64); + ipv6_eth1_publicnetwork1_ip1.AddIP(ip); + + NetworkManager::NetworkList list; + list.push_back(new Network(ipv4_network1)); + list.push_back(new Network(ipv6_eth1_publicnetwork1_ip1)); + Network* net1 = list[0]; + Network* net2 = list[1]; + + bool changed = false; + MergeNetworkList(manager, list, &changed); + ASSERT_TRUE(changed); + // After sorting IPv6 network should be higher order than IPv4 networks. + EXPECT_TRUE(net1->preference() < net2->preference()); +} + +TEST_F(NetworkTest, TestNetworkAdapterTypes) { + Network wifi("wlan0", "Wireless Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_WIFI); + EXPECT_EQ(ADAPTER_TYPE_WIFI, wifi.type()); + Network ethernet("eth0", "Ethernet", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_ETHERNET); + EXPECT_EQ(ADAPTER_TYPE_ETHERNET, ethernet.type()); + Network cellular("test_cell", "Cellular Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_CELLULAR); + EXPECT_EQ(ADAPTER_TYPE_CELLULAR, cellular.type()); + Network vpn("bridge_test", "VPN Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_VPN); + EXPECT_EQ(ADAPTER_TYPE_VPN, vpn.type()); + Network unknown("test", "Test Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_UNKNOWN); + EXPECT_EQ(ADAPTER_TYPE_UNKNOWN, unknown.type()); +} + +#if defined(WEBRTC_POSIX) +// Verify that we correctly handle interfaces with no address. +TEST_F(NetworkTest, TestConvertIfAddrsNoAddress) { + ifaddrs list; + memset(&list, 0, sizeof(list)); + list.ifa_name = const_cast("test_iface"); + + NetworkManager::NetworkList result; + BasicNetworkManager manager; + CallConvertIfAddrs(manager, &list, true, &result); + EXPECT_TRUE(result.empty()); +} +#endif // defined(WEBRTC_POSIX) + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +// If you want to test non-default routes, you can do the following on a linux +// machine: +// 1) Load the dummy network driver: +// sudo modprobe dummy +// sudo ifconfig dummy0 127.0.0.1 +// 2) Run this test and confirm the output says it found a dummy route (and +// passes). +// 3) When done: +// sudo rmmmod dummy +TEST_F(NetworkTest, TestIgnoreNonDefaultRoutes) { + BasicNetworkManager manager; + NetworkManager::NetworkList list; + list = GetNetworks(manager, false); + bool found_dummy = false; + LOG(LS_INFO) << "Looking for dummy network: "; + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + LOG(LS_INFO) << " Network name: " << (*it)->name(); + found_dummy |= (*it)->name().find("dummy0") != std::string::npos; + } + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } + if (!found_dummy) { + LOG(LS_INFO) << "No dummy found, quitting."; + return; + } + LOG(LS_INFO) << "Found dummy, running again while ignoring non-default " + << "routes."; + manager.set_ignore_non_default_routes(true); + list = GetNetworks(manager, false); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + LOG(LS_INFO) << " Network name: " << (*it)->name(); + EXPECT_TRUE((*it)->name().find("dummy0") == std::string::npos); + } + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } +} +#endif + +} // namespace rtc diff --git a/webrtc/base/nssidentity.cc b/webrtc/base/nssidentity.cc new file mode 100644 index 000000000..77635a2fc --- /dev/null +++ b/webrtc/base/nssidentity.cc @@ -0,0 +1,521 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_NSS_SSL_H + +#include "webrtc/base/nssidentity.h" + +#include "cert.h" +#include "cryptohi.h" +#include "keyhi.h" +#include "nss.h" +#include "pk11pub.h" +#include "sechash.h" + +#include "webrtc/base/logging.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/nssstreamadapter.h" +#include "webrtc/base/safe_conversions.h" + +namespace rtc { + +// Certificate validity lifetime in seconds. +static const int CERTIFICATE_LIFETIME = 60*60*24*30; // 30 days, arbitrarily +// Certificate validity window in seconds. +// This is to compensate for slightly incorrect system clocks. +static const int CERTIFICATE_WINDOW = -60*60*24; + +NSSKeyPair::~NSSKeyPair() { + if (privkey_) + SECKEY_DestroyPrivateKey(privkey_); + if (pubkey_) + SECKEY_DestroyPublicKey(pubkey_); +} + +NSSKeyPair *NSSKeyPair::Generate() { + SECKEYPrivateKey *privkey = NULL; + SECKEYPublicKey *pubkey = NULL; + PK11RSAGenParams rsaparams; + rsaparams.keySizeInBits = 1024; + rsaparams.pe = 0x010001; // 65537 -- a common RSA public exponent. + + privkey = PK11_GenerateKeyPair(NSSContext::GetSlot(), + CKM_RSA_PKCS_KEY_PAIR_GEN, + &rsaparams, &pubkey, PR_FALSE /*permanent*/, + PR_FALSE /*sensitive*/, NULL); + if (!privkey) { + LOG(LS_ERROR) << "Couldn't generate key pair"; + return NULL; + } + + return new NSSKeyPair(privkey, pubkey); +} + +// Just make a copy. +NSSKeyPair *NSSKeyPair::GetReference() { + SECKEYPrivateKey *privkey = SECKEY_CopyPrivateKey(privkey_); + if (!privkey) + return NULL; + + SECKEYPublicKey *pubkey = SECKEY_CopyPublicKey(pubkey_); + if (!pubkey) { + SECKEY_DestroyPrivateKey(privkey); + return NULL; + } + + return new NSSKeyPair(privkey, pubkey); +} + +NSSCertificate::NSSCertificate(CERTCertificate* cert) + : certificate_(CERT_DupCertificate(cert)) { + ASSERT(certificate_ != NULL); +} + +static void DeleteCert(SSLCertificate* cert) { + delete cert; +} + +NSSCertificate::NSSCertificate(CERTCertList* cert_list) { + // Copy the first cert into certificate_. + CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + certificate_ = CERT_DupCertificate(node->cert); + + // Put any remaining certificates into the chain. + node = CERT_LIST_NEXT(node); + std::vector certs; + for (; !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { + certs.push_back(new NSSCertificate(node->cert)); + } + + if (!certs.empty()) + chain_.reset(new SSLCertChain(certs)); + + // The SSLCertChain constructor copies its input, so now we have to delete + // the originals. + std::for_each(certs.begin(), certs.end(), DeleteCert); +} + +NSSCertificate::NSSCertificate(CERTCertificate* cert, SSLCertChain* chain) + : certificate_(CERT_DupCertificate(cert)) { + ASSERT(certificate_ != NULL); + if (chain) + chain_.reset(chain->Copy()); +} + + +NSSCertificate *NSSCertificate::FromPEMString(const std::string &pem_string) { + std::string der; + if (!SSLIdentity::PemToDer(kPemTypeCertificate, pem_string, &der)) + return NULL; + + SECItem der_cert; + der_cert.data = reinterpret_cast(const_cast( + der.data())); + der_cert.len = checked_cast(der.size()); + CERTCertificate *cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + &der_cert, NULL, PR_FALSE, PR_TRUE); + + if (!cert) + return NULL; + + NSSCertificate* ret = new NSSCertificate(cert); + CERT_DestroyCertificate(cert); + return ret; +} + +NSSCertificate *NSSCertificate::GetReference() const { + return new NSSCertificate(certificate_, chain_.get()); +} + +std::string NSSCertificate::ToPEMString() const { + return SSLIdentity::DerToPem(kPemTypeCertificate, + certificate_->derCert.data, + certificate_->derCert.len); +} + +void NSSCertificate::ToDER(Buffer* der_buffer) const { + der_buffer->SetData(certificate_->derCert.data, certificate_->derCert.len); +} + +static bool Certifies(CERTCertificate* parent, CERTCertificate* child) { + // TODO(bemasc): Identify stricter validation checks to use here. In the + // context of some future identity standard, it might make sense to check + // the certificates' roles, expiration dates, self-signatures (if + // self-signed), certificate transparency logging, or many other attributes. + // NOTE: Future changes to this validation may reject some previously allowed + // certificate chains. Users should be advised not to deploy chained + // certificates except in controlled environments until the validity + // requirements are finalized. + + // Check that the parent's name is the same as the child's claimed issuer. + SECComparison name_status = + CERT_CompareName(&child->issuer, &parent->subject); + if (name_status != SECEqual) + return false; + + // Extract the parent's public key, or fail if the key could not be read + // (e.g. certificate is corrupted). + SECKEYPublicKey* parent_key = CERT_ExtractPublicKey(parent); + if (!parent_key) + return false; + + // Check that the parent's privkey was actually used to generate the child's + // signature. + SECStatus verified = CERT_VerifySignedDataWithPublicKey( + &child->signatureWrap, parent_key, NULL); + SECKEY_DestroyPublicKey(parent_key); + return verified == SECSuccess; +} + +bool NSSCertificate::IsValidChain(const CERTCertList* cert_list) { + CERTCertListNode* child = CERT_LIST_HEAD(cert_list); + for (CERTCertListNode* parent = CERT_LIST_NEXT(child); + !CERT_LIST_END(parent, cert_list); + child = parent, parent = CERT_LIST_NEXT(parent)) { + if (!Certifies(parent->cert, child->cert)) + return false; + } + return true; +} + +bool NSSCertificate::GetDigestLength(const std::string& algorithm, + size_t* length) { + const SECHashObject *ho; + + if (!GetDigestObject(algorithm, &ho)) + return false; + + *length = ho->length; + + return true; +} + +bool NSSCertificate::GetSignatureDigestAlgorithm(std::string* algorithm) const { + // The function sec_DecodeSigAlg in NSS provides this mapping functionality. + // Unfortunately it is private, so the functionality must be duplicated here. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=925165 . + SECOidTag sig_alg = SECOID_GetAlgorithmTag(&certificate_->signature); + switch (sig_alg) { + case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_MD5; + break; + case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION: + case SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE: + case SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE: + case SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE: + case SEC_OID_MISSI_DSS: + case SEC_OID_MISSI_KEA_DSS: + case SEC_OID_MISSI_KEA_DSS_OLD: + case SEC_OID_MISSI_DSS_OLD: + *algorithm = DIGEST_SHA_1; + break; + case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE: + case SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST: + *algorithm = DIGEST_SHA_224; + break; + case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE: + case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST: + *algorithm = DIGEST_SHA_256; + break; + case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE: + case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_SHA_384; + break; + case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE: + case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_SHA_512; + break; + default: + // Unknown algorithm. There are several unhandled options that are less + // common and more complex. + algorithm->clear(); + return false; + } + return true; +} + +bool NSSCertificate::ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + const SECHashObject *ho; + + if (!GetDigestObject(algorithm, &ho)) + return false; + + if (size < ho->length) // Sanity check for fit + return false; + + SECStatus rv = HASH_HashBuf(ho->type, digest, + certificate_->derCert.data, + certificate_->derCert.len); + if (rv != SECSuccess) + return false; + + *length = ho->length; + + return true; +} + +bool NSSCertificate::GetChain(SSLCertChain** chain) const { + if (!chain_) + return false; + + *chain = chain_->Copy(); + return true; +} + +bool NSSCertificate::Equals(const NSSCertificate *tocompare) const { + if (!certificate_->derCert.len) + return false; + if (!tocompare->certificate_->derCert.len) + return false; + + if (certificate_->derCert.len != tocompare->certificate_->derCert.len) + return false; + + return memcmp(certificate_->derCert.data, + tocompare->certificate_->derCert.data, + certificate_->derCert.len) == 0; +} + + +bool NSSCertificate::GetDigestObject(const std::string &algorithm, + const SECHashObject **hop) { + const SECHashObject *ho; + HASH_HashType hash_type; + + if (algorithm == DIGEST_SHA_1) { + hash_type = HASH_AlgSHA1; + // HASH_AlgSHA224 is not supported in the chromium linux build system. +#if 0 + } else if (algorithm == DIGEST_SHA_224) { + hash_type = HASH_AlgSHA224; +#endif + } else if (algorithm == DIGEST_SHA_256) { + hash_type = HASH_AlgSHA256; + } else if (algorithm == DIGEST_SHA_384) { + hash_type = HASH_AlgSHA384; + } else if (algorithm == DIGEST_SHA_512) { + hash_type = HASH_AlgSHA512; + } else { + return false; + } + + ho = HASH_GetHashObject(hash_type); + + ASSERT(ho->length >= 20); // Can't happen + *hop = ho; + + return true; +} + + +NSSIdentity* NSSIdentity::GenerateInternal(const SSLIdentityParams& params) { + std::string subject_name_string = "CN=" + params.common_name; + CERTName *subject_name = CERT_AsciiToName( + const_cast(subject_name_string.c_str())); + NSSIdentity *identity = NULL; + CERTSubjectPublicKeyInfo *spki = NULL; + CERTCertificateRequest *certreq = NULL; + CERTValidity *validity = NULL; + CERTCertificate *certificate = NULL; + NSSKeyPair *keypair = NSSKeyPair::Generate(); + SECItem inner_der; + SECStatus rv; + PLArenaPool* arena; + SECItem signed_cert; + PRTime now = PR_Now(); + PRTime not_before = + now + static_cast(params.not_before) * PR_USEC_PER_SEC; + PRTime not_after = + now + static_cast(params.not_after) * PR_USEC_PER_SEC; + + inner_der.len = 0; + inner_der.data = NULL; + + if (!keypair) { + LOG(LS_ERROR) << "Couldn't generate key pair"; + goto fail; + } + + if (!subject_name) { + LOG(LS_ERROR) << "Couldn't convert subject name " << subject_name; + goto fail; + } + + spki = SECKEY_CreateSubjectPublicKeyInfo(keypair->pubkey()); + if (!spki) { + LOG(LS_ERROR) << "Couldn't create SPKI"; + goto fail; + } + + certreq = CERT_CreateCertificateRequest(subject_name, spki, NULL); + if (!certreq) { + LOG(LS_ERROR) << "Couldn't create certificate signing request"; + goto fail; + } + + validity = CERT_CreateValidity(not_before, not_after); + if (!validity) { + LOG(LS_ERROR) << "Couldn't create validity"; + goto fail; + } + + unsigned long serial; + // Note: This serial in principle could collide, but it's unlikely + rv = PK11_GenerateRandom(reinterpret_cast(&serial), + sizeof(serial)); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't generate random serial"; + goto fail; + } + + certificate = CERT_CreateCertificate(serial, subject_name, validity, certreq); + if (!certificate) { + LOG(LS_ERROR) << "Couldn't create certificate"; + goto fail; + } + + arena = certificate->arena; + + rv = SECOID_SetAlgorithmID(arena, &certificate->signature, + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, NULL); + if (rv != SECSuccess) + goto fail; + + // Set version to X509v3. + *(certificate->version.data) = 2; + certificate->version.len = 1; + + if (!SEC_ASN1EncodeItem(arena, &inner_der, certificate, + SEC_ASN1_GET(CERT_CertificateTemplate))) + goto fail; + + rv = SEC_DerSignData(arena, &signed_cert, inner_der.data, inner_der.len, + keypair->privkey(), + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't sign certificate"; + goto fail; + } + certificate->derCert = signed_cert; + + identity = new NSSIdentity(keypair, new NSSCertificate(certificate)); + + goto done; + + fail: + delete keypair; + + done: + if (certificate) CERT_DestroyCertificate(certificate); + if (subject_name) CERT_DestroyName(subject_name); + if (spki) SECKEY_DestroySubjectPublicKeyInfo(spki); + if (certreq) CERT_DestroyCertificateRequest(certreq); + if (validity) CERT_DestroyValidity(validity); + return identity; +} + +NSSIdentity* NSSIdentity::Generate(const std::string &common_name) { + SSLIdentityParams params; + params.common_name = common_name; + params.not_before = CERTIFICATE_WINDOW; + params.not_after = CERTIFICATE_LIFETIME; + return GenerateInternal(params); +} + +NSSIdentity* NSSIdentity::GenerateForTest(const SSLIdentityParams& params) { + return GenerateInternal(params); +} + +SSLIdentity* NSSIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + std::string private_key_der; + if (!SSLIdentity::PemToDer( + kPemTypeRsaPrivateKey, private_key, &private_key_der)) + return NULL; + + SECItem private_key_item; + private_key_item.data = reinterpret_cast( + const_cast(private_key_der.c_str())); + private_key_item.len = checked_cast(private_key_der.size()); + + const unsigned int key_usage = KU_KEY_ENCIPHERMENT | KU_DATA_ENCIPHERMENT | + KU_DIGITAL_SIGNATURE; + + SECKEYPrivateKey* privkey = NULL; + SECStatus rv = + PK11_ImportDERPrivateKeyInfoAndReturnKey(NSSContext::GetSlot(), + &private_key_item, + NULL, NULL, PR_FALSE, PR_FALSE, + key_usage, &privkey, NULL); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't import private key"; + return NULL; + } + + SECKEYPublicKey *pubkey = SECKEY_ConvertToPublicKey(privkey); + if (rv != SECSuccess) { + SECKEY_DestroyPrivateKey(privkey); + LOG(LS_ERROR) << "Couldn't convert private key to public key"; + return NULL; + } + + // Assign to a scoped_ptr so we don't leak on error. + scoped_ptr keypair(new NSSKeyPair(privkey, pubkey)); + + scoped_ptr cert(NSSCertificate::FromPEMString(certificate)); + if (!cert) { + LOG(LS_ERROR) << "Couldn't parse certificate"; + return NULL; + } + + // TODO(ekr@rtfm.com): Check the public key against the certificate. + + return new NSSIdentity(keypair.release(), cert.release()); +} + +NSSIdentity *NSSIdentity::GetReference() const { + NSSKeyPair *keypair = keypair_->GetReference(); + if (!keypair) + return NULL; + + NSSCertificate *certificate = certificate_->GetReference(); + if (!certificate) { + delete keypair; + return NULL; + } + + return new NSSIdentity(keypair, certificate); +} + + +NSSCertificate &NSSIdentity::certificate() const { + return *certificate_; +} + + +} // rtc namespace + +#endif // HAVE_NSS_SSL_H + diff --git a/webrtc/base/nssidentity.h b/webrtc/base/nssidentity.h new file mode 100644 index 000000000..2c56c002b --- /dev/null +++ b/webrtc/base/nssidentity.h @@ -0,0 +1,130 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NSSIDENTITY_H_ +#define WEBRTC_BASE_NSSIDENTITY_H_ + +#include + +#include "cert.h" +#include "nspr.h" +#include "hasht.h" +#include "keythi.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +class NSSKeyPair { + public: + NSSKeyPair(SECKEYPrivateKey* privkey, SECKEYPublicKey* pubkey) : + privkey_(privkey), pubkey_(pubkey) {} + ~NSSKeyPair(); + + // Generate a 1024-bit RSA key pair. + static NSSKeyPair* Generate(); + NSSKeyPair* GetReference(); + + SECKEYPrivateKey* privkey() const { return privkey_; } + SECKEYPublicKey * pubkey() const { return pubkey_; } + + private: + SECKEYPrivateKey* privkey_; + SECKEYPublicKey* pubkey_; + + DISALLOW_EVIL_CONSTRUCTORS(NSSKeyPair); +}; + + +class NSSCertificate : public SSLCertificate { + public: + static NSSCertificate* FromPEMString(const std::string& pem_string); + // The caller retains ownership of the argument to all the constructors, + // and the constructor makes a copy. + explicit NSSCertificate(CERTCertificate* cert); + explicit NSSCertificate(CERTCertList* cert_list); + virtual ~NSSCertificate() { + if (certificate_) + CERT_DestroyCertificate(certificate_); + } + + virtual NSSCertificate* GetReference() const; + + virtual std::string ToPEMString() const; + + virtual void ToDER(Buffer* der_buffer) const; + + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const; + + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const; + + virtual bool GetChain(SSLCertChain** chain) const; + + CERTCertificate* certificate() { return certificate_; } + + // Performs minimal checks to determine if the list is a valid chain. This + // only checks that each certificate certifies the preceding certificate, + // and ignores many other certificate features such as expiration dates. + static bool IsValidChain(const CERTCertList* cert_list); + + // Helper function to get the length of a digest + static bool GetDigestLength(const std::string& algorithm, size_t* length); + + // Comparison. Only the certificate itself is considered, not the chain. + bool Equals(const NSSCertificate* tocompare) const; + + private: + NSSCertificate(CERTCertificate* cert, SSLCertChain* chain); + static bool GetDigestObject(const std::string& algorithm, + const SECHashObject** hash_object); + + CERTCertificate* certificate_; + scoped_ptr chain_; + + DISALLOW_EVIL_CONSTRUCTORS(NSSCertificate); +}; + +// Represents a SSL key pair and certificate for NSS. +class NSSIdentity : public SSLIdentity { + public: + static NSSIdentity* Generate(const std::string& common_name); + static NSSIdentity* GenerateForTest(const SSLIdentityParams& params); + static SSLIdentity* FromPEMStrings(const std::string& private_key, + const std::string& certificate); + virtual ~NSSIdentity() { + LOG(LS_INFO) << "Destroying NSS identity"; + } + + virtual NSSIdentity* GetReference() const; + virtual NSSCertificate& certificate() const; + + NSSKeyPair* keypair() const { return keypair_.get(); } + + private: + NSSIdentity(NSSKeyPair* keypair, NSSCertificate* cert) : + keypair_(keypair), certificate_(cert) {} + + static NSSIdentity* GenerateInternal(const SSLIdentityParams& params); + + rtc::scoped_ptr keypair_; + rtc::scoped_ptr certificate_; + + DISALLOW_EVIL_CONSTRUCTORS(NSSIdentity); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NSSIDENTITY_H_ diff --git a/webrtc/base/nssstreamadapter.cc b/webrtc/base/nssstreamadapter.cc new file mode 100644 index 000000000..1d06c1c4f --- /dev/null +++ b/webrtc/base/nssstreamadapter.cc @@ -0,0 +1,1020 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_NSS_SSL_H + +#include "webrtc/base/nssstreamadapter.h" + +#include "keyhi.h" +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" +#include "secerr.h" + +#ifdef NSS_SSL_RELATIVE_PATH +#include "ssl.h" +#include "sslerr.h" +#include "sslproto.h" +#else +#include "net/third_party/nss/ssl/ssl.h" +#include "net/third_party/nss/ssl/sslerr.h" +#include "net/third_party/nss/ssl/sslproto.h" +#endif + +#include "webrtc/base/nssidentity.h" +#include "webrtc/base/safe_conversions.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +PRDescIdentity NSSStreamAdapter::nspr_layer_identity = PR_INVALID_IO_LAYER; + +#define UNIMPLEMENTED \ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); \ + LOG(LS_ERROR) \ + << "Call to unimplemented function "<< __FUNCTION__; ASSERT(false) + +#ifdef SRTP_AES128_CM_HMAC_SHA1_80 +#define HAVE_DTLS_SRTP +#endif + +#ifdef HAVE_DTLS_SRTP +// SRTP cipher suite table +struct SrtpCipherMapEntry { + const char* external_name; + PRUint16 cipher_id; +}; + +// This isn't elegant, but it's better than an external reference +static const SrtpCipherMapEntry kSrtpCipherMap[] = { + {"AES_CM_128_HMAC_SHA1_80", SRTP_AES128_CM_HMAC_SHA1_80 }, + {"AES_CM_128_HMAC_SHA1_32", SRTP_AES128_CM_HMAC_SHA1_32 }, + {NULL, 0} +}; +#endif + + +// Implementation of NSPR methods +static PRStatus StreamClose(PRFileDesc *socket) { + ASSERT(!socket->lower); + socket->dtor(socket); + return PR_SUCCESS; +} + +static PRInt32 StreamRead(PRFileDesc *socket, void *buf, PRInt32 length) { + StreamInterface *stream = reinterpret_cast(socket->secret); + size_t read; + int error; + StreamResult result = stream->Read(buf, length, &read, &error); + if (result == SR_SUCCESS) { + return checked_cast(read); + } + + if (result == SR_EOS) { + return 0; + } + + if (result == SR_BLOCK) { + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + PR_SetError(PR_UNKNOWN_ERROR, error); + return -1; +} + +static PRInt32 StreamWrite(PRFileDesc *socket, const void *buf, + PRInt32 length) { + StreamInterface *stream = reinterpret_cast(socket->secret); + size_t written; + int error; + StreamResult result = stream->Write(buf, length, &written, &error); + if (result == SR_SUCCESS) { + return checked_cast(written); + } + + if (result == SR_BLOCK) { + LOG(LS_INFO) << + "NSSStreamAdapter: write to underlying transport would block"; + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + LOG(LS_ERROR) << "Write error"; + PR_SetError(PR_UNKNOWN_ERROR, error); + return -1; +} + +static PRInt32 StreamAvailable(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +PRInt64 StreamAvailable64(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamSync(PRFileDesc *socket) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PROffset32 StreamSeek(PRFileDesc *socket, PROffset32 offset, + PRSeekWhence how) { + UNIMPLEMENTED; + return -1; +} + +static PROffset64 StreamSeek64(PRFileDesc *socket, PROffset64 offset, + PRSeekWhence how) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamFileInfo(PRFileDesc *socket, PRFileInfo *info) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamFileInfo64(PRFileDesc *socket, PRFileInfo64 *info) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRInt32 StreamWritev(PRFileDesc *socket, const PRIOVec *iov, + PRInt32 iov_size, PRIntervalTime timeout) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamConnect(PRFileDesc *socket, const PRNetAddr *addr, + PRIntervalTime timeout) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRFileDesc *StreamAccept(PRFileDesc *sd, PRNetAddr *addr, + PRIntervalTime timeout) { + UNIMPLEMENTED; + return NULL; +} + +static PRStatus StreamBind(PRFileDesc *socket, const PRNetAddr *addr) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamListen(PRFileDesc *socket, PRIntn depth) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamShutdown(PRFileDesc *socket, PRIntn how) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +// Note: this is always nonblocking and ignores the timeout. +// TODO(ekr@rtfm.com): In future verify that the socket is +// actually in non-blocking mode. +// This function does not support peek. +static PRInt32 StreamRecv(PRFileDesc *socket, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime to) { + ASSERT(flags == 0); + + if (flags != 0) { + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return -1; + } + + return StreamRead(socket, buf, amount); +} + +// Note: this is always nonblocking and assumes a zero timeout. +// This function does not support peek. +static PRInt32 StreamSend(PRFileDesc *socket, const void *buf, + PRInt32 amount, PRIntn flags, + PRIntervalTime to) { + ASSERT(flags == 0); + + return StreamWrite(socket, buf, amount); +} + +static PRInt32 StreamRecvfrom(PRFileDesc *socket, void *buf, + PRInt32 amount, PRIntn flags, + PRNetAddr *addr, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamSendto(PRFileDesc *socket, const void *buf, + PRInt32 amount, PRIntn flags, + const PRNetAddr *addr, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRInt16 StreamPoll(PRFileDesc *socket, PRInt16 in_flags, + PRInt16 *out_flags) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamAcceptRead(PRFileDesc *sd, PRFileDesc **nd, + PRNetAddr **raddr, + void *buf, PRInt32 amount, PRIntervalTime t) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamTransmitFile(PRFileDesc *sd, PRFileDesc *socket, + const void *headers, PRInt32 hlen, + PRTransmitFileFlags flags, PRIntervalTime t) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamGetPeerName(PRFileDesc *socket, PRNetAddr *addr) { + // TODO(ekr@rtfm.com): Modify to return unique names for each channel + // somehow, as opposed to always the same static address. The current + // implementation messes up the session cache, which is why it's off + // elsewhere + addr->inet.family = PR_AF_INET; + addr->inet.port = 0; + addr->inet.ip = 0; + + return PR_SUCCESS; +} + +static PRStatus StreamGetSockName(PRFileDesc *socket, PRNetAddr *addr) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamGetSockOption(PRFileDesc *socket, PRSocketOptionData *opt) { + switch (opt->option) { + case PR_SockOpt_Nonblocking: + opt->value.non_blocking = PR_TRUE; + return PR_SUCCESS; + default: + UNIMPLEMENTED; + break; + } + + return PR_FAILURE; +} + +// Imitate setting socket options. These are mostly noops. +static PRStatus StreamSetSockOption(PRFileDesc *socket, + const PRSocketOptionData *opt) { + switch (opt->option) { + case PR_SockOpt_Nonblocking: + return PR_SUCCESS; + case PR_SockOpt_NoDelay: + return PR_SUCCESS; + default: + UNIMPLEMENTED; + break; + } + + return PR_FAILURE; +} + +static PRInt32 StreamSendfile(PRFileDesc *out, PRSendFileData *in, + PRTransmitFileFlags flags, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamConnectContinue(PRFileDesc *socket, PRInt16 flags) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRIntn StreamReserved(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +static const struct PRIOMethods nss_methods = { + PR_DESC_LAYERED, + StreamClose, + StreamRead, + StreamWrite, + StreamAvailable, + StreamAvailable64, + StreamSync, + StreamSeek, + StreamSeek64, + StreamFileInfo, + StreamFileInfo64, + StreamWritev, + StreamConnect, + StreamAccept, + StreamBind, + StreamListen, + StreamShutdown, + StreamRecv, + StreamSend, + StreamRecvfrom, + StreamSendto, + StreamPoll, + StreamAcceptRead, + StreamTransmitFile, + StreamGetSockName, + StreamGetPeerName, + StreamReserved, + StreamReserved, + StreamGetSockOption, + StreamSetSockOption, + StreamSendfile, + StreamConnectContinue, + StreamReserved, + StreamReserved, + StreamReserved, + StreamReserved +}; + +NSSStreamAdapter::NSSStreamAdapter(StreamInterface *stream) + : SSLStreamAdapterHelper(stream), + ssl_fd_(NULL), + cert_ok_(false) { +} + +bool NSSStreamAdapter::Init() { + if (nspr_layer_identity == PR_INVALID_IO_LAYER) { + nspr_layer_identity = PR_GetUniqueIdentity("nssstreamadapter"); + } + PRFileDesc *pr_fd = PR_CreateIOLayerStub(nspr_layer_identity, &nss_methods); + if (!pr_fd) + return false; + pr_fd->secret = reinterpret_cast(stream()); + + PRFileDesc *ssl_fd; + if (ssl_mode_ == SSL_MODE_DTLS) { + ssl_fd = DTLS_ImportFD(NULL, pr_fd); + } else { + ssl_fd = SSL_ImportFD(NULL, pr_fd); + } + ASSERT(ssl_fd != NULL); // This should never happen + if (!ssl_fd) { + PR_Close(pr_fd); + return false; + } + + SECStatus rv; + // Turn on security. + rv = SSL_OptionSet(ssl_fd, SSL_SECURITY, PR_TRUE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error enabling security on SSL Socket"; + return false; + } + + // Disable SSLv2. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SSL2, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling SSL2"; + return false; + } + + // Disable caching. + // TODO(ekr@rtfm.com): restore this when I have the caching + // identity set. + rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling cache"; + return false; + } + + // Disable session tickets. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error enabling tickets"; + return false; + } + + // Disable renegotiation. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION, + SSL_RENEGOTIATE_NEVER); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling renegotiation"; + return false; + } + + // Disable false start. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling false start"; + return false; + } + + ssl_fd_ = ssl_fd; + + return true; +} + +NSSStreamAdapter::~NSSStreamAdapter() { + if (ssl_fd_) + PR_Close(ssl_fd_); +}; + + +int NSSStreamAdapter::BeginSSL() { + SECStatus rv; + + if (!Init()) { + Error("Init", -1, false); + return -1; + } + + ASSERT(state_ == SSL_CONNECTING); + // The underlying stream has been opened. If we are in peer-to-peer mode + // then a peer certificate must have been specified by now. + ASSERT(!ssl_server_name_.empty() || + peer_certificate_.get() != NULL || + !peer_certificate_digest_algorithm_.empty()); + LOG(LS_INFO) << "BeginSSL: " + << (!ssl_server_name_.empty() ? ssl_server_name_ : + "with peer"); + + if (role_ == SSL_CLIENT) { + LOG(LS_INFO) << "BeginSSL: as client"; + + rv = SSL_GetClientAuthDataHook(ssl_fd_, GetClientAuthDataHook, + this); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } else { + LOG(LS_INFO) << "BeginSSL: as server"; + NSSIdentity *identity; + + if (identity_.get()) { + identity = static_cast(identity_.get()); + } else { + LOG(LS_ERROR) << "Can't be an SSL server without an identity"; + Error("BeginSSL", -1, false); + return -1; + } + rv = SSL_ConfigSecureServer(ssl_fd_, identity->certificate().certificate(), + identity->keypair()->privkey(), + kt_rsa); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // Insist on a certificate from the client + rv = SSL_OptionSet(ssl_fd_, SSL_REQUEST_CERTIFICATE, PR_TRUE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + rv = SSL_OptionSet(ssl_fd_, SSL_REQUIRE_CERTIFICATE, PR_TRUE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } + + // Set the version range. + SSLVersionRange vrange; + vrange.min = (ssl_mode_ == SSL_MODE_DTLS) ? + SSL_LIBRARY_VERSION_TLS_1_1 : + SSL_LIBRARY_VERSION_TLS_1_0; + vrange.max = SSL_LIBRARY_VERSION_TLS_1_1; + + rv = SSL_VersionRangeSet(ssl_fd_, &vrange); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // SRTP +#ifdef HAVE_DTLS_SRTP + if (!srtp_ciphers_.empty()) { + rv = SSL_SetSRTPCiphers( + ssl_fd_, &srtp_ciphers_[0], + checked_cast(srtp_ciphers_.size())); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } +#endif + + // Certificate validation + rv = SSL_AuthCertificateHook(ssl_fd_, AuthCertificateHook, this); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // Now start the handshake + rv = SSL_ResetHandshake(ssl_fd_, role_ == SSL_SERVER ? PR_TRUE : PR_FALSE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + return ContinueSSL(); +} + +int NSSStreamAdapter::ContinueSSL() { + LOG(LS_INFO) << "ContinueSSL"; + ASSERT(state_ == SSL_CONNECTING); + + // Clear the DTLS timer + Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT); + + SECStatus rv = SSL_ForceHandshake(ssl_fd_); + + if (rv == SECSuccess) { + LOG(LS_INFO) << "Handshake complete"; + + ASSERT(cert_ok_); + if (!cert_ok_) { + Error("ContinueSSL", -1, true); + return -1; + } + + state_ = SSL_CONNECTED; + StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0); + return 0; + } + + PRInt32 err = PR_GetError(); + switch (err) { + case SSL_ERROR_RX_MALFORMED_HANDSHAKE: + if (ssl_mode_ != SSL_MODE_DTLS) { + Error("ContinueSSL", -1, true); + return -1; + } else { + LOG(LS_INFO) << "Malformed DTLS message. Ignoring."; + // Fall through + } + case PR_WOULD_BLOCK_ERROR: + LOG(LS_INFO) << "Would have blocked"; + if (ssl_mode_ == SSL_MODE_DTLS) { + PRIntervalTime timeout; + + SECStatus rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout); + if (rv == SECSuccess) { + LOG(LS_INFO) << "Timeout is " << timeout << " ms"; + Thread::Current()->PostDelayed(PR_IntervalToMilliseconds(timeout), + this, MSG_DTLS_TIMEOUT, 0); + } + } + + return 0; + default: + LOG(LS_INFO) << "Error " << err; + break; + } + + Error("ContinueSSL", -1, true); + return -1; +} + +void NSSStreamAdapter::Cleanup() { + if (state_ != SSL_ERROR) { + state_ = SSL_CLOSED; + } + + if (ssl_fd_) { + PR_Close(ssl_fd_); + ssl_fd_ = NULL; + } + + identity_.reset(); + peer_certificate_.reset(); + + Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT); +} + +StreamResult NSSStreamAdapter::Read(void* data, size_t data_len, + size_t* read, int* error) { + // SSL_CONNECTED sanity check. + switch (state_) { + case SSL_NONE: + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_CLOSED: + return SR_EOS; + + case SSL_ERROR: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + PRInt32 rv = PR_Read(ssl_fd_, data, checked_cast(data_len)); + + if (rv == 0) { + return SR_EOS; + } + + // Error + if (rv < 0) { + PRInt32 err = PR_GetError(); + + switch (err) { + case PR_WOULD_BLOCK_ERROR: + return SR_BLOCK; + default: + Error("Read", -1, false); + *error = err; // libjingle semantics are that this is impl-specific + return SR_ERROR; + } + } + + // Success + *read = rv; + + return SR_SUCCESS; +} + +StreamResult NSSStreamAdapter::Write(const void* data, size_t data_len, + size_t* written, int* error) { + // SSL_CONNECTED sanity check. + switch (state_) { + case SSL_NONE: + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + case SSL_CLOSED: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + PRInt32 rv = PR_Write(ssl_fd_, data, checked_cast(data_len)); + + // Error + if (rv < 0) { + PRInt32 err = PR_GetError(); + + switch (err) { + case PR_WOULD_BLOCK_ERROR: + return SR_BLOCK; + default: + Error("Write", -1, false); + *error = err; // libjingle semantics are that this is impl-specific + return SR_ERROR; + } + } + + // Success + *written = rv; + + return SR_SUCCESS; +} + +void NSSStreamAdapter::OnEvent(StreamInterface* stream, int events, + int err) { + int events_to_signal = 0; + int signal_error = 0; + ASSERT(stream == this->stream()); + if ((events & SE_OPEN)) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent SE_OPEN"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + events_to_signal |= SE_OPEN; + } else { + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, true); + return; + } + } + } + if ((events & (SE_READ|SE_WRITE))) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent" + << ((events & SE_READ) ? " SE_READ" : "") + << ((events & SE_WRITE) ? " SE_WRITE" : ""); + if (state_ == SSL_NONE) { + events_to_signal |= events & (SE_READ|SE_WRITE); + } else if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err, true); + return; + } + } else if (state_ == SSL_CONNECTED) { + if (events & SE_WRITE) { + LOG(LS_INFO) << " -- onStreamWriteable"; + events_to_signal |= SE_WRITE; + } + if (events & SE_READ) { + LOG(LS_INFO) << " -- onStreamReadable"; + events_to_signal |= SE_READ; + } + } + } + if ((events & SE_CLOSE)) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent(SE_CLOSE, " << err << ")"; + Cleanup(); + events_to_signal |= SE_CLOSE; + // SE_CLOSE is the only event that uses the final parameter to OnEvent(). + ASSERT(signal_error == 0); + signal_error = err; + } + if (events_to_signal) + StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error); +} + +void NSSStreamAdapter::OnMessage(Message* msg) { + // Process our own messages and then pass others to the superclass + if (MSG_DTLS_TIMEOUT == msg->message_id) { + LOG(LS_INFO) << "DTLS timeout expired"; + ContinueSSL(); + } else { + StreamInterface::OnMessage(msg); + } +} + +// Certificate verification callback. Called to check any certificate +SECStatus NSSStreamAdapter::AuthCertificateHook(void *arg, + PRFileDesc *fd, + PRBool checksig, + PRBool isServer) { + LOG(LS_INFO) << "NSSStreamAdapter::AuthCertificateHook"; + // SSL_PeerCertificate returns a pointer that is owned by the caller, and + // the NSSCertificate constructor copies its argument, so |raw_peer_cert| + // must be destroyed in this function. + CERTCertificate* raw_peer_cert = SSL_PeerCertificate(fd); + NSSCertificate peer_cert(raw_peer_cert); + CERT_DestroyCertificate(raw_peer_cert); + + NSSStreamAdapter *stream = reinterpret_cast(arg); + stream->cert_ok_ = false; + + // Read the peer's certificate chain. + CERTCertList* cert_list = SSL_PeerCertificateChain(fd); + ASSERT(cert_list != NULL); + + // If the peer provided multiple certificates, check that they form a valid + // chain as defined by RFC 5246 Section 7.4.2: "Each following certificate + // MUST directly certify the one preceding it.". This check does NOT + // verify other requirements, such as whether the chain reaches a trusted + // root, self-signed certificates have valid signatures, certificates are not + // expired, etc. + // Even if the chain is valid, the leaf certificate must still match a + // provided certificate or digest. + if (!NSSCertificate::IsValidChain(cert_list)) { + CERT_DestroyCertList(cert_list); + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + return SECFailure; + } + + if (stream->peer_certificate_.get()) { + LOG(LS_INFO) << "Checking against specified certificate"; + + // The peer certificate was specified + if (reinterpret_cast(stream->peer_certificate_.get())-> + Equals(&peer_cert)) { + LOG(LS_INFO) << "Accepted peer certificate"; + stream->cert_ok_ = true; + } + } else if (!stream->peer_certificate_digest_algorithm_.empty()) { + LOG(LS_INFO) << "Checking against specified digest"; + // The peer certificate digest was specified + unsigned char digest[64]; // Maximum size + size_t digest_length; + + if (!peer_cert.ComputeDigest( + stream->peer_certificate_digest_algorithm_, + digest, sizeof(digest), &digest_length)) { + LOG(LS_ERROR) << "Digest computation failed"; + } else { + Buffer computed_digest(digest, digest_length); + if (computed_digest == stream->peer_certificate_digest_value_) { + LOG(LS_INFO) << "Accepted peer certificate"; + stream->cert_ok_ = true; + } + } + } else { + // Other modes, but we haven't implemented yet + // TODO(ekr@rtfm.com): Implement real certificate validation + UNIMPLEMENTED; + } + + if (!stream->cert_ok_ && stream->ignore_bad_cert()) { + LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + stream->cert_ok_ = true; + } + + if (stream->cert_ok_) + stream->peer_certificate_.reset(new NSSCertificate(cert_list)); + + CERT_DestroyCertList(cert_list); + + if (stream->cert_ok_) + return SECSuccess; + + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + return SECFailure; +} + + +SECStatus NSSStreamAdapter::GetClientAuthDataHook(void *arg, PRFileDesc *fd, + CERTDistNames *caNames, + CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey) { + LOG(LS_INFO) << "Client cert requested"; + NSSStreamAdapter *stream = reinterpret_cast(arg); + + if (!stream->identity_.get()) { + LOG(LS_ERROR) << "No identity available"; + return SECFailure; + } + + NSSIdentity *identity = static_cast(stream->identity_.get()); + // Destroyed internally by NSS + *pRetCert = CERT_DupCertificate(identity->certificate().certificate()); + *pRetKey = SECKEY_CopyPrivateKey(identity->keypair()->privkey()); + + return SECSuccess; +} + +// RFC 5705 Key Exporter +bool NSSStreamAdapter::ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + SECStatus rv = SSL_ExportKeyingMaterial( + ssl_fd_, + label.c_str(), + checked_cast(label.size()), + use_context, + context, + checked_cast(context_len), + result, + checked_cast(result_len)); + + return rv == SECSuccess; +} + +bool NSSStreamAdapter::SetDtlsSrtpCiphers( + const std::vector& ciphers) { +#ifdef HAVE_DTLS_SRTP + std::vector internal_ciphers; + if (state_ != SSL_NONE) + return false; + + for (std::vector::const_iterator cipher = ciphers.begin(); + cipher != ciphers.end(); ++cipher) { + bool found = false; + for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; entry->cipher_id; + ++entry) { + if (*cipher == entry->external_name) { + found = true; + internal_ciphers.push_back(entry->cipher_id); + break; + } + } + + if (!found) { + LOG(LS_ERROR) << "Could not find cipher: " << *cipher; + return false; + } + } + + if (internal_ciphers.empty()) + return false; + + srtp_ciphers_ = internal_ciphers; + + return true; +#else + return false; +#endif +} + +bool NSSStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) { +#ifdef HAVE_DTLS_SRTP + ASSERT(state_ == SSL_CONNECTED); + if (state_ != SSL_CONNECTED) + return false; + + PRUint16 selected_cipher; + + SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, &selected_cipher); + if (rv == SECFailure) + return false; + + for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; + entry->cipher_id; ++entry) { + if (selected_cipher == entry->cipher_id) { + *cipher = entry->external_name; + return true; + } + } + + ASSERT(false); // This should never happen +#endif + return false; +} + + +bool NSSContext::initialized; +NSSContext *NSSContext::global_nss_context; + +// Static initialization and shutdown +NSSContext *NSSContext::Instance() { + if (!global_nss_context) { + NSSContext *new_ctx = new NSSContext(); + + if (!(new_ctx->slot_ = PK11_GetInternalSlot())) { + delete new_ctx; + goto fail; + } + + global_nss_context = new_ctx; + } + + fail: + return global_nss_context; +} + + + +bool NSSContext::InitializeSSL(VerificationCallback callback) { + ASSERT(!callback); + + if (!initialized) { + SECStatus rv; + + rv = NSS_NoDB_Init(NULL); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't initialize NSS error=" << + PORT_GetError(); + return false; + } + + NSS_SetDomesticPolicy(); + + initialized = true; + } + + return true; +} + +bool NSSContext::InitializeSSLThread() { + // Not needed + return true; +} + +bool NSSContext::CleanupSSL() { + // Not needed + return true; +} + +bool NSSStreamAdapter::HaveDtls() { + return true; +} + +bool NSSStreamAdapter::HaveDtlsSrtp() { +#ifdef HAVE_DTLS_SRTP + return true; +#else + return false; +#endif +} + +bool NSSStreamAdapter::HaveExporter() { + return true; +} + +} // namespace rtc + +#endif // HAVE_NSS_SSL_H diff --git a/webrtc/base/nssstreamadapter.h b/webrtc/base/nssstreamadapter.h new file mode 100644 index 000000000..210a47933 --- /dev/null +++ b/webrtc/base/nssstreamadapter.h @@ -0,0 +1,111 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NSSSTREAMADAPTER_H_ +#define WEBRTC_BASE_NSSSTREAMADAPTER_H_ + +#include +#include + +#include "nspr.h" +#include "nss.h" +#include "secmodt.h" + +#include "webrtc/base/buffer.h" +#include "webrtc/base/nssidentity.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/sslstreamadapterhelper.h" + +namespace rtc { + +// Singleton +class NSSContext { + public: + NSSContext() {} + ~NSSContext() { + } + + static PK11SlotInfo *GetSlot() { + return Instance() ? Instance()->slot_: NULL; + } + + static NSSContext *Instance(); + static bool InitializeSSL(VerificationCallback callback); + static bool InitializeSSLThread(); + static bool CleanupSSL(); + + private: + PK11SlotInfo *slot_; // The PKCS-11 slot + static bool initialized; // Was this initialized? + static NSSContext *global_nss_context; // The global context +}; + + +class NSSStreamAdapter : public SSLStreamAdapterHelper { + public: + explicit NSSStreamAdapter(StreamInterface* stream); + virtual ~NSSStreamAdapter(); + bool Init(); + + virtual StreamResult Read(void* data, size_t data_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + void OnMessage(Message *msg); + + // Key Extractor interface + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len); + + // DTLS-SRTP interface + virtual bool SetDtlsSrtpCiphers(const std::vector& ciphers); + virtual bool GetDtlsSrtpCipher(std::string* cipher); + + // Capabilities interfaces + static bool HaveDtls(); + static bool HaveDtlsSrtp(); + static bool HaveExporter(); + + protected: + // Override SSLStreamAdapter + virtual void OnEvent(StreamInterface* stream, int events, int err); + + // Override SSLStreamAdapterHelper + virtual int BeginSSL(); + virtual void Cleanup(); + virtual bool GetDigestLength(const std::string& algorithm, size_t* length) { + return NSSCertificate::GetDigestLength(algorithm, length); + } + + private: + int ContinueSSL(); + static SECStatus AuthCertificateHook(void *arg, PRFileDesc *fd, + PRBool checksig, PRBool isServer); + static SECStatus GetClientAuthDataHook(void *arg, PRFileDesc *fd, + CERTDistNames *caNames, + CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey); + + PRFileDesc *ssl_fd_; // NSS's SSL file descriptor + static bool initialized; // Was InitializeSSL() called? + bool cert_ok_; // Did we get and check a cert + std::vector srtp_ciphers_; // SRTP cipher list + + static PRDescIdentity nspr_layer_identity; // The NSPR layer identity +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NSSSTREAMADAPTER_H_ diff --git a/webrtc/base/nullsocketserver.h b/webrtc/base/nullsocketserver.h new file mode 100644 index 000000000..5378e4315 --- /dev/null +++ b/webrtc/base/nullsocketserver.h @@ -0,0 +1,61 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NULLSOCKETSERVER_H_ +#define WEBRTC_BASE_NULLSOCKETSERVER_H_ + +#include "webrtc/base/event.h" +#include "webrtc/base/physicalsocketserver.h" + +namespace rtc { + +// NullSocketServer + +class NullSocketServer : public rtc::SocketServer { + public: + NullSocketServer() : event_(false, false) {} + + virtual bool Wait(int cms, bool process_io) { + event_.Wait(cms); + return true; + } + + virtual void WakeUp() { + event_.Set(); + } + + virtual rtc::Socket* CreateSocket(int type) { + ASSERT(false); + return NULL; + } + + virtual rtc::Socket* CreateSocket(int family, int type) { + ASSERT(false); + return NULL; + } + + virtual rtc::AsyncSocket* CreateAsyncSocket(int type) { + ASSERT(false); + return NULL; + } + + virtual rtc::AsyncSocket* CreateAsyncSocket(int family, int type) { + ASSERT(false); + return NULL; + } + + + private: + rtc::Event event_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NULLSOCKETSERVER_H_ diff --git a/webrtc/base/nullsocketserver_unittest.cc b/webrtc/base/nullsocketserver_unittest.cc new file mode 100644 index 000000000..fe21f6ad0 --- /dev/null +++ b/webrtc/base/nullsocketserver_unittest.cc @@ -0,0 +1,47 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/nullsocketserver.h" + +namespace rtc { + +static const uint32 kTimeout = 5000U; + +class NullSocketServerTest + : public testing::Test, + public MessageHandler { + public: + NullSocketServerTest() {} + protected: + virtual void OnMessage(Message* message) { + ss_.WakeUp(); + } + NullSocketServer ss_; +}; + +TEST_F(NullSocketServerTest, WaitAndSet) { + Thread thread; + EXPECT_TRUE(thread.Start()); + thread.Post(this, 0); + // The process_io will be ignored. + const bool process_io = true; + EXPECT_TRUE_WAIT(ss_.Wait(rtc::kForever, process_io), kTimeout); +} + +TEST_F(NullSocketServerTest, TestWait) { + uint32 start = Time(); + ss_.Wait(200, true); + // The actual wait time is dependent on the resolution of the timer used by + // the Event class. Allow for the event to signal ~20ms early. + EXPECT_GE(TimeSince(start), 180); +} + +} // namespace rtc diff --git a/webrtc/base/openssl.h b/webrtc/base/openssl.h new file mode 100644 index 000000000..2071619d5 --- /dev/null +++ b/webrtc/base/openssl.h @@ -0,0 +1,20 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_OPENSSL_H_ +#define WEBRTC_BASE_OPENSSL_H_ + +#include + +#if (OPENSSL_VERSION_NUMBER < 0x10000000L) +#error OpenSSL is older than 1.0.0, which is the minimum supported version. +#endif + +#endif // WEBRTC_BASE_OPENSSL_H_ diff --git a/webrtc/base/openssladapter.cc b/webrtc/base/openssladapter.cc new file mode 100644 index 000000000..d0311100e --- /dev/null +++ b/webrtc/base/openssladapter.cc @@ -0,0 +1,884 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/openssladapter.h" + +#if defined(WEBRTC_POSIX) +#include +#endif + +// Must be included first before openssl headers. +#include "webrtc/base/win32.h" // NOLINT + +#include +#include +#include +#include +#include +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/openssl.h" +#include "webrtc/base/sslroots.h" +#include "webrtc/base/stringutils.h" + +// TODO: Use a nicer abstraction for mutex. + +#if defined(WEBRTC_WIN) + #define MUTEX_TYPE HANDLE + #define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL) + #define MUTEX_CLEANUP(x) CloseHandle(x) + #define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE) + #define MUTEX_UNLOCK(x) ReleaseMutex(x) + #define THREAD_ID GetCurrentThreadId() +#elif defined(WEBRTC_POSIX) + #define MUTEX_TYPE pthread_mutex_t + #define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL) + #define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x)) + #define MUTEX_LOCK(x) pthread_mutex_lock(&(x)) + #define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x)) + #define THREAD_ID pthread_self() +#else + #error You must define mutex operations appropriate for your platform! +#endif + +struct CRYPTO_dynlock_value { + MUTEX_TYPE mutex; +}; + +////////////////////////////////////////////////////////////////////// +// SocketBIO +////////////////////////////////////////////////////////////////////// + +static int socket_write(BIO* h, const char* buf, int num); +static int socket_read(BIO* h, char* buf, int size); +static int socket_puts(BIO* h, const char* str); +static long socket_ctrl(BIO* h, int cmd, long arg1, void* arg2); +static int socket_new(BIO* h); +static int socket_free(BIO* data); + +static BIO_METHOD methods_socket = { + BIO_TYPE_BIO, + "socket", + socket_write, + socket_read, + socket_puts, + 0, + socket_ctrl, + socket_new, + socket_free, + NULL, +}; + +BIO_METHOD* BIO_s_socket2() { return(&methods_socket); } + +BIO* BIO_new_socket(rtc::AsyncSocket* socket) { + BIO* ret = BIO_new(BIO_s_socket2()); + if (ret == NULL) { + return NULL; + } + ret->ptr = socket; + return ret; +} + +static int socket_new(BIO* b) { + b->shutdown = 0; + b->init = 1; + b->num = 0; // 1 means socket closed + b->ptr = 0; + return 1; +} + +static int socket_free(BIO* b) { + if (b == NULL) + return 0; + return 1; +} + +static int socket_read(BIO* b, char* out, int outl) { + if (!out) + return -1; + rtc::AsyncSocket* socket = static_cast(b->ptr); + BIO_clear_retry_flags(b); + int result = socket->Recv(out, outl); + if (result > 0) { + return result; + } else if (result == 0) { + b->num = 1; + } else if (socket->IsBlocking()) { + BIO_set_retry_read(b); + } + return -1; +} + +static int socket_write(BIO* b, const char* in, int inl) { + if (!in) + return -1; + rtc::AsyncSocket* socket = static_cast(b->ptr); + BIO_clear_retry_flags(b); + int result = socket->Send(in, inl); + if (result > 0) { + return result; + } else if (socket->IsBlocking()) { + BIO_set_retry_write(b); + } + return -1; +} + +static int socket_puts(BIO* b, const char* str) { + return socket_write(b, str, strlen(str)); +} + +static long socket_ctrl(BIO* b, int cmd, long num, void* ptr) { + UNUSED(num); + UNUSED(ptr); + + switch (cmd) { + case BIO_CTRL_RESET: + return 0; + case BIO_CTRL_EOF: + return b->num; + case BIO_CTRL_WPENDING: + case BIO_CTRL_PENDING: + return 0; + case BIO_CTRL_FLUSH: + return 1; + default: + return 0; + } +} + +///////////////////////////////////////////////////////////////////////////// +// OpenSSLAdapter +///////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +// This array will store all of the mutexes available to OpenSSL. +static MUTEX_TYPE* mutex_buf = NULL; + +static void locking_function(int mode, int n, const char * file, int line) { + if (mode & CRYPTO_LOCK) { + MUTEX_LOCK(mutex_buf[n]); + } else { + MUTEX_UNLOCK(mutex_buf[n]); + } +} + +static unsigned long id_function() { // NOLINT + // Use old-style C cast because THREAD_ID's type varies with the platform, + // in some cases requiring static_cast, and in others requiring + // reinterpret_cast. + return (unsigned long)THREAD_ID; // NOLINT +} + +static CRYPTO_dynlock_value* dyn_create_function(const char* file, int line) { + CRYPTO_dynlock_value* value = new CRYPTO_dynlock_value; + if (!value) + return NULL; + MUTEX_SETUP(value->mutex); + return value; +} + +static void dyn_lock_function(int mode, CRYPTO_dynlock_value* l, + const char* file, int line) { + if (mode & CRYPTO_LOCK) { + MUTEX_LOCK(l->mutex); + } else { + MUTEX_UNLOCK(l->mutex); + } +} + +static void dyn_destroy_function(CRYPTO_dynlock_value* l, + const char* file, int line) { + MUTEX_CLEANUP(l->mutex); + delete l; +} + +VerificationCallback OpenSSLAdapter::custom_verify_callback_ = NULL; + +bool OpenSSLAdapter::InitializeSSL(VerificationCallback callback) { + if (!InitializeSSLThread() || !SSL_library_init()) + return false; +#if !defined(ADDRESS_SANITIZER) || !defined(WEBRTC_MAC) || defined(WEBRTC_IOS) + // Loading the error strings crashes mac_asan. Omit this debugging aid there. + SSL_load_error_strings(); +#endif + ERR_load_BIO_strings(); + OpenSSL_add_all_algorithms(); + RAND_poll(); + custom_verify_callback_ = callback; + return true; +} + +bool OpenSSLAdapter::InitializeSSLThread() { + mutex_buf = new MUTEX_TYPE[CRYPTO_num_locks()]; + if (!mutex_buf) + return false; + for (int i = 0; i < CRYPTO_num_locks(); ++i) + MUTEX_SETUP(mutex_buf[i]); + + // we need to cast our id_function to return an unsigned long -- pthread_t is + // a pointer + CRYPTO_set_id_callback(id_function); + CRYPTO_set_locking_callback(locking_function); + CRYPTO_set_dynlock_create_callback(dyn_create_function); + CRYPTO_set_dynlock_lock_callback(dyn_lock_function); + CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); + return true; +} + +bool OpenSSLAdapter::CleanupSSL() { + if (!mutex_buf) + return false; + CRYPTO_set_id_callback(NULL); + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_dynlock_create_callback(NULL); + CRYPTO_set_dynlock_lock_callback(NULL); + CRYPTO_set_dynlock_destroy_callback(NULL); + for (int i = 0; i < CRYPTO_num_locks(); ++i) + MUTEX_CLEANUP(mutex_buf[i]); + delete [] mutex_buf; + mutex_buf = NULL; + return true; +} + +OpenSSLAdapter::OpenSSLAdapter(AsyncSocket* socket) + : SSLAdapter(socket), + state_(SSL_NONE), + ssl_read_needs_write_(false), + ssl_write_needs_read_(false), + restartable_(false), + ssl_(NULL), ssl_ctx_(NULL), + custom_verification_succeeded_(false) { +} + +OpenSSLAdapter::~OpenSSLAdapter() { + Cleanup(); +} + +int +OpenSSLAdapter::StartSSL(const char* hostname, bool restartable) { + if (state_ != SSL_NONE) + return -1; + + ssl_host_name_ = hostname; + restartable_ = restartable; + + if (socket_->GetState() != Socket::CS_CONNECTED) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +int +OpenSSLAdapter::BeginSSL() { + LOG(LS_INFO) << "BeginSSL: " << ssl_host_name_; + ASSERT(state_ == SSL_CONNECTING); + + int err = 0; + BIO* bio = NULL; + + // First set up the context + if (!ssl_ctx_) + ssl_ctx_ = SetupSSLContext(); + + if (!ssl_ctx_) { + err = -1; + goto ssl_error; + } + + bio = BIO_new_socket(static_cast(socket_)); + if (!bio) { + err = -1; + goto ssl_error; + } + + ssl_ = SSL_new(ssl_ctx_); + if (!ssl_) { + err = -1; + goto ssl_error; + } + + SSL_set_app_data(ssl_, this); + + SSL_set_bio(ssl_, bio, bio); + SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + // the SSL object owns the bio now + bio = NULL; + + // Do the connect + err = ContinueSSL(); + if (err != 0) + goto ssl_error; + + return err; + +ssl_error: + Cleanup(); + if (bio) + BIO_free(bio); + + return err; +} + +int +OpenSSLAdapter::ContinueSSL() { + ASSERT(state_ == SSL_CONNECTING); + + int code = SSL_connect(ssl_); + switch (SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + if (!SSLPostConnectionCheck(ssl_, ssl_host_name_.c_str())) { + LOG(LS_ERROR) << "TLS post connection check failed"; + // make sure we close the socket + Cleanup(); + // The connect failed so return -1 to shut down the socket + return -1; + } + + state_ = SSL_CONNECTED; + AsyncSocketAdapter::OnConnectEvent(this); +#if 0 // TODO: worry about this + // Don't let ourselves go away during the callbacks + PRefPtr lock(this); + LOG(LS_INFO) << " -- onStreamReadable"; + AsyncSocketAdapter::OnReadEvent(this); + LOG(LS_INFO) << " -- onStreamWriteable"; + AsyncSocketAdapter::OnWriteEvent(this); +#endif + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + + case SSL_ERROR_ZERO_RETURN: + default: + LOG(LS_WARNING) << "ContinueSSL -- error " << code; + return (code != 0) ? code : -1; + } + + return 0; +} + +void +OpenSSLAdapter::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "OpenSSLAdapter::Error(" + << context << ", " << err << ")"; + state_ = SSL_ERROR; + SetError(err); + if (signal) + AsyncSocketAdapter::OnCloseEvent(this, err); +} + +void +OpenSSLAdapter::Cleanup() { + LOG(LS_INFO) << "Cleanup"; + + state_ = SSL_NONE; + ssl_read_needs_write_ = false; + ssl_write_needs_read_ = false; + custom_verification_succeeded_ = false; + + if (ssl_) { + SSL_free(ssl_); + ssl_ = NULL; + } + + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + ssl_ctx_ = NULL; + } +} + +// +// AsyncSocket Implementation +// + +int +OpenSSLAdapter::Send(const void* pv, size_t cb) { + //LOG(LS_INFO) << "OpenSSLAdapter::Send(" << cb << ")"; + + switch (state_) { + case SSL_NONE: + return AsyncSocketAdapter::Send(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + // OpenSSL will return an error if we try to write zero bytes + if (cb == 0) + return 0; + + ssl_write_needs_read_ = false; + + int code = SSL_write(ssl_, pv, cb); + switch (SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + //LOG(LS_INFO) << " -- success"; + return code; + case SSL_ERROR_WANT_READ: + //LOG(LS_INFO) << " -- error want read"; + ssl_write_needs_read_ = true; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_WANT_WRITE: + //LOG(LS_INFO) << " -- error want write"; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_ZERO_RETURN: + //LOG(LS_INFO) << " -- remote side closed"; + SetError(EWOULDBLOCK); + // do we need to signal closure? + break; + default: + //LOG(LS_INFO) << " -- error " << code; + Error("SSL_write", (code ? code : -1), false); + break; + } + + return SOCKET_ERROR; +} + +int +OpenSSLAdapter::Recv(void* pv, size_t cb) { + //LOG(LS_INFO) << "OpenSSLAdapter::Recv(" << cb << ")"; + switch (state_) { + + case SSL_NONE: + return AsyncSocketAdapter::Recv(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + // Don't trust OpenSSL with zero byte reads + if (cb == 0) + return 0; + + ssl_read_needs_write_ = false; + + int code = SSL_read(ssl_, pv, cb); + switch (SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + //LOG(LS_INFO) << " -- success"; + return code; + case SSL_ERROR_WANT_READ: + //LOG(LS_INFO) << " -- error want read"; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_WANT_WRITE: + //LOG(LS_INFO) << " -- error want write"; + ssl_read_needs_write_ = true; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_ZERO_RETURN: + //LOG(LS_INFO) << " -- remote side closed"; + SetError(EWOULDBLOCK); + // do we need to signal closure? + break; + default: + //LOG(LS_INFO) << " -- error " << code; + Error("SSL_read", (code ? code : -1), false); + break; + } + + return SOCKET_ERROR; +} + +int +OpenSSLAdapter::Close() { + Cleanup(); + state_ = restartable_ ? SSL_WAIT : SSL_NONE; + return AsyncSocketAdapter::Close(); +} + +Socket::ConnState +OpenSSLAdapter::GetState() const { + //if (signal_close_) + // return CS_CONNECTED; + ConnState state = socket_->GetState(); + if ((state == CS_CONNECTED) + && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING))) + state = CS_CONNECTING; + return state; +} + +void +OpenSSLAdapter::OnConnectEvent(AsyncSocket* socket) { + LOG(LS_INFO) << "OpenSSLAdapter::OnConnectEvent"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + AsyncSocketAdapter::OnConnectEvent(socket); + return; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + AsyncSocketAdapter::OnCloseEvent(socket, err); + } +} + +void +OpenSSLAdapter::OnReadEvent(AsyncSocket* socket) { + //LOG(LS_INFO) << "OpenSSLAdapter::OnReadEvent"; + + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err); + } + return; + } + + if (state_ != SSL_CONNECTED) + return; + + // Don't let ourselves go away during the callbacks + //PRefPtr lock(this); // TODO: fix this + if (ssl_write_needs_read_) { + //LOG(LS_INFO) << " -- onStreamWriteable"; + AsyncSocketAdapter::OnWriteEvent(socket); + } + + //LOG(LS_INFO) << " -- onStreamReadable"; + AsyncSocketAdapter::OnReadEvent(socket); +} + +void +OpenSSLAdapter::OnWriteEvent(AsyncSocket* socket) { + //LOG(LS_INFO) << "OpenSSLAdapter::OnWriteEvent"; + + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnWriteEvent(socket); + return; + } + + if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err); + } + return; + } + + if (state_ != SSL_CONNECTED) + return; + + // Don't let ourselves go away during the callbacks + //PRefPtr lock(this); // TODO: fix this + + if (ssl_read_needs_write_) { + //LOG(LS_INFO) << " -- onStreamReadable"; + AsyncSocketAdapter::OnReadEvent(socket); + } + + //LOG(LS_INFO) << " -- onStreamWriteable"; + AsyncSocketAdapter::OnWriteEvent(socket); +} + +void +OpenSSLAdapter::OnCloseEvent(AsyncSocket* socket, int err) { + LOG(LS_INFO) << "OpenSSLAdapter::OnCloseEvent(" << err << ")"; + AsyncSocketAdapter::OnCloseEvent(socket, err); +} + +// This code is taken from the "Network Security with OpenSSL" +// sample in chapter 5 + +bool OpenSSLAdapter::VerifyServerName(SSL* ssl, const char* host, + bool ignore_bad_cert) { + if (!host) + return false; + + // Checking the return from SSL_get_peer_certificate here is not strictly + // necessary. With our setup, it is not possible for it to return + // NULL. However, it is good form to check the return. + X509* certificate = SSL_get_peer_certificate(ssl); + if (!certificate) + return false; + + // Logging certificates is extremely verbose. So it is disabled by default. +#ifdef LOG_CERTIFICATES + { + LOG(LS_INFO) << "Certificate from server:"; + BIO* mem = BIO_new(BIO_s_mem()); + X509_print_ex(mem, certificate, XN_FLAG_SEP_CPLUS_SPC, X509_FLAG_NO_HEADER); + BIO_write(mem, "\0", 1); + char* buffer; + BIO_get_mem_data(mem, &buffer); + LOG(LS_INFO) << buffer; + BIO_free(mem); + + char* cipher_description = + SSL_CIPHER_description(SSL_get_current_cipher(ssl), NULL, 128); + LOG(LS_INFO) << "Cipher: " << cipher_description; + OPENSSL_free(cipher_description); + } +#endif + + bool ok = false; + int extension_count = X509_get_ext_count(certificate); + for (int i = 0; i < extension_count; ++i) { + X509_EXTENSION* extension = X509_get_ext(certificate, i); + int extension_nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension)); + + if (extension_nid == NID_subject_alt_name) { + const X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension); + if (!meth) + break; + + void* ext_str = NULL; + + // We assign this to a local variable, instead of passing the address + // directly to ASN1_item_d2i. + // See http://readlist.com/lists/openssl.org/openssl-users/0/4761.html. + unsigned char* ext_value_data = extension->value->data; + + const unsigned char **ext_value_data_ptr = + (const_cast(&ext_value_data)); + + if (meth->it) { + ext_str = ASN1_item_d2i(NULL, ext_value_data_ptr, + extension->value->length, + ASN1_ITEM_ptr(meth->it)); + } else { + ext_str = meth->d2i(NULL, ext_value_data_ptr, extension->value->length); + } + + STACK_OF(CONF_VALUE)* value = meth->i2v(meth, ext_str, NULL); + for (int j = 0; j < sk_CONF_VALUE_num(value); ++j) { + CONF_VALUE* nval = sk_CONF_VALUE_value(value, j); + // The value for nval can contain wildcards + if (!strcmp(nval->name, "DNS") && string_match(host, nval->value)) { + ok = true; + break; + } + } + sk_CONF_VALUE_pop_free(value, X509V3_conf_free); + value = NULL; + + if (meth->it) { + ASN1_item_free(reinterpret_cast(ext_str), + ASN1_ITEM_ptr(meth->it)); + } else { + meth->ext_free(ext_str); + } + ext_str = NULL; + } + if (ok) + break; + } + + char data[256]; + X509_name_st* subject; + if (!ok + && ((subject = X509_get_subject_name(certificate)) != NULL) + && (X509_NAME_get_text_by_NID(subject, NID_commonName, + data, sizeof(data)) > 0)) { + data[sizeof(data)-1] = 0; + if (_stricmp(data, host) == 0) + ok = true; + } + + X509_free(certificate); + + // This should only ever be turned on for debugging and development. + if (!ok && ignore_bad_cert) { + LOG(LS_WARNING) << "TLS certificate check FAILED. " + << "Allowing connection anyway."; + ok = true; + } + + return ok; +} + +bool OpenSSLAdapter::SSLPostConnectionCheck(SSL* ssl, const char* host) { + bool ok = VerifyServerName(ssl, host, ignore_bad_cert()); + + if (ok) { + ok = (SSL_get_verify_result(ssl) == X509_V_OK || + custom_verification_succeeded_); + } + + if (!ok && ignore_bad_cert()) { + LOG(LS_INFO) << "Other TLS post connection checks failed."; + ok = true; + } + + return ok; +} + +#if _DEBUG + +// We only use this for tracing and so it is only needed in debug mode + +void +OpenSSLAdapter::SSLInfoCallback(const SSL* s, int where, int ret) { + const char* str = "undefined"; + int w = where & ~SSL_ST_MASK; + if (w & SSL_ST_CONNECT) { + str = "SSL_connect"; + } else if (w & SSL_ST_ACCEPT) { + str = "SSL_accept"; + } + if (where & SSL_CB_LOOP) { + LOG(LS_INFO) << str << ":" << SSL_state_string_long(s); + } else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + LOG(LS_INFO) << "SSL3 alert " << str + << ":" << SSL_alert_type_string_long(ret) + << ":" << SSL_alert_desc_string_long(ret); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) { + LOG(LS_INFO) << str << ":failed in " << SSL_state_string_long(s); + } else if (ret < 0) { + LOG(LS_INFO) << str << ":error in " << SSL_state_string_long(s); + } + } +} + +#endif // _DEBUG + +int +OpenSSLAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { +#if _DEBUG + if (!ok) { + char data[256]; + X509* cert = X509_STORE_CTX_get_current_cert(store); + int depth = X509_STORE_CTX_get_error_depth(store); + int err = X509_STORE_CTX_get_error(store); + + LOG(LS_INFO) << "Error with certificate at depth: " << depth; + X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data)); + LOG(LS_INFO) << " issuer = " << data; + X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data)); + LOG(LS_INFO) << " subject = " << data; + LOG(LS_INFO) << " err = " << err + << ":" << X509_verify_cert_error_string(err); + } +#endif + + // Get our stream pointer from the store + SSL* ssl = reinterpret_cast( + X509_STORE_CTX_get_ex_data(store, + SSL_get_ex_data_X509_STORE_CTX_idx())); + + OpenSSLAdapter* stream = + reinterpret_cast(SSL_get_app_data(ssl)); + + if (!ok && custom_verify_callback_) { + void* cert = + reinterpret_cast(X509_STORE_CTX_get_current_cert(store)); + if (custom_verify_callback_(cert)) { + stream->custom_verification_succeeded_ = true; + LOG(LS_INFO) << "validated certificate using custom callback"; + ok = true; + } + } + + // Should only be used for debugging and development. + if (!ok && stream->ignore_bad_cert()) { + LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + ok = 1; + } + + return ok; +} + +bool OpenSSLAdapter::ConfigureTrustedRootCertificates(SSL_CTX* ctx) { + // Add the root cert that we care about to the SSL context + int count_of_added_certs = 0; + for (int i = 0; i < ARRAY_SIZE(kSSLCertCertificateList); i++) { + const unsigned char* cert_buffer = kSSLCertCertificateList[i]; + size_t cert_buffer_len = kSSLCertCertificateSizeList[i]; + X509* cert = d2i_X509(NULL, &cert_buffer, cert_buffer_len); + if (cert) { + int return_value = X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert); + if (return_value == 0) { + LOG(LS_WARNING) << "Unable to add certificate."; + } else { + count_of_added_certs++; + } + X509_free(cert); + } + } + return count_of_added_certs > 0; +} + +SSL_CTX* +OpenSSLAdapter::SetupSSLContext() { + SSL_CTX* ctx = SSL_CTX_new(TLSv1_client_method()); + if (ctx == NULL) { + unsigned long error = ERR_get_error(); // NOLINT: type used by OpenSSL. + LOG(LS_WARNING) << "SSL_CTX creation failed: " + << '"' << ERR_reason_error_string(error) << "\" " + << "(error=" << error << ')'; + return NULL; + } + if (!ConfigureTrustedRootCertificates(ctx)) { + SSL_CTX_free(ctx); + return NULL; + } + +#ifdef _DEBUG + SSL_CTX_set_info_callback(ctx, SSLInfoCallback); +#endif + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback); + SSL_CTX_set_verify_depth(ctx, 4); + SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + + return ctx; +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H diff --git a/webrtc/base/openssladapter.h b/webrtc/base/openssladapter.h new file mode 100644 index 000000000..d244a7f5c --- /dev/null +++ b/webrtc/base/openssladapter.h @@ -0,0 +1,88 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_OPENSSLADAPTER_H__ +#define WEBRTC_BASE_OPENSSLADAPTER_H__ + +#include +#include "webrtc/base/ssladapter.h" + +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct x509_store_ctx_st X509_STORE_CTX; + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class OpenSSLAdapter : public SSLAdapter { +public: + static bool InitializeSSL(VerificationCallback callback); + static bool InitializeSSLThread(); + static bool CleanupSSL(); + + OpenSSLAdapter(AsyncSocket* socket); + virtual ~OpenSSLAdapter(); + + virtual int StartSSL(const char* hostname, bool restartable); + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + virtual int Close(); + + // Note that the socket returns ST_CONNECTING while SSL is being negotiated. + virtual ConnState GetState() const; + +protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void OnReadEvent(AsyncSocket* socket); + virtual void OnWriteEvent(AsyncSocket* socket); + virtual void OnCloseEvent(AsyncSocket* socket, int err); + +private: + enum SSLState { + SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR + }; + + int BeginSSL(); + int ContinueSSL(); + void Error(const char* context, int err, bool signal = true); + void Cleanup(); + + static bool VerifyServerName(SSL* ssl, const char* host, + bool ignore_bad_cert); + bool SSLPostConnectionCheck(SSL* ssl, const char* host); +#if _DEBUG + static void SSLInfoCallback(const SSL* s, int where, int ret); +#endif // !_DEBUG + static int SSLVerifyCallback(int ok, X509_STORE_CTX* store); + static VerificationCallback custom_verify_callback_; + friend class OpenSSLStreamAdapter; // for custom_verify_callback_; + + static bool ConfigureTrustedRootCertificates(SSL_CTX* ctx); + static SSL_CTX* SetupSSLContext(); + + SSLState state_; + bool ssl_read_needs_write_; + bool ssl_write_needs_read_; + // If true, socket will retain SSL configuration after Close. + bool restartable_; + + SSL* ssl_; + SSL_CTX* ssl_ctx_; + std::string ssl_host_name_; + + bool custom_verification_succeeded_; +}; + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLADAPTER_H__ diff --git a/webrtc/base/openssldigest.cc b/webrtc/base/openssldigest.cc new file mode 100644 index 000000000..0d22f4329 --- /dev/null +++ b/webrtc/base/openssldigest.cc @@ -0,0 +1,122 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/openssldigest.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/openssl.h" + +namespace rtc { + +OpenSSLDigest::OpenSSLDigest(const std::string& algorithm) { + EVP_MD_CTX_init(&ctx_); + if (GetDigestEVP(algorithm, &md_)) { + EVP_DigestInit_ex(&ctx_, md_, NULL); + } else { + md_ = NULL; + } +} + +OpenSSLDigest::~OpenSSLDigest() { + EVP_MD_CTX_cleanup(&ctx_); +} + +size_t OpenSSLDigest::Size() const { + if (!md_) { + return 0; + } + return EVP_MD_size(md_); +} + +void OpenSSLDigest::Update(const void* buf, size_t len) { + if (!md_) { + return; + } + EVP_DigestUpdate(&ctx_, buf, len); +} + +size_t OpenSSLDigest::Finish(void* buf, size_t len) { + if (!md_ || len < Size()) { + return 0; + } + unsigned int md_len; + EVP_DigestFinal_ex(&ctx_, static_cast(buf), &md_len); + EVP_DigestInit_ex(&ctx_, md_, NULL); // prepare for future Update()s + ASSERT(md_len == Size()); + return md_len; +} + +bool OpenSSLDigest::GetDigestEVP(const std::string& algorithm, + const EVP_MD** mdp) { + const EVP_MD* md; + if (algorithm == DIGEST_MD5) { + md = EVP_md5(); + } else if (algorithm == DIGEST_SHA_1) { + md = EVP_sha1(); + } else if (algorithm == DIGEST_SHA_224) { + md = EVP_sha224(); + } else if (algorithm == DIGEST_SHA_256) { + md = EVP_sha256(); + } else if (algorithm == DIGEST_SHA_384) { + md = EVP_sha384(); + } else if (algorithm == DIGEST_SHA_512) { + md = EVP_sha512(); + } else { + return false; + } + + // Can't happen + ASSERT(EVP_MD_size(md) >= 16); + *mdp = md; + return true; +} + +bool OpenSSLDigest::GetDigestName(const EVP_MD* md, + std::string* algorithm) { + ASSERT(md != NULL); + ASSERT(algorithm != NULL); + + int md_type = EVP_MD_type(md); + if (md_type == NID_md5) { + *algorithm = DIGEST_MD5; + } else if (md_type == NID_sha1) { + *algorithm = DIGEST_SHA_1; + } else if (md_type == NID_sha224) { + *algorithm = DIGEST_SHA_224; + } else if (md_type == NID_sha256) { + *algorithm = DIGEST_SHA_256; + } else if (md_type == NID_sha384) { + *algorithm = DIGEST_SHA_384; + } else if (md_type == NID_sha512) { + *algorithm = DIGEST_SHA_512; + } else { + algorithm->clear(); + return false; + } + + return true; +} + +bool OpenSSLDigest::GetDigestSize(const std::string& algorithm, + size_t* length) { + const EVP_MD *md; + if (!GetDigestEVP(algorithm, &md)) + return false; + + *length = EVP_MD_size(md); + return true; +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H + diff --git a/webrtc/base/openssldigest.h b/webrtc/base/openssldigest.h new file mode 100644 index 000000000..c4b0d8aed --- /dev/null +++ b/webrtc/base/openssldigest.h @@ -0,0 +1,50 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_OPENSSLDIGEST_H_ +#define WEBRTC_BASE_OPENSSLDIGEST_H_ + +#include + +#include "webrtc/base/messagedigest.h" + +namespace rtc { + +// An implementation of the digest class that uses OpenSSL. +class OpenSSLDigest : public MessageDigest { + public: + // Creates an OpenSSLDigest with |algorithm| as the hash algorithm. + explicit OpenSSLDigest(const std::string& algorithm); + ~OpenSSLDigest(); + // Returns the digest output size (e.g. 16 bytes for MD5). + virtual size_t Size() const; + // Updates the digest with |len| bytes from |buf|. + virtual void Update(const void* buf, size_t len); + // Outputs the digest value to |buf| with length |len|. + virtual size_t Finish(void* buf, size_t len); + + // Helper function to look up a digest's EVP by name. + static bool GetDigestEVP(const std::string &algorithm, + const EVP_MD** md); + // Helper function to look up a digest's name by EVP. + static bool GetDigestName(const EVP_MD* md, + std::string* algorithm); + // Helper function to get the length of a digest. + static bool GetDigestSize(const std::string &algorithm, + size_t* len); + + private: + EVP_MD_CTX ctx_; + const EVP_MD* md_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLDIGEST_H_ diff --git a/webrtc/base/opensslidentity.cc b/webrtc/base/opensslidentity.cc new file mode 100644 index 000000000..915680ce2 --- /dev/null +++ b/webrtc/base/opensslidentity.cc @@ -0,0 +1,366 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/opensslidentity.h" + +// Must be included first before openssl headers. +#include "webrtc/base/win32.h" // NOLINT + +#include +#include +#include +#include +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/openssl.h" +#include "webrtc/base/openssldigest.h" + +namespace rtc { + +// We could have exposed a myriad of parameters for the crypto stuff, +// but keeping it simple seems best. + +// Strength of generated keys. Those are RSA. +static const int KEY_LENGTH = 1024; + +// Random bits for certificate serial number +static const int SERIAL_RAND_BITS = 64; + +// Certificate validity lifetime +static const int CERTIFICATE_LIFETIME = 60*60*24*30; // 30 days, arbitrarily +// Certificate validity window. +// This is to compensate for slightly incorrect system clocks. +static const int CERTIFICATE_WINDOW = -60*60*24; + +// Generate a key pair. Caller is responsible for freeing the returned object. +static EVP_PKEY* MakeKey() { + LOG(LS_INFO) << "Making key pair"; + EVP_PKEY* pkey = EVP_PKEY_new(); + // RSA_generate_key is deprecated. Use _ex version. + BIGNUM* exponent = BN_new(); + RSA* rsa = RSA_new(); + if (!pkey || !exponent || !rsa || + !BN_set_word(exponent, 0x10001) || // 65537 RSA exponent + !RSA_generate_key_ex(rsa, KEY_LENGTH, exponent, NULL) || + !EVP_PKEY_assign_RSA(pkey, rsa)) { + EVP_PKEY_free(pkey); + BN_free(exponent); + RSA_free(rsa); + return NULL; + } + // ownership of rsa struct was assigned, don't free it. + BN_free(exponent); + LOG(LS_INFO) << "Returning key pair"; + return pkey; +} + +// Generate a self-signed certificate, with the public key from the +// given key pair. Caller is responsible for freeing the returned object. +static X509* MakeCertificate(EVP_PKEY* pkey, const SSLIdentityParams& params) { + LOG(LS_INFO) << "Making certificate for " << params.common_name; + X509* x509 = NULL; + BIGNUM* serial_number = NULL; + X509_NAME* name = NULL; + + if ((x509=X509_new()) == NULL) + goto error; + + if (!X509_set_pubkey(x509, pkey)) + goto error; + + // serial number + // temporary reference to serial number inside x509 struct + ASN1_INTEGER* asn1_serial_number; + if ((serial_number = BN_new()) == NULL || + !BN_pseudo_rand(serial_number, SERIAL_RAND_BITS, 0, 0) || + (asn1_serial_number = X509_get_serialNumber(x509)) == NULL || + !BN_to_ASN1_INTEGER(serial_number, asn1_serial_number)) + goto error; + + if (!X509_set_version(x509, 0L)) // version 1 + goto error; + + // There are a lot of possible components for the name entries. In + // our P2P SSL mode however, the certificates are pre-exchanged + // (through the secure XMPP channel), and so the certificate + // identification is arbitrary. It can't be empty, so we set some + // arbitrary common_name. Note that this certificate goes out in + // clear during SSL negotiation, so there may be a privacy issue in + // putting anything recognizable here. + if ((name = X509_NAME_new()) == NULL || + !X509_NAME_add_entry_by_NID( + name, NID_commonName, MBSTRING_UTF8, + (unsigned char*)params.common_name.c_str(), -1, -1, 0) || + !X509_set_subject_name(x509, name) || + !X509_set_issuer_name(x509, name)) + goto error; + + if (!X509_gmtime_adj(X509_get_notBefore(x509), params.not_before) || + !X509_gmtime_adj(X509_get_notAfter(x509), params.not_after)) + goto error; + + if (!X509_sign(x509, pkey, EVP_sha1())) + goto error; + + BN_free(serial_number); + X509_NAME_free(name); + LOG(LS_INFO) << "Returning certificate"; + return x509; + + error: + BN_free(serial_number); + X509_NAME_free(name); + X509_free(x509); + return NULL; +} + +// This dumps the SSL error stack to the log. +static void LogSSLErrors(const std::string& prefix) { + char error_buf[200]; + unsigned long err; + + while ((err = ERR_get_error()) != 0) { + ERR_error_string_n(err, error_buf, sizeof(error_buf)); + LOG(LS_ERROR) << prefix << ": " << error_buf << "\n"; + } +} + +OpenSSLKeyPair* OpenSSLKeyPair::Generate() { + EVP_PKEY* pkey = MakeKey(); + if (!pkey) { + LogSSLErrors("Generating key pair"); + return NULL; + } + return new OpenSSLKeyPair(pkey); +} + +OpenSSLKeyPair::~OpenSSLKeyPair() { + EVP_PKEY_free(pkey_); +} + +void OpenSSLKeyPair::AddReference() { + CRYPTO_add(&pkey_->references, 1, CRYPTO_LOCK_EVP_PKEY); +} + +#ifdef _DEBUG +// Print a certificate to the log, for debugging. +static void PrintCert(X509* x509) { + BIO* temp_memory_bio = BIO_new(BIO_s_mem()); + if (!temp_memory_bio) { + LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; + return; + } + X509_print_ex(temp_memory_bio, x509, XN_FLAG_SEP_CPLUS_SPC, 0); + BIO_write(temp_memory_bio, "\0", 1); + char* buffer; + BIO_get_mem_data(temp_memory_bio, &buffer); + LOG(LS_VERBOSE) << buffer; + BIO_free(temp_memory_bio); +} +#endif + +OpenSSLCertificate* OpenSSLCertificate::Generate( + OpenSSLKeyPair* key_pair, const SSLIdentityParams& params) { + SSLIdentityParams actual_params(params); + if (actual_params.common_name.empty()) { + // Use a random string, arbitrarily 8chars long. + actual_params.common_name = CreateRandomString(8); + } + X509* x509 = MakeCertificate(key_pair->pkey(), actual_params); + if (!x509) { + LogSSLErrors("Generating certificate"); + return NULL; + } +#ifdef _DEBUG + PrintCert(x509); +#endif + OpenSSLCertificate* ret = new OpenSSLCertificate(x509); + X509_free(x509); + return ret; +} + +OpenSSLCertificate* OpenSSLCertificate::FromPEMString( + const std::string& pem_string) { + BIO* bio = BIO_new_mem_buf(const_cast(pem_string.c_str()), -1); + if (!bio) + return NULL; + BIO_set_mem_eof_return(bio, 0); + X509 *x509 = PEM_read_bio_X509(bio, NULL, NULL, + const_cast("\0")); + BIO_free(bio); // Frees the BIO, but not the pointed-to string. + + if (!x509) + return NULL; + + OpenSSLCertificate* ret = new OpenSSLCertificate(x509); + X509_free(x509); + return ret; +} + +// NOTE: This implementation only functions correctly after InitializeSSL +// and before CleanupSSL. +bool OpenSSLCertificate::GetSignatureDigestAlgorithm( + std::string* algorithm) const { + return OpenSSLDigest::GetDigestName( + EVP_get_digestbyobj(x509_->sig_alg->algorithm), algorithm); +} + +bool OpenSSLCertificate::ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + return ComputeDigest(x509_, algorithm, digest, size, length); +} + +bool OpenSSLCertificate::ComputeDigest(const X509* x509, + const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) { + const EVP_MD *md; + unsigned int n; + + if (!OpenSSLDigest::GetDigestEVP(algorithm, &md)) + return false; + + if (size < static_cast(EVP_MD_size(md))) + return false; + + X509_digest(x509, md, digest, &n); + + *length = n; + + return true; +} + +OpenSSLCertificate::~OpenSSLCertificate() { + X509_free(x509_); +} + +std::string OpenSSLCertificate::ToPEMString() const { + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) { + UNREACHABLE(); + return std::string(); + } + if (!PEM_write_bio_X509(bio, x509_)) { + BIO_free(bio); + UNREACHABLE(); + return std::string(); + } + BIO_write(bio, "\0", 1); + char* buffer; + BIO_get_mem_data(bio, &buffer); + std::string ret(buffer); + BIO_free(bio); + return ret; +} + +void OpenSSLCertificate::ToDER(Buffer* der_buffer) const { + // In case of failure, make sure to leave the buffer empty. + der_buffer->SetData(NULL, 0); + + // Calculates the DER representation of the certificate, from scratch. + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) { + UNREACHABLE(); + return; + } + if (!i2d_X509_bio(bio, x509_)) { + BIO_free(bio); + UNREACHABLE(); + return; + } + char* data; + size_t length = BIO_get_mem_data(bio, &data); + der_buffer->SetData(data, length); + BIO_free(bio); +} + +void OpenSSLCertificate::AddReference() const { + ASSERT(x509_ != NULL); + CRYPTO_add(&x509_->references, 1, CRYPTO_LOCK_X509); +} + +OpenSSLIdentity* OpenSSLIdentity::GenerateInternal( + const SSLIdentityParams& params) { + OpenSSLKeyPair *key_pair = OpenSSLKeyPair::Generate(); + if (key_pair) { + OpenSSLCertificate *certificate = OpenSSLCertificate::Generate( + key_pair, params); + if (certificate) + return new OpenSSLIdentity(key_pair, certificate); + delete key_pair; + } + LOG(LS_INFO) << "Identity generation failed"; + return NULL; +} + +OpenSSLIdentity* OpenSSLIdentity::Generate(const std::string& common_name) { + SSLIdentityParams params; + params.common_name = common_name; + params.not_before = CERTIFICATE_WINDOW; + params.not_after = CERTIFICATE_LIFETIME; + return GenerateInternal(params); +} + +OpenSSLIdentity* OpenSSLIdentity::GenerateForTest( + const SSLIdentityParams& params) { + return GenerateInternal(params); +} + +SSLIdentity* OpenSSLIdentity::FromPEMStrings( + const std::string& private_key, + const std::string& certificate) { + scoped_ptr cert( + OpenSSLCertificate::FromPEMString(certificate)); + if (!cert) { + LOG(LS_ERROR) << "Failed to create OpenSSLCertificate from PEM string."; + return NULL; + } + + BIO* bio = BIO_new_mem_buf(const_cast(private_key.c_str()), -1); + if (!bio) { + LOG(LS_ERROR) << "Failed to create a new BIO buffer."; + return NULL; + } + BIO_set_mem_eof_return(bio, 0); + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, + const_cast("\0")); + BIO_free(bio); // Frees the BIO, but not the pointed-to string. + + if (!pkey) { + LOG(LS_ERROR) << "Failed to create the private key from PEM string."; + return NULL; + } + + return new OpenSSLIdentity(new OpenSSLKeyPair(pkey), + cert.release()); +} + +bool OpenSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) { + // 1 is the documented success return code. + if (SSL_CTX_use_certificate(ctx, certificate_->x509()) != 1 || + SSL_CTX_use_PrivateKey(ctx, key_pair_->pkey()) != 1) { + LogSSLErrors("Configuring key and certificate"); + return false; + } + return true; +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H diff --git a/webrtc/base/opensslidentity.h b/webrtc/base/opensslidentity.h new file mode 100644 index 000000000..e52cd10a9 --- /dev/null +++ b/webrtc/base/opensslidentity.h @@ -0,0 +1,150 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_OPENSSLIDENTITY_H_ +#define WEBRTC_BASE_OPENSSLIDENTITY_H_ + +#include +#include + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslidentity.h" + +typedef struct ssl_ctx_st SSL_CTX; + +namespace rtc { + +// OpenSSLKeyPair encapsulates an OpenSSL EVP_PKEY* keypair object, +// which is reference counted inside the OpenSSL library. +class OpenSSLKeyPair { + public: + explicit OpenSSLKeyPair(EVP_PKEY* pkey) : pkey_(pkey) { + ASSERT(pkey_ != NULL); + } + + static OpenSSLKeyPair* Generate(); + + virtual ~OpenSSLKeyPair(); + + virtual OpenSSLKeyPair* GetReference() { + AddReference(); + return new OpenSSLKeyPair(pkey_); + } + + EVP_PKEY* pkey() const { return pkey_; } + + private: + void AddReference(); + + EVP_PKEY* pkey_; + + DISALLOW_EVIL_CONSTRUCTORS(OpenSSLKeyPair); +}; + +// OpenSSLCertificate encapsulates an OpenSSL X509* certificate object, +// which is also reference counted inside the OpenSSL library. +class OpenSSLCertificate : public SSLCertificate { + public: + // Caller retains ownership of the X509 object. + explicit OpenSSLCertificate(X509* x509) : x509_(x509) { + AddReference(); + } + + static OpenSSLCertificate* Generate(OpenSSLKeyPair* key_pair, + const SSLIdentityParams& params); + static OpenSSLCertificate* FromPEMString(const std::string& pem_string); + + virtual ~OpenSSLCertificate(); + + virtual OpenSSLCertificate* GetReference() const { + return new OpenSSLCertificate(x509_); + } + + X509* x509() const { return x509_; } + + virtual std::string ToPEMString() const; + + virtual void ToDER(Buffer* der_buffer) const; + + // Compute the digest of the certificate given algorithm + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const; + + // Compute the digest of a certificate as an X509 * + static bool ComputeDigest(const X509* x509, + const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length); + + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const; + + virtual bool GetChain(SSLCertChain** chain) const { + // Chains are not yet supported when using OpenSSL. + // OpenSSLStreamAdapter::SSLVerifyCallback currently requires the remote + // certificate to be self-signed. + return false; + } + + private: + void AddReference() const; + + X509* x509_; + + DISALLOW_EVIL_CONSTRUCTORS(OpenSSLCertificate); +}; + +// Holds a keypair and certificate together, and a method to generate +// them consistently. +class OpenSSLIdentity : public SSLIdentity { + public: + static OpenSSLIdentity* Generate(const std::string& common_name); + static OpenSSLIdentity* GenerateForTest(const SSLIdentityParams& params); + static SSLIdentity* FromPEMStrings(const std::string& private_key, + const std::string& certificate); + virtual ~OpenSSLIdentity() { } + + virtual const OpenSSLCertificate& certificate() const { + return *certificate_; + } + + virtual OpenSSLIdentity* GetReference() const { + return new OpenSSLIdentity(key_pair_->GetReference(), + certificate_->GetReference()); + } + + // Configure an SSL context object to use our key and certificate. + bool ConfigureIdentity(SSL_CTX* ctx); + + private: + OpenSSLIdentity(OpenSSLKeyPair* key_pair, + OpenSSLCertificate* certificate) + : key_pair_(key_pair), certificate_(certificate) { + ASSERT(key_pair != NULL); + ASSERT(certificate != NULL); + } + + static OpenSSLIdentity* GenerateInternal(const SSLIdentityParams& params); + + scoped_ptr key_pair_; + scoped_ptr certificate_; + + DISALLOW_EVIL_CONSTRUCTORS(OpenSSLIdentity); +}; + + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLIDENTITY_H_ diff --git a/webrtc/base/opensslstreamadapter.cc b/webrtc/base/opensslstreamadapter.cc new file mode 100644 index 000000000..203cc6d6a --- /dev/null +++ b/webrtc/base/opensslstreamadapter.cc @@ -0,0 +1,843 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/opensslstreamadapter.h" + +#include +#include +#include +#include +#include + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/openssl.h" +#include "webrtc/base/openssladapter.h" +#include "webrtc/base/openssldigest.h" +#include "webrtc/base/opensslidentity.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#if (OPENSSL_VERSION_NUMBER >= 0x10001000L) +#define HAVE_DTLS_SRTP +#endif + +#ifdef HAVE_DTLS_SRTP +// SRTP cipher suite table +struct SrtpCipherMapEntry { + const char* external_name; + const char* internal_name; +}; + +// This isn't elegant, but it's better than an external reference +static SrtpCipherMapEntry SrtpCipherMap[] = { + {"AES_CM_128_HMAC_SHA1_80", "SRTP_AES128_CM_SHA1_80"}, + {"AES_CM_128_HMAC_SHA1_32", "SRTP_AES128_CM_SHA1_32"}, + {NULL, NULL} +}; +#endif + +////////////////////////////////////////////////////////////////////// +// StreamBIO +////////////////////////////////////////////////////////////////////// + +static int stream_write(BIO* h, const char* buf, int num); +static int stream_read(BIO* h, char* buf, int size); +static int stream_puts(BIO* h, const char* str); +static long stream_ctrl(BIO* h, int cmd, long arg1, void* arg2); +static int stream_new(BIO* h); +static int stream_free(BIO* data); + +static BIO_METHOD methods_stream = { + BIO_TYPE_BIO, + "stream", + stream_write, + stream_read, + stream_puts, + 0, + stream_ctrl, + stream_new, + stream_free, + NULL, +}; + +static BIO_METHOD* BIO_s_stream() { return(&methods_stream); } + +static BIO* BIO_new_stream(StreamInterface* stream) { + BIO* ret = BIO_new(BIO_s_stream()); + if (ret == NULL) + return NULL; + ret->ptr = stream; + return ret; +} + +// bio methods return 1 (or at least non-zero) on success and 0 on failure. + +static int stream_new(BIO* b) { + b->shutdown = 0; + b->init = 1; + b->num = 0; // 1 means end-of-stream + b->ptr = 0; + return 1; +} + +static int stream_free(BIO* b) { + if (b == NULL) + return 0; + return 1; +} + +static int stream_read(BIO* b, char* out, int outl) { + if (!out) + return -1; + StreamInterface* stream = static_cast(b->ptr); + BIO_clear_retry_flags(b); + size_t read; + int error; + StreamResult result = stream->Read(out, outl, &read, &error); + if (result == SR_SUCCESS) { + return read; + } else if (result == SR_EOS) { + b->num = 1; + } else if (result == SR_BLOCK) { + BIO_set_retry_read(b); + } + return -1; +} + +static int stream_write(BIO* b, const char* in, int inl) { + if (!in) + return -1; + StreamInterface* stream = static_cast(b->ptr); + BIO_clear_retry_flags(b); + size_t written; + int error; + StreamResult result = stream->Write(in, inl, &written, &error); + if (result == SR_SUCCESS) { + return written; + } else if (result == SR_BLOCK) { + BIO_set_retry_write(b); + } + return -1; +} + +static int stream_puts(BIO* b, const char* str) { + return stream_write(b, str, strlen(str)); +} + +static long stream_ctrl(BIO* b, int cmd, long num, void* ptr) { + UNUSED(num); + UNUSED(ptr); + + switch (cmd) { + case BIO_CTRL_RESET: + return 0; + case BIO_CTRL_EOF: + return b->num; + case BIO_CTRL_WPENDING: + case BIO_CTRL_PENDING: + return 0; + case BIO_CTRL_FLUSH: + return 1; + default: + return 0; + } +} + +///////////////////////////////////////////////////////////////////////////// +// OpenSSLStreamAdapter +///////////////////////////////////////////////////////////////////////////// + +OpenSSLStreamAdapter::OpenSSLStreamAdapter(StreamInterface* stream) + : SSLStreamAdapter(stream), + state_(SSL_NONE), + role_(SSL_CLIENT), + ssl_read_needs_write_(false), ssl_write_needs_read_(false), + ssl_(NULL), ssl_ctx_(NULL), + custom_verification_succeeded_(false), + ssl_mode_(SSL_MODE_TLS) { +} + +OpenSSLStreamAdapter::~OpenSSLStreamAdapter() { + Cleanup(); +} + +void OpenSSLStreamAdapter::SetIdentity(SSLIdentity* identity) { + ASSERT(!identity_); + identity_.reset(static_cast(identity)); +} + +void OpenSSLStreamAdapter::SetServerRole(SSLRole role) { + role_ = role; +} + +bool OpenSSLStreamAdapter::GetPeerCertificate(SSLCertificate** cert) const { + if (!peer_certificate_) + return false; + + *cert = peer_certificate_->GetReference(); + return true; +} + +bool OpenSSLStreamAdapter::SetPeerCertificateDigest(const std::string + &digest_alg, + const unsigned char* + digest_val, + size_t digest_len) { + ASSERT(!peer_certificate_); + ASSERT(peer_certificate_digest_algorithm_.size() == 0); + ASSERT(ssl_server_name_.empty()); + size_t expected_len; + + if (!OpenSSLDigest::GetDigestSize(digest_alg, &expected_len)) { + LOG(LS_WARNING) << "Unknown digest algorithm: " << digest_alg; + return false; + } + if (expected_len != digest_len) + return false; + + peer_certificate_digest_value_.SetData(digest_val, digest_len); + peer_certificate_digest_algorithm_ = digest_alg; + + return true; +} + +// Key Extractor interface +bool OpenSSLStreamAdapter::ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { +#ifdef HAVE_DTLS_SRTP + int i; + + i = SSL_export_keying_material(ssl_, result, result_len, + label.c_str(), label.length(), + const_cast(context), + context_len, use_context); + + if (i != 1) + return false; + + return true; +#else + return false; +#endif +} + +bool OpenSSLStreamAdapter::SetDtlsSrtpCiphers( + const std::vector& ciphers) { +#ifdef HAVE_DTLS_SRTP + std::string internal_ciphers; + + if (state_ != SSL_NONE) + return false; + + for (std::vector::const_iterator cipher = ciphers.begin(); + cipher != ciphers.end(); ++cipher) { + bool found = false; + for (SrtpCipherMapEntry *entry = SrtpCipherMap; entry->internal_name; + ++entry) { + if (*cipher == entry->external_name) { + found = true; + if (!internal_ciphers.empty()) + internal_ciphers += ":"; + internal_ciphers += entry->internal_name; + break; + } + } + + if (!found) { + LOG(LS_ERROR) << "Could not find cipher: " << *cipher; + return false; + } + } + + if (internal_ciphers.empty()) + return false; + + srtp_ciphers_ = internal_ciphers; + return true; +#else + return false; +#endif +} + +bool OpenSSLStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) { +#ifdef HAVE_DTLS_SRTP + ASSERT(state_ == SSL_CONNECTED); + if (state_ != SSL_CONNECTED) + return false; + + SRTP_PROTECTION_PROFILE *srtp_profile = + SSL_get_selected_srtp_profile(ssl_); + + if (!srtp_profile) + return false; + + for (SrtpCipherMapEntry *entry = SrtpCipherMap; + entry->internal_name; ++entry) { + if (!strcmp(entry->internal_name, srtp_profile->name)) { + *cipher = entry->external_name; + return true; + } + } + + ASSERT(false); // This should never happen + + return false; +#else + return false; +#endif +} + +int OpenSSLStreamAdapter::StartSSLWithServer(const char* server_name) { + ASSERT(server_name != NULL && server_name[0] != '\0'); + ssl_server_name_ = server_name; + return StartSSL(); +} + +int OpenSSLStreamAdapter::StartSSLWithPeer() { + ASSERT(ssl_server_name_.empty()); + // It is permitted to specify peer_certificate_ only later. + return StartSSL(); +} + +void OpenSSLStreamAdapter::SetMode(SSLMode mode) { + ASSERT(state_ == SSL_NONE); + ssl_mode_ = mode; +} + +// +// StreamInterface Implementation +// + +StreamResult OpenSSLStreamAdapter::Write(const void* data, size_t data_len, + size_t* written, int* error) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::Write(" << data_len << ")"; + + switch (state_) { + case SSL_NONE: + // pass-through in clear text + return StreamAdapterInterface::Write(data, data_len, written, error); + + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + case SSL_CLOSED: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + // OpenSSL will return an error if we try to write zero bytes + if (data_len == 0) { + if (written) + *written = 0; + return SR_SUCCESS; + } + + ssl_write_needs_read_ = false; + + int code = SSL_write(ssl_, data, data_len); + int ssl_error = SSL_get_error(ssl_, code); + switch (ssl_error) { + case SSL_ERROR_NONE: + LOG(LS_VERBOSE) << " -- success"; + ASSERT(0 < code && static_cast(code) <= data_len); + if (written) + *written = code; + return SR_SUCCESS; + case SSL_ERROR_WANT_READ: + LOG(LS_VERBOSE) << " -- error want read"; + ssl_write_needs_read_ = true; + return SR_BLOCK; + case SSL_ERROR_WANT_WRITE: + LOG(LS_VERBOSE) << " -- error want write"; + return SR_BLOCK; + + case SSL_ERROR_ZERO_RETURN: + default: + Error("SSL_write", (ssl_error ? ssl_error : -1), false); + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + // not reached +} + +StreamResult OpenSSLStreamAdapter::Read(void* data, size_t data_len, + size_t* read, int* error) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::Read(" << data_len << ")"; + switch (state_) { + case SSL_NONE: + // pass-through in clear text + return StreamAdapterInterface::Read(data, data_len, read, error); + + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_CLOSED: + return SR_EOS; + + case SSL_ERROR: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + // Don't trust OpenSSL with zero byte reads + if (data_len == 0) { + if (read) + *read = 0; + return SR_SUCCESS; + } + + ssl_read_needs_write_ = false; + + int code = SSL_read(ssl_, data, data_len); + int ssl_error = SSL_get_error(ssl_, code); + switch (ssl_error) { + case SSL_ERROR_NONE: + LOG(LS_VERBOSE) << " -- success"; + ASSERT(0 < code && static_cast(code) <= data_len); + if (read) + *read = code; + + if (ssl_mode_ == SSL_MODE_DTLS) { + // Enforce atomic reads -- this is a short read + unsigned int pending = SSL_pending(ssl_); + + if (pending) { + LOG(LS_INFO) << " -- short DTLS read. flushing"; + FlushInput(pending); + if (error) + *error = SSE_MSG_TRUNC; + return SR_ERROR; + } + } + return SR_SUCCESS; + case SSL_ERROR_WANT_READ: + LOG(LS_VERBOSE) << " -- error want read"; + return SR_BLOCK; + case SSL_ERROR_WANT_WRITE: + LOG(LS_VERBOSE) << " -- error want write"; + ssl_read_needs_write_ = true; + return SR_BLOCK; + case SSL_ERROR_ZERO_RETURN: + LOG(LS_VERBOSE) << " -- remote side closed"; + return SR_EOS; + break; + default: + LOG(LS_VERBOSE) << " -- error " << code; + Error("SSL_read", (ssl_error ? ssl_error : -1), false); + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + // not reached +} + +void OpenSSLStreamAdapter::FlushInput(unsigned int left) { + unsigned char buf[2048]; + + while (left) { + // This should always succeed + int toread = (sizeof(buf) < left) ? sizeof(buf) : left; + int code = SSL_read(ssl_, buf, toread); + + int ssl_error = SSL_get_error(ssl_, code); + ASSERT(ssl_error == SSL_ERROR_NONE); + + if (ssl_error != SSL_ERROR_NONE) { + LOG(LS_VERBOSE) << " -- error " << code; + Error("SSL_read", (ssl_error ? ssl_error : -1), false); + return; + } + + LOG(LS_VERBOSE) << " -- flushed " << code << " bytes"; + left -= code; + } +} + +void OpenSSLStreamAdapter::Close() { + Cleanup(); + ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR); + StreamAdapterInterface::Close(); +} + +StreamState OpenSSLStreamAdapter::GetState() const { + switch (state_) { + case SSL_WAIT: + case SSL_CONNECTING: + return SS_OPENING; + case SSL_CONNECTED: + return SS_OPEN; + default: + return SS_CLOSED; + }; + // not reached +} + +void OpenSSLStreamAdapter::OnEvent(StreamInterface* stream, int events, + int err) { + int events_to_signal = 0; + int signal_error = 0; + ASSERT(stream == this->stream()); + if ((events & SE_OPEN)) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent SE_OPEN"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + events_to_signal |= SE_OPEN; + } else { + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, true); + return; + } + } + } + if ((events & (SE_READ|SE_WRITE))) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent" + << ((events & SE_READ) ? " SE_READ" : "") + << ((events & SE_WRITE) ? " SE_WRITE" : ""); + if (state_ == SSL_NONE) { + events_to_signal |= events & (SE_READ|SE_WRITE); + } else if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err, true); + return; + } + } else if (state_ == SSL_CONNECTED) { + if (((events & SE_READ) && ssl_write_needs_read_) || + (events & SE_WRITE)) { + LOG(LS_VERBOSE) << " -- onStreamWriteable"; + events_to_signal |= SE_WRITE; + } + if (((events & SE_WRITE) && ssl_read_needs_write_) || + (events & SE_READ)) { + LOG(LS_VERBOSE) << " -- onStreamReadable"; + events_to_signal |= SE_READ; + } + } + } + if ((events & SE_CLOSE)) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent(SE_CLOSE, " << err << ")"; + Cleanup(); + events_to_signal |= SE_CLOSE; + // SE_CLOSE is the only event that uses the final parameter to OnEvent(). + ASSERT(signal_error == 0); + signal_error = err; + } + if (events_to_signal) + StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error); +} + +int OpenSSLStreamAdapter::StartSSL() { + ASSERT(state_ == SSL_NONE); + + if (StreamAdapterInterface::GetState() != SS_OPEN) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +int OpenSSLStreamAdapter::BeginSSL() { + ASSERT(state_ == SSL_CONNECTING); + // The underlying stream has open. If we are in peer-to-peer mode + // then a peer certificate must have been specified by now. + ASSERT(!ssl_server_name_.empty() || + !peer_certificate_digest_algorithm_.empty()); + LOG(LS_INFO) << "BeginSSL: " + << (!ssl_server_name_.empty() ? ssl_server_name_ : + "with peer"); + + BIO* bio = NULL; + + // First set up the context + ASSERT(ssl_ctx_ == NULL); + ssl_ctx_ = SetupSSLContext(); + if (!ssl_ctx_) + return -1; + + bio = BIO_new_stream(static_cast(stream())); + if (!bio) + return -1; + + ssl_ = SSL_new(ssl_ctx_); + if (!ssl_) { + BIO_free(bio); + return -1; + } + + SSL_set_app_data(ssl_, this); + + SSL_set_bio(ssl_, bio, bio); // the SSL object owns the bio now. + + SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + // Do the connect + return ContinueSSL(); +} + +int OpenSSLStreamAdapter::ContinueSSL() { + LOG(LS_VERBOSE) << "ContinueSSL"; + ASSERT(state_ == SSL_CONNECTING); + + // Clear the DTLS timer + Thread::Current()->Clear(this, MSG_TIMEOUT); + + int code = (role_ == SSL_CLIENT) ? SSL_connect(ssl_) : SSL_accept(ssl_); + int ssl_error; + switch (ssl_error = SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + LOG(LS_VERBOSE) << " -- success"; + + if (!SSLPostConnectionCheck(ssl_, ssl_server_name_.c_str(), NULL, + peer_certificate_digest_algorithm_)) { + LOG(LS_ERROR) << "TLS post connection check failed"; + return -1; + } + + state_ = SSL_CONNECTED; + StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0); + break; + + case SSL_ERROR_WANT_READ: { + LOG(LS_VERBOSE) << " -- error want read"; + struct timeval timeout; + if (DTLSv1_get_timeout(ssl_, &timeout)) { + int delay = timeout.tv_sec * 1000 + timeout.tv_usec/1000; + + Thread::Current()->PostDelayed(delay, this, MSG_TIMEOUT, 0); + } + } + break; + + case SSL_ERROR_WANT_WRITE: + LOG(LS_VERBOSE) << " -- error want write"; + break; + + case SSL_ERROR_ZERO_RETURN: + default: + LOG(LS_VERBOSE) << " -- error " << code; + return (ssl_error != 0) ? ssl_error : -1; + } + + return 0; +} + +void OpenSSLStreamAdapter::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "OpenSSLStreamAdapter::Error(" + << context << ", " << err << ")"; + state_ = SSL_ERROR; + ssl_error_code_ = err; + Cleanup(); + if (signal) + StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err); +} + +void OpenSSLStreamAdapter::Cleanup() { + LOG(LS_INFO) << "Cleanup"; + + if (state_ != SSL_ERROR) { + state_ = SSL_CLOSED; + ssl_error_code_ = 0; + } + + if (ssl_) { + SSL_free(ssl_); + ssl_ = NULL; + } + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + ssl_ctx_ = NULL; + } + identity_.reset(); + peer_certificate_.reset(); + + // Clear the DTLS timer + Thread::Current()->Clear(this, MSG_TIMEOUT); +} + + +void OpenSSLStreamAdapter::OnMessage(Message* msg) { + // Process our own messages and then pass others to the superclass + if (MSG_TIMEOUT == msg->message_id) { + LOG(LS_INFO) << "DTLS timeout expired"; + DTLSv1_handle_timeout(ssl_); + ContinueSSL(); + } else { + StreamInterface::OnMessage(msg); + } +} + +SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() { + SSL_CTX *ctx = NULL; + + if (role_ == SSL_CLIENT) { + ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ? + DTLSv1_client_method() : TLSv1_client_method()); + } else { + ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ? + DTLSv1_server_method() : TLSv1_server_method()); + } + if (ctx == NULL) + return NULL; + + if (identity_ && !identity_->ConfigureIdentity(ctx)) { + SSL_CTX_free(ctx); + return NULL; + } + +#ifdef _DEBUG + SSL_CTX_set_info_callback(ctx, OpenSSLAdapter::SSLInfoCallback); +#endif + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER |SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + SSLVerifyCallback); + SSL_CTX_set_verify_depth(ctx, 4); + SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + +#ifdef HAVE_DTLS_SRTP + if (!srtp_ciphers_.empty()) { + if (SSL_CTX_set_tlsext_use_srtp(ctx, srtp_ciphers_.c_str())) { + SSL_CTX_free(ctx); + return NULL; + } + } +#endif + + return ctx; +} + +int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { + // Get our SSL structure from the store + SSL* ssl = reinterpret_cast(X509_STORE_CTX_get_ex_data( + store, + SSL_get_ex_data_X509_STORE_CTX_idx())); + OpenSSLStreamAdapter* stream = + reinterpret_cast(SSL_get_app_data(ssl)); + + if (stream->peer_certificate_digest_algorithm_.empty()) { + return 0; + } + X509* cert = X509_STORE_CTX_get_current_cert(store); + unsigned char digest[EVP_MAX_MD_SIZE]; + size_t digest_length; + if (!OpenSSLCertificate::ComputeDigest( + cert, + stream->peer_certificate_digest_algorithm_, + digest, sizeof(digest), + &digest_length)) { + LOG(LS_WARNING) << "Failed to compute peer cert digest."; + return 0; + } + Buffer computed_digest(digest, digest_length); + if (computed_digest != stream->peer_certificate_digest_value_) { + LOG(LS_WARNING) << "Rejected peer certificate due to mismatched digest."; + return 0; + } + // Ignore any verification error if the digest matches, since there is no + // value in checking the validity of a self-signed cert issued by untrusted + // sources. + LOG(LS_INFO) << "Accepted peer certificate."; + // Record the peer's certificate. + stream->peer_certificate_.reset(new OpenSSLCertificate(cert)); + return 1; +} + +// This code is taken from the "Network Security with OpenSSL" +// sample in chapter 5 +bool OpenSSLStreamAdapter::SSLPostConnectionCheck(SSL* ssl, + const char* server_name, + const X509* peer_cert, + const std::string + &peer_digest) { + ASSERT(server_name != NULL); + bool ok; + if (server_name[0] != '\0') { // traditional mode + ok = OpenSSLAdapter::VerifyServerName(ssl, server_name, ignore_bad_cert()); + + if (ok) { + ok = (SSL_get_verify_result(ssl) == X509_V_OK || + custom_verification_succeeded_); + } + } else { // peer-to-peer mode + ASSERT((peer_cert != NULL) || (!peer_digest.empty())); + // no server name validation + ok = true; + } + + if (!ok && ignore_bad_cert()) { + LOG(LS_ERROR) << "SSL_get_verify_result(ssl) = " + << SSL_get_verify_result(ssl); + LOG(LS_INFO) << "Other TLS post connection checks failed."; + ok = true; + } + + return ok; +} + +bool OpenSSLStreamAdapter::HaveDtls() { + return true; +} + +bool OpenSSLStreamAdapter::HaveDtlsSrtp() { +#ifdef HAVE_DTLS_SRTP + return true; +#else + return false; +#endif +} + +bool OpenSSLStreamAdapter::HaveExporter() { +#ifdef HAVE_DTLS_SRTP + return true; +#else + return false; +#endif +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H diff --git a/webrtc/base/opensslstreamadapter.h b/webrtc/base/opensslstreamadapter.h new file mode 100644 index 000000000..9506217b4 --- /dev/null +++ b/webrtc/base/opensslstreamadapter.h @@ -0,0 +1,198 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_OPENSSLSTREAMADAPTER_H__ +#define WEBRTC_BASE_OPENSSLSTREAMADAPTER_H__ + +#include +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/opensslidentity.h" + +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct x509_store_ctx_st X509_STORE_CTX; + +namespace rtc { + +// This class was written with OpenSSLAdapter (a socket adapter) as a +// starting point. It has similar structure and functionality, with +// the peer-to-peer mode added. +// +// Static methods to initialize and deinit the SSL library are in +// OpenSSLAdapter. This class also uses +// OpenSSLAdapter::custom_verify_callback_ (a static field). These +// should probably be moved out to a neutral class. +// +// In a few cases I have factored out some OpenSSLAdapter code into +// static methods so it can be reused from this class. Eventually that +// code should probably be moved to a common support +// class. Unfortunately there remain a few duplicated sections of +// code. I have not done more restructuring because I did not want to +// affect existing code that uses OpenSSLAdapter. +// +// This class does not support the SSL connection restart feature +// present in OpenSSLAdapter. I am not entirely sure how the feature +// is useful and I am not convinced that it works properly. +// +// This implementation is careful to disallow data exchange after an +// SSL error, and it has an explicit SSL_CLOSED state. It should not +// be possible to send any data in clear after one of the StartSSL +// methods has been called. + +// Look in sslstreamadapter.h for documentation of the methods. + +class OpenSSLIdentity; + +/////////////////////////////////////////////////////////////////////////////// + +class OpenSSLStreamAdapter : public SSLStreamAdapter { + public: + explicit OpenSSLStreamAdapter(StreamInterface* stream); + virtual ~OpenSSLStreamAdapter(); + + virtual void SetIdentity(SSLIdentity* identity); + + // Default argument is for compatibility + virtual void SetServerRole(SSLRole role = SSL_SERVER); + virtual bool SetPeerCertificateDigest(const std::string& digest_alg, + const unsigned char* digest_val, + size_t digest_len); + + virtual bool GetPeerCertificate(SSLCertificate** cert) const; + + virtual int StartSSLWithServer(const char* server_name); + virtual int StartSSLWithPeer(); + virtual void SetMode(SSLMode mode); + + virtual StreamResult Read(void* data, size_t data_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual StreamState GetState() const; + + // Key Extractor interface + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len); + + + // DTLS-SRTP interface + virtual bool SetDtlsSrtpCiphers(const std::vector& ciphers); + virtual bool GetDtlsSrtpCipher(std::string* cipher); + + // Capabilities interfaces + static bool HaveDtls(); + static bool HaveDtlsSrtp(); + static bool HaveExporter(); + + protected: + virtual void OnEvent(StreamInterface* stream, int events, int err); + + private: + enum SSLState { + // Before calling one of the StartSSL methods, data flows + // in clear text. + SSL_NONE, + SSL_WAIT, // waiting for the stream to open to start SSL negotiation + SSL_CONNECTING, // SSL negotiation in progress + SSL_CONNECTED, // SSL stream successfully established + SSL_ERROR, // some SSL error occurred, stream is closed + SSL_CLOSED // Clean close + }; + + enum { MSG_TIMEOUT = MSG_MAX+1}; + + // The following three methods return 0 on success and a negative + // error code on failure. The error code may be from OpenSSL or -1 + // on some other error cases, so it can't really be interpreted + // unfortunately. + + // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT, + // depending on whether the underlying stream is already open or + // not. + int StartSSL(); + // Prepare SSL library, state is SSL_CONNECTING. + int BeginSSL(); + // Perform SSL negotiation steps. + int ContinueSSL(); + + // Error handler helper. signal is given as true for errors in + // asynchronous contexts (when an error method was not returned + // through some other method), and in that case an SE_CLOSE event is + // raised on the stream with the specified error. + // A 0 error means a graceful close, otherwise there is not really enough + // context to interpret the error code. + void Error(const char* context, int err, bool signal); + void Cleanup(); + + // Override MessageHandler + virtual void OnMessage(Message* msg); + + // Flush the input buffers by reading left bytes (for DTLS) + void FlushInput(unsigned int left); + + // SSL library configuration + SSL_CTX* SetupSSLContext(); + // SSL verification check + bool SSLPostConnectionCheck(SSL* ssl, const char* server_name, + const X509* peer_cert, + const std::string& peer_digest); + // SSL certification verification error handler, called back from + // the openssl library. Returns an int interpreted as a boolean in + // the C style: zero means verification failure, non-zero means + // passed. + static int SSLVerifyCallback(int ok, X509_STORE_CTX* store); + + SSLState state_; + SSLRole role_; + int ssl_error_code_; // valid when state_ == SSL_ERROR or SSL_CLOSED + // Whether the SSL negotiation is blocked on needing to read or + // write to the wrapped stream. + bool ssl_read_needs_write_; + bool ssl_write_needs_read_; + + SSL* ssl_; + SSL_CTX* ssl_ctx_; + + // Our key and certificate, mostly useful in peer-to-peer mode. + scoped_ptr identity_; + // in traditional mode, the server name that the server's certificate + // must specify. Empty in peer-to-peer mode. + std::string ssl_server_name_; + // The certificate that the peer must present or did present. Initially + // null in traditional mode, until the connection is established. + scoped_ptr peer_certificate_; + // In peer-to-peer mode, the digest of the certificate that + // the peer must present. + Buffer peer_certificate_digest_value_; + std::string peer_certificate_digest_algorithm_; + + // OpenSSLAdapter::custom_verify_callback_ result + bool custom_verification_succeeded_; + + // The DtlsSrtp ciphers + std::string srtp_ciphers_; + + // Do DTLS or not + SSLMode ssl_mode_; +}; + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLSTREAMADAPTER_H__ diff --git a/webrtc/base/optionsfile.cc b/webrtc/base/optionsfile.cc new file mode 100644 index 000000000..d84c948e3 --- /dev/null +++ b/webrtc/base/optionsfile.cc @@ -0,0 +1,184 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/optionsfile.h" + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +OptionsFile::OptionsFile(const std::string &path) : path_(path) { +} + +bool OptionsFile::Load() { + options_.clear(); + // Open file. + FileStream stream; + int err; + if (!stream.Open(path_, "r", &err)) { + LOG_F(LS_WARNING) << "Could not open file, err=" << err; + // We do not consider this an error because we expect there to be no file + // until the user saves a setting. + return true; + } + // Read in all its data. + std::string line; + StreamResult res; + for (;;) { + res = stream.ReadLine(&line); + if (res != SR_SUCCESS) { + break; + } + size_t equals_pos = line.find('='); + if (equals_pos == std::string::npos) { + // We do not consider this an error. Instead we ignore the line and + // keep going. + LOG_F(LS_WARNING) << "Ignoring malformed line in " << path_; + continue; + } + std::string key(line, 0, equals_pos); + std::string value(line, equals_pos + 1, line.length() - (equals_pos + 1)); + options_[key] = value; + } + if (res != SR_EOS) { + LOG_F(LS_ERROR) << "Error when reading from file"; + return false; + } else { + return true; + } +} + +bool OptionsFile::Save() { + // Open file. + FileStream stream; + int err; + if (!stream.Open(path_, "w", &err)) { + LOG_F(LS_ERROR) << "Could not open file, err=" << err; + return false; + } + // Write out all the data. + StreamResult res = SR_SUCCESS; + size_t written; + int error; + for (OptionsMap::const_iterator i = options_.begin(); i != options_.end(); + ++i) { + res = stream.WriteAll(i->first.c_str(), i->first.length(), &written, + &error); + if (res != SR_SUCCESS) { + break; + } + res = stream.WriteAll("=", 1, &written, &error); + if (res != SR_SUCCESS) { + break; + } + res = stream.WriteAll(i->second.c_str(), i->second.length(), &written, + &error); + if (res != SR_SUCCESS) { + break; + } + res = stream.WriteAll("\n", 1, &written, &error); + if (res != SR_SUCCESS) { + break; + } + } + if (res != SR_SUCCESS) { + LOG_F(LS_ERROR) << "Unable to write to file"; + return false; + } else { + return true; + } +} + +bool OptionsFile::IsLegalName(const std::string &name) { + for (size_t pos = 0; pos < name.length(); ++pos) { + if (name[pos] == '\n' || name[pos] == '\\' || name[pos] == '=') { + // Illegal character. + LOG(LS_WARNING) << "Ignoring operation for illegal option " << name; + return false; + } + } + return true; +} + +bool OptionsFile::IsLegalValue(const std::string &value) { + for (size_t pos = 0; pos < value.length(); ++pos) { + if (value[pos] == '\n' || value[pos] == '\\') { + // Illegal character. + LOG(LS_WARNING) << "Ignoring operation for illegal value " << value; + return false; + } + } + return true; +} + +bool OptionsFile::GetStringValue(const std::string& option, + std::string *out_val) const { + LOG(LS_VERBOSE) << "OptionsFile::GetStringValue " + << option; + if (!IsLegalName(option)) { + return false; + } + OptionsMap::const_iterator i = options_.find(option); + if (i == options_.end()) { + return false; + } + *out_val = i->second; + return true; +} + +bool OptionsFile::GetIntValue(const std::string& option, + int *out_val) const { + LOG(LS_VERBOSE) << "OptionsFile::GetIntValue " + << option; + if (!IsLegalName(option)) { + return false; + } + OptionsMap::const_iterator i = options_.find(option); + if (i == options_.end()) { + return false; + } + return FromString(i->second, out_val); +} + +bool OptionsFile::SetStringValue(const std::string& option, + const std::string& value) { + LOG(LS_VERBOSE) << "OptionsFile::SetStringValue " + << option << ":" << value; + if (!IsLegalName(option) || !IsLegalValue(value)) { + return false; + } + options_[option] = value; + return true; +} + +bool OptionsFile::SetIntValue(const std::string& option, + int value) { + LOG(LS_VERBOSE) << "OptionsFile::SetIntValue " + << option << ":" << value; + if (!IsLegalName(option)) { + return false; + } + return ToString(value, &options_[option]); +} + +bool OptionsFile::RemoveValue(const std::string& option) { + LOG(LS_VERBOSE) << "OptionsFile::RemoveValue " << option; + if (!IsLegalName(option)) { + return false; + } + options_.erase(option); + return true; +} + +} // namespace rtc diff --git a/webrtc/base/optionsfile.h b/webrtc/base/optionsfile.h new file mode 100644 index 000000000..c740ce4fa --- /dev/null +++ b/webrtc/base/optionsfile.h @@ -0,0 +1,49 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_OPTIONSFILE_H_ +#define WEBRTC_BASE_OPTIONSFILE_H_ + +#include +#include + +namespace rtc { + +// Implements storage of simple options in a text file on disk. This is +// cross-platform, but it is intended mostly for Linux where there is no +// first-class options storage system. +class OptionsFile { + public: + OptionsFile(const std::string &path); + + // Loads the file from disk, overwriting the in-memory values. + bool Load(); + // Saves the contents in memory, overwriting the on-disk values. + bool Save(); + + bool GetStringValue(const std::string& option, std::string* out_val) const; + bool GetIntValue(const std::string& option, int* out_val) const; + bool SetStringValue(const std::string& option, const std::string& val); + bool SetIntValue(const std::string& option, int val); + bool RemoveValue(const std::string& option); + + private: + typedef std::map OptionsMap; + + static bool IsLegalName(const std::string &name); + static bool IsLegalValue(const std::string &value); + + std::string path_; + OptionsMap options_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_OPTIONSFILE_H_ diff --git a/webrtc/base/optionsfile_unittest.cc b/webrtc/base/optionsfile_unittest.cc new file mode 100644 index 000000000..adddb9521 --- /dev/null +++ b/webrtc/base/optionsfile_unittest.cc @@ -0,0 +1,168 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/optionsfile.h" +#include "webrtc/base/pathutils.h" + +namespace rtc { + +static const std::string kTestOptionA = "test-option-a"; +static const std::string kTestOptionB = "test-option-b"; +static const std::string kTestString1 = "a string"; +static const std::string kTestString2 = "different string"; +static const std::string kOptionWithEquals = "foo=bar"; +static const std::string kOptionWithNewline = "foo\nbar"; +static const std::string kValueWithEquals = "baz=quux"; +static const std::string kValueWithNewline = "baz\nquux"; +static const std::string kEmptyString = ""; +static const char kOptionWithUtf8[] = {'O', 'p', 't', '\302', '\256', 'i', 'o', + 'n', '\342', '\204', '\242', '\0'}; // Opt(R)io(TM). +static const char kValueWithUtf8[] = {'V', 'a', 'l', '\302', '\256', 'v', 'e', + '\342', '\204', '\242', '\0'}; // Val(R)ue(TM). +static int kTestInt1 = 12345; +static int kTestInt2 = 67890; +static int kNegInt = -634; +static int kZero = 0; + +class OptionsFileTest : public testing::Test { + public: + OptionsFileTest() { + Pathname dir; + ASSERT(Filesystem::GetTemporaryFolder(dir, true, NULL)); + test_file_ = Filesystem::TempFilename(dir, ".testfile"); + OpenStore(); + } + + protected: + void OpenStore() { + store_.reset(new OptionsFile(test_file_)); + } + + rtc::scoped_ptr store_; + + private: + std::string test_file_; +}; + +TEST_F(OptionsFileTest, GetSetString) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + std::string out1, out2; + EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kTestString2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out2)); + EXPECT_EQ(kTestString1, out1); + EXPECT_EQ(kTestString2, out2); + EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); +} + +TEST_F(OptionsFileTest, GetSetInt) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + int out1, out2; + EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kTestInt1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kTestInt2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_EQ(kTestInt1, out1); + EXPECT_EQ(kTestInt2, out2); + EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kNegInt)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_EQ(kNegInt, out1); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kZero)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_EQ(kZero, out1); +} + +TEST_F(OptionsFileTest, Persist) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kNegInt)); + EXPECT_TRUE(store_->Save()); + + // Load the saved contents from above. + OpenStore(); + EXPECT_TRUE(store_->Load()); + std::string out1; + int out2; + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_EQ(kTestString1, out1); + EXPECT_EQ(kNegInt, out2); +} + +TEST_F(OptionsFileTest, SpecialCharacters) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + std::string out; + EXPECT_FALSE(store_->SetStringValue(kOptionWithEquals, kTestString1)); + EXPECT_FALSE(store_->GetStringValue(kOptionWithEquals, &out)); + EXPECT_FALSE(store_->SetStringValue(kOptionWithNewline, kTestString1)); + EXPECT_FALSE(store_->GetStringValue(kOptionWithNewline, &out)); + EXPECT_TRUE(store_->SetStringValue(kOptionWithUtf8, kValueWithUtf8)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); + EXPECT_EQ(kTestString1, out); + EXPECT_TRUE(store_->GetStringValue(kOptionWithUtf8, &out)); + EXPECT_EQ(kValueWithUtf8, out); + EXPECT_FALSE(store_->SetStringValue(kTestOptionA, kValueWithNewline)); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); + EXPECT_EQ(kTestString1, out); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kValueWithEquals)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); + EXPECT_EQ(kValueWithEquals, out); + EXPECT_TRUE(store_->SetStringValue(kEmptyString, kTestString2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kEmptyString, &out)); + EXPECT_EQ(kTestString2, out); + EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kEmptyString)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out)); + EXPECT_EQ(kEmptyString, out); +} + +} // namespace rtc diff --git a/webrtc/base/pathutils.cc b/webrtc/base/pathutils.cc new file mode 100644 index 000000000..7671bfc29 --- /dev/null +++ b/webrtc/base/pathutils.cc @@ -0,0 +1,251 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#include +#include +#endif // WEBRTC_WIN + +#include "webrtc/base/common.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/urlencode.h" + +namespace rtc { + +static const char EMPTY_STR[] = ""; + +// EXT_DELIM separates a file basename from extension +const char EXT_DELIM = '.'; + +// FOLDER_DELIMS separate folder segments and the filename +const char* const FOLDER_DELIMS = "/\\"; + +// DEFAULT_FOLDER_DELIM is the preferred delimiter for this platform +#if WEBRTC_WIN +const char DEFAULT_FOLDER_DELIM = '\\'; +#else // !WEBRTC_WIN +const char DEFAULT_FOLDER_DELIM = '/'; +#endif // !WEBRTC_WIN + +/////////////////////////////////////////////////////////////////////////////// +// Pathname - parsing of pathnames into components, and vice versa +/////////////////////////////////////////////////////////////////////////////// + +bool Pathname::IsFolderDelimiter(char ch) { + return (NULL != ::strchr(FOLDER_DELIMS, ch)); +} + +char Pathname::DefaultFolderDelimiter() { + return DEFAULT_FOLDER_DELIM; +} + +Pathname::Pathname() + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { +} + +Pathname::Pathname(const std::string& pathname) + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { + SetPathname(pathname); +} + +Pathname::Pathname(const std::string& folder, const std::string& filename) + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { + SetPathname(folder, filename); +} + +void Pathname::SetFolderDelimiter(char delimiter) { + ASSERT(IsFolderDelimiter(delimiter)); + folder_delimiter_ = delimiter; +} + +void Pathname::Normalize() { + for (size_t i=0; i= 2) { + pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2); + } + if (pos != std::string::npos) { + return folder_.substr(pos + 1); + } else { + return folder_; + } +} + +std::string Pathname::parent_folder() const { + std::string::size_type pos = std::string::npos; + if (folder_.size() >= 2) { + pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2); + } + if (pos != std::string::npos) { + return folder_.substr(0, pos + 1); + } else { + return EMPTY_STR; + } +} + +void Pathname::SetFolder(const std::string& folder) { + folder_.assign(folder); + // Ensure folder ends in a path delimiter + if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) { + folder_.push_back(folder_delimiter_); + } +} + +void Pathname::AppendFolder(const std::string& folder) { + folder_.append(folder); + // Ensure folder ends in a path delimiter + if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) { + folder_.push_back(folder_delimiter_); + } +} + +std::string Pathname::basename() const { + return basename_; +} + +bool Pathname::SetBasename(const std::string& basename) { + if(basename.find_first_of(FOLDER_DELIMS) != std::string::npos) { + return false; + } + basename_.assign(basename); + return true; +} + +std::string Pathname::extension() const { + return extension_; +} + +bool Pathname::SetExtension(const std::string& extension) { + if (extension.find_first_of(FOLDER_DELIMS) != std::string::npos || + extension.find_first_of(EXT_DELIM, 1) != std::string::npos) { + return false; + } + extension_.assign(extension); + // Ensure extension begins with the extension delimiter + if (!extension_.empty() && (extension_[0] != EXT_DELIM)) { + extension_.insert(extension_.begin(), EXT_DELIM); + } + return true; +} + +std::string Pathname::filename() const { + std::string filename(basename_); + filename.append(extension_); + return filename; +} + +bool Pathname::SetFilename(const std::string& filename) { + std::string::size_type pos = filename.rfind(EXT_DELIM); + if ((pos == std::string::npos) || (pos == 0)) { + return SetExtension(EMPTY_STR) && SetBasename(filename); + } else { + return SetExtension(filename.substr(pos)) && SetBasename(filename.substr(0, pos)); + } +} + +#if defined(WEBRTC_WIN) +bool Pathname::GetDrive(char *drive, uint32 bytes) const { + return GetDrive(drive, bytes, folder_); +} + +// static +bool Pathname::GetDrive(char *drive, uint32 bytes, + const std::string& pathname) { + // need at lease 4 bytes to save c: + if (bytes < 4 || pathname.size() < 3) { + return false; + } + + memcpy(drive, pathname.c_str(), 3); + drive[3] = 0; + // sanity checking + return (isalpha(drive[0]) && + drive[1] == ':' && + drive[2] == '\\'); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/pathutils.h b/webrtc/base/pathutils.h new file mode 100644 index 000000000..8f07e1dbc --- /dev/null +++ b/webrtc/base/pathutils.h @@ -0,0 +1,163 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_PATHUTILS_H__ +#define WEBRTC_BASE_PATHUTILS_H__ + +#include +// Temporary, until deprecated helpers are removed. +#include "webrtc/base/fileutils.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Pathname - parsing of pathnames into components, and vice versa. +// +// To establish consistent terminology, a filename never contains a folder +// component. A folder never contains a filename. A pathname may include +// a folder and/or filename component. Here are some examples: +// +// pathname() /home/john/example.txt +// folder() /home/john/ +// filename() example.txt +// parent_folder() /home/ +// folder_name() john/ +// basename() example +// extension() .txt +// +// Basename may begin, end, and/or include periods, but no folder delimiters. +// If extension exists, it consists of a period followed by zero or more +// non-period/non-delimiter characters, and basename is non-empty. +/////////////////////////////////////////////////////////////////////////////// + +class Pathname { +public: + // Folder delimiters are slash and backslash + static bool IsFolderDelimiter(char ch); + static char DefaultFolderDelimiter(); + + Pathname(); + Pathname(const std::string& pathname); + Pathname(const std::string& folder, const std::string& filename); + + // Set's the default folder delimiter for this Pathname + char folder_delimiter() const { return folder_delimiter_; } + void SetFolderDelimiter(char delimiter); + + // Normalize changes all folder delimiters to folder_delimiter() + void Normalize(); + + // Reset to the empty pathname + void clear(); + + // Returns true if the pathname is empty. Note: this->pathname().empty() + // is always false. + bool empty() const; + + std::string url() const; + + // Returns the folder and filename components. If the pathname is empty, + // returns a string representing the current directory (as a relative path, + // i.e., "."). + std::string pathname() const; + void SetPathname(const std::string& pathname); + void SetPathname(const std::string& folder, const std::string& filename); + + // Append pathname to the current folder (if any). Any existing filename + // will be discarded. + void AppendPathname(const std::string& pathname); + + std::string folder() const; + std::string folder_name() const; + std::string parent_folder() const; + // SetFolder and AppendFolder will append a folder delimiter, if needed. + void SetFolder(const std::string& folder); + void AppendFolder(const std::string& folder); + + std::string basename() const; + bool SetBasename(const std::string& basename); + + std::string extension() const; + // SetExtension will prefix a period, if needed. + bool SetExtension(const std::string& extension); + + std::string filename() const; + bool SetFilename(const std::string& filename); + +#if defined(WEBRTC_WIN) + bool GetDrive(char *drive, uint32 bytes) const; + static bool GetDrive(char *drive, uint32 bytes,const std::string& pathname); +#endif + +private: + std::string folder_, basename_, extension_; + char folder_delimiter_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Global Helpers (deprecated) +/////////////////////////////////////////////////////////////////////////////// + +inline void SetOrganizationName(const std::string& organization) { + Filesystem::SetOrganizationName(organization); +} +inline void SetApplicationName(const std::string& application) { + Filesystem::SetApplicationName(application); +} +inline void GetOrganizationName(std::string* organization) { + Filesystem::GetOrganizationName(organization); +} +inline void GetApplicationName(std::string* application) { + Filesystem::GetApplicationName(application); +} +inline bool CreateFolder(const Pathname& path) { + return Filesystem::CreateFolder(path); +} +inline bool FinishPath(Pathname& path, bool create, const std::string& append) { + if (!append.empty()) + path.AppendFolder(append); + return !create || CreateFolder(path); +} +// Note: this method uses the convention of / for the temporary +// folder. Filesystem uses /. We will be migrating exclusively +// to // eventually. Since these are temp folders, +// it's probably ok to orphan them during the transition. +inline bool GetTemporaryFolder(Pathname& path, bool create, + const std::string& append) { + std::string application_name; + Filesystem::GetApplicationName(&application_name); + ASSERT(!application_name.empty()); + return Filesystem::GetTemporaryFolder(path, create, &application_name) + && FinishPath(path, create, append); +} +inline bool GetAppDataFolder(Pathname& path, bool create, + const std::string& append) { + ASSERT(!create); // TODO: Support create flag on Filesystem::GetAppDataFolder. + return Filesystem::GetAppDataFolder(&path, true) + && FinishPath(path, create, append); +} +inline bool CleanupTemporaryFolder() { + Pathname path; + if (!GetTemporaryFolder(path, false, "")) + return false; + if (Filesystem::IsAbsent(path)) + return true; + if (!Filesystem::IsTemporaryPath(path)) { + ASSERT(false); + return false; + } + return Filesystem::DeleteFolderContents(path); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_PATHUTILS_H__ diff --git a/webrtc/base/pathutils_unittest.cc b/webrtc/base/pathutils_unittest.cc new file mode 100644 index 000000000..a04effa68 --- /dev/null +++ b/webrtc/base/pathutils_unittest.cc @@ -0,0 +1,48 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/pathutils.h" +#include "webrtc/base/gunit.h" + +TEST(Pathname, ReturnsDotForEmptyPathname) { + const std::string kCWD = + std::string(".") + rtc::Pathname::DefaultFolderDelimiter(); + + rtc::Pathname path("/", ""); + EXPECT_FALSE(path.empty()); + EXPECT_FALSE(path.folder().empty()); + EXPECT_TRUE (path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(std::string("/"), path.pathname()); + + path.SetPathname("", "foo"); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE (path.folder().empty()); + EXPECT_FALSE(path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(std::string("foo"), path.pathname()); + + path.SetPathname("", ""); + EXPECT_TRUE (path.empty()); + EXPECT_TRUE (path.folder().empty()); + EXPECT_TRUE (path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(kCWD, path.pathname()); + + path.SetPathname(kCWD, ""); + EXPECT_FALSE(path.empty()); + EXPECT_FALSE(path.folder().empty()); + EXPECT_TRUE (path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(kCWD, path.pathname()); + + rtc::Pathname path2("c:/foo bar.txt"); + EXPECT_EQ(path2.url(), std::string("file:///c:/foo%20bar.txt")); +} diff --git a/webrtc/base/physicalsocketserver.cc b/webrtc/base/physicalsocketserver.cc new file mode 100644 index 000000000..cff5e4dcb --- /dev/null +++ b/webrtc/base/physicalsocketserver.cc @@ -0,0 +1,1659 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#undef SetPort +#endif + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/winping.h" +#include "webrtc/base/win32socketinit.h" + +// stm: this will tell us if we are on OSX +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(WEBRTC_POSIX) +#include // for TCP_NODELAY +#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h +typedef void* SockOptArg; +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +typedef char* SockOptArg; +#endif + +namespace rtc { + +#if defined(WEBRTC_WIN) +// Standard MTUs, from RFC 1191 +const uint16 PACKET_MAXIMUMS[] = { + 65535, // Theoretical maximum, Hyperchannel + 32000, // Nothing + 17914, // 16Mb IBM Token Ring + 8166, // IEEE 802.4 + //4464, // IEEE 802.5 (4Mb max) + 4352, // FDDI + //2048, // Wideband Network + 2002, // IEEE 802.5 (4Mb recommended) + //1536, // Expermental Ethernet Networks + //1500, // Ethernet, Point-to-Point (default) + 1492, // IEEE 802.3 + 1006, // SLIP, ARPANET + //576, // X.25 Networks + //544, // DEC IP Portal + //512, // NETBIOS + 508, // IEEE 802/Source-Rt Bridge, ARCNET + 296, // Point-to-Point (low delay) + 68, // Official minimum + 0, // End of list marker +}; + +static const int IP_HEADER_SIZE = 20u; +static const int IPV6_HEADER_SIZE = 40u; +static const int ICMP_HEADER_SIZE = 8u; +static const int ICMP_PING_TIMEOUT_MILLIS = 10000u; +#endif + +class PhysicalSocket : public AsyncSocket, public sigslot::has_slots<> { + public: + PhysicalSocket(PhysicalSocketServer* ss, SOCKET s = INVALID_SOCKET) + : ss_(ss), s_(s), enabled_events_(0), error_(0), + state_((s == INVALID_SOCKET) ? CS_CLOSED : CS_CONNECTED), + resolver_(NULL) { +#if defined(WEBRTC_WIN) + // EnsureWinsockInit() ensures that winsock is initialized. The default + // version of this function doesn't do anything because winsock is + // initialized by constructor of a static object. If neccessary libjingle + // users can link it with a different version of this function by replacing + // win32socketinit.cc. See win32socketinit.cc for more details. + EnsureWinsockInit(); +#endif + if (s_ != INVALID_SOCKET) { + enabled_events_ = DE_READ | DE_WRITE; + + int type = SOCK_STREAM; + socklen_t len = sizeof(type); + VERIFY(0 == getsockopt(s_, SOL_SOCKET, SO_TYPE, (SockOptArg)&type, &len)); + udp_ = (SOCK_DGRAM == type); + } + } + + virtual ~PhysicalSocket() { + Close(); + } + + // Creates the underlying OS socket (same as the "socket" function). + virtual bool Create(int family, int type) { + Close(); + s_ = ::socket(family, type, 0); + udp_ = (SOCK_DGRAM == type); + UpdateLastError(); + if (udp_) + enabled_events_ = DE_READ | DE_WRITE; + return s_ != INVALID_SOCKET; + } + + SocketAddress GetLocalAddress() const { + sockaddr_storage addr_storage = {0}; + socklen_t addrlen = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int result = ::getsockname(s_, addr, &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr_storage, &address); + } else { + LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket=" + << s_; + } + return address; + } + + SocketAddress GetRemoteAddress() const { + sockaddr_storage addr_storage = {0}; + socklen_t addrlen = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int result = ::getpeername(s_, addr, &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr_storage, &address); + } else { + LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket=" + << s_; + } + return address; + } + + int Bind(const SocketAddress& bind_addr) { + sockaddr_storage addr_storage; + size_t len = bind_addr.ToSockAddrStorage(&addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int err = ::bind(s_, addr, static_cast(len)); + UpdateLastError(); +#ifdef _DEBUG + if (0 == err) { + dbg_addr_ = "Bound @ "; + dbg_addr_.append(GetLocalAddress().ToString()); + } +#endif // _DEBUG + return err; + } + + int Connect(const SocketAddress& addr) { + // TODO: Implicit creation is required to reconnect... + // ...but should we make it more explicit? + if (state_ != CS_CLOSED) { + SetError(EALREADY); + return SOCKET_ERROR; + } + if (addr.IsUnresolved()) { + LOG(LS_VERBOSE) << "Resolving addr in PhysicalSocket::Connect"; + resolver_ = new AsyncResolver(); + resolver_->SignalDone.connect(this, &PhysicalSocket::OnResolveResult); + resolver_->Start(addr); + state_ = CS_CONNECTING; + return 0; + } + + return DoConnect(addr); + } + + int DoConnect(const SocketAddress& connect_addr) { + if ((s_ == INVALID_SOCKET) && + !Create(connect_addr.family(), SOCK_STREAM)) { + return SOCKET_ERROR; + } + sockaddr_storage addr_storage; + size_t len = connect_addr.ToSockAddrStorage(&addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int err = ::connect(s_, addr, static_cast(len)); + UpdateLastError(); + if (err == 0) { + state_ = CS_CONNECTED; + } else if (IsBlockingError(GetError())) { + state_ = CS_CONNECTING; + enabled_events_ |= DE_CONNECT; + } else { + return SOCKET_ERROR; + } + + enabled_events_ |= DE_READ | DE_WRITE; + return 0; + } + + int GetError() const { + CritScope cs(&crit_); + return error_; + } + + void SetError(int error) { + CritScope cs(&crit_); + error_ = error; + } + + ConnState GetState() const { + return state_; + } + + int GetOption(Option opt, int* value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + socklen_t optlen = sizeof(*value); + int ret = ::getsockopt(s_, slevel, sopt, (SockOptArg)value, &optlen); + if (ret != -1 && opt == OPT_DONTFRAGMENT) { +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + *value = (*value != IP_PMTUDISC_DONT) ? 1 : 0; +#endif + } + return ret; + } + + int SetOption(Option opt, int value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + if (opt == OPT_DONTFRAGMENT) { +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + value = (value) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT; +#endif + } + return ::setsockopt(s_, slevel, sopt, (SockOptArg)&value, sizeof(value)); + } + + int Send(const void *pv, size_t cb) { + int sent = ::send(s_, reinterpret_cast(pv), (int)cb, +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // Suppress SIGPIPE. Without this, attempting to send on a socket whose + // other end is closed will result in a SIGPIPE signal being raised to + // our process, which by default will terminate the process, which we + // don't want. By specifying this flag, we'll just get the error EPIPE + // instead and can handle the error gracefully. + MSG_NOSIGNAL +#else + 0 +#endif + ); + UpdateLastError(); + MaybeRemapSendError(); + // We have seen minidumps where this may be false. + ASSERT(sent <= static_cast(cb)); + if ((sent < 0) && IsBlockingError(GetError())) { + enabled_events_ |= DE_WRITE; + } + return sent; + } + + int SendTo(const void* buffer, size_t length, const SocketAddress& addr) { + sockaddr_storage saddr; + size_t len = addr.ToSockAddrStorage(&saddr); + int sent = ::sendto( + s_, static_cast(buffer), static_cast(length), +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // Suppress SIGPIPE. See above for explanation. + MSG_NOSIGNAL, +#else + 0, +#endif + reinterpret_cast(&saddr), static_cast(len)); + UpdateLastError(); + MaybeRemapSendError(); + // We have seen minidumps where this may be false. + ASSERT(sent <= static_cast(length)); + if ((sent < 0) && IsBlockingError(GetError())) { + enabled_events_ |= DE_WRITE; + } + return sent; + } + + int Recv(void* buffer, size_t length) { + int received = ::recv(s_, static_cast(buffer), + static_cast(length), 0); + if ((received == 0) && (length != 0)) { + // Note: on graceful shutdown, recv can return 0. In this case, we + // pretend it is blocking, and then signal close, so that simplifying + // assumptions can be made about Recv. + LOG(LS_WARNING) << "EOF from socket; deferring close event"; + // Must turn this back on so that the select() loop will notice the close + // event. + enabled_events_ |= DE_READ; + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + } + UpdateLastError(); + int error = GetError(); + bool success = (received >= 0) || IsBlockingError(error); + if (udp_ || success) { + enabled_events_ |= DE_READ; + } + if (!success) { + LOG_F(LS_VERBOSE) << "Error = " << error; + } + return received; + } + + int RecvFrom(void* buffer, size_t length, SocketAddress *out_addr) { + sockaddr_storage addr_storage; + socklen_t addr_len = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int received = ::recvfrom(s_, static_cast(buffer), + static_cast(length), 0, addr, &addr_len); + UpdateLastError(); + if ((received >= 0) && (out_addr != NULL)) + SocketAddressFromSockAddrStorage(addr_storage, out_addr); + int error = GetError(); + bool success = (received >= 0) || IsBlockingError(error); + if (udp_ || success) { + enabled_events_ |= DE_READ; + } + if (!success) { + LOG_F(LS_VERBOSE) << "Error = " << error; + } + return received; + } + + int Listen(int backlog) { + int err = ::listen(s_, backlog); + UpdateLastError(); + if (err == 0) { + state_ = CS_CONNECTING; + enabled_events_ |= DE_ACCEPT; +#ifdef _DEBUG + dbg_addr_ = "Listening @ "; + dbg_addr_.append(GetLocalAddress().ToString()); +#endif // _DEBUG + } + return err; + } + + AsyncSocket* Accept(SocketAddress *out_addr) { + sockaddr_storage addr_storage; + socklen_t addr_len = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + SOCKET s = ::accept(s_, addr, &addr_len); + UpdateLastError(); + if (s == INVALID_SOCKET) + return NULL; + enabled_events_ |= DE_ACCEPT; + if (out_addr != NULL) + SocketAddressFromSockAddrStorage(addr_storage, out_addr); + return ss_->WrapSocket(s); + } + + int Close() { + if (s_ == INVALID_SOCKET) + return 0; + int err = ::closesocket(s_); + UpdateLastError(); + s_ = INVALID_SOCKET; + state_ = CS_CLOSED; + enabled_events_ = 0; + if (resolver_) { + resolver_->Destroy(false); + resolver_ = NULL; + } + return err; + } + + int EstimateMTU(uint16* mtu) { + SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + SetError(ENOTCONN); + return -1; + } + +#if defined(WEBRTC_WIN) + // Gets the interface MTU (TTL=1) for the interface used to reach |addr|. + WinPing ping; + if (!ping.IsValid()) { + SetError(EINVAL); // can't think of a better error ID + return -1; + } + int header_size = ICMP_HEADER_SIZE; + if (addr.family() == AF_INET6) { + header_size += IPV6_HEADER_SIZE; + } else if (addr.family() == AF_INET) { + header_size += IP_HEADER_SIZE; + } + + for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) { + int32 size = PACKET_MAXIMUMS[level] - header_size; + WinPing::PingResult result = ping.Ping(addr.ipaddr(), size, + ICMP_PING_TIMEOUT_MILLIS, + 1, false); + if (result == WinPing::PING_FAIL) { + SetError(EINVAL); // can't think of a better error ID + return -1; + } else if (result != WinPing::PING_TOO_LARGE) { + *mtu = PACKET_MAXIMUMS[level]; + return 0; + } + } + + ASSERT(false); + return -1; +#elif defined(WEBRTC_MAC) + // No simple way to do this on Mac OS X. + // SIOCGIFMTU would work if we knew which interface would be used, but + // figuring that out is pretty complicated. For now we'll return an error + // and let the caller pick a default MTU. + SetError(EINVAL); + return -1; +#elif defined(WEBRTC_LINUX) + // Gets the path MTU. + int value; + socklen_t vlen = sizeof(value); + int err = getsockopt(s_, IPPROTO_IP, IP_MTU, &value, &vlen); + if (err < 0) { + UpdateLastError(); + return err; + } + + ASSERT((0 <= value) && (value <= 65536)); + *mtu = value; + return 0; +#elif defined(__native_client__) + // Most socket operations, including this, will fail in NaCl's sandbox. + error_ = EACCES; + return -1; +#endif + } + + SocketServer* socketserver() { return ss_; } + + protected: + void OnResolveResult(AsyncResolverInterface* resolver) { + if (resolver != resolver_) { + return; + } + + int error = resolver_->GetError(); + if (error == 0) { + error = DoConnect(resolver_->address()); + } else { + Close(); + } + + if (error) { + SetError(error); + SignalCloseEvent(this, error); + } + } + + void UpdateLastError() { + SetError(LAST_SYSTEM_ERROR); + } + + void MaybeRemapSendError() { +#if defined(WEBRTC_MAC) + // https://developer.apple.com/library/mac/documentation/Darwin/ + // Reference/ManPages/man2/sendto.2.html + // ENOBUFS - The output queue for a network interface is full. + // This generally indicates that the interface has stopped sending, + // but may be caused by transient congestion. + if (GetError() == ENOBUFS) { + SetError(EWOULDBLOCK); + } +#endif + } + + static int TranslateOption(Option opt, int* slevel, int* sopt) { + switch (opt) { + case OPT_DONTFRAGMENT: +#if defined(WEBRTC_WIN) + *slevel = IPPROTO_IP; + *sopt = IP_DONTFRAGMENT; + break; +#elif defined(WEBRTC_MAC) || defined(BSD) || defined(__native_client__) + LOG(LS_WARNING) << "Socket::OPT_DONTFRAGMENT not supported."; + return -1; +#elif defined(WEBRTC_POSIX) + *slevel = IPPROTO_IP; + *sopt = IP_MTU_DISCOVER; + break; +#endif + case OPT_RCVBUF: + *slevel = SOL_SOCKET; + *sopt = SO_RCVBUF; + break; + case OPT_SNDBUF: + *slevel = SOL_SOCKET; + *sopt = SO_SNDBUF; + break; + case OPT_NODELAY: + *slevel = IPPROTO_TCP; + *sopt = TCP_NODELAY; + break; + case OPT_DSCP: + LOG(LS_WARNING) << "Socket::OPT_DSCP not supported."; + return -1; + case OPT_RTP_SENDTIME_EXTN_ID: + return -1; // No logging is necessary as this not a OS socket option. + default: + ASSERT(false); + return -1; + } + return 0; + } + + PhysicalSocketServer* ss_; + SOCKET s_; + uint8 enabled_events_; + bool udp_; + int error_; + // Protects |error_| that is accessed from different threads. + mutable CriticalSection crit_; + ConnState state_; + AsyncResolver* resolver_; + +#ifdef _DEBUG + std::string dbg_addr_; +#endif // _DEBUG; +}; + +#if defined(WEBRTC_POSIX) +class EventDispatcher : public Dispatcher { + public: + EventDispatcher(PhysicalSocketServer* ss) : ss_(ss), fSignaled_(false) { + if (pipe(afd_) < 0) + LOG(LERROR) << "pipe failed"; + ss_->Add(this); + } + + virtual ~EventDispatcher() { + ss_->Remove(this); + close(afd_[0]); + close(afd_[1]); + } + + virtual void Signal() { + CritScope cs(&crit_); + if (!fSignaled_) { + const uint8 b[1] = { 0 }; + if (VERIFY(1 == write(afd_[1], b, sizeof(b)))) { + fSignaled_ = true; + } + } + } + + virtual uint32 GetRequestedEvents() { + return DE_READ; + } + + virtual void OnPreEvent(uint32 ff) { + // It is not possible to perfectly emulate an auto-resetting event with + // pipes. This simulates it by resetting before the event is handled. + + CritScope cs(&crit_); + if (fSignaled_) { + uint8 b[4]; // Allow for reading more than 1 byte, but expect 1. + VERIFY(1 == read(afd_[0], b, sizeof(b))); + fSignaled_ = false; + } + } + + virtual void OnEvent(uint32 ff, int err) { + ASSERT(false); + } + + virtual int GetDescriptor() { + return afd_[0]; + } + + virtual bool IsDescriptorClosed() { + return false; + } + + private: + PhysicalSocketServer *ss_; + int afd_[2]; + bool fSignaled_; + CriticalSection crit_; +}; + +// These two classes use the self-pipe trick to deliver POSIX signals to our +// select loop. This is the only safe, reliable, cross-platform way to do +// non-trivial things with a POSIX signal in an event-driven program (until +// proper pselect() implementations become ubiquitous). + +class PosixSignalHandler { + public: + // POSIX only specifies 32 signals, but in principle the system might have + // more and the programmer might choose to use them, so we size our array + // for 128. + static const int kNumPosixSignals = 128; + + // There is just a single global instance. (Signal handlers do not get any + // sort of user-defined void * parameter, so they can't access anything that + // isn't global.) + static PosixSignalHandler* Instance() { + LIBJINGLE_DEFINE_STATIC_LOCAL(PosixSignalHandler, instance, ()); + return &instance; + } + + // Returns true if the given signal number is set. + bool IsSignalSet(int signum) const { + ASSERT(signum < ARRAY_SIZE(received_signal_)); + if (signum < ARRAY_SIZE(received_signal_)) { + return received_signal_[signum]; + } else { + return false; + } + } + + // Clears the given signal number. + void ClearSignal(int signum) { + ASSERT(signum < ARRAY_SIZE(received_signal_)); + if (signum < ARRAY_SIZE(received_signal_)) { + received_signal_[signum] = false; + } + } + + // Returns the file descriptor to monitor for signal events. + int GetDescriptor() const { + return afd_[0]; + } + + // This is called directly from our real signal handler, so it must be + // signal-handler-safe. That means it cannot assume anything about the + // user-level state of the process, since the handler could be executed at any + // time on any thread. + void OnPosixSignalReceived(int signum) { + if (signum >= ARRAY_SIZE(received_signal_)) { + // We don't have space in our array for this. + return; + } + // Set a flag saying we've seen this signal. + received_signal_[signum] = true; + // Notify application code that we got a signal. + const uint8 b[1] = { 0 }; + if (-1 == write(afd_[1], b, sizeof(b))) { + // Nothing we can do here. If there's an error somehow then there's + // nothing we can safely do from a signal handler. + // No, we can't even safely log it. + // But, we still have to check the return value here. Otherwise, + // GCC 4.4.1 complains ignoring return value. Even (void) doesn't help. + return; + } + } + + private: + PosixSignalHandler() { + if (pipe(afd_) < 0) { + LOG_ERR(LS_ERROR) << "pipe failed"; + return; + } + if (fcntl(afd_[0], F_SETFL, O_NONBLOCK) < 0) { + LOG_ERR(LS_WARNING) << "fcntl #1 failed"; + } + if (fcntl(afd_[1], F_SETFL, O_NONBLOCK) < 0) { + LOG_ERR(LS_WARNING) << "fcntl #2 failed"; + } + memset(const_cast(static_cast(received_signal_)), + 0, + sizeof(received_signal_)); + } + + ~PosixSignalHandler() { + int fd1 = afd_[0]; + int fd2 = afd_[1]; + // We clobber the stored file descriptor numbers here or else in principle + // a signal that happens to be delivered during application termination + // could erroneously write a zero byte to an unrelated file handle in + // OnPosixSignalReceived() if some other file happens to be opened later + // during shutdown and happens to be given the same file descriptor number + // as our pipe had. Unfortunately even with this precaution there is still a + // race where that could occur if said signal happens to be handled + // concurrently with this code and happens to have already read the value of + // afd_[1] from memory before we clobber it, but that's unlikely. + afd_[0] = -1; + afd_[1] = -1; + close(fd1); + close(fd2); + } + + int afd_[2]; + // These are boolean flags that will be set in our signal handler and read + // and cleared from Wait(). There is a race involved in this, but it is + // benign. The signal handler sets the flag before signaling the pipe, so + // we'll never end up blocking in select() while a flag is still true. + // However, if two of the same signal arrive close to each other then it's + // possible that the second time the handler may set the flag while it's still + // true, meaning that signal will be missed. But the first occurrence of it + // will still be handled, so this isn't a problem. + // Volatile is not necessary here for correctness, but this data _is_ volatile + // so I've marked it as such. + volatile uint8 received_signal_[kNumPosixSignals]; +}; + +class PosixSignalDispatcher : public Dispatcher { + public: + PosixSignalDispatcher(PhysicalSocketServer *owner) : owner_(owner) { + owner_->Add(this); + } + + virtual ~PosixSignalDispatcher() { + owner_->Remove(this); + } + + virtual uint32 GetRequestedEvents() { + return DE_READ; + } + + virtual void OnPreEvent(uint32 ff) { + // Events might get grouped if signals come very fast, so we read out up to + // 16 bytes to make sure we keep the pipe empty. + uint8 b[16]; + ssize_t ret = read(GetDescriptor(), b, sizeof(b)); + if (ret < 0) { + LOG_ERR(LS_WARNING) << "Error in read()"; + } else if (ret == 0) { + LOG(LS_WARNING) << "Should have read at least one byte"; + } + } + + virtual void OnEvent(uint32 ff, int err) { + for (int signum = 0; signum < PosixSignalHandler::kNumPosixSignals; + ++signum) { + if (PosixSignalHandler::Instance()->IsSignalSet(signum)) { + PosixSignalHandler::Instance()->ClearSignal(signum); + HandlerMap::iterator i = handlers_.find(signum); + if (i == handlers_.end()) { + // This can happen if a signal is delivered to our process at around + // the same time as we unset our handler for it. It is not an error + // condition, but it's unusual enough to be worth logging. + LOG(LS_INFO) << "Received signal with no handler: " << signum; + } else { + // Otherwise, execute our handler. + (*i->second)(signum); + } + } + } + } + + virtual int GetDescriptor() { + return PosixSignalHandler::Instance()->GetDescriptor(); + } + + virtual bool IsDescriptorClosed() { + return false; + } + + void SetHandler(int signum, void (*handler)(int)) { + handlers_[signum] = handler; + } + + void ClearHandler(int signum) { + handlers_.erase(signum); + } + + bool HasHandlers() { + return !handlers_.empty(); + } + + private: + typedef std::map HandlerMap; + + HandlerMap handlers_; + // Our owner. + PhysicalSocketServer *owner_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { + public: + explicit SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) { + } + SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) { + } + + virtual ~SocketDispatcher() { + Close(); + } + + bool Initialize() { + ss_->Add(this); + fcntl(s_, F_SETFL, fcntl(s_, F_GETFL, 0) | O_NONBLOCK); + return true; + } + + virtual bool Create(int type) { + return Create(AF_INET, type); + } + + virtual bool Create(int family, int type) { + // Change the socket to be non-blocking. + if (!PhysicalSocket::Create(family, type)) + return false; + + return Initialize(); + } + + virtual int GetDescriptor() { + return s_; + } + + virtual bool IsDescriptorClosed() { + // We don't have a reliable way of distinguishing end-of-stream + // from readability. So test on each readable call. Is this + // inefficient? Probably. + char ch; + ssize_t res = ::recv(s_, &ch, 1, MSG_PEEK); + if (res > 0) { + // Data available, so not closed. + return false; + } else if (res == 0) { + // EOF, so closed. + return true; + } else { // error + switch (errno) { + // Returned if we've already closed s_. + case EBADF: + // Returned during ungraceful peer shutdown. + case ECONNRESET: + return true; + default: + // Assume that all other errors are just blocking errors, meaning the + // connection is still good but we just can't read from it right now. + // This should only happen when connecting (and at most once), because + // in all other cases this function is only called if the file + // descriptor is already known to be in the readable state. However, + // it's not necessary a problem if we spuriously interpret a + // "connection lost"-type error as a blocking error, because typically + // the next recv() will get EOF, so we'll still eventually notice that + // the socket is closed. + LOG_ERR(LS_WARNING) << "Assuming benign blocking error"; + return false; + } + } + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + if ((ff & DE_CONNECT) != 0) + state_ = CS_CONNECTED; + if ((ff & DE_CLOSE) != 0) + state_ = CS_CLOSED; + } + + virtual void OnEvent(uint32 ff, int err) { + // Make sure we deliver connect/accept first. Otherwise, consumers may see + // something like a READ followed by a CONNECT, which would be odd. + if ((ff & DE_CONNECT) != 0) { + enabled_events_ &= ~DE_CONNECT; + SignalConnectEvent(this); + } + if ((ff & DE_ACCEPT) != 0) { + enabled_events_ &= ~DE_ACCEPT; + SignalReadEvent(this); + } + if ((ff & DE_READ) != 0) { + enabled_events_ &= ~DE_READ; + SignalReadEvent(this); + } + if ((ff & DE_WRITE) != 0) { + enabled_events_ &= ~DE_WRITE; + SignalWriteEvent(this); + } + if ((ff & DE_CLOSE) != 0) { + // The socket is now dead to us, so stop checking it. + enabled_events_ = 0; + SignalCloseEvent(this, err); + } + } + + virtual int Close() { + if (s_ == INVALID_SOCKET) + return 0; + + ss_->Remove(this); + return PhysicalSocket::Close(); + } +}; + +class FileDispatcher: public Dispatcher, public AsyncFile { + public: + FileDispatcher(int fd, PhysicalSocketServer *ss) : ss_(ss), fd_(fd) { + set_readable(true); + + ss_->Add(this); + + fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL, 0) | O_NONBLOCK); + } + + virtual ~FileDispatcher() { + ss_->Remove(this); + } + + SocketServer* socketserver() { return ss_; } + + virtual int GetDescriptor() { + return fd_; + } + + virtual bool IsDescriptorClosed() { + return false; + } + + virtual uint32 GetRequestedEvents() { + return flags_; + } + + virtual void OnPreEvent(uint32 ff) { + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & DE_READ) != 0) + SignalReadEvent(this); + if ((ff & DE_WRITE) != 0) + SignalWriteEvent(this); + if ((ff & DE_CLOSE) != 0) + SignalCloseEvent(this, err); + } + + virtual bool readable() { + return (flags_ & DE_READ) != 0; + } + + virtual void set_readable(bool value) { + flags_ = value ? (flags_ | DE_READ) : (flags_ & ~DE_READ); + } + + virtual bool writable() { + return (flags_ & DE_WRITE) != 0; + } + + virtual void set_writable(bool value) { + flags_ = value ? (flags_ | DE_WRITE) : (flags_ & ~DE_WRITE); + } + + private: + PhysicalSocketServer* ss_; + int fd_; + int flags_; +}; + +AsyncFile* PhysicalSocketServer::CreateFile(int fd) { + return new FileDispatcher(fd, this); +} + +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +static uint32 FlagsToEvents(uint32 events) { + uint32 ffFD = FD_CLOSE; + if (events & DE_READ) + ffFD |= FD_READ; + if (events & DE_WRITE) + ffFD |= FD_WRITE; + if (events & DE_CONNECT) + ffFD |= FD_CONNECT; + if (events & DE_ACCEPT) + ffFD |= FD_ACCEPT; + return ffFD; +} + +class EventDispatcher : public Dispatcher { + public: + EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) { + hev_ = WSACreateEvent(); + if (hev_) { + ss_->Add(this); + } + } + + ~EventDispatcher() { + if (hev_ != NULL) { + ss_->Remove(this); + WSACloseEvent(hev_); + hev_ = NULL; + } + } + + virtual void Signal() { + if (hev_ != NULL) + WSASetEvent(hev_); + } + + virtual uint32 GetRequestedEvents() { + return 0; + } + + virtual void OnPreEvent(uint32 ff) { + WSAResetEvent(hev_); + } + + virtual void OnEvent(uint32 ff, int err) { + } + + virtual WSAEVENT GetWSAEvent() { + return hev_; + } + + virtual SOCKET GetSocket() { + return INVALID_SOCKET; + } + + virtual bool CheckSignalClose() { return false; } + +private: + PhysicalSocketServer* ss_; + WSAEVENT hev_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { + public: + static int next_id_; + int id_; + bool signal_close_; + int signal_err_; + + SocketDispatcher(PhysicalSocketServer* ss) + : PhysicalSocket(ss), + id_(0), + signal_close_(false) { + } + + SocketDispatcher(SOCKET s, PhysicalSocketServer* ss) + : PhysicalSocket(ss, s), + id_(0), + signal_close_(false) { + } + + virtual ~SocketDispatcher() { + Close(); + } + + bool Initialize() { + ASSERT(s_ != INVALID_SOCKET); + // Must be a non-blocking + u_long argp = 1; + ioctlsocket(s_, FIONBIO, &argp); + ss_->Add(this); + return true; + } + + virtual bool Create(int type) { + return Create(AF_INET, type); + } + + virtual bool Create(int family, int type) { + // Create socket + if (!PhysicalSocket::Create(family, type)) + return false; + + if (!Initialize()) + return false; + + do { id_ = ++next_id_; } while (id_ == 0); + return true; + } + + virtual int Close() { + if (s_ == INVALID_SOCKET) + return 0; + + id_ = 0; + signal_close_ = false; + ss_->Remove(this); + return PhysicalSocket::Close(); + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + if ((ff & DE_CONNECT) != 0) + state_ = CS_CONNECTED; + // We set CS_CLOSED from CheckSignalClose. + } + + virtual void OnEvent(uint32 ff, int err) { + int cache_id = id_; + // Make sure we deliver connect/accept first. Otherwise, consumers may see + // something like a READ followed by a CONNECT, which would be odd. + if (((ff & DE_CONNECT) != 0) && (id_ == cache_id)) { + if (ff != DE_CONNECT) + LOG(LS_VERBOSE) << "Signalled with DE_CONNECT: " << ff; + enabled_events_ &= ~DE_CONNECT; +#ifdef _DEBUG + dbg_addr_ = "Connected @ "; + dbg_addr_.append(GetRemoteAddress().ToString()); +#endif // _DEBUG + SignalConnectEvent(this); + } + if (((ff & DE_ACCEPT) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~DE_ACCEPT; + SignalReadEvent(this); + } + if ((ff & DE_READ) != 0) { + enabled_events_ &= ~DE_READ; + SignalReadEvent(this); + } + if (((ff & DE_WRITE) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~DE_WRITE; + SignalWriteEvent(this); + } + if (((ff & DE_CLOSE) != 0) && (id_ == cache_id)) { + signal_close_ = true; + signal_err_ = err; + } + } + + virtual WSAEVENT GetWSAEvent() { + return WSA_INVALID_EVENT; + } + + virtual SOCKET GetSocket() { + return s_; + } + + virtual bool CheckSignalClose() { + if (!signal_close_) + return false; + + char ch; + if (recv(s_, &ch, 1, MSG_PEEK) > 0) + return false; + + state_ = CS_CLOSED; + signal_close_ = false; + SignalCloseEvent(this, signal_err_); + return true; + } +}; + +int SocketDispatcher::next_id_ = 0; + +#endif // WEBRTC_WIN + +// Sets the value of a boolean value to false when signaled. +class Signaler : public EventDispatcher { + public: + Signaler(PhysicalSocketServer* ss, bool* pf) + : EventDispatcher(ss), pf_(pf) { + } + virtual ~Signaler() { } + + void OnEvent(uint32 ff, int err) { + if (pf_) + *pf_ = false; + } + + private: + bool *pf_; +}; + +PhysicalSocketServer::PhysicalSocketServer() + : fWait_(false) { + signal_wakeup_ = new Signaler(this, &fWait_); +#if defined(WEBRTC_WIN) + socket_ev_ = WSACreateEvent(); +#endif +} + +PhysicalSocketServer::~PhysicalSocketServer() { +#if defined(WEBRTC_WIN) + WSACloseEvent(socket_ev_); +#endif +#if defined(WEBRTC_POSIX) + signal_dispatcher_.reset(); +#endif + delete signal_wakeup_; + ASSERT(dispatchers_.empty()); +} + +void PhysicalSocketServer::WakeUp() { + signal_wakeup_->Signal(); +} + +Socket* PhysicalSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* PhysicalSocketServer::CreateSocket(int family, int type) { + PhysicalSocket* socket = new PhysicalSocket(this); + if (socket->Create(family, type)) { + return socket; + } else { + delete socket; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int family, int type) { + SocketDispatcher* dispatcher = new SocketDispatcher(this); + if (dispatcher->Create(family, type)) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::WrapSocket(SOCKET s) { + SocketDispatcher* dispatcher = new SocketDispatcher(s, this); + if (dispatcher->Initialize()) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +void PhysicalSocketServer::Add(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + // Prevent duplicates. This can cause dead dispatchers to stick around. + DispatcherList::iterator pos = std::find(dispatchers_.begin(), + dispatchers_.end(), + pdispatcher); + if (pos != dispatchers_.end()) + return; + dispatchers_.push_back(pdispatcher); +} + +void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + DispatcherList::iterator pos = std::find(dispatchers_.begin(), + dispatchers_.end(), + pdispatcher); + // We silently ignore duplicate calls to Add, so we should silently ignore + // the (expected) symmetric calls to Remove. Note that this may still hide + // a real issue, so we at least log a warning about it. + if (pos == dispatchers_.end()) { + LOG(LS_WARNING) << "PhysicalSocketServer asked to remove a unknown " + << "dispatcher, potentially from a duplicate call to Add."; + return; + } + size_t index = pos - dispatchers_.begin(); + dispatchers_.erase(pos); + for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end(); + ++it) { + if (index < **it) { + --**it; + } + } +} + +#if defined(WEBRTC_POSIX) +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) { + // Calculate timing information + + struct timeval *ptvWait = NULL; + struct timeval tvWait; + struct timeval tvStop; + if (cmsWait != kForever) { + // Calculate wait timeval + tvWait.tv_sec = cmsWait / 1000; + tvWait.tv_usec = (cmsWait % 1000) * 1000; + ptvWait = &tvWait; + + // Calculate when to return in a timeval + gettimeofday(&tvStop, NULL); + tvStop.tv_sec += tvWait.tv_sec; + tvStop.tv_usec += tvWait.tv_usec; + if (tvStop.tv_usec >= 1000000) { + tvStop.tv_usec -= 1000000; + tvStop.tv_sec += 1; + } + } + + // Zero all fd_sets. Don't need to do this inside the loop since + // select() zeros the descriptors not signaled + + fd_set fdsRead; + FD_ZERO(&fdsRead); + fd_set fdsWrite; + FD_ZERO(&fdsWrite); + + fWait_ = true; + + while (fWait_) { + int fdmax = -1; + { + CritScope cr(&crit_); + for (size_t i = 0; i < dispatchers_.size(); ++i) { + // Query dispatchers for read and write wait state + Dispatcher *pdispatcher = dispatchers_[i]; + ASSERT(pdispatcher); + if (!process_io && (pdispatcher != signal_wakeup_)) + continue; + int fd = pdispatcher->GetDescriptor(); + if (fd > fdmax) + fdmax = fd; + + uint32 ff = pdispatcher->GetRequestedEvents(); + if (ff & (DE_READ | DE_ACCEPT)) + FD_SET(fd, &fdsRead); + if (ff & (DE_WRITE | DE_CONNECT)) + FD_SET(fd, &fdsWrite); + } + } + + // Wait then call handlers as appropriate + // < 0 means error + // 0 means timeout + // > 0 means count of descriptors ready + int n = select(fdmax + 1, &fdsRead, &fdsWrite, NULL, ptvWait); + + // If error, return error. + if (n < 0) { + if (errno != EINTR) { + LOG_E(LS_ERROR, EN, errno) << "select"; + return false; + } + // Else ignore the error and keep going. If this EINTR was for one of the + // signals managed by this PhysicalSocketServer, the + // PosixSignalDeliveryDispatcher will be in the signaled state in the next + // iteration. + } else if (n == 0) { + // If timeout, return success + return true; + } else { + // We have signaled descriptors + CritScope cr(&crit_); + for (size_t i = 0; i < dispatchers_.size(); ++i) { + Dispatcher *pdispatcher = dispatchers_[i]; + int fd = pdispatcher->GetDescriptor(); + uint32 ff = 0; + int errcode = 0; + + // Reap any error code, which can be signaled through reads or writes. + // TODO: Should we set errcode if getsockopt fails? + if (FD_ISSET(fd, &fdsRead) || FD_ISSET(fd, &fdsWrite)) { + socklen_t len = sizeof(errcode); + ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &len); + } + + // Check readable descriptors. If we're waiting on an accept, signal + // that. Otherwise we're waiting for data, check to see if we're + // readable or really closed. + // TODO: Only peek at TCP descriptors. + if (FD_ISSET(fd, &fdsRead)) { + FD_CLR(fd, &fdsRead); + if (pdispatcher->GetRequestedEvents() & DE_ACCEPT) { + ff |= DE_ACCEPT; + } else if (errcode || pdispatcher->IsDescriptorClosed()) { + ff |= DE_CLOSE; + } else { + ff |= DE_READ; + } + } + + // Check writable descriptors. If we're waiting on a connect, detect + // success versus failure by the reaped error code. + if (FD_ISSET(fd, &fdsWrite)) { + FD_CLR(fd, &fdsWrite); + if (pdispatcher->GetRequestedEvents() & DE_CONNECT) { + if (!errcode) { + ff |= DE_CONNECT; + } else { + ff |= DE_CLOSE; + } + } else { + ff |= DE_WRITE; + } + } + + // Tell the descriptor about the event. + if (ff != 0) { + pdispatcher->OnPreEvent(ff); + pdispatcher->OnEvent(ff, errcode); + } + } + } + + // Recalc the time remaining to wait. Doing it here means it doesn't get + // calced twice the first time through the loop + if (ptvWait) { + ptvWait->tv_sec = 0; + ptvWait->tv_usec = 0; + struct timeval tvT; + gettimeofday(&tvT, NULL); + if ((tvStop.tv_sec > tvT.tv_sec) + || ((tvStop.tv_sec == tvT.tv_sec) + && (tvStop.tv_usec > tvT.tv_usec))) { + ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec; + ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec; + if (ptvWait->tv_usec < 0) { + ASSERT(ptvWait->tv_sec > 0); + ptvWait->tv_usec += 1000000; + ptvWait->tv_sec -= 1; + } + } + } + } + + return true; +} + +static void GlobalSignalHandler(int signum) { + PosixSignalHandler::Instance()->OnPosixSignalReceived(signum); +} + +bool PhysicalSocketServer::SetPosixSignalHandler(int signum, + void (*handler)(int)) { + // If handler is SIG_IGN or SIG_DFL then clear our user-level handler, + // otherwise set one. + if (handler == SIG_IGN || handler == SIG_DFL) { + if (!InstallSignal(signum, handler)) { + return false; + } + if (signal_dispatcher_) { + signal_dispatcher_->ClearHandler(signum); + if (!signal_dispatcher_->HasHandlers()) { + signal_dispatcher_.reset(); + } + } + } else { + if (!signal_dispatcher_) { + signal_dispatcher_.reset(new PosixSignalDispatcher(this)); + } + signal_dispatcher_->SetHandler(signum, handler); + if (!InstallSignal(signum, &GlobalSignalHandler)) { + return false; + } + } + return true; +} + +Dispatcher* PhysicalSocketServer::signal_dispatcher() { + return signal_dispatcher_.get(); +} + +bool PhysicalSocketServer::InstallSignal(int signum, void (*handler)(int)) { + struct sigaction act; + // It doesn't really matter what we set this mask to. + if (sigemptyset(&act.sa_mask) != 0) { + LOG_ERR(LS_ERROR) << "Couldn't set mask"; + return false; + } + act.sa_handler = handler; +#if !defined(__native_client__) + // Use SA_RESTART so that our syscalls don't get EINTR, since we don't need it + // and it's a nuisance. Though some syscalls still return EINTR and there's no + // real standard for which ones. :( + act.sa_flags = SA_RESTART; +#else + act.sa_flags = 0; +#endif + if (sigaction(signum, &act, NULL) != 0) { + LOG_ERR(LS_ERROR) << "Couldn't set sigaction"; + return false; + } + return true; +} +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) { + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = Time(); + + fWait_ = true; + while (fWait_) { + std::vector events; + std::vector event_owners; + + events.push_back(socket_ev_); + + { + CritScope cr(&crit_); + size_t i = 0; + iterators_.push_back(&i); + // Don't track dispatchers_.size(), because we want to pick up any new + // dispatchers that were added while processing the loop. + while (i < dispatchers_.size()) { + Dispatcher* disp = dispatchers_[i++]; + if (!process_io && (disp != signal_wakeup_)) + continue; + SOCKET s = disp->GetSocket(); + if (disp->CheckSignalClose()) { + // We just signalled close, don't poll this socket + } else if (s != INVALID_SOCKET) { + WSAEventSelect(s, + events[0], + FlagsToEvents(disp->GetRequestedEvents())); + } else { + events.push_back(disp->GetWSAEvent()); + event_owners.push_back(disp); + } + } + ASSERT(iterators_.back() == &i); + iterators_.pop_back(); + } + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == kForever) { + cmsNext = cmsWait; + } else { + cmsNext = _max(0, cmsTotal - cmsElapsed); + } + + // Wait for one of the events to signal + DWORD dw = WSAWaitForMultipleEvents(static_cast(events.size()), + &events[0], + false, + cmsNext, + false); + + if (dw == WSA_WAIT_FAILED) { + // Failed? + // TODO: need a better strategy than this! + WSAGetLastError(); + ASSERT(false); + return false; + } else if (dw == WSA_WAIT_TIMEOUT) { + // Timeout? + return true; + } else { + // Figure out which one it is and call it + CritScope cr(&crit_); + int index = dw - WSA_WAIT_EVENT_0; + if (index > 0) { + --index; // The first event is the socket event + event_owners[index]->OnPreEvent(0); + event_owners[index]->OnEvent(0, 0); + } else if (process_io) { + size_t i = 0, end = dispatchers_.size(); + iterators_.push_back(&i); + iterators_.push_back(&end); // Don't iterate over new dispatchers. + while (i < end) { + Dispatcher* disp = dispatchers_[i++]; + SOCKET s = disp->GetSocket(); + if (s == INVALID_SOCKET) + continue; + + WSANETWORKEVENTS wsaEvents; + int err = WSAEnumNetworkEvents(s, events[0], &wsaEvents); + if (err == 0) { + +#if LOGGING + { + if ((wsaEvents.lNetworkEvents & FD_READ) && + wsaEvents.iErrorCode[FD_READ_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_READ_BIT error " + << wsaEvents.iErrorCode[FD_READ_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_WRITE) && + wsaEvents.iErrorCode[FD_WRITE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_WRITE_BIT error " + << wsaEvents.iErrorCode[FD_WRITE_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CONNECT) && + wsaEvents.iErrorCode[FD_CONNECT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CONNECT_BIT error " + << wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_ACCEPT) && + wsaEvents.iErrorCode[FD_ACCEPT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_ACCEPT_BIT error " + << wsaEvents.iErrorCode[FD_ACCEPT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CLOSE) && + wsaEvents.iErrorCode[FD_CLOSE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CLOSE_BIT error " + << wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + } +#endif + uint32 ff = 0; + int errcode = 0; + if (wsaEvents.lNetworkEvents & FD_READ) + ff |= DE_READ; + if (wsaEvents.lNetworkEvents & FD_WRITE) + ff |= DE_WRITE; + if (wsaEvents.lNetworkEvents & FD_CONNECT) { + if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) { + ff |= DE_CONNECT; + } else { + ff |= DE_CLOSE; + errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + } + if (wsaEvents.lNetworkEvents & FD_ACCEPT) + ff |= DE_ACCEPT; + if (wsaEvents.lNetworkEvents & FD_CLOSE) { + ff |= DE_CLOSE; + errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + if (ff != 0) { + disp->OnPreEvent(ff); + disp->OnEvent(ff, errcode); + } + } + } + ASSERT(iterators_.back() == &end); + iterators_.pop_back(); + ASSERT(iterators_.back() == &i); + iterators_.pop_back(); + } + + // Reset the network event until new activity occurs + WSAResetEvent(socket_ev_); + } + + // Break? + if (!fWait_) + break; + cmsElapsed = TimeSince(msStart); + if ((cmsWait != kForever) && (cmsElapsed >= cmsWait)) { + break; + } + } + + // Done + return true; +} +#endif // WEBRTC_WIN + +} // namespace rtc diff --git a/webrtc/base/physicalsocketserver.h b/webrtc/base/physicalsocketserver.h new file mode 100644 index 000000000..8a289de7e --- /dev/null +++ b/webrtc/base/physicalsocketserver.h @@ -0,0 +1,120 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_PHYSICALSOCKETSERVER_H__ +#define WEBRTC_BASE_PHYSICALSOCKETSERVER_H__ + +#include + +#include "webrtc/base/asyncfile.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/criticalsection.h" + +#if defined(WEBRTC_POSIX) +typedef int SOCKET; +#endif // WEBRTC_POSIX + +namespace rtc { + +// Event constants for the Dispatcher class. +enum DispatcherEvent { + DE_READ = 0x0001, + DE_WRITE = 0x0002, + DE_CONNECT = 0x0004, + DE_CLOSE = 0x0008, + DE_ACCEPT = 0x0010, +}; + +class Signaler; +#if defined(WEBRTC_POSIX) +class PosixSignalDispatcher; +#endif + +class Dispatcher { + public: + virtual ~Dispatcher() {} + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; +#if defined(WEBRTC_WIN) + virtual WSAEVENT GetWSAEvent() = 0; + virtual SOCKET GetSocket() = 0; + virtual bool CheckSignalClose() = 0; +#elif defined(WEBRTC_POSIX) + virtual int GetDescriptor() = 0; + virtual bool IsDescriptorClosed() = 0; +#endif +}; + +// A socket server that provides the real sockets of the underlying OS. +class PhysicalSocketServer : public SocketServer { + public: + PhysicalSocketServer(); + virtual ~PhysicalSocketServer(); + + // SocketFactory: + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + // Internal Factory for Accept + AsyncSocket* WrapSocket(SOCKET s); + + // SocketServer: + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + void Add(Dispatcher* dispatcher); + void Remove(Dispatcher* dispatcher); + +#if defined(WEBRTC_POSIX) + AsyncFile* CreateFile(int fd); + + // Sets the function to be executed in response to the specified POSIX signal. + // The function is executed from inside Wait() using the "self-pipe trick"-- + // regardless of which thread receives the signal--and hence can safely + // manipulate user-level data structures. + // "handler" may be SIG_IGN, SIG_DFL, or a user-specified function, just like + // with signal(2). + // Only one PhysicalSocketServer should have user-level signal handlers. + // Dispatching signals on multiple PhysicalSocketServers is not reliable. + // The signal mask is not modified. It is the caller's responsibily to + // maintain it as desired. + virtual bool SetPosixSignalHandler(int signum, void (*handler)(int)); + + protected: + Dispatcher* signal_dispatcher(); +#endif + + private: + typedef std::vector DispatcherList; + typedef std::vector IteratorList; + +#if defined(WEBRTC_POSIX) + static bool InstallSignal(int signum, void (*handler)(int)); + + scoped_ptr signal_dispatcher_; +#endif + DispatcherList dispatchers_; + IteratorList iterators_; + Signaler* signal_wakeup_; + CriticalSection crit_; + bool fWait_; +#if defined(WEBRTC_WIN) + WSAEVENT socket_ev_; +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_PHYSICALSOCKETSERVER_H__ diff --git a/webrtc/base/physicalsocketserver_unittest.cc b/webrtc/base/physicalsocketserver_unittest.cc new file mode 100644 index 000000000..f29c5fc12 --- /dev/null +++ b/webrtc/base/physicalsocketserver_unittest.cc @@ -0,0 +1,274 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socket_unittest.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +class PhysicalSocketTest : public SocketTest { +}; + +TEST_F(PhysicalSocketTest, TestConnectIPv4) { + SocketTest::TestConnectIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectIPv6) { + SocketTest::TestConnectIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv4) { + SocketTest::TestConnectWithDnsLookupIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv6) { + SocketTest::TestConnectWithDnsLookupIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectFailIPv4) { + SocketTest::TestConnectFailIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectFailIPv6) { + SocketTest::TestConnectFailIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv4) { + SocketTest::TestConnectWithDnsLookupFailIPv4(); +} + + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv6) { + SocketTest::TestConnectWithDnsLookupFailIPv6(); +} + + +TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv4) { + SocketTest::TestConnectWithClosedSocketIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv6) { + SocketTest::TestConnectWithClosedSocketIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv4) { + SocketTest::TestConnectWhileNotClosedIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv6) { + SocketTest::TestConnectWhileNotClosedIPv6(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv4) { + SocketTest::TestServerCloseDuringConnectIPv4(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv6) { + SocketTest::TestServerCloseDuringConnectIPv6(); +} + +TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv4) { + SocketTest::TestClientCloseDuringConnectIPv4(); +} + +TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv6) { + SocketTest::TestClientCloseDuringConnectIPv6(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseIPv4) { + SocketTest::TestServerCloseIPv4(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseIPv6) { + SocketTest::TestServerCloseIPv6(); +} + +TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv4) { + SocketTest::TestCloseInClosedCallbackIPv4(); +} + +TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv6) { + SocketTest::TestCloseInClosedCallbackIPv6(); +} + +TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} + +TEST_F(PhysicalSocketTest, TestTcpIPv4) { + SocketTest::TestTcpIPv4(); +} + +TEST_F(PhysicalSocketTest, TestTcpIPv6) { + SocketTest::TestTcpIPv6(); +} + +TEST_F(PhysicalSocketTest, TestUdpIPv4) { + SocketTest::TestUdpIPv4(); +} + +TEST_F(PhysicalSocketTest, TestUdpIPv6) { + SocketTest::TestUdpIPv6(); +} + +TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv4) { + SocketTest::TestUdpReadyToSendIPv4(); +} + +TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv6) { + SocketTest::TestUdpReadyToSendIPv6(); +} + +TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv4) { + SocketTest::TestGetSetOptionsIPv4(); +} + +TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv6) { + SocketTest::TestGetSetOptionsIPv6(); +} + +#if defined(WEBRTC_POSIX) + +class PosixSignalDeliveryTest : public testing::Test { + public: + static void RecordSignal(int signum) { + signals_received_.push_back(signum); + signaled_thread_ = Thread::Current(); + } + + protected: + void SetUp() { + ss_.reset(new PhysicalSocketServer()); + } + + void TearDown() { + ss_.reset(NULL); + signals_received_.clear(); + signaled_thread_ = NULL; + } + + bool ExpectSignal(int signum) { + if (signals_received_.empty()) { + LOG(LS_ERROR) << "ExpectSignal(): No signal received"; + return false; + } + if (signals_received_[0] != signum) { + LOG(LS_ERROR) << "ExpectSignal(): Received signal " << + signals_received_[0] << ", expected " << signum; + return false; + } + signals_received_.erase(signals_received_.begin()); + return true; + } + + bool ExpectNone() { + bool ret = signals_received_.empty(); + if (!ret) { + LOG(LS_ERROR) << "ExpectNone(): Received signal " << signals_received_[0] + << ", expected none"; + } + return ret; + } + + static std::vector signals_received_; + static Thread *signaled_thread_; + + scoped_ptr ss_; +}; + +std::vector PosixSignalDeliveryTest::signals_received_; +Thread *PosixSignalDeliveryTest::signaled_thread_ = NULL; + +// Test receiving a synchronous signal while not in Wait() and then entering +// Wait() afterwards. +TEST_F(PosixSignalDeliveryTest, RaiseThenWait) { + ASSERT_TRUE(ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal)); + raise(SIGTERM); + EXPECT_TRUE(ss_->Wait(0, true)); + EXPECT_TRUE(ExpectSignal(SIGTERM)); + EXPECT_TRUE(ExpectNone()); +} + +// Test that we can handle getting tons of repeated signals and that we see all +// the different ones. +TEST_F(PosixSignalDeliveryTest, InsanelyManySignals) { + ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal); + ss_->SetPosixSignalHandler(SIGINT, &RecordSignal); + for (int i = 0; i < 10000; ++i) { + raise(SIGTERM); + } + raise(SIGINT); + EXPECT_TRUE(ss_->Wait(0, true)); + // Order will be lowest signal numbers first. + EXPECT_TRUE(ExpectSignal(SIGINT)); + EXPECT_TRUE(ExpectSignal(SIGTERM)); + EXPECT_TRUE(ExpectNone()); +} + +// Test that a signal during a Wait() call is detected. +TEST_F(PosixSignalDeliveryTest, SignalDuringWait) { + ss_->SetPosixSignalHandler(SIGALRM, &RecordSignal); + alarm(1); + EXPECT_TRUE(ss_->Wait(1500, true)); + EXPECT_TRUE(ExpectSignal(SIGALRM)); + EXPECT_TRUE(ExpectNone()); +} + +class RaiseSigTermRunnable : public Runnable { + void Run(Thread *thread) { + thread->socketserver()->Wait(1000, false); + + // Allow SIGTERM. This will be the only thread with it not masked so it will + // be delivered to us. + sigset_t mask; + sigemptyset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); + + // Raise it. + raise(SIGTERM); + } +}; + +// Test that it works no matter what thread the kernel chooses to give the +// signal to (since it's not guaranteed to be the one that Wait() runs on). +TEST_F(PosixSignalDeliveryTest, SignalOnDifferentThread) { + ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal); + // Mask out SIGTERM so that it can't be delivered to this thread. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + EXPECT_EQ(0, pthread_sigmask(SIG_SETMASK, &mask, NULL)); + // Start a new thread that raises it. It will have to be delivered to that + // thread. Our implementation should safely handle it and dispatch + // RecordSignal() on this thread. + scoped_ptr thread(new Thread()); + scoped_ptr runnable(new RaiseSigTermRunnable()); + thread->Start(runnable.get()); + EXPECT_TRUE(ss_->Wait(1500, true)); + EXPECT_TRUE(ExpectSignal(SIGTERM)); + EXPECT_EQ(Thread::Current(), signaled_thread_); + EXPECT_TRUE(ExpectNone()); +} + +#endif + +} // namespace rtc diff --git a/webrtc/base/posix.cc b/webrtc/base/posix.cc new file mode 100644 index 000000000..0eb24ee64 --- /dev/null +++ b/webrtc/base/posix.cc @@ -0,0 +1,131 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/posix.h" + +#include +#include +#include + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include "webrtc/base/linuxfdwalk.h" +#endif +#include "webrtc/base/logging.h" + +namespace rtc { + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +static void closefds(void *close_errors, int fd) { + if (fd <= 2) { + // We leave stdin/out/err open to the browser's terminal, if any. + return; + } + if (close(fd) < 0) { + *static_cast(close_errors) = true; + } +} +#endif + +enum { + EXIT_FLAG_CHDIR_ERRORS = 1 << 0, +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + EXIT_FLAG_FDWALK_ERRORS = 1 << 1, + EXIT_FLAG_CLOSE_ERRORS = 1 << 2, +#endif + EXIT_FLAG_SECOND_FORK_FAILED = 1 << 3, +}; + +bool RunAsDaemon(const char *file, const char *const argv[]) { + // Fork intermediate child to daemonize. + pid_t pid = fork(); + if (pid < 0) { + LOG_ERR(LS_ERROR) << "fork()"; + return false; + } else if (!pid) { + // Child. + + // We try to close all fds and change directory to /, but if that fails we + // keep going because it's not critical. + int exit_code = 0; + if (chdir("/") < 0) { + exit_code |= EXIT_FLAG_CHDIR_ERRORS; + } +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + bool close_errors = false; + if (fdwalk(&closefds, &close_errors) < 0) { + exit_code |= EXIT_FLAG_FDWALK_ERRORS; + } + if (close_errors) { + exit_code |= EXIT_FLAG_CLOSE_ERRORS; + } +#endif + + // Fork again to become a daemon. + pid = fork(); + // It is important that everything here use _exit() and not exit(), because + // exit() would call the destructors of all global variables in the whole + // process, which is both unnecessary and unsafe. + if (pid < 0) { + exit_code |= EXIT_FLAG_SECOND_FORK_FAILED; + _exit(exit_code); // if second fork failed + } else if (!pid) { + // Child. + // Successfully daemonized. Run command. + // WEBRTC_POSIX requires the args to be typed as non-const for historical + // reasons, but it mandates that the actual implementation be const, so + // the cast is safe. + execvp(file, const_cast(argv)); + _exit(255); // if execvp failed + } + + // Parent. + // Successfully spawned process, but report any problems to the parent where + // we can log them. + _exit(exit_code); + } + + // Parent. Reap intermediate child. + int status; + pid_t child = waitpid(pid, &status, 0); + if (child < 0) { + LOG_ERR(LS_ERROR) << "Error in waitpid()"; + return false; + } + if (child != pid) { + // Should never happen (see man page). + LOG(LS_ERROR) << "waitpid() chose wrong child???"; + return false; + } + if (!WIFEXITED(status)) { + LOG(LS_ERROR) << "Intermediate child killed uncleanly"; // Probably crashed + return false; + } + + int exit_code = WEXITSTATUS(status); + if (exit_code & EXIT_FLAG_CHDIR_ERRORS) { + LOG(LS_WARNING) << "Child reported probles calling chdir()"; + } +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + if (exit_code & EXIT_FLAG_FDWALK_ERRORS) { + LOG(LS_WARNING) << "Child reported problems calling fdwalk()"; + } + if (exit_code & EXIT_FLAG_CLOSE_ERRORS) { + LOG(LS_WARNING) << "Child reported problems calling close()"; + } +#endif + if (exit_code & EXIT_FLAG_SECOND_FORK_FAILED) { + LOG(LS_ERROR) << "Failed to daemonize"; + // This means the command was not launched, so failure. + return false; + } + return true; +} + +} // namespace rtc diff --git a/webrtc/base/posix.h b/webrtc/base/posix.h new file mode 100644 index 000000000..8d1c2b11e --- /dev/null +++ b/webrtc/base/posix.h @@ -0,0 +1,25 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_POSIX_H_ +#define WEBRTC_BASE_POSIX_H_ + +namespace rtc { + +// Runs the given executable name as a daemon, so that it executes concurrently +// with this process. Upon completion, the daemon process will automatically be +// reaped by init(8), so an error exit status or a failure to start the +// executable are not reported. Returns true if the daemon process was forked +// successfully, else false. +bool RunAsDaemon(const char *file, const char *const argv[]); + +} // namespace rtc + +#endif // WEBRTC_BASE_POSIX_H_ diff --git a/webrtc/base/profiler.cc b/webrtc/base/profiler.cc new file mode 100644 index 000000000..f57344853 --- /dev/null +++ b/webrtc/base/profiler.cc @@ -0,0 +1,186 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/profiler.h" + +#include + +#include "webrtc/base/timeutils.h" + +namespace { + +// When written to an ostream, FormattedTime chooses an appropriate scale and +// suffix for a time value given in seconds. +class FormattedTime { + public: + explicit FormattedTime(double t) : time_(t) {} + double time() const { return time_; } + private: + double time_; +}; + +std::ostream& operator<<(std::ostream& stream, const FormattedTime& time) { + if (time.time() < 1.0) { + stream << (time.time() * 1000.0) << "ms"; + } else { + stream << time.time() << 's'; + } + return stream; +} + +} // namespace + +namespace rtc { + +ProfilerEvent::ProfilerEvent() + : total_time_(0.0), + mean_(0.0), + sum_of_squared_differences_(0.0), + start_count_(0), + event_count_(0) { +} + +void ProfilerEvent::Start() { + if (start_count_ == 0) { + current_start_time_ = TimeNanos(); + } + ++start_count_; +} + +void ProfilerEvent::Stop(uint64 stop_time) { + --start_count_; + ASSERT(start_count_ >= 0); + if (start_count_ == 0) { + double elapsed = static_cast(stop_time - current_start_time_) / + kNumNanosecsPerSec; + total_time_ += elapsed; + if (event_count_ == 0) { + minimum_ = maximum_ = elapsed; + } else { + minimum_ = _min(minimum_, elapsed); + maximum_ = _max(maximum_, elapsed); + } + // Online variance and mean algorithm: http://en.wikipedia.org/wiki/ + // Algorithms_for_calculating_variance#Online_algorithm + ++event_count_; + double delta = elapsed - mean_; + mean_ = mean_ + delta / event_count_; + sum_of_squared_differences_ += delta * (elapsed - mean_); + } +} + +void ProfilerEvent::Stop() { + Stop(TimeNanos()); +} + +double ProfilerEvent::standard_deviation() const { + if (event_count_ <= 1) return 0.0; + return sqrt(sum_of_squared_differences_ / (event_count_ - 1.0)); +} + +Profiler* Profiler::Instance() { + LIBJINGLE_DEFINE_STATIC_LOCAL(Profiler, instance, ()); + return &instance; +} + +void Profiler::StartEvent(const std::string& event_name) { + lock_.LockShared(); + EventMap::iterator it = events_.find(event_name); + bool needs_insert = (it == events_.end()); + lock_.UnlockShared(); + + if (needs_insert) { + // Need an exclusive lock to modify the map. + ExclusiveScope scope(&lock_); + it = events_.insert( + EventMap::value_type(event_name, ProfilerEvent())).first; + } + + it->second.Start(); +} + +void Profiler::StopEvent(const std::string& event_name) { + // Get the time ASAP, then wait for the lock. + uint64 stop_time = TimeNanos(); + SharedScope scope(&lock_); + EventMap::iterator it = events_.find(event_name); + if (it != events_.end()) { + it->second.Stop(stop_time); + } +} + +void Profiler::ReportToLog(const char* file, int line, + LoggingSeverity severity_to_use, + const std::string& event_prefix) { + if (!LogMessage::Loggable(severity_to_use)) { + return; + } + + SharedScope scope(&lock_); + + { // Output first line. + LogMessage msg(file, line, severity_to_use); + msg.stream() << "=== Profile report "; + if (event_prefix.empty()) { + msg.stream() << "(prefix: '" << event_prefix << "') "; + } + msg.stream() << "==="; + } + for (EventMap::const_iterator it = events_.begin(); + it != events_.end(); ++it) { + if (event_prefix.empty() || it->first.find(event_prefix) == 0) { + LogMessage(file, line, severity_to_use).stream() + << it->first << " " << it->second; + } + } + LogMessage(file, line, severity_to_use).stream() + << "=== End profile report ==="; +} + +void Profiler::ReportAllToLog(const char* file, int line, + LoggingSeverity severity_to_use) { + ReportToLog(file, line, severity_to_use, ""); +} + +const ProfilerEvent* Profiler::GetEvent(const std::string& event_name) const { + SharedScope scope(&lock_); + EventMap::const_iterator it = + events_.find(event_name); + return (it == events_.end()) ? NULL : &it->second; +} + +bool Profiler::Clear() { + ExclusiveScope scope(&lock_); + bool result = true; + // Clear all events that aren't started. + EventMap::iterator it = events_.begin(); + while (it != events_.end()) { + if (it->second.is_started()) { + ++it; // Can't clear started events. + result = false; + } else { + events_.erase(it++); + } + } + return result; +} + +std::ostream& operator<<(std::ostream& stream, + const ProfilerEvent& profiler_event) { + stream << "count=" << profiler_event.event_count() + << " total=" << FormattedTime(profiler_event.total_time()) + << " mean=" << FormattedTime(profiler_event.mean()) + << " min=" << FormattedTime(profiler_event.minimum()) + << " max=" << FormattedTime(profiler_event.maximum()) + << " sd=" << profiler_event.standard_deviation(); + return stream; +} + +} // namespace rtc diff --git a/webrtc/base/profiler.h b/webrtc/base/profiler.h new file mode 100644 index 000000000..13b99f7c9 --- /dev/null +++ b/webrtc/base/profiler.h @@ -0,0 +1,161 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// A simple wall-clock profiler for instrumented code. +// Example: +// void MyLongFunction() { +// PROFILE_F(); // Time the execution of this function. +// // Do something +// { // Time just what is in this scope. +// PROFILE("My event"); +// // Do something else +// } +// } +// Another example: +// void StartAsyncProcess() { +// PROFILE_START("My async event"); +// DoSomethingAsyncAndThenCall(&Callback); +// } +// void Callback() { +// PROFILE_STOP("My async event"); +// // Handle callback. +// } + +#ifndef WEBRTC_BASE_PROFILER_H_ +#define WEBRTC_BASE_PROFILER_H_ + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/sharedexclusivelock.h" + +// Profiling could be switched via a build flag, but for now, it's always on. +#ifndef ENABLE_PROFILING +#define ENABLE_PROFILING +#endif + +#ifdef ENABLE_PROFILING + +#define UV_HELPER2(x) _uv_ ## x +#define UV_HELPER(x) UV_HELPER2(x) +#define UNIQUE_VAR UV_HELPER(__LINE__) + +// Profiles the current scope. +#define PROFILE(msg) rtc::ProfilerScope UNIQUE_VAR(msg) +// When placed at the start of a function, profiles the current function. +#define PROFILE_F() PROFILE(__FUNCTION__) +// Reports current timings to the log at severity |sev|. +#define PROFILE_DUMP_ALL(sev) \ + rtc::Profiler::Instance()->ReportAllToLog(__FILE__, __LINE__, sev) +// Reports current timings for all events whose names are prefixed by |prefix| +// to the log at severity |sev|. Using a unique event name as |prefix| will +// report only that event. +#define PROFILE_DUMP(sev, prefix) \ + rtc::Profiler::Instance()->ReportToLog(__FILE__, __LINE__, sev, prefix) +// Starts and stops a profile event. Useful when an event is not easily +// captured within a scope (eg, an async call with a callback when done). +#define PROFILE_START(msg) rtc::Profiler::Instance()->StartEvent(msg) +#define PROFILE_STOP(msg) rtc::Profiler::Instance()->StopEvent(msg) +// TODO(ryanpetrie): Consider adding PROFILE_DUMP_EVERY(sev, iterations) + +#undef UV_HELPER2 +#undef UV_HELPER +#undef UNIQUE_VAR + +#else // ENABLE_PROFILING + +#define PROFILE(msg) (void)0 +#define PROFILE_F() (void)0 +#define PROFILE_DUMP_ALL(sev) (void)0 +#define PROFILE_DUMP(sev, prefix) (void)0 +#define PROFILE_START(msg) (void)0 +#define PROFILE_STOP(msg) (void)0 + +#endif // ENABLE_PROFILING + +namespace rtc { + +// Tracks information for one profiler event. +class ProfilerEvent { + public: + ProfilerEvent(); + void Start(); + void Stop(); + void Stop(uint64 stop_time); + double standard_deviation() const; + double total_time() const { return total_time_; } + double mean() const { return mean_; } + double minimum() const { return minimum_; } + double maximum() const { return maximum_; } + int event_count() const { return event_count_; } + bool is_started() const { return start_count_ > 0; } + + private: + uint64 current_start_time_; + double total_time_; + double mean_; + double sum_of_squared_differences_; + double minimum_; + double maximum_; + int start_count_; + int event_count_; +}; + +// Singleton that owns ProfilerEvents and reports results. Prefer to use +// macros, defined above, rather than directly calling Profiler methods. +class Profiler { + public: + void StartEvent(const std::string& event_name); + void StopEvent(const std::string& event_name); + void ReportToLog(const char* file, int line, LoggingSeverity severity_to_use, + const std::string& event_prefix); + void ReportAllToLog(const char* file, int line, + LoggingSeverity severity_to_use); + const ProfilerEvent* GetEvent(const std::string& event_name) const; + // Clears all _stopped_ events. Returns true if _all_ events were cleared. + bool Clear(); + + static Profiler* Instance(); + private: + Profiler() {} + + typedef std::map EventMap; + EventMap events_; + mutable SharedExclusiveLock lock_; + + DISALLOW_COPY_AND_ASSIGN(Profiler); +}; + +// Starts an event on construction and stops it on destruction. +// Used by PROFILE macro. +class ProfilerScope { + public: + explicit ProfilerScope(const std::string& event_name) + : event_name_(event_name) { + Profiler::Instance()->StartEvent(event_name_); + } + ~ProfilerScope() { + Profiler::Instance()->StopEvent(event_name_); + } + private: + std::string event_name_; + + DISALLOW_COPY_AND_ASSIGN(ProfilerScope); +}; + +std::ostream& operator<<(std::ostream& stream, + const ProfilerEvent& profiler_event); + +} // namespace rtc + +#endif // WEBRTC_BASE_PROFILER_H_ diff --git a/webrtc/base/profiler_unittest.cc b/webrtc/base/profiler_unittest.cc new file mode 100644 index 000000000..5a607914a --- /dev/null +++ b/webrtc/base/profiler_unittest.cc @@ -0,0 +1,109 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/profiler.h" +#include "webrtc/base/thread.h" + +namespace { + +const int kWaitMs = 250; +const double kWaitSec = 0.250; +const double kTolerance = 0.1; + +const char* TestFunc() { + PROFILE_F(); + rtc::Thread::SleepMs(kWaitMs); + return __FUNCTION__; +} + +} // namespace + +namespace rtc { + +TEST(ProfilerTest, TestFunction) { + ASSERT_TRUE(Profiler::Instance()->Clear()); + // Profile a long-running function. + const char* function_name = TestFunc(); + const ProfilerEvent* event = Profiler::Instance()->GetEvent(function_name); + ASSERT_TRUE(event != NULL); + EXPECT_FALSE(event->is_started()); + EXPECT_EQ(1, event->event_count()); + EXPECT_NEAR(kWaitSec, event->mean(), kTolerance); + // Run it a second time. + TestFunc(); + EXPECT_FALSE(event->is_started()); + EXPECT_EQ(2, event->event_count()); + EXPECT_NEAR(kWaitSec, event->mean(), kTolerance); + EXPECT_NEAR(kWaitSec * 2, event->total_time(), kTolerance * 2); + EXPECT_DOUBLE_EQ(event->mean(), event->total_time() / event->event_count()); +} + +TEST(ProfilerTest, TestScopedEvents) { + const std::string kEvent1Name = "Event 1"; + const std::string kEvent2Name = "Event 2"; + const int kEvent2WaitMs = 150; + const double kEvent2WaitSec = 0.150; + const ProfilerEvent* event1; + const ProfilerEvent* event2; + ASSERT_TRUE(Profiler::Instance()->Clear()); + { // Profile a scope. + PROFILE(kEvent1Name); + event1 = Profiler::Instance()->GetEvent(kEvent1Name); + ASSERT_TRUE(event1 != NULL); + EXPECT_TRUE(event1->is_started()); + EXPECT_EQ(0, event1->event_count()); + rtc::Thread::SleepMs(kWaitMs); + EXPECT_TRUE(event1->is_started()); + } + // Check the result. + EXPECT_FALSE(event1->is_started()); + EXPECT_EQ(1, event1->event_count()); + EXPECT_NEAR(kWaitSec, event1->mean(), kTolerance); + { // Profile a second event. + PROFILE(kEvent2Name); + event2 = Profiler::Instance()->GetEvent(kEvent2Name); + ASSERT_TRUE(event2 != NULL); + EXPECT_FALSE(event1->is_started()); + EXPECT_TRUE(event2->is_started()); + rtc::Thread::SleepMs(kEvent2WaitMs); + } + // Check the result. + EXPECT_FALSE(event2->is_started()); + EXPECT_EQ(1, event2->event_count()); + EXPECT_NEAR(kEvent2WaitSec, event2->mean(), kTolerance); + // Make sure event1 is unchanged. + EXPECT_FALSE(event1->is_started()); + EXPECT_EQ(1, event1->event_count()); + { // Run another event 1. + PROFILE(kEvent1Name); + EXPECT_TRUE(event1->is_started()); + rtc::Thread::SleepMs(kWaitMs); + } + // Check the result. + EXPECT_FALSE(event1->is_started()); + EXPECT_EQ(2, event1->event_count()); + EXPECT_NEAR(kWaitSec, event1->mean(), kTolerance); + EXPECT_NEAR(kWaitSec * 2, event1->total_time(), kTolerance * 2); + EXPECT_DOUBLE_EQ(event1->mean(), + event1->total_time() / event1->event_count()); +} + +TEST(ProfilerTest, Clear) { + ASSERT_TRUE(Profiler::Instance()->Clear()); + PROFILE_START("event"); + EXPECT_FALSE(Profiler::Instance()->Clear()); + EXPECT_TRUE(Profiler::Instance()->GetEvent("event") != NULL); + PROFILE_STOP("event"); + EXPECT_TRUE(Profiler::Instance()->Clear()); + EXPECT_EQ(NULL, Profiler::Instance()->GetEvent("event")); +} + +} // namespace rtc diff --git a/webrtc/base/proxy_unittest.cc b/webrtc/base/proxy_unittest.cc new file mode 100644 index 000000000..d8a523fe1 --- /dev/null +++ b/webrtc/base/proxy_unittest.cc @@ -0,0 +1,135 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "webrtc/base/autodetectproxy.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/proxyserver.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testechoserver.h" +#include "webrtc/base/virtualsocketserver.h" + +using rtc::Socket; +using rtc::Thread; +using rtc::SocketAddress; + +static const SocketAddress kSocksProxyIntAddr("1.2.3.4", 1080); +static const SocketAddress kSocksProxyExtAddr("1.2.3.5", 0); +static const SocketAddress kHttpsProxyIntAddr("1.2.3.4", 443); +static const SocketAddress kHttpsProxyExtAddr("1.2.3.5", 0); +static const SocketAddress kBogusProxyIntAddr("1.2.3.4", 999); + +// Used to run a proxy detect on the current thread. Otherwise we would need +// to make both threads share the same VirtualSocketServer. +class AutoDetectProxyRunner : public rtc::AutoDetectProxy { + public: + explicit AutoDetectProxyRunner(const std::string& agent) + : AutoDetectProxy(agent) {} + void Run() { + DoWork(); + Thread::Current()->Restart(); // needed to reset the messagequeue + } +}; + +// Sets up a virtual socket server and HTTPS/SOCKS5 proxy servers. +class ProxyTest : public testing::Test { + public: + ProxyTest() : ss_(new rtc::VirtualSocketServer(NULL)) { + Thread::Current()->set_socketserver(ss_.get()); + socks_.reset(new rtc::SocksProxyServer( + ss_.get(), kSocksProxyIntAddr, ss_.get(), kSocksProxyExtAddr)); + https_.reset(new rtc::HttpListenServer()); + https_->Listen(kHttpsProxyIntAddr); + } + ~ProxyTest() { + Thread::Current()->set_socketserver(NULL); + } + + rtc::SocketServer* ss() { return ss_.get(); } + + rtc::ProxyType DetectProxyType(const SocketAddress& address) { + rtc::ProxyType type; + AutoDetectProxyRunner* detect = new AutoDetectProxyRunner("unittest/1.0"); + detect->set_proxy(address); + detect->Run(); // blocks until done + type = detect->proxy().type; + detect->Destroy(false); + return type; + } + + private: + rtc::scoped_ptr ss_; + rtc::scoped_ptr socks_; + // TODO: Make this a real HTTPS proxy server. + rtc::scoped_ptr https_; +}; + +// Tests whether we can use a SOCKS5 proxy to connect to a server. +TEST_F(ProxyTest, TestSocks5Connect) { + rtc::AsyncSocket* socket = + ss()->CreateAsyncSocket(kSocksProxyIntAddr.family(), SOCK_STREAM); + rtc::AsyncSocksProxySocket* proxy_socket = + new rtc::AsyncSocksProxySocket(socket, kSocksProxyIntAddr, + "", rtc::CryptString()); + // TODO: IPv6-ize these tests when proxy supports IPv6. + + rtc::TestEchoServer server(Thread::Current(), + SocketAddress(INADDR_ANY, 0)); + + rtc::AsyncTCPSocket* packet_socket = rtc::AsyncTCPSocket::Create( + proxy_socket, SocketAddress(INADDR_ANY, 0), server.address()); + EXPECT_TRUE(packet_socket != NULL); + rtc::TestClient client(packet_socket); + + EXPECT_EQ(Socket::CS_CONNECTING, proxy_socket->GetState()); + EXPECT_TRUE(client.CheckConnected()); + EXPECT_EQ(Socket::CS_CONNECTED, proxy_socket->GetState()); + EXPECT_EQ(server.address(), client.remote_address()); + client.Send("foo", 3); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL)); + EXPECT_TRUE(client.CheckNoPacket()); +} + +/* +// Tests whether we can use a HTTPS proxy to connect to a server. +TEST_F(ProxyTest, TestHttpsConnect) { + AsyncSocket* socket = ss()->CreateAsyncSocket(SOCK_STREAM); + AsyncHttpsProxySocket* proxy_socket = new AsyncHttpsProxySocket( + socket, "unittest/1.0", kHttpsProxyIntAddress, "", CryptString()); + TestClient client(new AsyncTCPSocket(proxy_socket)); + TestEchoServer server(Thread::Current(), SocketAddress()); + + EXPECT_TRUE(client.Connect(server.address())); + EXPECT_TRUE(client.CheckConnected()); + EXPECT_EQ(server.address(), client.remote_address()); + client.Send("foo", 3); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL)); + EXPECT_TRUE(client.CheckNoPacket()); +} +*/ + +// Tests whether we can autodetect a SOCKS5 proxy. +TEST_F(ProxyTest, TestAutoDetectSocks5) { + EXPECT_EQ(rtc::PROXY_SOCKS5, DetectProxyType(kSocksProxyIntAddr)); +} + +/* +// Tests whether we can autodetect a HTTPS proxy. +TEST_F(ProxyTest, TestAutoDetectHttps) { + EXPECT_EQ(rtc::PROXY_HTTPS, DetectProxyType(kHttpsProxyIntAddr)); +} +*/ + +// Tests whether we fail properly for no proxy. +TEST_F(ProxyTest, TestAutoDetectBogus) { + EXPECT_EQ(rtc::PROXY_UNKNOWN, DetectProxyType(kBogusProxyIntAddr)); +} diff --git a/webrtc/base/proxydetect.cc b/webrtc/base/proxydetect.cc new file mode 100644 index 000000000..716888637 --- /dev/null +++ b/webrtc/base/proxydetect.cc @@ -0,0 +1,1246 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/proxydetect.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif // WEBRTC_WIN + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#include +#include +#include "macconversion.h" +#endif + +#include + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stringutils.h" + +#if defined(WEBRTC_WIN) +#define _TRY_WINHTTP 1 +#define _TRY_JSPROXY 0 +#define _TRY_WM_FINDPROXY 0 +#define _TRY_IE_LAN_SETTINGS 1 +#endif // WEBRTC_WIN + +// For all platforms try Firefox. +#define _TRY_FIREFOX 1 + +// Use profiles.ini to find the correct profile for this user. +// If not set, we'll just look for the default one. +#define USE_FIREFOX_PROFILES_INI 1 + +static const size_t kMaxLineLength = 1024; +static const char kFirefoxPattern[] = "Firefox"; +static const char kInternetExplorerPattern[] = "MSIE"; + +struct StringMap { + public: + void Add(const char * name, const char * value) { map_[name] = value; } + const std::string& Get(const char * name, const char * def = "") const { + std::map::const_iterator it = + map_.find(name); + if (it != map_.end()) + return it->second; + def_ = def; + return def_; + } + bool IsSet(const char * name) const { + return (map_.find(name) != map_.end()); + } + private: + std::map map_; + mutable std::string def_; +}; + +enum UserAgent { + UA_FIREFOX, + UA_INTERNETEXPLORER, + UA_OTHER, + UA_UNKNOWN +}; + +#if _TRY_WINHTTP +//#include +// Note: From winhttp.h + +const char WINHTTP[] = "winhttp"; + +typedef LPVOID HINTERNET; + +typedef struct { + DWORD dwAccessType; // see WINHTTP_ACCESS_* types below + LPWSTR lpszProxy; // proxy server list + LPWSTR lpszProxyBypass; // proxy bypass list +} WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO; + +typedef struct { + DWORD dwFlags; + DWORD dwAutoDetectFlags; + LPCWSTR lpszAutoConfigUrl; + LPVOID lpvReserved; + DWORD dwReserved; + BOOL fAutoLogonIfChallenged; +} WINHTTP_AUTOPROXY_OPTIONS; + +typedef struct { + BOOL fAutoDetect; + LPWSTR lpszAutoConfigUrl; + LPWSTR lpszProxy; + LPWSTR lpszProxyBypass; +} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG; + +extern "C" { + typedef HINTERNET (WINAPI * pfnWinHttpOpen) + ( + IN LPCWSTR pwszUserAgent, + IN DWORD dwAccessType, + IN LPCWSTR pwszProxyName OPTIONAL, + IN LPCWSTR pwszProxyBypass OPTIONAL, + IN DWORD dwFlags + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpCloseHandle) + ( + IN HINTERNET hInternet + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetProxyForUrl) + ( + IN HINTERNET hSession, + IN LPCWSTR lpcwszUrl, + IN WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions, + OUT WINHTTP_PROXY_INFO * pProxyInfo + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetIEProxyConfig) + ( + IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * pProxyConfig + ); + +} // extern "C" + +#define WINHTTP_AUTOPROXY_AUTO_DETECT 0x00000001 +#define WINHTTP_AUTOPROXY_CONFIG_URL 0x00000002 +#define WINHTTP_AUTOPROXY_RUN_INPROCESS 0x00010000 +#define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY 0x00020000 +#define WINHTTP_AUTO_DETECT_TYPE_DHCP 0x00000001 +#define WINHTTP_AUTO_DETECT_TYPE_DNS_A 0x00000002 +#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 +#define WINHTTP_ACCESS_TYPE_NO_PROXY 1 +#define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 +#define WINHTTP_NO_PROXY_NAME NULL +#define WINHTTP_NO_PROXY_BYPASS NULL + +#endif // _TRY_WINHTTP + +#if _TRY_JSPROXY +extern "C" { + typedef BOOL (STDAPICALLTYPE * pfnInternetGetProxyInfo) + ( + LPCSTR lpszUrl, + DWORD dwUrlLength, + LPSTR lpszUrlHostName, + DWORD dwUrlHostNameLength, + LPSTR * lplpszProxyHostName, + LPDWORD lpdwProxyHostNameLength + ); +} // extern "C" +#endif // _TRY_JSPROXY + +#if _TRY_WM_FINDPROXY +#include +#include +#include +#endif // _TRY_WM_FINDPROXY + +#if _TRY_IE_LAN_SETTINGS +#include +#include +#endif // _TRY_IE_LAN_SETTINGS + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Utility Functions +////////////////////////////////////////////////////////////////////// + +#if defined(WEBRTC_WIN) +#ifdef _UNICODE + +typedef std::wstring tstring; +std::string Utf8String(const tstring& str) { return ToUtf8(str); } + +#else // !_UNICODE + +typedef std::string tstring; +std::string Utf8String(const tstring& str) { return str; } + +#endif // !_UNICODE +#endif // WEBRTC_WIN + +bool ProxyItemMatch(const Url& url, char * item, size_t len) { + // hostname:443 + if (char * port = ::strchr(item, ':')) { + *port++ = '\0'; + if (url.port() != atol(port)) { + return false; + } + } + + // A.B.C.D or A.B.C.D/24 + int a, b, c, d, m; + int match = sscanf(item, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &m); + if (match >= 4) { + uint32 ip = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | + (d & 0xFF); + if ((match < 5) || (m > 32)) + m = 32; + else if (m < 0) + m = 0; + uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m); + SocketAddress addr(url.host(), 0); + // TODO: Support IPv6 proxyitems. This code block is IPv4 only anyway. + return !addr.IsUnresolved() && + ((addr.ipaddr().v4AddressAsHostOrderInteger() & mask) == (ip & mask)); + } + + // .foo.com + if (*item == '.') { + size_t hostlen = url.host().length(); + return (hostlen > len) + && (stricmp(url.host().c_str() + (hostlen - len), item) == 0); + } + + // localhost or www.*.com + if (!string_match(url.host().c_str(), item)) + return false; + + return true; +} + +bool ProxyListMatch(const Url& url, const std::string& proxy_list, + char sep) { + const size_t BUFSIZE = 256; + char buffer[BUFSIZE]; + const char* list = proxy_list.c_str(); + while (*list) { + // Remove leading space + if (isspace(*list)) { + ++list; + continue; + } + // Break on separator + size_t len; + const char * start = list; + if (const char * end = ::strchr(list, sep)) { + len = (end - list); + list += len + 1; + } else { + len = strlen(list); + list += len; + } + // Remove trailing space + while ((len > 0) && isspace(start[len-1])) + --len; + // Check for oversized entry + if (len >= BUFSIZE) + continue; + memcpy(buffer, start, len); + buffer[len] = 0; + if (!ProxyItemMatch(url, buffer, len)) + continue; + return true; + } + return false; +} + +bool Better(ProxyType lhs, const ProxyType rhs) { + // PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN + const int PROXY_VALUE[5] = { 0, 2, 3, 1 }; + return (PROXY_VALUE[lhs] > PROXY_VALUE[rhs]); +} + +bool ParseProxy(const std::string& saddress, ProxyInfo* proxy) { + const size_t kMaxAddressLength = 1024; + // Allow semicolon, space, or tab as an address separator + const char* const kAddressSeparator = " ;\t"; + + ProxyType ptype; + std::string host; + uint16 port; + + const char* address = saddress.c_str(); + while (*address) { + size_t len; + const char * start = address; + if (const char * sep = strchr(address, kAddressSeparator)) { + len = (sep - address); + address += len + 1; + while (*address != '\0' && ::strchr(kAddressSeparator, *address)) { + address += 1; + } + } else { + len = strlen(address); + address += len; + } + + if (len > kMaxAddressLength - 1) { + LOG(LS_WARNING) << "Proxy address too long [" << start << "]"; + continue; + } + + char buffer[kMaxAddressLength]; + memcpy(buffer, start, len); + buffer[len] = 0; + + char * colon = ::strchr(buffer, ':'); + if (!colon) { + LOG(LS_WARNING) << "Proxy address without port [" << buffer << "]"; + continue; + } + + *colon = 0; + char * endptr; + port = static_cast(strtol(colon + 1, &endptr, 0)); + if (*endptr != 0) { + LOG(LS_WARNING) << "Proxy address with invalid port [" << buffer << "]"; + continue; + } + + if (char * equals = ::strchr(buffer, '=')) { + *equals = 0; + host = equals + 1; + if (_stricmp(buffer, "socks") == 0) { + ptype = PROXY_SOCKS5; + } else if (_stricmp(buffer, "https") == 0) { + ptype = PROXY_HTTPS; + } else { + LOG(LS_WARNING) << "Proxy address with unknown protocol [" + << buffer << "]"; + ptype = PROXY_UNKNOWN; + } + } else { + host = buffer; + ptype = PROXY_UNKNOWN; + } + + if (Better(ptype, proxy->type)) { + proxy->type = ptype; + proxy->address.SetIP(host); + proxy->address.SetPort(port); + } + } + + return proxy->type != PROXY_NONE; +} + +UserAgent GetAgent(const char* agent) { + if (agent) { + std::string agent_str(agent); + if (agent_str.find(kFirefoxPattern) != std::string::npos) { + return UA_FIREFOX; + } else if (agent_str.find(kInternetExplorerPattern) != std::string::npos) { + return UA_INTERNETEXPLORER; + } else if (agent_str.empty()) { + return UA_UNKNOWN; + } + } + return UA_OTHER; +} + +bool EndsWith(const std::string& a, const std::string& b) { + if (b.size() > a.size()) { + return false; + } + int result = a.compare(a.size() - b.size(), b.size(), b); + return result == 0; +} + +bool GetFirefoxProfilePath(Pathname* path) { +#if defined(WEBRTC_WIN) + wchar_t w_path[MAX_PATH]; + if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, w_path) != + S_OK) { + LOG(LS_ERROR) << "SHGetFolderPath failed"; + return false; + } + path->SetFolder(ToUtf8(w_path, wcslen(w_path))); + path->AppendFolder("Mozilla"); + path->AppendFolder("Firefox"); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + FSRef fr; + if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType, + kCreateFolder, &fr)) { + LOG(LS_ERROR) << "FSFindFolder failed"; + return false; + } + char buffer[NAME_MAX + 1]; + if (0 != FSRefMakePath(&fr, reinterpret_cast(buffer), + ARRAY_SIZE(buffer))) { + LOG(LS_ERROR) << "FSRefMakePath failed"; + return false; + } + path->SetFolder(std::string(buffer)); + path->AppendFolder("Firefox"); +#else + char* user_home = getenv("HOME"); + if (user_home == NULL) { + return false; + } + path->SetFolder(std::string(user_home)); + path->AppendFolder(".mozilla"); + path->AppendFolder("firefox"); +#endif // WEBRTC_WIN + return true; +} + +bool GetDefaultFirefoxProfile(Pathname* profile_path) { + ASSERT(NULL != profile_path); + Pathname path; + if (!GetFirefoxProfilePath(&path)) { + return false; + } + +#if USE_FIREFOX_PROFILES_INI + // [Profile0] + // Name=default + // IsRelative=1 + // Path=Profiles/2de53ejb.default + // Default=1 + + // Note: we are looking for the first entry with "Default=1", or the last + // entry in the file + path.SetFilename("profiles.ini"); + scoped_ptr fs(Filesystem::OpenFile(path, "r")); + if (!fs) { + return false; + } + Pathname candidate; + bool relative = true; + std::string line; + while (fs->ReadLine(&line) == SR_SUCCESS) { + if (line.length() == 0) { + continue; + } + if (line.at(0) == '[') { + relative = true; + candidate.clear(); + } else if (line.find("IsRelative=") == 0 && + line.length() >= 12) { + // TODO: The initial Linux public launch revealed a fairly + // high number of machines where IsRelative= did not have anything after + // it. Perhaps that is legal profiles.ini syntax? + relative = (line.at(11) != '0'); + } else if (line.find("Path=") == 0 && + line.length() >= 6) { + if (relative) { + candidate = path; + } else { + candidate.clear(); + } + candidate.AppendFolder(line.substr(5)); + } else if (line.find("Default=") == 0 && + line.length() >= 9) { + if ((line.at(8) != '0') && !candidate.empty()) { + break; + } + } + } + fs->Close(); + if (candidate.empty()) { + return false; + } + profile_path->SetPathname(candidate.pathname()); + +#else // !USE_FIREFOX_PROFILES_INI + path.AppendFolder("Profiles"); + DirectoryIterator* it = Filesystem::IterateDirectory(); + it->Iterate(path); + std::string extension(".default"); + while (!EndsWith(it->Name(), extension)) { + if (!it->Next()) { + return false; + } + } + + profile_path->SetPathname(path); + profile->AppendFolder("Profiles"); + profile->AppendFolder(it->Name()); + delete it; + +#endif // !USE_FIREFOX_PROFILES_INI + + return true; +} + +bool ReadFirefoxPrefs(const Pathname& filename, + const char * prefix, + StringMap* settings) { + scoped_ptr fs(Filesystem::OpenFile(filename, "r")); + if (!fs) { + LOG(LS_ERROR) << "Failed to open file: " << filename.pathname(); + return false; + } + + std::string line; + while (fs->ReadLine(&line) == SR_SUCCESS) { + size_t prefix_len = strlen(prefix); + + // Skip blank lines and too long lines. + if ((line.length() == 0) || (line.length() > kMaxLineLength) + || (line.at(0) == '#') || line.compare(0, 2, "/*") == 0 + || line.compare(0, 2, " *") == 0) { + continue; + } + + char buffer[kMaxLineLength]; + strcpyn(buffer, sizeof(buffer), line.c_str()); + int nstart = 0, nend = 0, vstart = 0, vend = 0; + sscanf(buffer, "user_pref(\"%n%*[^\"]%n\", %n%*[^)]%n);", + &nstart, &nend, &vstart, &vend); + if (vend > 0) { + char* name = buffer + nstart; + name[nend - nstart] = 0; + if ((vend - vstart >= 2) && (buffer[vstart] == '"')) { + vstart += 1; + vend -= 1; + } + char* value = buffer + vstart; + value[vend - vstart] = 0; + if ((strncmp(name, prefix, prefix_len) == 0) && *value) { + settings->Add(name + prefix_len, value); + } + } else { + LOG_F(LS_WARNING) << "Unparsed pref [" << buffer << "]"; + } + } + fs->Close(); + return true; +} + +bool GetFirefoxProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + Pathname path; + bool success = false; + if (GetDefaultFirefoxProfile(&path)) { + StringMap settings; + path.SetFilename("prefs.js"); + if (ReadFirefoxPrefs(path, "network.proxy.", &settings)) { + success = true; + proxy->bypass_list = + settings.Get("no_proxies_on", "localhost, 127.0.0.1"); + if (settings.Get("type") == "1") { + // User has manually specified a proxy, try to figure out what + // type it is. + if (ProxyListMatch(purl, proxy->bypass_list.c_str(), ',')) { + // Our url is in the list of url's to bypass proxy. + } else if (settings.Get("share_proxy_settings") == "true") { + proxy->type = PROXY_UNKNOWN; + proxy->address.SetIP(settings.Get("http")); + proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); + } else if (settings.IsSet("socks")) { + proxy->type = PROXY_SOCKS5; + proxy->address.SetIP(settings.Get("socks")); + proxy->address.SetPort(atoi(settings.Get("socks_port").c_str())); + } else if (settings.IsSet("ssl")) { + proxy->type = PROXY_HTTPS; + proxy->address.SetIP(settings.Get("ssl")); + proxy->address.SetPort(atoi(settings.Get("ssl_port").c_str())); + } else if (settings.IsSet("http")) { + proxy->type = PROXY_HTTPS; + proxy->address.SetIP(settings.Get("http")); + proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); + } + } else if (settings.Get("type") == "2") { + // Browser is configured to get proxy settings from a given url. + proxy->autoconfig_url = settings.Get("autoconfig_url").c_str(); + } else if (settings.Get("type") == "4") { + // Browser is configured to auto detect proxy config. + proxy->autodetect = true; + } else { + // No proxy set. + } + } + } + return success; +} + +#if defined(WEBRTC_WIN) // Windows specific implementation for reading Internet + // Explorer proxy settings. + +void LogGetProxyFault() { + LOG_GLEM(LERROR, WINHTTP) << "WinHttpGetProxyForUrl faulted!!"; +} + +BOOL MyWinHttpGetProxyForUrl(pfnWinHttpGetProxyForUrl pWHGPFU, + HINTERNET hWinHttp, LPCWSTR url, + WINHTTP_AUTOPROXY_OPTIONS *options, + WINHTTP_PROXY_INFO *info) { + // WinHttpGetProxyForUrl() can call plugins which can crash. + // In the case of McAfee scriptproxy.dll, it does crash in + // older versions. Try to catch crashes here and treat as an + // error. + BOOL success = FALSE; + +#if (_HAS_EXCEPTIONS == 0) + __try { + success = pWHGPFU(hWinHttp, url, options, info); + } __except(EXCEPTION_EXECUTE_HANDLER) { + // This is a separate function to avoid + // Visual C++ error 2712 when compiling with C++ EH + LogGetProxyFault(); + } +#else + success = pWHGPFU(hWinHttp, url, options, info); +#endif // (_HAS_EXCEPTIONS == 0) + + return success; +} + +bool IsDefaultBrowserFirefox() { + HKEY key; + LONG result = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"http\\shell\\open\\command", + 0, KEY_READ, &key); + if (ERROR_SUCCESS != result) + return false; + + wchar_t* value = NULL; + DWORD size, type; + result = RegQueryValueEx(key, L"", 0, &type, NULL, &size); + if (REG_SZ != type) { + result = ERROR_ACCESS_DENIED; // Any error is fine + } else if (ERROR_SUCCESS == result) { + value = new wchar_t[size+1]; + BYTE* buffer = reinterpret_cast(value); + result = RegQueryValueEx(key, L"", 0, &type, buffer, &size); + } + RegCloseKey(key); + + bool success = false; + if (ERROR_SUCCESS == result) { + value[size] = L'\0'; + for (size_t i = 0; i < size; ++i) { + value[i] = tolowercase(value[i]); + } + success = (NULL != strstr(value, L"firefox.exe")); + } + delete [] value; + return success; +} + +bool GetWinHttpProxySettings(const char* url, ProxyInfo* proxy) { + HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); + if (winhttp_handle == NULL) { + LOG(LS_ERROR) << "Failed to load winhttp.dll."; + return false; + } + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg; + memset(&iecfg, 0, sizeof(iecfg)); + Url purl(url); + pfnWinHttpGetIEProxyConfig pWHGIEPC = + reinterpret_cast( + GetProcAddress(winhttp_handle, + "WinHttpGetIEProxyConfigForCurrentUser")); + bool success = false; + if (pWHGIEPC && pWHGIEPC(&iecfg)) { + // We were read proxy config successfully. + success = true; + if (iecfg.fAutoDetect) { + proxy->autodetect = true; + } + if (iecfg.lpszAutoConfigUrl) { + proxy->autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl); + GlobalFree(iecfg.lpszAutoConfigUrl); + } + if (iecfg.lpszProxyBypass) { + proxy->bypass_list = ToUtf8(iecfg.lpszProxyBypass); + GlobalFree(iecfg.lpszProxyBypass); + } + if (iecfg.lpszProxy) { + if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { + ParseProxy(ToUtf8(iecfg.lpszProxy), proxy); + } + GlobalFree(iecfg.lpszProxy); + } + } + FreeLibrary(winhttp_handle); + return success; +} + +// Uses the WinHTTP API to auto detect proxy for the given url. Firefox and IE +// have slightly different option dialogs for proxy settings. In Firefox, +// either a location of a proxy configuration file can be specified or auto +// detection can be selected. In IE theese two options can be independently +// selected. For the case where both options are selected (only IE) we try to +// fetch the config file first, and if that fails we'll perform an auto +// detection. +// +// Returns true if we successfully performed an auto detection not depending on +// whether we found a proxy or not. Returns false on error. +bool WinHttpAutoDetectProxyForUrl(const char* agent, const char* url, + ProxyInfo* proxy) { + Url purl(url); + bool success = true; + HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); + if (winhttp_handle == NULL) { + LOG(LS_ERROR) << "Failed to load winhttp.dll."; + return false; + } + pfnWinHttpOpen pWHO = + reinterpret_cast(GetProcAddress(winhttp_handle, + "WinHttpOpen")); + pfnWinHttpCloseHandle pWHCH = + reinterpret_cast( + GetProcAddress(winhttp_handle, "WinHttpCloseHandle")); + pfnWinHttpGetProxyForUrl pWHGPFU = + reinterpret_cast( + GetProcAddress(winhttp_handle, "WinHttpGetProxyForUrl")); + if (pWHO && pWHCH && pWHGPFU) { + if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(), + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0)) { + BOOL result = FALSE; + WINHTTP_PROXY_INFO info; + memset(&info, 0, sizeof(info)); + if (proxy->autodetect) { + // Use DHCP and DNS to try to find any proxy to use. + WINHTTP_AUTOPROXY_OPTIONS options; + memset(&options, 0, sizeof(options)); + options.fAutoLogonIfChallenged = TRUE; + + options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT; + options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP + | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + result = MyWinHttpGetProxyForUrl( + pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); + } + if (!result && !proxy->autoconfig_url.empty()) { + // We have the location of a proxy config file. Download it and + // execute it to find proxy settings for our url. + WINHTTP_AUTOPROXY_OPTIONS options; + memset(&options, 0, sizeof(options)); + memset(&info, 0, sizeof(info)); + options.fAutoLogonIfChallenged = TRUE; + + std::wstring autoconfig_url16((ToUtf16)(proxy->autoconfig_url)); + options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; + options.lpszAutoConfigUrl = autoconfig_url16.c_str(); + + result = MyWinHttpGetProxyForUrl( + pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); + } + if (result) { + // Either the given auto config url was valid or auto + // detection found a proxy on this network. + if (info.lpszProxy) { + // TODO: Does this bypass list differ from the list + // retreived from GetWinHttpProxySettings earlier? + if (info.lpszProxyBypass) { + proxy->bypass_list = ToUtf8(info.lpszProxyBypass); + GlobalFree(info.lpszProxyBypass); + } else { + proxy->bypass_list.clear(); + } + if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { + // Found proxy for this URL. If parsing the address turns + // out ok then we are successful. + success = ParseProxy(ToUtf8(info.lpszProxy), proxy); + } + GlobalFree(info.lpszProxy); + } + } else { + // We could not find any proxy for this url. + LOG(LS_INFO) << "No proxy detected for " << url; + } + pWHCH(hWinHttp); + } + } else { + LOG(LS_ERROR) << "Failed loading WinHTTP functions."; + success = false; + } + FreeLibrary(winhttp_handle); + return success; +} + +#if 0 // Below functions currently not used. + +bool GetJsProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) { + pfnInternetGetProxyInfo pIGPI = + reinterpret_cast( + GetProcAddress(hModJS, "InternetGetProxyInfo")); + if (pIGPI) { + char proxy[256], host[256]; + memset(proxy, 0, sizeof(proxy)); + char * ptr = proxy; + DWORD proxylen = sizeof(proxy); + std::string surl = Utf8String(url); + DWORD hostlen = _snprintf(host, sizeof(host), "http%s://%S", + purl.secure() ? "s" : "", purl.server()); + if (pIGPI(surl.data(), surl.size(), host, hostlen, &ptr, &proxylen)) { + LOG(INFO) << "Proxy: " << proxy; + } else { + LOG_GLE(INFO) << "InternetGetProxyInfo"; + } + } + FreeLibrary(hModJS); + } + return success; +} + +bool GetWmProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + INSNetSourceCreator * nsc = 0; + HRESULT hr = CoCreateInstance(CLSID_ClientNetManager, 0, CLSCTX_ALL, + IID_INSNetSourceCreator, (LPVOID *) &nsc); + if (SUCCEEDED(hr)) { + if (SUCCEEDED(hr = nsc->Initialize())) { + VARIANT dispatch; + VariantInit(&dispatch); + if (SUCCEEDED(hr = nsc->GetNetSourceAdminInterface(L"http", &dispatch))) { + IWMSInternalAdminNetSource * ians = 0; + if (SUCCEEDED(hr = dispatch.pdispVal->QueryInterface( + IID_IWMSInternalAdminNetSource, (LPVOID *) &ians))) { + _bstr_t host(purl.server()); + BSTR proxy = 0; + BOOL bProxyEnabled = FALSE; + DWORD port, context = 0; + if (SUCCEEDED(hr = ians->FindProxyForURL( + L"http", host, &bProxyEnabled, &proxy, &port, &context))) { + success = true; + if (bProxyEnabled) { + _bstr_t sproxy = proxy; + proxy->ptype = PT_HTTPS; + proxy->host = sproxy; + proxy->port = port; + } + } + SysFreeString(proxy); + if (FAILED(hr = ians->ShutdownProxyContext(context))) { + LOG(LS_INFO) << "IWMSInternalAdminNetSource::ShutdownProxyContext" + << "failed: " << hr; + } + ians->Release(); + } + } + VariantClear(&dispatch); + if (FAILED(hr = nsc->Shutdown())) { + LOG(LS_INFO) << "INSNetSourceCreator::Shutdown failed: " << hr; + } + } + nsc->Release(); + } + return success; +} + +bool GetIePerConnectionProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + INTERNET_PER_CONN_OPTION_LIST list; + INTERNET_PER_CONN_OPTION options[3]; + memset(&list, 0, sizeof(list)); + memset(&options, 0, sizeof(options)); + + list.dwSize = sizeof(list); + list.dwOptionCount = 3; + list.pOptions = options; + options[0].dwOption = INTERNET_PER_CONN_FLAGS; + options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER; + options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; + DWORD dwSize = sizeof(list); + + if (!InternetQueryOption(0, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, + &dwSize)) { + LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); + } else if ((options[0].Value.dwValue & PROXY_TYPE_PROXY) != 0) { + success = true; + if (!ProxyListMatch(purl, nonnull(options[2].Value.pszValue), _T(';'))) { + ParseProxy(nonnull(options[1].Value.pszValue), proxy); + } + } else if ((options[0].Value.dwValue & PROXY_TYPE_DIRECT) != 0) { + success = true; + } else { + LOG(LS_INFO) << "unknown internet access type: " + << options[0].Value.dwValue; + } + if (options[1].Value.pszValue) { + GlobalFree(options[1].Value.pszValue); + } + if (options[2].Value.pszValue) { + GlobalFree(options[2].Value.pszValue); + } + return success; +} + +#endif // 0 + +// Uses the InternetQueryOption function to retrieve proxy settings +// from the registry. This will only give us the 'static' settings, +// ie, not any information about auto config etc. +bool GetIeLanProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + wchar_t buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + INTERNET_PROXY_INFO * info = reinterpret_cast(buffer); + DWORD dwSize = sizeof(buffer); + + if (!InternetQueryOption(0, INTERNET_OPTION_PROXY, info, &dwSize)) { + LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); + } else if (info->dwAccessType == INTERNET_OPEN_TYPE_DIRECT) { + success = true; + } else if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY) { + success = true; + if (!ProxyListMatch(purl, nonnull(reinterpret_cast( + info->lpszProxyBypass)), ' ')) { + ParseProxy(nonnull(reinterpret_cast(info->lpszProxy)), + proxy); + } + } else { + LOG(LS_INFO) << "unknown internet access type: " << info->dwAccessType; + } + return success; +} + +bool GetIeProxySettings(const char* agent, const char* url, ProxyInfo* proxy) { + bool success = GetWinHttpProxySettings(url, proxy); + if (!success) { + // TODO: Should always call this if no proxy were detected by + // GetWinHttpProxySettings? + // WinHttp failed. Try using the InternetOptionQuery method instead. + return GetIeLanProxySettings(url, proxy); + } + return true; +} + +#endif // WEBRTC_WIN + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) // WEBRTC_MAC && !defined(WEBRTC_IOS) specific implementation for reading system wide + // proxy settings. + +bool p_getProxyInfoForTypeFromDictWithKeys(ProxyInfo* proxy, + ProxyType type, + const CFDictionaryRef proxyDict, + const CFStringRef enabledKey, + const CFStringRef hostKey, + const CFStringRef portKey) { + // whether or not we set up the proxy info. + bool result = false; + + // we use this as a scratch variable for determining if operations + // succeeded. + bool converted = false; + + // the data we need to construct the SocketAddress for the proxy. + std::string hostname; + int port; + + if ((proxyDict != NULL) && + (CFGetTypeID(proxyDict) == CFDictionaryGetTypeID())) { + // CoreFoundation stuff that we'll have to get from + // the dictionaries and interpret or convert into more usable formats. + CFNumberRef enabledCFNum; + CFNumberRef portCFNum; + CFStringRef hostCFStr; + + enabledCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, enabledKey); + + if (p_isCFNumberTrue(enabledCFNum)) { + // let's see if we can get the address and port. + hostCFStr = (CFStringRef)CFDictionaryGetValue(proxyDict, hostKey); + converted = p_convertHostCFStringRefToCPPString(hostCFStr, hostname); + if (converted) { + portCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, portKey); + converted = p_convertCFNumberToInt(portCFNum, &port); + if (converted) { + // we have something enabled, with a hostname and a port. + // That's sufficient to set up the proxy info. + proxy->type = type; + proxy->address.SetIP(hostname); + proxy->address.SetPort(port); + result = true; + } + } + } + } + + return result; +} + +// Looks for proxy information in the given dictionary, +// return true if it found sufficient information to define one, +// false otherwise. This is guaranteed to not change the values in proxy +// unless a full-fledged proxy description was discovered in the dictionary. +// However, at the present time this does not support username or password. +// Checks first for a SOCKS proxy, then for HTTPS, then HTTP. +bool GetMacProxySettingsFromDictionary(ProxyInfo* proxy, + const CFDictionaryRef proxyDict) { + // the function result. + bool gotProxy = false; + + + // first we see if there's a SOCKS proxy in place. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, + PROXY_SOCKS5, + proxyDict, + kSCPropNetProxiesSOCKSEnable, + kSCPropNetProxiesSOCKSProxy, + kSCPropNetProxiesSOCKSPort); + + if (!gotProxy) { + // okay, no SOCKS proxy, let's look for https. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, + PROXY_HTTPS, + proxyDict, + kSCPropNetProxiesHTTPSEnable, + kSCPropNetProxiesHTTPSProxy, + kSCPropNetProxiesHTTPSPort); + if (!gotProxy) { + // Finally, try HTTP proxy. Note that flute doesn't + // differentiate between HTTPS and HTTP, hence we are using the + // same flute type here, ie. PROXY_HTTPS. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys( + proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPEnable, + kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort); + } + } + return gotProxy; +} + +// TODO(hughv) Update keychain functions. They work on 10.8, but are depricated. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +bool p_putPasswordInProxyInfo(ProxyInfo* proxy) { + bool result = true; // by default we assume we're good. + // for all we know there isn't any password. We'll set to false + // if we find a problem. + + // Ask the keychain for an internet password search for the given protocol. + OSStatus oss = 0; + SecKeychainAttributeList attrList; + attrList.count = 3; + SecKeychainAttribute attributes[3]; + attrList.attr = attributes; + + attributes[0].tag = kSecProtocolItemAttr; + attributes[0].length = sizeof(SecProtocolType); + SecProtocolType protocol; + switch (proxy->type) { + case PROXY_HTTPS : + protocol = kSecProtocolTypeHTTPS; + break; + case PROXY_SOCKS5 : + protocol = kSecProtocolTypeSOCKS; + break; + default : + LOG(LS_ERROR) << "asked for proxy password for unknown proxy type."; + result = false; + break; + } + attributes[0].data = &protocol; + + UInt32 port = proxy->address.port(); + attributes[1].tag = kSecPortItemAttr; + attributes[1].length = sizeof(UInt32); + attributes[1].data = &port; + + std::string ip = proxy->address.ipaddr().ToString(); + attributes[2].tag = kSecServerItemAttr; + attributes[2].length = ip.length(); + attributes[2].data = const_cast(ip.c_str()); + + if (result) { + LOG(LS_INFO) << "trying to get proxy username/password"; + SecKeychainSearchRef sref; + oss = SecKeychainSearchCreateFromAttributes(NULL, + kSecInternetPasswordItemClass, + &attrList, &sref); + if (0 == oss) { + LOG(LS_INFO) << "SecKeychainSearchCreateFromAttributes was good"; + // Get the first item, if there is one. + SecKeychainItemRef iref; + oss = SecKeychainSearchCopyNext(sref, &iref); + if (0 == oss) { + LOG(LS_INFO) << "...looks like we have the username/password data"; + // If there is, get the username and the password. + + SecKeychainAttributeInfo attribsToGet; + attribsToGet.count = 1; + UInt32 tag = kSecAccountItemAttr; + UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING; + void *data; + UInt32 length; + SecKeychainAttributeList *localList; + + attribsToGet.tag = &tag; + attribsToGet.format = &format; + OSStatus copyres = SecKeychainItemCopyAttributesAndData(iref, + &attribsToGet, + NULL, + &localList, + &length, + &data); + if (0 == copyres) { + LOG(LS_INFO) << "...and we can pull it out."; + // now, we know from experimentation (sadly not from docs) + // that the username is in the local attribute list, + // and the password in the data, + // both without null termination but with info on their length. + // grab the password from the data. + std::string password; + password.append(static_cast(data), length); + + // make the password into a CryptString + // huh, at the time of writing, you can't. + // so we'll skip that for now and come back to it later. + + // now put the username in the proxy. + if (1 <= localList->attr->length) { + proxy->username.append( + static_cast(localList->attr->data), + localList->attr->length); + LOG(LS_INFO) << "username is " << proxy->username; + } else { + LOG(LS_ERROR) << "got keychain entry with no username"; + result = false; + } + } else { + LOG(LS_ERROR) << "couldn't copy info from keychain."; + result = false; + } + SecKeychainItemFreeAttributesAndData(localList, data); + } else if (errSecItemNotFound == oss) { + LOG(LS_INFO) << "...username/password info not found"; + } else { + // oooh, neither 0 nor itemNotFound. + LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; + result = false; + } + } else if (errSecItemNotFound == oss) { // noop + } else { + // oooh, neither 0 nor itemNotFound. + LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; + result = false; + } + } + + return result; +} + +bool GetMacProxySettings(ProxyInfo* proxy) { + // based on the Apple Technical Q&A QA1234 + // http://developer.apple.com/qa/qa2001/qa1234.html + CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL); + bool result = false; + + if (proxyDict != NULL) { + // sending it off to another function makes it easier to unit test + // since we can make our own dictionary to hand to that function. + result = GetMacProxySettingsFromDictionary(proxy, proxyDict); + + if (result) { + result = p_putPasswordInProxyInfo(proxy); + } + + // We created the dictionary with something that had the + // word 'copy' in it, so we have to release it, according + // to the Carbon memory management standards. + CFRelease(proxyDict); + } else { + LOG(LS_ERROR) << "SCDynamicStoreCopyProxies failed"; + } + + return result; +} +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +bool AutoDetectProxySettings(const char* agent, const char* url, + ProxyInfo* proxy) { +#if defined(WEBRTC_WIN) + return WinHttpAutoDetectProxyForUrl(agent, url, proxy); +#else + LOG(LS_WARNING) << "Proxy auto-detection not implemented for this platform"; + return false; +#endif +} + +bool GetSystemDefaultProxySettings(const char* agent, const char* url, + ProxyInfo* proxy) { +#if defined(WEBRTC_WIN) + return GetIeProxySettings(agent, url, proxy); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + return GetMacProxySettings(proxy); +#else + // TODO: Get System settings if browser is not firefox. + return GetFirefoxProxySettings(url, proxy); +#endif +} + +bool GetProxySettingsForUrl(const char* agent, const char* url, + ProxyInfo* proxy, bool long_operation) { + UserAgent a = GetAgent(agent); + bool result; + switch (a) { + case UA_FIREFOX: { + result = GetFirefoxProxySettings(url, proxy); + break; + } +#if defined(WEBRTC_WIN) + case UA_INTERNETEXPLORER: + result = GetIeProxySettings(agent, url, proxy); + break; + case UA_UNKNOWN: + // Agent not defined, check default browser. + if (IsDefaultBrowserFirefox()) { + result = GetFirefoxProxySettings(url, proxy); + } else { + result = GetIeProxySettings(agent, url, proxy); + } + break; +#endif // WEBRTC_WIN + default: + result = GetSystemDefaultProxySettings(agent, url, proxy); + break; + } + + // TODO: Consider using the 'long_operation' parameter to + // decide whether to do the auto detection. + if (result && (proxy->autodetect || + !proxy->autoconfig_url.empty())) { + // Use WinHTTP to auto detect proxy for us. + result = AutoDetectProxySettings(agent, url, proxy); + if (!result) { + // Either auto detection is not supported or we simply didn't + // find any proxy, reset type. + proxy->type = rtc::PROXY_NONE; + } + } + return result; +} + +} // namespace rtc diff --git a/webrtc/base/proxydetect.h b/webrtc/base/proxydetect.h new file mode 100644 index 000000000..f9bf5f873 --- /dev/null +++ b/webrtc/base/proxydetect.h @@ -0,0 +1,31 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef _PROXYDETECT_H_ +#define _PROXYDETECT_H_ + +#include "webrtc/base/proxyinfo.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +namespace rtc { +// Auto-detect the proxy server. Returns true if a proxy is configured, +// although hostname may be empty if the proxy is not required for +// the given URL. + +bool GetProxySettingsForUrl(const char* agent, const char* url, + rtc::ProxyInfo* proxy, + bool long_operation = false); + +} // namespace rtc + +#endif // _PROXYDETECT_H_ diff --git a/webrtc/base/proxydetect_unittest.cc b/webrtc/base/proxydetect_unittest.cc new file mode 100644 index 000000000..ca0b428fc --- /dev/null +++ b/webrtc/base/proxydetect_unittest.cc @@ -0,0 +1,164 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/fileutils_mock.h" +#include "webrtc/base/proxydetect.h" + +namespace rtc { + +static const std::string kFirefoxProfilesIni = + "[Profile0]\n" + "Name=default\n" + "IsRelative=1\n" + "Path=Profiles/2de53ejb.default\n" + "Default=1\n"; + +static const std::string kFirefoxHeader = + "# Mozilla User Preferences\n" + "\n" + "/* Some Comments\n" + "*\n" + "*/\n" + "\n"; + +static const std::string kFirefoxCorruptHeader = + "iuahueqe32164"; + +static const std::string kProxyAddress = "proxy.net.com"; + +// Mocking out platform specific path to firefox prefs file. +class FirefoxPrefsFileSystem : public FakeFileSystem { + public: + explicit FirefoxPrefsFileSystem(const std::vector& all_files) : + FakeFileSystem(all_files) { + } + virtual FileStream* OpenFile(const Pathname& filename, + const std::string& mode) { + // TODO: We could have a platform dependent check of paths here. + std::string name = filename.basename(); + name.append(filename.extension()); + EXPECT_TRUE(name.compare("prefs.js") == 0 || + name.compare("profiles.ini") == 0); + FileStream* stream = FakeFileSystem::OpenFile(name, mode); + return stream; + } +}; + +class ProxyDetectTest : public testing::Test { +}; + +bool GetProxyInfo(const std::string prefs, ProxyInfo* info) { + std::vector files; + files.push_back(rtc::FakeFileSystem::File("profiles.ini", + kFirefoxProfilesIni)); + files.push_back(rtc::FakeFileSystem::File("prefs.js", prefs)); + rtc::FilesystemScope fs(new rtc::FirefoxPrefsFileSystem(files)); + return GetProxySettingsForUrl("Firefox", "www.google.com", info, false); +} + +// Verifies that an empty Firefox prefs file results in no proxy detected. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxEmptyPrefs) { + ProxyInfo proxy_info; + EXPECT_TRUE(GetProxyInfo(kFirefoxHeader, &proxy_info)); + EXPECT_EQ(PROXY_NONE, proxy_info.type); +} + +// Verifies that corrupted prefs file results in no proxy detected. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxCorruptedPrefs) { + ProxyInfo proxy_info; + EXPECT_TRUE(GetProxyInfo(kFirefoxCorruptHeader, &proxy_info)); + EXPECT_EQ(PROXY_NONE, proxy_info.type); +} + +// Verifies that SOCKS5 proxy is detected if configured. SOCKS uses a +// handshake protocol to inform the proxy software about the +// connection that the client is trying to make and may be used for +// any form of TCP or UDP socket connection. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySocks) { + ProxyInfo proxy_info; + SocketAddress proxy_address("proxy.socks.com", 6666); + std::string prefs(kFirefoxHeader); + prefs.append("user_pref(\"network.proxy.socks\", \"proxy.socks.com\");\n"); + prefs.append("user_pref(\"network.proxy.socks_port\", 6666);\n"); + prefs.append("user_pref(\"network.proxy.type\", 1);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_SOCKS5, proxy_info.type); + EXPECT_EQ(proxy_address, proxy_info.address); +} + +// Verified that SSL proxy is detected if configured. SSL proxy is an +// extention of a HTTP proxy to support secure connections. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySsl) { + ProxyInfo proxy_info; + SocketAddress proxy_address("proxy.ssl.com", 7777); + std::string prefs(kFirefoxHeader); + + prefs.append("user_pref(\"network.proxy.ssl\", \"proxy.ssl.com\");\n"); + prefs.append("user_pref(\"network.proxy.ssl_port\", 7777);\n"); + prefs.append("user_pref(\"network.proxy.type\", 1);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_HTTPS, proxy_info.type); + EXPECT_EQ(proxy_address, proxy_info.address); +} + +// Verifies that a HTTP proxy is detected if configured. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyHttp) { + ProxyInfo proxy_info; + SocketAddress proxy_address("proxy.http.com", 8888); + std::string prefs(kFirefoxHeader); + + prefs.append("user_pref(\"network.proxy.http\", \"proxy.http.com\");\n"); + prefs.append("user_pref(\"network.proxy.http_port\", 8888);\n"); + prefs.append("user_pref(\"network.proxy.type\", 1);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_HTTPS, proxy_info.type); + EXPECT_EQ(proxy_address, proxy_info.address); +} + +// Verifies detection of automatic proxy detection. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAuto) { + ProxyInfo proxy_info; + std::string prefs(kFirefoxHeader); + + prefs.append("user_pref(\"network.proxy.type\", 4);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_NONE, proxy_info.type); + EXPECT_TRUE(proxy_info.autodetect); + EXPECT_TRUE(proxy_info.autoconfig_url.empty()); +} + +// Verifies detection of automatic proxy detection using a static url +// to config file. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAutoUrl) { + ProxyInfo proxy_info; + std::string prefs(kFirefoxHeader); + + prefs.append( + "user_pref(\"network.proxy.autoconfig_url\", \"http://a/b.pac\");\n"); + prefs.append("user_pref(\"network.proxy.type\", 2);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_FALSE(proxy_info.autodetect); + EXPECT_EQ(PROXY_NONE, proxy_info.type); + EXPECT_EQ(0, proxy_info.autoconfig_url.compare("http://a/b.pac")); +} + +} // namespace rtc diff --git a/webrtc/base/proxyinfo.cc b/webrtc/base/proxyinfo.cc new file mode 100644 index 000000000..70c3b5584 --- /dev/null +++ b/webrtc/base/proxyinfo.cc @@ -0,0 +1,20 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/proxyinfo.h" + +namespace rtc { + +const char * ProxyToString(ProxyType proxy) { + const char * const PROXY_NAMES[] = { "none", "https", "socks5", "unknown" }; + return PROXY_NAMES[proxy]; +} + +} // namespace rtc diff --git a/webrtc/base/proxyinfo.h b/webrtc/base/proxyinfo.h new file mode 100644 index 000000000..9947f4552 --- /dev/null +++ b/webrtc/base/proxyinfo.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_PROXYINFO_H__ +#define WEBRTC_BASE_PROXYINFO_H__ + +#include +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/cryptstring.h" + +namespace rtc { + +enum ProxyType { + PROXY_NONE, + PROXY_HTTPS, + PROXY_SOCKS5, + PROXY_UNKNOWN +}; +const char * ProxyToString(ProxyType proxy); + +struct ProxyInfo { + ProxyType type; + SocketAddress address; + std::string autoconfig_url; + bool autodetect; + std::string bypass_list; + std::string username; + CryptString password; + + ProxyInfo() : type(PROXY_NONE), autodetect(false) { } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_PROXYINFO_H__ diff --git a/webrtc/base/proxyserver.cc b/webrtc/base/proxyserver.cc new file mode 100644 index 000000000..548cfbf5b --- /dev/null +++ b/webrtc/base/proxyserver.cc @@ -0,0 +1,144 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/proxyserver.h" + +#include +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +// ProxyServer +ProxyServer::ProxyServer( + SocketFactory* int_factory, const SocketAddress& int_addr, + SocketFactory* ext_factory, const SocketAddress& ext_ip) + : ext_factory_(ext_factory), ext_ip_(ext_ip.ipaddr(), 0), // strip off port + server_socket_(int_factory->CreateAsyncSocket(int_addr.family(), + SOCK_STREAM)) { + ASSERT(server_socket_.get() != NULL); + ASSERT(int_addr.family() == AF_INET || int_addr.family() == AF_INET6); + server_socket_->Bind(int_addr); + server_socket_->Listen(5); + server_socket_->SignalReadEvent.connect(this, &ProxyServer::OnAcceptEvent); +} + +ProxyServer::~ProxyServer() { + for (BindingList::iterator it = bindings_.begin(); + it != bindings_.end(); ++it) { + delete (*it); + } +} + +void ProxyServer::OnAcceptEvent(AsyncSocket* socket) { + ASSERT(socket != NULL && socket == server_socket_.get()); + AsyncSocket* int_socket = socket->Accept(NULL); + AsyncProxyServerSocket* wrapped_socket = WrapSocket(int_socket); + AsyncSocket* ext_socket = ext_factory_->CreateAsyncSocket(ext_ip_.family(), + SOCK_STREAM); + if (ext_socket) { + ext_socket->Bind(ext_ip_); + bindings_.push_back(new ProxyBinding(wrapped_socket, ext_socket)); + } else { + LOG(LS_ERROR) << "Unable to create external socket on proxy accept event"; + } +} + +void ProxyServer::OnBindingDestroyed(ProxyBinding* binding) { + BindingList::iterator it = + std::find(bindings_.begin(), bindings_.end(), binding); + delete (*it); + bindings_.erase(it); +} + +// ProxyBinding +ProxyBinding::ProxyBinding(AsyncProxyServerSocket* int_socket, + AsyncSocket* ext_socket) + : int_socket_(int_socket), ext_socket_(ext_socket), connected_(false), + out_buffer_(kBufferSize), in_buffer_(kBufferSize) { + int_socket_->SignalConnectRequest.connect(this, + &ProxyBinding::OnConnectRequest); + int_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnInternalRead); + int_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnInternalWrite); + int_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnInternalClose); + ext_socket_->SignalConnectEvent.connect(this, + &ProxyBinding::OnExternalConnect); + ext_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnExternalRead); + ext_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnExternalWrite); + ext_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnExternalClose); +} + +void ProxyBinding::OnConnectRequest(AsyncProxyServerSocket* socket, + const SocketAddress& addr) { + ASSERT(!connected_ && ext_socket_.get() != NULL); + ext_socket_->Connect(addr); + // TODO: handle errors here +} + +void ProxyBinding::OnInternalRead(AsyncSocket* socket) { + Read(int_socket_.get(), &out_buffer_); + Write(ext_socket_.get(), &out_buffer_); +} + +void ProxyBinding::OnInternalWrite(AsyncSocket* socket) { + Write(int_socket_.get(), &in_buffer_); +} + +void ProxyBinding::OnInternalClose(AsyncSocket* socket, int err) { + Destroy(); +} + +void ProxyBinding::OnExternalConnect(AsyncSocket* socket) { + ASSERT(socket != NULL); + connected_ = true; + int_socket_->SendConnectResult(0, socket->GetRemoteAddress()); +} + +void ProxyBinding::OnExternalRead(AsyncSocket* socket) { + Read(ext_socket_.get(), &in_buffer_); + Write(int_socket_.get(), &in_buffer_); +} + +void ProxyBinding::OnExternalWrite(AsyncSocket* socket) { + Write(ext_socket_.get(), &out_buffer_); +} + +void ProxyBinding::OnExternalClose(AsyncSocket* socket, int err) { + if (!connected_) { + int_socket_->SendConnectResult(err, SocketAddress()); + } + Destroy(); +} + +void ProxyBinding::Read(AsyncSocket* socket, FifoBuffer* buffer) { + // Only read if the buffer is empty. + ASSERT(socket != NULL); + size_t size; + int read; + if (buffer->GetBuffered(&size) && size == 0) { + void* p = buffer->GetWriteBuffer(&size); + read = socket->Recv(p, size); + buffer->ConsumeWriteBuffer(_max(read, 0)); + } +} + +void ProxyBinding::Write(AsyncSocket* socket, FifoBuffer* buffer) { + ASSERT(socket != NULL); + size_t size; + int written; + const void* p = buffer->GetReadData(&size); + written = socket->Send(p, size); + buffer->ConsumeReadData(_max(written, 0)); +} + +void ProxyBinding::Destroy() { + SignalDestroyed(this); +} + +} // namespace rtc diff --git a/webrtc/base/proxyserver.h b/webrtc/base/proxyserver.h new file mode 100644 index 000000000..80e15d969 --- /dev/null +++ b/webrtc/base/proxyserver.h @@ -0,0 +1,96 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_PROXYSERVER_H_ +#define WEBRTC_BASE_PROXYSERVER_H_ + +#include +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +class SocketFactory; + +// ProxyServer is a base class that allows for easy construction of proxy +// servers. With its helper class ProxyBinding, it contains all the necessary +// logic for receiving and bridging connections. The specific client-server +// proxy protocol is implemented by an instance of the AsyncProxyServerSocket +// class; children of ProxyServer implement WrapSocket appropriately to return +// the correct protocol handler. + +class ProxyBinding : public sigslot::has_slots<> { + public: + ProxyBinding(AsyncProxyServerSocket* in_socket, AsyncSocket* out_socket); + sigslot::signal1 SignalDestroyed; + + private: + void OnConnectRequest(AsyncProxyServerSocket* socket, + const SocketAddress& addr); + void OnInternalRead(AsyncSocket* socket); + void OnInternalWrite(AsyncSocket* socket); + void OnInternalClose(AsyncSocket* socket, int err); + void OnExternalConnect(AsyncSocket* socket); + void OnExternalRead(AsyncSocket* socket); + void OnExternalWrite(AsyncSocket* socket); + void OnExternalClose(AsyncSocket* socket, int err); + + static void Read(AsyncSocket* socket, FifoBuffer* buffer); + static void Write(AsyncSocket* socket, FifoBuffer* buffer); + void Destroy(); + + static const int kBufferSize = 4096; + scoped_ptr int_socket_; + scoped_ptr ext_socket_; + bool connected_; + FifoBuffer out_buffer_; + FifoBuffer in_buffer_; + DISALLOW_EVIL_CONSTRUCTORS(ProxyBinding); +}; + +class ProxyServer : public sigslot::has_slots<> { + public: + ProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr, + SocketFactory* ext_factory, const SocketAddress& ext_ip); + virtual ~ProxyServer(); + + protected: + void OnAcceptEvent(AsyncSocket* socket); + virtual AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) = 0; + void OnBindingDestroyed(ProxyBinding* binding); + + private: + typedef std::list BindingList; + SocketFactory* ext_factory_; + SocketAddress ext_ip_; + scoped_ptr server_socket_; + BindingList bindings_; + DISALLOW_EVIL_CONSTRUCTORS(ProxyServer); +}; + +// SocksProxyServer is a simple extension of ProxyServer to implement SOCKS. +class SocksProxyServer : public ProxyServer { + public: + SocksProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr, + SocketFactory* ext_factory, const SocketAddress& ext_ip) + : ProxyServer(int_factory, int_addr, ext_factory, ext_ip) { + } + protected: + AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) { + return new AsyncSocksProxyServerSocket(socket); + } + DISALLOW_EVIL_CONSTRUCTORS(SocksProxyServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_PROXYSERVER_H_ diff --git a/webrtc/base/ratelimiter.cc b/webrtc/base/ratelimiter.cc new file mode 100644 index 000000000..c4a251d14 --- /dev/null +++ b/webrtc/base/ratelimiter.cc @@ -0,0 +1,29 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/ratelimiter.h" + +namespace rtc { + +bool RateLimiter::CanUse(size_t desired, double time) { + return ((time > period_end_ && desired <= max_per_period_) || + (used_in_period_ + desired) <= max_per_period_); +} + +void RateLimiter::Use(size_t used, double time) { + if (time > period_end_) { + period_start_ = time; + period_end_ = time + period_length_; + used_in_period_ = 0; + } + used_in_period_ += used; +} + +} // namespace rtc diff --git a/webrtc/base/ratelimiter.h b/webrtc/base/ratelimiter.h new file mode 100644 index 000000000..cf5d6b05b --- /dev/null +++ b/webrtc/base/ratelimiter.h @@ -0,0 +1,63 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_RATELIMITER_H_ +#define WEBRTC_BASE_RATELIMITER_H_ + +#include +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Limits the rate of use to a certain maximum quantity per period of +// time. Use, for example, for simple bandwidth throttling. +// +// It's implemented like a diet plan: You have so many calories per +// day. If you hit the limit, you can't eat any more until the next +// day. +class RateLimiter { + public: + // For example, 100kb per second. + RateLimiter(size_t max, double period) + : max_per_period_(max), + period_length_(period), + used_in_period_(0), + period_start_(0.0), + period_end_(period) { + } + virtual ~RateLimiter() {} + + // Returns true if if the desired quantity is available in the + // current period (< (max - used)). Once the given time passes the + // end of the period, used is set to zero and more use is available. + bool CanUse(size_t desired, double time); + // Increment the quantity used this period. If past the end of a + // period, a new period is started. + void Use(size_t used, double time); + + size_t used_in_period() const { + return used_in_period_; + } + + size_t max_per_period() const { + return max_per_period_; + } + + private: + size_t max_per_period_; + double period_length_; + size_t used_in_period_; + double period_start_; + double period_end_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_RATELIMITER_H_ diff --git a/webrtc/base/ratelimiter_unittest.cc b/webrtc/base/ratelimiter_unittest.cc new file mode 100644 index 000000000..b54a751b7 --- /dev/null +++ b/webrtc/base/ratelimiter_unittest.cc @@ -0,0 +1,59 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/ratelimiter.h" + +namespace rtc { + +TEST(RateLimiterTest, TestCanUse) { + // Diet: Can eat 2,000 calories per day. + RateLimiter limiter = RateLimiter(2000, 1.0); + + double monday = 1.0; + double tuesday = 2.0; + double thursday = 4.0; + + EXPECT_TRUE(limiter.CanUse(0, monday)); + EXPECT_TRUE(limiter.CanUse(1000, monday)); + EXPECT_TRUE(limiter.CanUse(1999, monday)); + EXPECT_TRUE(limiter.CanUse(2000, monday)); + EXPECT_FALSE(limiter.CanUse(2001, monday)); + + limiter.Use(1000, monday); + + EXPECT_TRUE(limiter.CanUse(0, monday)); + EXPECT_TRUE(limiter.CanUse(999, monday)); + EXPECT_TRUE(limiter.CanUse(1000, monday)); + EXPECT_FALSE(limiter.CanUse(1001, monday)); + + limiter.Use(1000, monday); + + EXPECT_TRUE(limiter.CanUse(0, monday)); + EXPECT_FALSE(limiter.CanUse(1, monday)); + + EXPECT_TRUE(limiter.CanUse(0, tuesday)); + EXPECT_TRUE(limiter.CanUse(1, tuesday)); + EXPECT_TRUE(limiter.CanUse(1999, tuesday)); + EXPECT_TRUE(limiter.CanUse(2000, tuesday)); + EXPECT_FALSE(limiter.CanUse(2001, tuesday)); + + limiter.Use(1000, tuesday); + + EXPECT_TRUE(limiter.CanUse(1000, tuesday)); + EXPECT_FALSE(limiter.CanUse(1001, tuesday)); + + limiter.Use(1000, thursday); + + EXPECT_TRUE(limiter.CanUse(1000, tuesday)); + EXPECT_FALSE(limiter.CanUse(1001, tuesday)); +} + +} // namespace rtc diff --git a/webrtc/base/ratetracker.cc b/webrtc/base/ratetracker.cc new file mode 100644 index 000000000..31ecd9bbb --- /dev/null +++ b/webrtc/base/ratetracker.cc @@ -0,0 +1,63 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/ratetracker.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +RateTracker::RateTracker() + : total_units_(0), units_second_(0), + last_units_second_time_(static_cast(-1)), + last_units_second_calc_(0) { +} + +size_t RateTracker::total_units() const { + return total_units_; +} + +size_t RateTracker::units_second() { + // Snapshot units / second calculator. Determine how many seconds have + // elapsed since our last reference point. If over 1 second, establish + // a new reference point that is an integer number of seconds since the + // last one, and compute the units over that interval. + uint32 current_time = Time(); + if (last_units_second_time_ != static_cast(-1)) { + int delta = rtc::TimeDiff(current_time, last_units_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds = delta / 1000; + int fraction_units = + static_cast(total_units_ - last_units_second_calc_) * + fraction_time / delta; + // Compute "units received during the interval" / "seconds in interval" + units_second_ = + (total_units_ - last_units_second_calc_ - fraction_units) / seconds; + last_units_second_time_ = current_time - fraction_time; + last_units_second_calc_ = total_units_ - fraction_units; + } + } + if (last_units_second_time_ == static_cast(-1)) { + last_units_second_time_ = current_time; + last_units_second_calc_ = total_units_; + } + + return units_second_; +} + +void RateTracker::Update(size_t units) { + total_units_ += units; +} + +uint32 RateTracker::Time() const { + return rtc::Time(); +} + +} // namespace rtc diff --git a/webrtc/base/ratetracker.h b/webrtc/base/ratetracker.h new file mode 100644 index 000000000..575bff75a --- /dev/null +++ b/webrtc/base/ratetracker.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_RATETRACKER_H_ +#define WEBRTC_BASE_RATETRACKER_H_ + +#include +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Computes instantaneous units per second. +class RateTracker { + public: + RateTracker(); + virtual ~RateTracker() {} + + size_t total_units() const; + size_t units_second(); + void Update(size_t units); + + protected: + // overrideable for tests + virtual uint32 Time() const; + + private: + size_t total_units_; + size_t units_second_; + uint32 last_units_second_time_; + size_t last_units_second_calc_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_RATETRACKER_H_ diff --git a/webrtc/base/ratetracker_unittest.cc b/webrtc/base/ratetracker_unittest.cc new file mode 100644 index 000000000..e9fee2b9f --- /dev/null +++ b/webrtc/base/ratetracker_unittest.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/ratetracker.h" + +namespace rtc { + +class RateTrackerForTest : public RateTracker { + public: + RateTrackerForTest() : time_(0) {} + virtual uint32 Time() const { return time_; } + void AdvanceTime(uint32 delta) { time_ += delta; } + + private: + uint32 time_; +}; + +TEST(RateTrackerTest, TestBasics) { + RateTrackerForTest tracker; + EXPECT_EQ(0U, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Add a sample. + tracker.Update(1234); + // Advance the clock by 100 ms. + tracker.AdvanceTime(100); + // total_units should advance, but units_second should stay 0. + EXPECT_EQ(1234U, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Repeat. + tracker.Update(1234); + tracker.AdvanceTime(100); + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Advance the clock by 800 ms, so we've elapsed a full second. + // units_second should now be filled in properly. + tracker.AdvanceTime(800); + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(1234U * 2, tracker.units_second()); + + // Poll the tracker again immediately. The reported rate should stay the same. + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(1234U * 2, tracker.units_second()); + + // Do nothing and advance by a second. We should drop down to zero. + tracker.AdvanceTime(1000); + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Send a bunch of data at a constant rate for 5.5 "seconds". + // We should report the rate properly. + for (int i = 0; i < 5500; i += 100) { + tracker.Update(9876U); + tracker.AdvanceTime(100); + } + EXPECT_EQ(9876U * 10, tracker.units_second()); + + // Advance the clock by 500 ms. Since we sent nothing over this half-second, + // the reported rate should be reduced by half. + tracker.AdvanceTime(500); + EXPECT_EQ(9876U * 5, tracker.units_second()); +} + +} // namespace rtc diff --git a/webrtc/base/refcount.h b/webrtc/base/refcount.h new file mode 100644 index 000000000..7bb6da36a --- /dev/null +++ b/webrtc/base/refcount.h @@ -0,0 +1,78 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TALK_APP_BASE_REFCOUNT_H_ +#define TALK_APP_BASE_REFCOUNT_H_ + +#include + +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +// Reference count interface. +class RefCountInterface { + public: + virtual int AddRef() = 0; + virtual int Release() = 0; + protected: + virtual ~RefCountInterface() {} +}; + +template +class RefCountedObject : public T { + public: + RefCountedObject() : ref_count_(0) { + } + + template + explicit RefCountedObject(P p) : T(p), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2) : T(p1, p2), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2, P3 p3) : T(p1, p2, p3), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4) + : T(p1, p2, p3, p4), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) + : T(p1, p2, p3, p4, p5), ref_count_(0) { + } + + virtual int AddRef() { + return rtc::AtomicOps::Increment(&ref_count_); + } + + virtual int Release() { + int count = rtc::AtomicOps::Decrement(&ref_count_); + if (!count) { + delete this; + } + return count; + } + + protected: + virtual ~RefCountedObject() { + } + + int ref_count_; +}; + +} // namespace rtc + +#endif // TALK_APP_BASE_REFCOUNT_H_ diff --git a/webrtc/base/referencecountedsingletonfactory.h b/webrtc/base/referencecountedsingletonfactory.h new file mode 100644 index 000000000..7138c8c5e --- /dev/null +++ b/webrtc/base/referencecountedsingletonfactory.h @@ -0,0 +1,157 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_ +#define WEBRTC_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_ + +#include "webrtc/base/common.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +template class rcsf_ptr; + +// A ReferenceCountedSingletonFactory is an object which owns another object, +// and doles out the owned object to consumers in a reference-counted manner. +// Thus, the factory owns at most one object of the desired kind, and +// hands consumers a special pointer to it, through which they can access it. +// When the consumers delete the pointer, the reference count goes down, +// and if the reference count hits zero, the factory can throw the object +// away. If a consumer requests the pointer and the factory has none, +// it can create one on the fly and pass it back. +template +class ReferenceCountedSingletonFactory { + friend class rcsf_ptr; + public: + ReferenceCountedSingletonFactory() : ref_count_(0) {} + + virtual ~ReferenceCountedSingletonFactory() { + ASSERT(ref_count_ == 0); + } + + protected: + // Must be implemented in a sub-class. The sub-class may choose whether or not + // to cache the instance across lifetimes by either reset()'ing or not + // reset()'ing the scoped_ptr in CleanupInstance(). + virtual bool SetupInstance() = 0; + virtual void CleanupInstance() = 0; + + scoped_ptr instance_; + + private: + Interface* GetInstance() { + rtc::CritScope cs(&crit_); + if (ref_count_ == 0) { + if (!SetupInstance()) { + LOG(LS_VERBOSE) << "Failed to setup instance"; + return NULL; + } + ASSERT(instance_.get() != NULL); + } + ++ref_count_; + + LOG(LS_VERBOSE) << "Number of references: " << ref_count_; + return instance_.get(); + } + + void ReleaseInstance() { + rtc::CritScope cs(&crit_); + ASSERT(ref_count_ > 0); + ASSERT(instance_.get() != NULL); + --ref_count_; + LOG(LS_VERBOSE) << "Number of references: " << ref_count_; + if (ref_count_ == 0) { + CleanupInstance(); + } + } + + CriticalSection crit_; + int ref_count_; + + DISALLOW_COPY_AND_ASSIGN(ReferenceCountedSingletonFactory); +}; + +template +class rcsf_ptr { + public: + // Create a pointer that uses the factory to get the instance. + // This is lazy - it won't generate the instance until it is requested. + explicit rcsf_ptr(ReferenceCountedSingletonFactory* factory) + : instance_(NULL), + factory_(factory) { + } + + ~rcsf_ptr() { + release(); + } + + Interface& operator*() { + EnsureAcquired(); + return *instance_; + } + + Interface* operator->() { + EnsureAcquired(); + return instance_; + } + + // Gets the pointer, creating the singleton if necessary. May return NULL if + // creation failed. + Interface* get() { + Acquire(); + return instance_; + } + + // Set instance to NULL and tell the factory we aren't using the instance + // anymore. + void release() { + if (instance_) { + instance_ = NULL; + factory_->ReleaseInstance(); + } + } + + // Lets us know whether instance is valid or not right now. + // Even though attempts to use the instance will automatically create it, it + // is advisable to check this because creation can fail. + bool valid() const { + return instance_ != NULL; + } + + // Returns the factory that this pointer is using. + ReferenceCountedSingletonFactory* factory() const { + return factory_; + } + + private: + void EnsureAcquired() { + Acquire(); + ASSERT(instance_ != NULL); + } + + void Acquire() { + // Since we're getting a singleton back, acquire is a noop if instance is + // already populated. + if (!instance_) { + instance_ = factory_->GetInstance(); + } + } + + Interface* instance_; + ReferenceCountedSingletonFactory* factory_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(rcsf_ptr); +}; + +}; // namespace rtc + +#endif // WEBRTC_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_ diff --git a/webrtc/base/referencecountedsingletonfactory_unittest.cc b/webrtc/base/referencecountedsingletonfactory_unittest.cc new file mode 100644 index 000000000..75d97a639 --- /dev/null +++ b/webrtc/base/referencecountedsingletonfactory_unittest.cc @@ -0,0 +1,132 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/referencecountedsingletonfactory.h" + +namespace rtc { + +class MyExistenceWatcher { + public: + MyExistenceWatcher() { create_called_ = true; } + ~MyExistenceWatcher() { delete_called_ = true; } + + static bool create_called_; + static bool delete_called_; +}; + +bool MyExistenceWatcher::create_called_ = false; +bool MyExistenceWatcher::delete_called_ = false; + +class TestReferenceCountedSingletonFactory : + public ReferenceCountedSingletonFactory { + protected: + virtual bool SetupInstance() { + instance_.reset(new MyExistenceWatcher()); + return true; + } + + virtual void CleanupInstance() { + instance_.reset(); + } +}; + +static void DoCreateAndGoOutOfScope( + ReferenceCountedSingletonFactory *factory) { + rcsf_ptr ptr(factory); + ptr.get(); + // and now ptr should go out of scope. +} + +TEST(ReferenceCountedSingletonFactory, ZeroReferenceCountCausesDeletion) { + TestReferenceCountedSingletonFactory factory; + MyExistenceWatcher::delete_called_ = false; + DoCreateAndGoOutOfScope(&factory); + EXPECT_TRUE(MyExistenceWatcher::delete_called_); +} + +TEST(ReferenceCountedSingletonFactory, NonZeroReferenceCountDoesNotDelete) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr ptr(&factory); + ptr.get(); + MyExistenceWatcher::delete_called_ = false; + DoCreateAndGoOutOfScope(&factory); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); +} + +TEST(ReferenceCountedSingletonFactory, ReturnedPointersReferToSameThing) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory), two(&factory); + + EXPECT_EQ(one.get(), two.get()); +} + +TEST(ReferenceCountedSingletonFactory, Release) { + TestReferenceCountedSingletonFactory factory; + + rcsf_ptr one(&factory); + one.get(); + + MyExistenceWatcher::delete_called_ = false; + one.release(); + EXPECT_TRUE(MyExistenceWatcher::delete_called_); +} + +TEST(ReferenceCountedSingletonFactory, GetWithoutRelease) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory); + one.get(); + + MyExistenceWatcher::create_called_ = false; + one.get(); + EXPECT_FALSE(MyExistenceWatcher::create_called_); +} + +TEST(ReferenceCountedSingletonFactory, GetAfterRelease) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory); + + MyExistenceWatcher::create_called_ = false; + one.release(); + one.get(); + EXPECT_TRUE(MyExistenceWatcher::create_called_); +} + +TEST(ReferenceCountedSingletonFactory, MultipleReleases) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory), two(&factory); + + MyExistenceWatcher::create_called_ = false; + MyExistenceWatcher::delete_called_ = false; + one.release(); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); + one.release(); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); + one.release(); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); + one.get(); + EXPECT_TRUE(MyExistenceWatcher::create_called_); +} + +TEST(ReferenceCountedSingletonFactory, Existentialism) { + TestReferenceCountedSingletonFactory factory; + + rcsf_ptr one(&factory); + + MyExistenceWatcher::create_called_ = false; + MyExistenceWatcher::delete_called_ = false; + + one.get(); + EXPECT_TRUE(MyExistenceWatcher::create_called_); + one.release(); + EXPECT_TRUE(MyExistenceWatcher::delete_called_); +} + +} // namespace rtc diff --git a/webrtc/base/rollingaccumulator.h b/webrtc/base/rollingaccumulator.h new file mode 100644 index 000000000..0dce0c3a0 --- /dev/null +++ b/webrtc/base/rollingaccumulator.h @@ -0,0 +1,172 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ROLLINGACCUMULATOR_H_ +#define WEBRTC_BASE_ROLLINGACCUMULATOR_H_ + +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +// RollingAccumulator stores and reports statistics +// over N most recent samples. +// +// T is assumed to be an int, long, double or float. +template +class RollingAccumulator { + public: + explicit RollingAccumulator(size_t max_count) + : samples_(max_count) { + Reset(); + } + ~RollingAccumulator() { + } + + size_t max_count() const { + return samples_.size(); + } + + size_t count() const { + return count_; + } + + void Reset() { + count_ = 0U; + next_index_ = 0U; + sum_ = 0.0; + sum_2_ = 0.0; + max_ = T(); + max_stale_ = false; + min_ = T(); + min_stale_ = false; + } + + void AddSample(T sample) { + if (count_ == max_count()) { + // Remove oldest sample. + T sample_to_remove = samples_[next_index_]; + sum_ -= sample_to_remove; + sum_2_ -= sample_to_remove * sample_to_remove; + if (sample_to_remove >= max_) { + max_stale_ = true; + } + if (sample_to_remove <= min_) { + min_stale_ = true; + } + } else { + // Increase count of samples. + ++count_; + } + // Add new sample. + samples_[next_index_] = sample; + sum_ += sample; + sum_2_ += sample * sample; + if (count_ == 1 || sample >= max_) { + max_ = sample; + max_stale_ = false; + } + if (count_ == 1 || sample <= min_) { + min_ = sample; + min_stale_ = false; + } + // Update next_index_. + next_index_ = (next_index_ + 1) % max_count(); + } + + T ComputeSum() const { + return static_cast(sum_); + } + + double ComputeMean() const { + if (count_ == 0) { + return 0.0; + } + return sum_ / count_; + } + + T ComputeMax() const { + if (max_stale_) { + ASSERT(count_ > 0 && + "It shouldn't be possible for max_stale_ && count_ == 0"); + max_ = samples_[next_index_]; + for (size_t i = 1u; i < count_; i++) { + max_ = _max(max_, samples_[(next_index_ + i) % max_count()]); + } + max_stale_ = false; + } + return max_; + } + + T ComputeMin() const { + if (min_stale_) { + ASSERT(count_ > 0 && + "It shouldn't be possible for min_stale_ && count_ == 0"); + min_ = samples_[next_index_]; + for (size_t i = 1u; i < count_; i++) { + min_ = _min(min_, samples_[(next_index_ + i) % max_count()]); + } + min_stale_ = false; + } + return min_; + } + + // O(n) time complexity. + // Weights nth sample with weight (learning_rate)^n. Learning_rate should be + // between (0.0, 1.0], otherwise the non-weighted mean is returned. + double ComputeWeightedMean(double learning_rate) const { + if (count_ < 1 || learning_rate <= 0.0 || learning_rate >= 1.0) { + return ComputeMean(); + } + double weighted_mean = 0.0; + double current_weight = 1.0; + double weight_sum = 0.0; + const size_t max_size = max_count(); + for (size_t i = 0; i < count_; ++i) { + current_weight *= learning_rate; + weight_sum += current_weight; + // Add max_size to prevent underflow. + size_t index = (next_index_ + max_size - i - 1) % max_size; + weighted_mean += current_weight * samples_[index]; + } + return weighted_mean / weight_sum; + } + + // Compute estimated variance. Estimation is more accurate + // as the number of samples grows. + double ComputeVariance() const { + if (count_ == 0) { + return 0.0; + } + // Var = E[x^2] - (E[x])^2 + double count_inv = 1.0 / count_; + double mean_2 = sum_2_ * count_inv; + double mean = sum_ * count_inv; + return mean_2 - (mean * mean); + } + + private: + size_t count_; + size_t next_index_; + double sum_; // Sum(x) - double to avoid overflow + double sum_2_; // Sum(x*x) - double to avoid overflow + mutable T max_; + mutable bool max_stale_; + mutable T min_; + mutable bool min_stale_; + std::vector samples_; + + DISALLOW_COPY_AND_ASSIGN(RollingAccumulator); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ROLLINGACCUMULATOR_H_ diff --git a/webrtc/base/rollingaccumulator_unittest.cc b/webrtc/base/rollingaccumulator_unittest.cc new file mode 100644 index 000000000..7e3d8cdf0 --- /dev/null +++ b/webrtc/base/rollingaccumulator_unittest.cc @@ -0,0 +1,118 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/rollingaccumulator.h" + +namespace rtc { + +namespace { + +const double kLearningRate = 0.5; + +} // namespace + +TEST(RollingAccumulatorTest, ZeroSamples) { + RollingAccumulator accum(10); + + EXPECT_EQ(0U, accum.count()); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeMean()); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeVariance()); + EXPECT_EQ(0, accum.ComputeMin()); + EXPECT_EQ(0, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, SomeSamples) { + RollingAccumulator accum(10); + for (int i = 0; i < 4; ++i) { + accum.AddSample(i); + } + + EXPECT_EQ(4U, accum.count()); + EXPECT_EQ(6, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(1.5, accum.ComputeMean()); + EXPECT_NEAR(2.26666, accum.ComputeWeightedMean(kLearningRate), 0.01); + EXPECT_DOUBLE_EQ(1.25, accum.ComputeVariance()); + EXPECT_EQ(0, accum.ComputeMin()); + EXPECT_EQ(3, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, RollingSamples) { + RollingAccumulator accum(10); + for (int i = 0; i < 12; ++i) { + accum.AddSample(i); + } + + EXPECT_EQ(10U, accum.count()); + EXPECT_EQ(65, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(6.5, accum.ComputeMean()); + EXPECT_NEAR(10.0, accum.ComputeWeightedMean(kLearningRate), 0.01); + EXPECT_NEAR(9.0, accum.ComputeVariance(), 1.0); + EXPECT_EQ(2, accum.ComputeMin()); + EXPECT_EQ(11, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, ResetSamples) { + RollingAccumulator accum(10); + + for (int i = 0; i < 10; ++i) { + accum.AddSample(100); + } + EXPECT_EQ(10U, accum.count()); + EXPECT_DOUBLE_EQ(100.0, accum.ComputeMean()); + EXPECT_EQ(100, accum.ComputeMin()); + EXPECT_EQ(100, accum.ComputeMax()); + + accum.Reset(); + EXPECT_EQ(0U, accum.count()); + + for (int i = 0; i < 5; ++i) { + accum.AddSample(i); + } + + EXPECT_EQ(5U, accum.count()); + EXPECT_EQ(10, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(2.0, accum.ComputeMean()); + EXPECT_EQ(0, accum.ComputeMin()); + EXPECT_EQ(4, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, RollingSamplesDouble) { + RollingAccumulator accum(10); + for (int i = 0; i < 23; ++i) { + accum.AddSample(5 * i); + } + + EXPECT_EQ(10u, accum.count()); + EXPECT_DOUBLE_EQ(875.0, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(87.5, accum.ComputeMean()); + EXPECT_NEAR(105.049, accum.ComputeWeightedMean(kLearningRate), 0.1); + EXPECT_NEAR(229.166667, accum.ComputeVariance(), 25); + EXPECT_DOUBLE_EQ(65.0, accum.ComputeMin()); + EXPECT_DOUBLE_EQ(110.0, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, ComputeWeightedMeanCornerCases) { + RollingAccumulator accum(10); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeWeightedMean(kLearningRate)); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeWeightedMean(0.0)); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeWeightedMean(1.1)); + + for (int i = 0; i < 8; ++i) { + accum.AddSample(i); + } + + EXPECT_DOUBLE_EQ(3.5, accum.ComputeMean()); + EXPECT_DOUBLE_EQ(3.5, accum.ComputeWeightedMean(0)); + EXPECT_DOUBLE_EQ(3.5, accum.ComputeWeightedMean(1.1)); + EXPECT_NEAR(6.0, accum.ComputeWeightedMean(kLearningRate), 0.1); +} + +} // namespace rtc diff --git a/webrtc/base/safe_conversions.h b/webrtc/base/safe_conversions.h new file mode 100644 index 000000000..f6cb24e41 --- /dev/null +++ b/webrtc/base/safe_conversions.h @@ -0,0 +1,79 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Borrowed from Chromium's src/base/numerics/safe_conversions.h. + +#ifndef WEBRTC_BASE_SAFE_CONVERSIONS_H_ +#define WEBRTC_BASE_SAFE_CONVERSIONS_H_ + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/safe_conversions_impl.h" + +namespace rtc { + +inline void Check(bool condition) { + if (!condition) { + LOG(LS_ERROR) << "CHECK failed."; + Break(); + // The program should have crashed at this point. + } +} + +// Convenience function that returns true if the supplied value is in range +// for the destination type. +template +inline bool IsValueInRangeForNumericType(Src value) { + return internal::RangeCheck(value) == internal::TYPE_VALID; +} + +// checked_cast<> is analogous to static_cast<> for numeric types, +// except that it CHECKs that the specified numeric conversion will not +// overflow or underflow. NaN source will always trigger a CHECK. +template +inline Dst checked_cast(Src value) { + Check(IsValueInRangeForNumericType(value)); + return static_cast(value); +} + +// saturated_cast<> is analogous to static_cast<> for numeric types, except +// that the specified numeric conversion will saturate rather than overflow or +// underflow. NaN assignment to an integral will trigger a CHECK condition. +template +inline Dst saturated_cast(Src value) { + // Optimization for floating point values, which already saturate. + if (std::numeric_limits::is_iec559) + return static_cast(value); + + switch (internal::RangeCheck(value)) { + case internal::TYPE_VALID: + return static_cast(value); + + case internal::TYPE_UNDERFLOW: + return std::numeric_limits::min(); + + case internal::TYPE_OVERFLOW: + return std::numeric_limits::max(); + + // Should fail only on attempting to assign NaN to a saturated integer. + case internal::TYPE_INVALID: + Check(false); + return std::numeric_limits::max(); + } + + Check(false); // NOTREACHED(); + return static_cast(value); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_SAFE_CONVERSIONS_H_ diff --git a/webrtc/base/safe_conversions_impl.h b/webrtc/base/safe_conversions_impl.h new file mode 100644 index 000000000..2950f970c --- /dev/null +++ b/webrtc/base/safe_conversions_impl.h @@ -0,0 +1,188 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Borrowed from Chromium's src/base/numerics/safe_conversions_impl.h. + +#ifndef WEBRTC_BASE_SAFE_CONVERSIONS_IMPL_H_ +#define WEBRTC_BASE_SAFE_CONVERSIONS_IMPL_H_ + +#include + +namespace rtc { +namespace internal { + +enum DstSign { + DST_UNSIGNED, + DST_SIGNED +}; + +enum SrcSign { + SRC_UNSIGNED, + SRC_SIGNED +}; + +enum DstRange { + OVERLAPS_RANGE, + CONTAINS_RANGE +}; + +// Helper templates to statically determine if our destination type can contain +// all values represented by the source type. + +template ::is_signed ? + DST_SIGNED : DST_UNSIGNED, + SrcSign IsSrcSigned = std::numeric_limits::is_signed ? + SRC_SIGNED : SRC_UNSIGNED> +struct StaticRangeCheck {}; + +template +struct StaticRangeCheck { + typedef std::numeric_limits DstLimits; + typedef std::numeric_limits SrcLimits; + // Compare based on max_exponent, which we must compute for integrals. + static const size_t kDstMaxExponent = DstLimits::is_iec559 ? + DstLimits::max_exponent : + (sizeof(Dst) * 8 - 1); + static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? + SrcLimits::max_exponent : + (sizeof(Src) * 8 - 1); + static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? + CONTAINS_RANGE : OVERLAPS_RANGE; +}; + +template +struct StaticRangeCheck { + static const DstRange value = sizeof(Dst) >= sizeof(Src) ? + CONTAINS_RANGE : OVERLAPS_RANGE; +}; + +template +struct StaticRangeCheck { + typedef std::numeric_limits DstLimits; + typedef std::numeric_limits SrcLimits; + // Compare based on max_exponent, which we must compute for integrals. + static const size_t kDstMaxExponent = DstLimits::is_iec559 ? + DstLimits::max_exponent : + (sizeof(Dst) * 8 - 1); + static const size_t kSrcMaxExponent = sizeof(Src) * 8; + static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? + CONTAINS_RANGE : OVERLAPS_RANGE; +}; + +template +struct StaticRangeCheck { + static const DstRange value = OVERLAPS_RANGE; +}; + + +enum RangeCheckResult { + TYPE_VALID = 0, // Value can be represented by the destination type. + TYPE_UNDERFLOW = 1, // Value would overflow. + TYPE_OVERFLOW = 2, // Value would underflow. + TYPE_INVALID = 3 // Source value is invalid (i.e. NaN). +}; + +// This macro creates a RangeCheckResult from an upper and lower bound +// check by taking advantage of the fact that only NaN can be out of range in +// both directions at once. +#define BASE_NUMERIC_RANGE_CHECK_RESULT(is_in_upper_bound, is_in_lower_bound) \ + RangeCheckResult(((is_in_upper_bound) ? 0 : TYPE_OVERFLOW) | \ + ((is_in_lower_bound) ? 0 : TYPE_UNDERFLOW)) + +template ::is_signed ? + DST_SIGNED : DST_UNSIGNED, + SrcSign IsSrcSigned = std::numeric_limits::is_signed ? + SRC_SIGNED : SRC_UNSIGNED, + DstRange IsSrcRangeContained = StaticRangeCheck::value> +struct RangeCheckImpl {}; + +// The following templates are for ranges that must be verified at runtime. We +// split it into checks based on signedness to avoid confusing casts and +// compiler warnings on signed an unsigned comparisons. + +// Dst range always contains the result: nothing to check. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + return TYPE_VALID; + } +}; + +// Signed to signed narrowing. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + return DstLimits::is_iec559 ? + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), + value >= static_cast(DstLimits::max() * -1)) : + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), + value >= static_cast(DstLimits::min())); + } +}; + +// Unsigned to unsigned narrowing. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + return BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), true); + } +}; + +// Unsigned to signed. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + return sizeof(Dst) > sizeof(Src) ? TYPE_VALID : + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), true); + } +}; + +// Signed to unsigned. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + typedef std::numeric_limits SrcLimits; + // Compare based on max_exponent, which we must compute for integrals. + static const size_t kDstMaxExponent = sizeof(Dst) * 8; + static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? + SrcLimits::max_exponent : + (sizeof(Src) * 8 - 1); + return (kDstMaxExponent >= kSrcMaxExponent) ? + BASE_NUMERIC_RANGE_CHECK_RESULT(true, value >= static_cast(0)) : + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), + value >= static_cast(0)); + } +}; + +template +inline RangeCheckResult RangeCheck(Src value) { + COMPILE_ASSERT(std::numeric_limits::is_specialized, + argument_must_be_numeric); + COMPILE_ASSERT(std::numeric_limits::is_specialized, + result_must_be_numeric); + return RangeCheckImpl::Check(value); +} + +} // namespace internal +} // namespace rtc + +#endif // WEBRTC_BASE_SAFE_CONVERSIONS_IMPL_H_ diff --git a/webrtc/base/schanneladapter.cc b/webrtc/base/schanneladapter.cc new file mode 100644 index 000000000..50c0638fd --- /dev/null +++ b/webrtc/base/schanneladapter.cc @@ -0,0 +1,702 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/win32.h" +#define SECURITY_WIN32 +#include +#include + +#include +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/schanneladapter.h" +#include "webrtc/base/sec_buffer.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +///////////////////////////////////////////////////////////////////////////// +// SChannelAdapter +///////////////////////////////////////////////////////////////////////////// + +extern const ConstantLabel SECURITY_ERRORS[]; + +const ConstantLabel SCHANNEL_BUFFER_TYPES[] = { + KLABEL(SECBUFFER_EMPTY), // 0 + KLABEL(SECBUFFER_DATA), // 1 + KLABEL(SECBUFFER_TOKEN), // 2 + KLABEL(SECBUFFER_PKG_PARAMS), // 3 + KLABEL(SECBUFFER_MISSING), // 4 + KLABEL(SECBUFFER_EXTRA), // 5 + KLABEL(SECBUFFER_STREAM_TRAILER), // 6 + KLABEL(SECBUFFER_STREAM_HEADER), // 7 + KLABEL(SECBUFFER_MECHLIST), // 11 + KLABEL(SECBUFFER_MECHLIST_SIGNATURE), // 12 + KLABEL(SECBUFFER_TARGET), // 13 + KLABEL(SECBUFFER_CHANNEL_BINDINGS), // 14 + LASTLABEL +}; + +void DescribeBuffer(LoggingSeverity severity, const char* prefix, + const SecBuffer& sb) { + LOG_V(severity) + << prefix + << "(" << sb.cbBuffer + << ", " << FindLabel(sb.BufferType & ~SECBUFFER_ATTRMASK, + SCHANNEL_BUFFER_TYPES) + << ", " << sb.pvBuffer << ")"; +} + +void DescribeBuffers(LoggingSeverity severity, const char* prefix, + const SecBufferDesc* sbd) { + if (!LOG_CHECK_LEVEL_V(severity)) + return; + LOG_V(severity) << prefix << "("; + for (size_t i=0; icBuffers; ++i) { + DescribeBuffer(severity, " ", sbd->pBuffers[i]); + } + LOG_V(severity) << ")"; +} + +const ULONG SSL_FLAGS_DEFAULT = ISC_REQ_ALLOCATE_MEMORY + | ISC_REQ_CONFIDENTIALITY + | ISC_REQ_EXTENDED_ERROR + | ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + | ISC_REQ_STREAM; + //| ISC_REQ_USE_SUPPLIED_CREDS; + +typedef std::vector SChannelBuffer; + +struct SChannelAdapter::SSLImpl { + CredHandle cred; + CtxtHandle ctx; + bool cred_init, ctx_init; + SChannelBuffer inbuf, outbuf, readable; + SecPkgContext_StreamSizes sizes; + + SSLImpl() : cred_init(false), ctx_init(false) { } +}; + +SChannelAdapter::SChannelAdapter(AsyncSocket* socket) + : SSLAdapter(socket), state_(SSL_NONE), + restartable_(false), signal_close_(false), message_pending_(false), + impl_(new SSLImpl) { +} + +SChannelAdapter::~SChannelAdapter() { + Cleanup(); +} + +int +SChannelAdapter::StartSSL(const char* hostname, bool restartable) { + if (state_ != SSL_NONE) + return ERROR_ALREADY_INITIALIZED; + + ssl_host_name_ = hostname; + restartable_ = restartable; + + if (socket_->GetState() != Socket::CS_CONNECTED) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +int +SChannelAdapter::BeginSSL() { + LOG(LS_VERBOSE) << "BeginSSL: " << ssl_host_name_; + ASSERT(state_ == SSL_CONNECTING); + + SECURITY_STATUS ret; + + SCHANNEL_CRED sc_cred = { 0 }; + sc_cred.dwVersion = SCHANNEL_CRED_VERSION; + //sc_cred.dwMinimumCipherStrength = 128; // Note: use system default + sc_cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_AUTO_CRED_VALIDATION; + + ret = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, + &sc_cred, NULL, NULL, &impl_->cred, NULL); + if (ret != SEC_E_OK) { + LOG(LS_ERROR) << "AcquireCredentialsHandle error: " + << ErrorName(ret, SECURITY_ERRORS); + return ret; + } + impl_->cred_init = true; + + if (LOG_CHECK_LEVEL(LS_VERBOSE)) { + SecPkgCred_CipherStrengths cipher_strengths = { 0 }; + ret = QueryCredentialsAttributes(&impl_->cred, + SECPKG_ATTR_CIPHER_STRENGTHS, + &cipher_strengths); + if (SUCCEEDED(ret)) { + LOG(LS_VERBOSE) << "SChannel cipher strength: " + << cipher_strengths.dwMinimumCipherStrength << " - " + << cipher_strengths.dwMaximumCipherStrength; + } + + SecPkgCred_SupportedAlgs supported_algs = { 0 }; + ret = QueryCredentialsAttributes(&impl_->cred, + SECPKG_ATTR_SUPPORTED_ALGS, + &supported_algs); + if (SUCCEEDED(ret)) { + LOG(LS_VERBOSE) << "SChannel supported algorithms:"; + for (DWORD i=0; ipwszName : L"Unknown"; + LOG(LS_VERBOSE) << " " << ToUtf8(alg_name) << " (" << alg_id << ")"; + } + CSecBufferBase::FreeSSPI(supported_algs.palgSupportedAlgs); + } + } + + ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0; + if (ignore_bad_cert()) + flags |= ISC_REQ_MANUAL_CRED_VALIDATION; + + CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out; + ret = InitializeSecurityContextA(&impl_->cred, NULL, + const_cast(ssl_host_name_.c_str()), + flags, 0, 0, NULL, 0, + &impl_->ctx, sb_out.desc(), + &ret_flags, NULL); + if (SUCCEEDED(ret)) + impl_->ctx_init = true; + return ProcessContext(ret, NULL, sb_out.desc()); +} + +int +SChannelAdapter::ContinueSSL() { + LOG(LS_VERBOSE) << "ContinueSSL"; + ASSERT(state_ == SSL_CONNECTING); + + SECURITY_STATUS ret; + + CSecBufferBundle<2> sb_in; + sb_in[0].BufferType = SECBUFFER_TOKEN; + sb_in[0].cbBuffer = static_cast(impl_->inbuf.size()); + sb_in[0].pvBuffer = &impl_->inbuf[0]; + //DescribeBuffers(LS_VERBOSE, "Input Buffer ", sb_in.desc()); + + ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0; + if (ignore_bad_cert()) + flags |= ISC_REQ_MANUAL_CRED_VALIDATION; + + CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out; + ret = InitializeSecurityContextA(&impl_->cred, &impl_->ctx, + const_cast(ssl_host_name_.c_str()), + flags, 0, 0, sb_in.desc(), 0, + NULL, sb_out.desc(), + &ret_flags, NULL); + return ProcessContext(ret, sb_in.desc(), sb_out.desc()); +} + +int +SChannelAdapter::ProcessContext(long int status, _SecBufferDesc* sbd_in, + _SecBufferDesc* sbd_out) { + if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED && + status != SEC_E_INCOMPLETE_MESSAGE) { + LOG(LS_ERROR) + << "InitializeSecurityContext error: " + << ErrorName(status, SECURITY_ERRORS); + } + //if (sbd_in) + // DescribeBuffers(LS_VERBOSE, "Input Buffer ", sbd_in); + //if (sbd_out) + // DescribeBuffers(LS_VERBOSE, "Output Buffer ", sbd_out); + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + // Wait for more input from server. + return Flush(); + } + + if (FAILED(status)) { + // We can't continue. Common errors: + // SEC_E_CERT_EXPIRED - Typically, this means the computer clock is wrong. + return status; + } + + // Note: we check both input and output buffers for SECBUFFER_EXTRA. + // Experience shows it appearing in the input, but the documentation claims + // it should appear in the output. + size_t extra = 0; + if (sbd_in) { + for (size_t i=0; icBuffers; ++i) { + SecBuffer& buffer = sbd_in->pBuffers[i]; + if (buffer.BufferType == SECBUFFER_EXTRA) { + extra += buffer.cbBuffer; + } + } + } + if (sbd_out) { + for (size_t i=0; icBuffers; ++i) { + SecBuffer& buffer = sbd_out->pBuffers[i]; + if (buffer.BufferType == SECBUFFER_EXTRA) { + extra += buffer.cbBuffer; + } else if (buffer.BufferType == SECBUFFER_TOKEN) { + impl_->outbuf.insert(impl_->outbuf.end(), + reinterpret_cast(buffer.pvBuffer), + reinterpret_cast(buffer.pvBuffer) + buffer.cbBuffer); + } + } + } + + if (extra) { + ASSERT(extra <= impl_->inbuf.size()); + size_t consumed = impl_->inbuf.size() - extra; + memmove(&impl_->inbuf[0], &impl_->inbuf[consumed], extra); + impl_->inbuf.resize(extra); + } else { + impl_->inbuf.clear(); + } + + if (SEC_I_CONTINUE_NEEDED == status) { + // Send data to server and wait for response. + // Note: ContinueSSL will result in a Flush, anyway. + return impl_->inbuf.empty() ? Flush() : ContinueSSL(); + } + + if (SEC_E_OK == status) { + LOG(LS_VERBOSE) << "QueryContextAttributes"; + status = QueryContextAttributes(&impl_->ctx, SECPKG_ATTR_STREAM_SIZES, + &impl_->sizes); + if (FAILED(status)) { + LOG(LS_ERROR) << "QueryContextAttributes error: " + << ErrorName(status, SECURITY_ERRORS); + return status; + } + + state_ = SSL_CONNECTED; + + if (int err = DecryptData()) { + return err; + } else if (int err = Flush()) { + return err; + } else { + // If we decrypted any data, queue up a notification here + PostEvent(); + // Signal our connectedness + AsyncSocketAdapter::OnConnectEvent(this); + } + return 0; + } + + if (SEC_I_INCOMPLETE_CREDENTIALS == status) { + // We don't support client authentication in schannel. + return status; + } + + // We don't expect any other codes + ASSERT(false); + return status; +} + +int +SChannelAdapter::DecryptData() { + SChannelBuffer& inbuf = impl_->inbuf; + SChannelBuffer& readable = impl_->readable; + + while (!inbuf.empty()) { + CSecBufferBundle<4> in_buf; + in_buf[0].BufferType = SECBUFFER_DATA; + in_buf[0].cbBuffer = static_cast(inbuf.size()); + in_buf[0].pvBuffer = &inbuf[0]; + + //DescribeBuffers(LS_VERBOSE, "Decrypt In ", in_buf.desc()); + SECURITY_STATUS status = DecryptMessage(&impl_->ctx, in_buf.desc(), 0, 0); + //DescribeBuffers(LS_VERBOSE, "Decrypt Out ", in_buf.desc()); + + // Note: We are explicitly treating SEC_E_OK, SEC_I_CONTEXT_EXPIRED, and + // any other successful results as continue. + if (SUCCEEDED(status)) { + size_t data_len = 0, extra_len = 0; + for (size_t i=0; icBuffers; ++i) { + if (in_buf[i].BufferType == SECBUFFER_DATA) { + data_len += in_buf[i].cbBuffer; + readable.insert(readable.end(), + reinterpret_cast(in_buf[i].pvBuffer), + reinterpret_cast(in_buf[i].pvBuffer) + in_buf[i].cbBuffer); + } else if (in_buf[i].BufferType == SECBUFFER_EXTRA) { + extra_len += in_buf[i].cbBuffer; + } + } + // There is a bug on Win2K where SEC_I_CONTEXT_EXPIRED is misclassified. + if ((data_len == 0) && (inbuf[0] == 0x15)) { + status = SEC_I_CONTEXT_EXPIRED; + } + if (extra_len) { + size_t consumed = inbuf.size() - extra_len; + memmove(&inbuf[0], &inbuf[consumed], extra_len); + inbuf.resize(extra_len); + } else { + inbuf.clear(); + } + // TODO: Handle SEC_I_CONTEXT_EXPIRED to do clean shutdown + if (status != SEC_E_OK) { + LOG(LS_INFO) << "DecryptMessage returned continuation code: " + << ErrorName(status, SECURITY_ERRORS); + } + continue; + } + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + break; + } else { + return status; + } + } + + return 0; +} + +void +SChannelAdapter::Cleanup() { + if (impl_->ctx_init) + DeleteSecurityContext(&impl_->ctx); + if (impl_->cred_init) + FreeCredentialsHandle(&impl_->cred); + delete impl_; +} + +void +SChannelAdapter::PostEvent() { + // Check if there's anything notable to signal + if (impl_->readable.empty() && !signal_close_) + return; + + // Only one post in the queue at a time + if (message_pending_) + return; + + if (Thread* thread = Thread::Current()) { + message_pending_ = true; + thread->Post(this); + } else { + LOG(LS_ERROR) << "No thread context available for SChannelAdapter"; + ASSERT(false); + } +} + +void +SChannelAdapter::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "SChannelAdapter::Error(" + << context << ", " + << ErrorName(err, SECURITY_ERRORS) << ")"; + state_ = SSL_ERROR; + SetError(err); + if (signal) + AsyncSocketAdapter::OnCloseEvent(this, err); +} + +int +SChannelAdapter::Read() { + char buffer[4096]; + SChannelBuffer& inbuf = impl_->inbuf; + while (true) { + int ret = AsyncSocketAdapter::Recv(buffer, sizeof(buffer)); + if (ret > 0) { + inbuf.insert(inbuf.end(), buffer, buffer + ret); + } else if (GetError() == EWOULDBLOCK) { + return 0; // Blocking + } else { + return GetError(); + } + } +} + +int +SChannelAdapter::Flush() { + int result = 0; + size_t pos = 0; + SChannelBuffer& outbuf = impl_->outbuf; + while (pos < outbuf.size()) { + int sent = AsyncSocketAdapter::Send(&outbuf[pos], outbuf.size() - pos); + if (sent > 0) { + pos += sent; + } else if (GetError() == EWOULDBLOCK) { + break; // Blocking + } else { + result = GetError(); + break; + } + } + if (int remainder = static_cast(outbuf.size() - pos)) { + memmove(&outbuf[0], &outbuf[pos], remainder); + outbuf.resize(remainder); + } else { + outbuf.clear(); + } + return result; +} + +// +// AsyncSocket Implementation +// + +int +SChannelAdapter::Send(const void* pv, size_t cb) { + switch (state_) { + case SSL_NONE: + return AsyncSocketAdapter::Send(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + size_t written = 0; + SChannelBuffer& outbuf = impl_->outbuf; + while (written < cb) { + const size_t encrypt_len = std::min(cb - written, + impl_->sizes.cbMaximumMessage); + + CSecBufferBundle<4> out_buf; + out_buf[0].BufferType = SECBUFFER_STREAM_HEADER; + out_buf[0].cbBuffer = impl_->sizes.cbHeader; + out_buf[1].BufferType = SECBUFFER_DATA; + out_buf[1].cbBuffer = static_cast(encrypt_len); + out_buf[2].BufferType = SECBUFFER_STREAM_TRAILER; + out_buf[2].cbBuffer = impl_->sizes.cbTrailer; + + size_t packet_len = out_buf[0].cbBuffer + + out_buf[1].cbBuffer + + out_buf[2].cbBuffer; + + SChannelBuffer message; + message.resize(packet_len); + out_buf[0].pvBuffer = &message[0]; + out_buf[1].pvBuffer = &message[out_buf[0].cbBuffer]; + out_buf[2].pvBuffer = &message[out_buf[0].cbBuffer + out_buf[1].cbBuffer]; + + memcpy(out_buf[1].pvBuffer, + static_cast(pv) + written, + encrypt_len); + + //DescribeBuffers(LS_VERBOSE, "Encrypt In ", out_buf.desc()); + SECURITY_STATUS res = EncryptMessage(&impl_->ctx, 0, out_buf.desc(), 0); + //DescribeBuffers(LS_VERBOSE, "Encrypt Out ", out_buf.desc()); + + if (FAILED(res)) { + Error("EncryptMessage", res, false); + return SOCKET_ERROR; + } + + // We assume that the header and data segments do not change length, + // or else encrypting the concatenated packet in-place is wrong. + ASSERT(out_buf[0].cbBuffer == impl_->sizes.cbHeader); + ASSERT(out_buf[1].cbBuffer == static_cast(encrypt_len)); + + // However, the length of the trailer may change due to padding. + ASSERT(out_buf[2].cbBuffer <= impl_->sizes.cbTrailer); + + packet_len = out_buf[0].cbBuffer + + out_buf[1].cbBuffer + + out_buf[2].cbBuffer; + + written += encrypt_len; + outbuf.insert(outbuf.end(), &message[0], &message[packet_len-1]+1); + } + + if (int err = Flush()) { + state_ = SSL_ERROR; + SetError(err); + return SOCKET_ERROR; + } + + return static_cast(written); +} + +int +SChannelAdapter::Recv(void* pv, size_t cb) { + switch (state_) { + case SSL_NONE: + return AsyncSocketAdapter::Recv(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + SChannelBuffer& readable = impl_->readable; + if (readable.empty()) { + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + } + size_t read = _min(cb, readable.size()); + memcpy(pv, &readable[0], read); + if (size_t remaining = readable.size() - read) { + memmove(&readable[0], &readable[read], remaining); + readable.resize(remaining); + } else { + readable.clear(); + } + + PostEvent(); + return static_cast(read); +} + +int +SChannelAdapter::Close() { + if (!impl_->readable.empty()) { + LOG(WARNING) << "SChannelAdapter::Close with readable data"; + // Note: this isn't strictly an error, but we're using it temporarily to + // track bugs. + //ASSERT(false); + } + if (state_ == SSL_CONNECTED) { + DWORD token = SCHANNEL_SHUTDOWN; + CSecBufferBundle<1> sb_in; + sb_in[0].BufferType = SECBUFFER_TOKEN; + sb_in[0].cbBuffer = sizeof(token); + sb_in[0].pvBuffer = &token; + ApplyControlToken(&impl_->ctx, sb_in.desc()); + // TODO: In theory, to do a nice shutdown, we need to begin shutdown + // negotiation with more calls to InitializeSecurityContext. Since the + // socket api doesn't support nice shutdown at this point, we don't bother. + } + Cleanup(); + impl_ = new SSLImpl; + state_ = restartable_ ? SSL_WAIT : SSL_NONE; + signal_close_ = false; + message_pending_ = false; + return AsyncSocketAdapter::Close(); +} + +Socket::ConnState +SChannelAdapter::GetState() const { + if (signal_close_) + return CS_CONNECTED; + ConnState state = socket_->GetState(); + if ((state == CS_CONNECTED) + && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING))) + state = CS_CONNECTING; + return state; +} + +void +SChannelAdapter::OnConnectEvent(AsyncSocket* socket) { + LOG(LS_VERBOSE) << "SChannelAdapter::OnConnectEvent"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + AsyncSocketAdapter::OnConnectEvent(socket); + return; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err); + } +} + +void +SChannelAdapter::OnReadEvent(AsyncSocket* socket) { + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (int err = Read()) { + Error("Read", err); + return; + } + + if (impl_->inbuf.empty()) + return; + + if (state_ == SSL_CONNECTED) { + if (int err = DecryptData()) { + Error("DecryptData", err); + } else if (!impl_->readable.empty()) { + AsyncSocketAdapter::OnReadEvent(this); + } + } else if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err); + } + } +} + +void +SChannelAdapter::OnWriteEvent(AsyncSocket* socket) { + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnWriteEvent(socket); + return; + } + + if (int err = Flush()) { + Error("Flush", err); + return; + } + + // See if we have more data to write + if (!impl_->outbuf.empty()) + return; + + // Buffer is empty, submit notification + if (state_ == SSL_CONNECTED) { + AsyncSocketAdapter::OnWriteEvent(socket); + } +} + +void +SChannelAdapter::OnCloseEvent(AsyncSocket* socket, int err) { + if ((state_ == SSL_NONE) || impl_->readable.empty()) { + AsyncSocketAdapter::OnCloseEvent(socket, err); + return; + } + + // If readable is non-empty, then we have a pending Message + // that will allow us to signal close (eventually). + signal_close_ = true; +} + +void +SChannelAdapter::OnMessage(Message* pmsg) { + if (!message_pending_) + return; // This occurs when socket is closed + + message_pending_ = false; + if (!impl_->readable.empty()) { + AsyncSocketAdapter::OnReadEvent(this); + } else if (signal_close_) { + signal_close_ = false; + AsyncSocketAdapter::OnCloseEvent(this, 0); // TODO: cache this error? + } +} + +} // namespace rtc diff --git a/webrtc/base/schanneladapter.h b/webrtc/base/schanneladapter.h new file mode 100644 index 000000000..d174b593f --- /dev/null +++ b/webrtc/base/schanneladapter.h @@ -0,0 +1,77 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SCHANNELADAPTER_H__ +#define WEBRTC_BASE_SCHANNELADAPTER_H__ + +#include +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/messagequeue.h" +struct _SecBufferDesc; + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class SChannelAdapter : public SSLAdapter, public MessageHandler { +public: + SChannelAdapter(AsyncSocket* socket); + virtual ~SChannelAdapter(); + + virtual int StartSSL(const char* hostname, bool restartable); + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + virtual int Close(); + + // Note that the socket returns ST_CONNECTING while SSL is being negotiated. + virtual ConnState GetState() const; + +protected: + enum SSLState { + SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR + }; + struct SSLImpl; + + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void OnReadEvent(AsyncSocket* socket); + virtual void OnWriteEvent(AsyncSocket* socket); + virtual void OnCloseEvent(AsyncSocket* socket, int err); + virtual void OnMessage(Message* pmsg); + + int BeginSSL(); + int ContinueSSL(); + int ProcessContext(long int status, _SecBufferDesc* sbd_in, + _SecBufferDesc* sbd_out); + int DecryptData(); + + int Read(); + int Flush(); + void Error(const char* context, int err, bool signal = true); + void Cleanup(); + + void PostEvent(); + +private: + SSLState state_; + std::string ssl_host_name_; + // If true, socket will retain SSL configuration after Close. + bool restartable_; + // If true, we are delaying signalling close until all data is read. + bool signal_close_; + // If true, we are waiting to be woken up to signal readability or closure. + bool message_pending_; + SSLImpl* impl_; +}; + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SCHANNELADAPTER_H__ diff --git a/webrtc/base/scoped_autorelease_pool.h b/webrtc/base/scoped_autorelease_pool.h new file mode 100644 index 000000000..d9cc3cb36 --- /dev/null +++ b/webrtc/base/scoped_autorelease_pool.h @@ -0,0 +1,59 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Automatically initialize and and free an autoreleasepool. Never allocate +// an instance of this class using "new" - that will result in a compile-time +// error. Only use it as a stack object. +// +// Note: NSAutoreleasePool docs say that you should not normally need to +// declare an NSAutoreleasePool as a member of an object - but there's nothing +// that indicates it will be a problem, as long as the stack lifetime of the +// pool exactly matches the stack lifetime of the object. + +#ifndef WEBRTC_BASE_SCOPED_AUTORELEASE_POOL_H__ +#define WEBRTC_BASE_SCOPED_AUTORELEASE_POOL_H__ + +#if defined(WEBRTC_MAC) + +#include "webrtc/base/common.h" + +// This header may be included from Obj-C files or C++ files. +#ifdef __OBJC__ +@class NSAutoreleasePool; +#else +class NSAutoreleasePool; +#endif + +namespace rtc { + +class ScopedAutoreleasePool { + public: + ScopedAutoreleasePool(); + ~ScopedAutoreleasePool(); + + private: + // Declaring private overrides of new and delete here enforces the "only use + // as a stack object" discipline. + // + // Note: new is declared as "throw()" to get around a gcc warning about new + // returning NULL, but this method will never get called and therefore will + // never actually throw any exception. + void* operator new(size_t size) throw() { return NULL; } + void operator delete (void* ptr) {} + + NSAutoreleasePool* pool_; + + DISALLOW_EVIL_CONSTRUCTORS(ScopedAutoreleasePool); +}; + +} // namespace rtc + +#endif // WEBRTC_MAC +#endif // WEBRTC_BASE_SCOPED_AUTORELEASE_POOL_H__ diff --git a/webrtc/base/scoped_autorelease_pool.mm b/webrtc/base/scoped_autorelease_pool.mm new file mode 100644 index 000000000..4176aad0e --- /dev/null +++ b/webrtc/base/scoped_autorelease_pool.mm @@ -0,0 +1,25 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import "webrtc/base/scoped_autorelease_pool.h" + +namespace rtc { + +ScopedAutoreleasePool::ScopedAutoreleasePool() { + pool_ = [[NSAutoreleasePool alloc] init]; +} + +ScopedAutoreleasePool::~ScopedAutoreleasePool() { + [pool_ drain]; +} + +} // namespace rtc diff --git a/webrtc/base/scoped_ptr.h b/webrtc/base/scoped_ptr.h new file mode 100644 index 000000000..0c0a637a2 --- /dev/null +++ b/webrtc/base/scoped_ptr.h @@ -0,0 +1,595 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Scopers help you manage ownership of a pointer, helping you easily manage the +// a pointer within a scope, and automatically destroying the pointer at the +// end of a scope. There are two main classes you will use, which correspond +// to the operators new/delete and new[]/delete[]. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo("wee")); +// } // foo goes out of scope, releasing the pointer with it. +// +// { +// scoped_ptr foo; // No pointer managed. +// foo.reset(new Foo("wee")); // Now a pointer is managed. +// foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. +// foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. +// foo->Method(); // Foo::Method() called. +// foo.get()->Method(); // Foo::Method() called. +// SomeFunc(foo.release()); // SomeFunc takes ownership, foo no longer +// // manages a pointer. +// foo.reset(new Foo("wee4")); // foo manages a pointer again. +// foo.reset(); // Foo("wee4") destroyed, foo no longer +// // manages a pointer. +// } // foo wasn't managing a pointer, so nothing was destroyed. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo[100]); +// foo.get()->Method(); // Foo::Method on the 0th element. +// foo[10].Method(); // Foo::Method on the 10th element. +// } +// +// These scopers also implement part of the functionality of C++11 unique_ptr +// in that they are "movable but not copyable." You can use the scopers in +// the parameter and return types of functions to signify ownership transfer +// in to and out of a function. When calling a function that has a scoper +// as the argument type, it must be called with the result of an analogous +// scoper's Pass() function or another function that generates a temporary; +// passing by copy will NOT work. Here is an example using scoped_ptr: +// +// void TakesOwnership(scoped_ptr arg) { +// // Do something with arg +// } +// scoped_ptr CreateFoo() { +// // No need for calling Pass() because we are constructing a temporary +// // for the return value. +// return scoped_ptr(new Foo("new")); +// } +// scoped_ptr PassThru(scoped_ptr arg) { +// return arg.Pass(); +// } +// +// { +// scoped_ptr ptr(new Foo("yay")); // ptr manages Foo("yay"). +// TakesOwnership(ptr.Pass()); // ptr no longer owns Foo("yay"). +// scoped_ptr ptr2 = CreateFoo(); // ptr2 owns the return Foo. +// scoped_ptr ptr3 = // ptr3 now owns what was in ptr2. +// PassThru(ptr2.Pass()); // ptr2 is correspondingly NULL. +// } +// +// Notice that if you do not call Pass() when returning from PassThru(), or +// when invoking TakesOwnership(), the code will not compile because scopers +// are not copyable; they only implement move semantics which require calling +// the Pass() function to signify a destructive transfer of state. CreateFoo() +// is different though because we are constructing a temporary on the return +// line and thus can avoid needing to call Pass(). +// +// Pass() properly handles upcast in initialization, i.e. you can use a +// scoped_ptr to initialize a scoped_ptr: +// +// scoped_ptr foo(new Foo()); +// scoped_ptr parent(foo.Pass()); +// +// PassAs<>() should be used to upcast return value in return statement: +// +// scoped_ptr CreateFoo() { +// scoped_ptr result(new FooChild()); +// return result.PassAs(); +// } +// +// Note that PassAs<>() is implemented only for scoped_ptr, but not for +// scoped_ptr. This is because casting array pointers may not be safe. + +#ifndef WEBRTC_BASE_SCOPED_PTR_H__ +#define WEBRTC_BASE_SCOPED_PTR_H__ + +#include // for ptrdiff_t +#include // for free() decl + +#include // For std::swap(). + +#include "webrtc/base/common.h" // for ASSERT +#include "webrtc/base/compile_assert.h" // for COMPILE_ASSERT +#include "webrtc/base/move.h" // for TALK_MOVE_ONLY_TYPE_FOR_CPP_03 +#include "webrtc/base/template_util.h" // for is_convertible, is_array + +#ifdef WEBRTC_WIN +namespace std { using ::ptrdiff_t; }; +#endif // WEBRTC_WIN + +namespace rtc { + +// Function object which deletes its parameter, which must be a pointer. +// If C is an array type, invokes 'delete[]' on the parameter; otherwise, +// invokes 'delete'. The default deleter for scoped_ptr. +template +struct DefaultDeleter { + DefaultDeleter() {} + template DefaultDeleter(const DefaultDeleter& other) { + // IMPLEMENTATION NOTE: C++11 20.7.1.1.2p2 only provides this constructor + // if U* is implicitly convertible to T* and U is not an array type. + // + // Correct implementation should use SFINAE to disable this + // constructor. However, since there are no other 1-argument constructors, + // using a COMPILE_ASSERT() based on is_convertible<> and requiring + // complete types is simpler and will cause compile failures for equivalent + // misuses. + // + // Note, the is_convertible check also ensures that U is not an + // array. T is guaranteed to be a non-array, so any U* where U is an array + // cannot convert to T*. + enum { T_must_be_complete = sizeof(T) }; + enum { U_must_be_complete = sizeof(U) }; + COMPILE_ASSERT((rtc::is_convertible::value), + U_ptr_must_implicitly_convert_to_T_ptr); + } + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete ptr; + } +}; + +// Specialization of DefaultDeleter for array types. +template +struct DefaultDeleter { + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete[] ptr; + } + + private: + // Disable this operator for any U != T because it is undefined to execute + // an array delete when the static type of the array mismatches the dynamic + // type. + // + // References: + // C++98 [expr.delete]p3 + // http://cplusplus.github.com/LWG/lwg-defects.html#938 + template void operator()(U* array) const; +}; + +template +struct DefaultDeleter { + // Never allow someone to declare something like scoped_ptr. + COMPILE_ASSERT(sizeof(T) == -1, do_not_use_array_with_size_as_type); +}; + +// Function object which invokes 'free' on its parameter, which must be +// a pointer. Can be used to store malloc-allocated pointers in scoped_ptr: +// +// scoped_ptr foo_ptr( +// static_cast(malloc(sizeof(int)))); +struct FreeDeleter { + inline void operator()(void* ptr) const { + free(ptr); + } +}; + +namespace internal { + +// Minimal implementation of the core logic of scoped_ptr, suitable for +// reuse in both scoped_ptr and its specializations. +template +class scoped_ptr_impl { + public: + explicit scoped_ptr_impl(T* p) : data_(p) { } + + // Initializer for deleters that have data parameters. + scoped_ptr_impl(T* p, const D& d) : data_(p, d) {} + + // Templated constructor that destructively takes the value from another + // scoped_ptr_impl. + template + scoped_ptr_impl(scoped_ptr_impl* other) + : data_(other->release(), other->get_deleter()) { + // We do not support move-only deleters. We could modify our move + // emulation to have rtc::subtle::move() and + // rtc::subtle::forward() + // functions that are imperfect emulations of their C++11 equivalents, + // but until there's a requirement, just assume deleters are copyable. + } + + template + void TakeState(scoped_ptr_impl* other) { + // See comment in templated constructor above regarding lack of support + // for move-only deleters. + reset(other->release()); + get_deleter() = other->get_deleter(); + } + + ~scoped_ptr_impl() { + if (data_.ptr != NULL) { + // Not using get_deleter() saves one function call in non-optimized + // builds. + static_cast(data_)(data_.ptr); + } + } + + void reset(T* p) { + // This is a self-reset, which is no longer allowed: http://crbug.com/162971 + if (p != NULL && p == data_.ptr) + abort(); + + // Note that running data_.ptr = p can lead to undefined behavior if + // get_deleter()(get()) deletes this. In order to pevent this, reset() + // should update the stored pointer before deleting its old value. + // + // However, changing reset() to use that behavior may cause current code to + // break in unexpected ways. If the destruction of the owned object + // dereferences the scoped_ptr when it is destroyed by a call to reset(), + // then it will incorrectly dispatch calls to |p| rather than the original + // value of |data_.ptr|. + // + // During the transition period, set the stored pointer to NULL while + // deleting the object. Eventually, this safety check will be removed to + // prevent the scenario initially described from occuring and + // http://crbug.com/176091 can be closed. + T* old = data_.ptr; + data_.ptr = NULL; + if (old != NULL) + static_cast(data_)(old); + data_.ptr = p; + } + + T* get() const { return data_.ptr; } + + D& get_deleter() { return data_; } + const D& get_deleter() const { return data_; } + + void swap(scoped_ptr_impl& p2) { + // Standard swap idiom: 'using std::swap' ensures that std::swap is + // present in the overload set, but we call swap unqualified so that + // any more-specific overloads can be used, if available. + using std::swap; + swap(static_cast(data_), static_cast(p2.data_)); + swap(data_.ptr, p2.data_.ptr); + } + + T* release() { + T* old_ptr = data_.ptr; + data_.ptr = NULL; + return old_ptr; + } + + T** accept() { + reset(NULL); + return &(data_.ptr); + } + + T** use() { + return &(data_.ptr); + } + + private: + // Needed to allow type-converting constructor. + template friend class scoped_ptr_impl; + + // Use the empty base class optimization to allow us to have a D + // member, while avoiding any space overhead for it when D is an + // empty class. See e.g. http://www.cantrip.org/emptyopt.html for a good + // discussion of this technique. + struct Data : public D { + explicit Data(T* ptr_in) : ptr(ptr_in) {} + Data(T* ptr_in, const D& other) : D(other), ptr(ptr_in) {} + T* ptr; + }; + + Data data_; + + DISALLOW_COPY_AND_ASSIGN(scoped_ptr_impl); +}; + +} // namespace internal + +// A scoped_ptr is like a T*, except that the destructor of scoped_ptr +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr owns the T object that it points to. +// Like a T*, a scoped_ptr may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr is thread-compatible, and once you +// dereference it, you get the thread safety guarantees of T. +// +// The size of scoped_ptr is small. On most compilers, when using the +// DefaultDeleter, sizeof(scoped_ptr) == sizeof(T*). Custom deleters will +// increase the size proportional to whatever state they need to have. See +// comments inside scoped_ptr_impl<> for details. +// +// Current implementation targets having a strict subset of C++11's +// unique_ptr<> features. Known deficiencies include not supporting move-only +// deleteres, function pointers as deleters, and deleters with reference +// types. +template > +class scoped_ptr { + TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Takes ownership of p. + explicit scoped_ptr(element_type* p) : impl_(p) { } + + // Constructor. Allows initialization of a stateful deleter. + scoped_ptr(element_type* p, const D& d) : impl_(p, d) { } + + // Constructor. Allows construction from a scoped_ptr rvalue for a + // convertible type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this constructor distinct + // from the normal move constructor. By C++11 20.7.1.2.1.21, this constructor + // has different post-conditions if D is a reference type. Since this + // implementation does not support deleters with reference type, + // we do not need a separate move constructor allowing us to avoid one + // use of SFINAE. You only need to care about this if you modify the + // implementation of scoped_ptr. + template + scoped_ptr(scoped_ptr other) : impl_(&other.impl_) { + COMPILE_ASSERT(!rtc::is_array::value, U_cannot_be_an_array); + } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Allows assignment from a scoped_ptr rvalue for a convertible + // type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this operator= distinct from + // the normal move assignment operator. By C++11 20.7.1.2.3.4, this templated + // form has different requirements on for move-only Deleters. Since this + // implementation does not support move-only Deleters, we do not need a + // separate move assignment operator allowing us to avoid one use of SFINAE. + // You only need to care about this if you modify the implementation of + // scoped_ptr. + template + scoped_ptr& operator=(scoped_ptr rhs) { + COMPILE_ASSERT(!rtc::is_array::value, U_cannot_be_an_array); + impl_.TakeState(&rhs.impl_); + return *this; + } + + // Reset. Deletes the currently owned object, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* p = NULL) { impl_.reset(p); } + + // Accessors to get the owned object. + // operator* and operator-> will assert() if there is no current object. + element_type& operator*() const { + ASSERT(impl_.get() != NULL); + return *impl_.get(); + } + element_type* operator->() const { + ASSERT(impl_.get() != NULL); + return impl_.get(); + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + // + // Note that this trick is only safe when the == and != operators + // are declared explicitly, as otherwise "scoped_ptr1 == + // scoped_ptr2" will compile but do the wrong thing (i.e., convert + // to Testable and then do the comparison). + private: + typedef rtc::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(const element_type* p) const { return impl_.get() == p; } + bool operator!=(const element_type* p) const { return impl_.get() != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + // Delete the currently held pointer and return a pointer + // to allow overwriting of the current pointer address. + element_type** accept() WARN_UNUSED_RESULT { + return impl_.accept(); + } + + // Return a pointer to the current pointer address. + element_type** use() WARN_UNUSED_RESULT { + return impl_.use(); + } + + // C++98 doesn't support functions templates with default parameters which + // makes it hard to write a PassAs() that understands converting the deleter + // while preserving simple calling semantics. + // + // Until there is a use case for PassAs() with custom deleters, just ignore + // the custom deleter. + template + scoped_ptr PassAs() { + return scoped_ptr(Pass()); + } + + private: + // Needed to reach into |impl_| in the constructor. + template friend class scoped_ptr; + rtc::internal::scoped_ptr_impl impl_; + + // Forbidden for API compatibility with std::unique_ptr. + explicit scoped_ptr(int disallow_construction_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +template +class scoped_ptr { + TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Stores the given array. Note that the argument's type + // must exactly match T*. In particular: + // - it cannot be a pointer to a type derived from T, because it is + // inherently unsafe in the general case to access an array through a + // pointer whose dynamic type does not match its static type (eg., if + // T and the derived types had different sizes access would be + // incorrectly calculated). Deletion is also always undefined + // (C++98 [expr.delete]p3). If you're doing this, fix your code. + // - it cannot be NULL, because NULL is an integral expression, not a + // pointer to T. Use the no-argument version instead of explicitly + // passing NULL. + // - it cannot be const-qualified differently from T per unique_ptr spec + // (http://cplusplus.github.com/LWG/lwg-active.html#2118). Users wanting + // to work around this may use implicit_cast(). + // However, because of the first bullet in this comment, users MUST + // NOT use implicit_cast() to upcast the static type of the array. + explicit scoped_ptr(element_type* array) : impl_(array) { } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr& operator=(RValue rhs) { + impl_.TakeState(&rhs.object->impl_); + return *this; + } + + // Reset. Deletes the currently owned array, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* array = NULL) { impl_.reset(array); } + + // Accessors to get the owned array. + element_type& operator[](size_t i) const { + ASSERT(impl_.get() != NULL); + return impl_.get()[i]; + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + typedef rtc::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(element_type* array) const { return impl_.get() == array; } + bool operator!=(element_type* array) const { return impl_.get() != array; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + // Delete the currently held pointer and return a pointer + // to allow overwriting of the current pointer address. + element_type** accept() WARN_UNUSED_RESULT { + return impl_.accept(); + } + + // Return a pointer to the current pointer address. + element_type** use() WARN_UNUSED_RESULT { + return impl_.use(); + } + + private: + // Force element_type to be a complete type. + enum { type_must_be_complete = sizeof(element_type) }; + + // Actually hold the data. + rtc::internal::scoped_ptr_impl impl_; + + // Disable initialization from any type other than element_type*, by + // providing a constructor that matches such an initialization, but is + // private and has no definition. This is disabled because it is not safe to + // call delete[] on an array whose static type does not match its dynamic + // type. + template explicit scoped_ptr(U* array); + explicit scoped_ptr(int disallow_construction_from_null); + + // Disable reset() from any type other than element_type*, for the same + // reasons as the constructor above. + template void reset(U* array); + void reset(int disallow_reset_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +} // namespace rtc + +// Free functions +template +void swap(rtc::scoped_ptr& p1, rtc::scoped_ptr& p2) { + p1.swap(p2); +} + +template +bool operator==(T* p1, const rtc::scoped_ptr& p2) { + return p1 == p2.get(); +} + +template +bool operator!=(T* p1, const rtc::scoped_ptr& p2) { + return p1 != p2.get(); +} + +#endif // #ifndef WEBRTC_BASE_SCOPED_PTR_H__ diff --git a/webrtc/base/scoped_ref_ptr.h b/webrtc/base/scoped_ref_ptr.h new file mode 100644 index 000000000..a71c20ae3 --- /dev/null +++ b/webrtc/base/scoped_ref_ptr.h @@ -0,0 +1,147 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Originally these classes are from Chromium. +// http://src.chromium.org/viewvc/chrome/trunk/src/base/memory/ref_counted.h?view=markup + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted { +// ... +// }; +// +// void some_function() { +// scoped_refptr foo = new MyFoo(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr foo = new MyFoo(); +// ... +// foo = NULL; // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr acts like a pointer to T. +// Given two scoped_refptr classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b.swap(a); +// // now, |b| references the MyFoo object, and |a| references NULL. +// } +// +// To make both |a| and |b| in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b = a; +// // now, |a| and |b| each own a reference to the same MyFoo object. +// } +// + +#ifndef WEBRTC_BASE_SCOPED_REF_PTR_H_ +#define WEBRTC_BASE_SCOPED_REF_PTR_H_ + +#include + +namespace rtc { + +template +class scoped_refptr { + public: + scoped_refptr() : ptr_(NULL) { + } + + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(const scoped_refptr& r) : ptr_(r.ptr_) { + if (ptr_) + ptr_->AddRef(); + } + + template + scoped_refptr(const scoped_refptr& r) : ptr_(r.get()) { + if (ptr_) + ptr_->AddRef(); + } + + ~scoped_refptr() { + if (ptr_) + ptr_->Release(); + } + + T* get() const { return ptr_; } + operator T*() const { return ptr_; } + T* operator->() const { return ptr_; } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + T* release() { + T* retVal = ptr_; + ptr_ = NULL; + return retVal; + } + + scoped_refptr& operator=(T* p) { + // AddRef first so that self assignment should work + if (p) + p->AddRef(); + if (ptr_ ) + ptr_ ->Release(); + ptr_ = p; + return *this; + } + + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.ptr_; + } + + template + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.get(); + } + + void swap(T** pp) { + T* p = ptr_; + ptr_ = *pp; + *pp = p; + } + + void swap(scoped_refptr& r) { + swap(&r.ptr_); + } + + protected: + T* ptr_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SCOPED_REF_PTR_H_ diff --git a/webrtc/base/scopedptrcollection.h b/webrtc/base/scopedptrcollection.h new file mode 100644 index 000000000..47dff6503 --- /dev/null +++ b/webrtc/base/scopedptrcollection.h @@ -0,0 +1,60 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Stores a collection of pointers that are deleted when the container is +// destructed. + +#ifndef WEBRTC_BASE_SCOPEDPTRCOLLECTION_H_ +#define WEBRTC_BASE_SCOPEDPTRCOLLECTION_H_ + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +template +class ScopedPtrCollection { + public: + typedef std::vector VectorT; + + ScopedPtrCollection() { } + ~ScopedPtrCollection() { + for (typename VectorT::iterator it = collection_.begin(); + it != collection_.end(); ++it) { + delete *it; + } + } + + const VectorT& collection() const { return collection_; } + void Reserve(size_t size) { + collection_.reserve(size); + } + void PushBack(T* t) { + collection_.push_back(t); + } + + // Remove |t| from the collection without deleting it. + void Remove(T* t) { + collection_.erase(std::remove(collection_.begin(), collection_.end(), t), + collection_.end()); + } + + private: + VectorT collection_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPtrCollection); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SCOPEDPTRCOLLECTION_H_ diff --git a/webrtc/base/scopedptrcollection_unittest.cc b/webrtc/base/scopedptrcollection_unittest.cc new file mode 100644 index 000000000..30b8ed9ed --- /dev/null +++ b/webrtc/base/scopedptrcollection_unittest.cc @@ -0,0 +1,73 @@ +/* + * Copyright 2014 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/scopedptrcollection.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +namespace { + +class InstanceCounter { + public: + explicit InstanceCounter(int* num_instances) + : num_instances_(num_instances) { + ++(*num_instances_); + } + ~InstanceCounter() { + --(*num_instances_); + } + + private: + int* num_instances_; + + DISALLOW_COPY_AND_ASSIGN(InstanceCounter); +}; + +} // namespace + +class ScopedPtrCollectionTest : public testing::Test { + protected: + ScopedPtrCollectionTest() + : num_instances_(0), + collection_(new ScopedPtrCollection()) { + } + + int num_instances_; + scoped_ptr > collection_; +}; + +TEST_F(ScopedPtrCollectionTest, PushBack) { + EXPECT_EQ(0u, collection_->collection().size()); + EXPECT_EQ(0, num_instances_); + const int kNum = 100; + for (int i = 0; i < kNum; ++i) { + collection_->PushBack(new InstanceCounter(&num_instances_)); + } + EXPECT_EQ(static_cast(kNum), collection_->collection().size()); + EXPECT_EQ(kNum, num_instances_); + collection_.reset(); + EXPECT_EQ(0, num_instances_); +} + +TEST_F(ScopedPtrCollectionTest, Remove) { + InstanceCounter* ic = new InstanceCounter(&num_instances_); + collection_->PushBack(ic); + EXPECT_EQ(1u, collection_->collection().size()); + collection_->Remove(ic); + EXPECT_EQ(1, num_instances_); + collection_.reset(); + EXPECT_EQ(1, num_instances_); + delete ic; + EXPECT_EQ(0, num_instances_); +} + + +} // namespace rtc diff --git a/webrtc/base/sec_buffer.h b/webrtc/base/sec_buffer.h new file mode 100644 index 000000000..d4cda00d4 --- /dev/null +++ b/webrtc/base/sec_buffer.h @@ -0,0 +1,156 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// @file Contains utility classes that make it easier to use SecBuffers + +#ifndef WEBRTC_BASE_SEC_BUFFER_H__ +#define WEBRTC_BASE_SEC_BUFFER_H__ + +namespace rtc { + +// A base class for CSecBuffer. Contains +// all implementation that does not require +// template arguments. +class CSecBufferBase : public SecBuffer { + public: + CSecBufferBase() { + Clear(); + } + + // Uses the SSPI to free a pointer, must be + // used for buffers returned from SSPI APIs. + static void FreeSSPI(void *ptr) { + if ( ptr ) { + SECURITY_STATUS status; + status = ::FreeContextBuffer(ptr); + ASSERT(SEC_E_OK == status); // "Freeing context buffer" + } + } + + // Deletes a buffer with operator delete + static void FreeDelete(void *ptr) { + delete [] reinterpret_cast(ptr); + } + + // A noop delete, for buffers over other + // people's memory + static void FreeNone(void *ptr) { + } + + protected: + // Clears the buffer to EMPTY & NULL + void Clear() { + this->BufferType = SECBUFFER_EMPTY; + this->cbBuffer = 0; + this->pvBuffer = NULL; + } +}; + +// Wrapper class for SecBuffer to take care +// of initialization and destruction. +template +class CSecBuffer: public CSecBufferBase { + public: + // Initializes buffer to empty & NULL + CSecBuffer() { + } + + // Frees any allocated memory + ~CSecBuffer() { + Release(); + } + + // Frees the buffer appropriately, and re-nulls + void Release() { + pfnFreeBuffer(this->pvBuffer); + Clear(); + } + + private: + // A placeholder function for compile-time asserts on the class + void CompileAsserts() { + // never invoked... + assert(false); // _T("Notreached") + + // This class must not extend the size of SecBuffer, since + // we use arrays of CSecBuffer in CSecBufferBundle below + cassert(sizeof(CSecBuffer == sizeof(SecBuffer))); + } +}; + +// Contains all generic implementation for the +// SecBufferBundle class +class SecBufferBundleBase { + public: +}; + +// A template class that bundles a SecBufferDesc with +// one or more SecBuffers for convenience. Can take +// care of deallocating buffers appropriately, as indicated +// by pfnFreeBuffer function. +// By default does no deallocation. +template +class CSecBufferBundle : public SecBufferBundleBase { + public: + // Constructs a security buffer bundle with num_buffers + // buffers, all of which are empty and nulled. + CSecBufferBundle() { + desc_.ulVersion = SECBUFFER_VERSION; + desc_.cBuffers = num_buffers; + desc_.pBuffers = buffers_; + } + + // Frees all currently used buffers. + ~CSecBufferBundle() { + Release(); + } + + // Accessor for the descriptor + PSecBufferDesc desc() { + return &desc_; + } + + // Accessor for the descriptor + const PSecBufferDesc desc() const { + return &desc_; + } + + // returns the i-th security buffer + SecBuffer &operator[] (size_t num) { + ASSERT(num < num_buffers); // "Buffer index out of bounds" + return buffers_[num]; + } + + // returns the i-th security buffer + const SecBuffer &operator[] (size_t num) const { + ASSERT(num < num_buffers); // "Buffer index out of bounds" + return buffers_[num]; + } + + // Frees all non-NULL security buffers, + // using the deallocation function + void Release() { + for ( size_t i = 0; i < num_buffers; ++i ) { + buffers_[i].Release(); + } + } + + private: + // Our descriptor + SecBufferDesc desc_; + // Our bundled buffers, each takes care of its own + // initialization and destruction + CSecBuffer buffers_[num_buffers]; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SEC_BUFFER_H__ diff --git a/webrtc/base/sha1.cc b/webrtc/base/sha1.cc new file mode 100644 index 000000000..8f8bd3d4a --- /dev/null +++ b/webrtc/base/sha1.cc @@ -0,0 +1,282 @@ +/* + * SHA-1 in C + * By Steve Reid + * 100% Public Domain + * + * ----------------- + * Modified 7/98 + * By James H. Brown + * Still 100% Public Domain + * + * Corrected a problem which generated improper hash values on 16 bit machines + * Routine SHA1Update changed from + * void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int + * len) + * to + * void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned + * long len) + * + * The 'len' parameter was declared an int which works fine on 32 bit machines. + * However, on 16 bit machines an int is too small for the shifts being done + * against + * it. This caused the hash function to generate incorrect values if len was + * greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + * + * Since the file IO in main() reads 16K at a time, any file 8K or larger would + * be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million + * "a"s). + * + * I also changed the declaration of variables i & j in SHA1Update to + * unsigned long from unsigned int for the same reason. + * + * These changes should make no difference to any 32 bit implementations since + * an + * int and a long are the same size in those environments. + * + * -- + * I also corrected a few compiler warnings generated by Borland C. + * 1. Added #include for exit() prototype + * 2. Removed unused variable 'j' in SHA1Final + * 3. Changed exit(0) to return(0) at end of main. + * + * ALL changes I made can be located by searching for comments containing 'JHB' + * ----------------- + * Modified 8/98 + * By Steve Reid + * Still 100% public domain + * + * 1- Removed #include and used return() instead of exit() + * 2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) + * 3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + * + * ----------------- + * Modified 4/01 + * By Saul Kravitz + * Still 100% PD + * Modified to run on Compaq Alpha hardware. + * + * ----------------- + * Modified 07/2002 + * By Ralph Giles + * Still 100% public domain + * modified for use with stdint types, autoconf + * code cleanup, removed attribution comments + * switched SHA1Final() argument order for consistency + * use SHA1_ prefix for public api + * move public api to sha1.h + * + * ----------------- + * Modified 02/2012 + * By Justin Uberti + * Remove underscore from SHA1 prefix to avoid conflict with OpenSSL + * Remove test code + * Untabify + * + * ----------------- + * Modified 03/2012 + * By Ronghua Wu + * Change the typedef of uint32(8)_t to uint32(8). We need this because in the + * chromium android build, the stdio.h will include stdint.h which already + * defined uint32(8)_t. + * + * ----------------- + * Modified 04/2012 + * By Frank Barchard + * Ported to C++, Google style, change len to size_t, enable SHA1HANDSOFF + * + * Test Vectors (from FIPS PUB 180-1) + * "abc" + * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D + * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 + * A million repetitions of "a" + * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F + */ + +// Enabling SHA1HANDSOFF preserves the caller's data buffer. +// Disabling SHA1HANDSOFF the buffer will be modified (end swapped). +#define SHA1HANDSOFF + +#include "webrtc/base/sha1.h" + +#include +#include + +void SHA1Transform(uint32 state[5], const uint8 buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +// blk0() and blk() perform the initial expand. +// I got the idea of expanding during the round function from SSLeay +// FIXME: can we do this in an endian-proof way? +#ifdef ARCH_CPU_BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \ + (rol(block->l[i], 8) & 0x00FF00FF)) +#endif +#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ \ + block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1)) + +// (R0+R1), R2, R3, R4 are the different operations used in SHA1. +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5);\ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +#ifdef VERBOSE // SAK +void SHAPrintContext(SHA1_CTX *context, char *msg) { + printf("%s (%d,%d) %x %x %x %x %x\n", + msg, + context->count[0], context->count[1], + context->state[0], + context->state[1], + context->state[2], + context->state[3], + context->state[4]); +} +#endif /* VERBOSE */ + +// Hash a single 512-bit block. This is the core of the algorithm. +void SHA1Transform(uint32 state[5], const uint8 buffer[64]) { + union CHAR64LONG16 { + uint8 c[64]; + uint32 l[16]; + }; +#ifdef SHA1HANDSOFF + static uint8 workspace[64]; + memcpy(workspace, buffer, 64); + CHAR64LONG16* block = reinterpret_cast(workspace); +#else + // Note(fbarchard): This option does modify the user's data buffer. + CHAR64LONG16* block = const_cast( + reinterpret_cast(buffer)); +#endif + + // Copy context->state[] to working vars. + uint32 a = state[0]; + uint32 b = state[1]; + uint32 c = state[2]; + uint32 d = state[3]; + uint32 e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + // Note(fbarchard): The following has lint warnings for multiple ; on + // a line and no space after , but is left as-is to be similar to the + // original code. + R0(a,b,c,d,e,0); R0(e,a,b,c,d,1); R0(d,e,a,b,c,2); R0(c,d,e,a,b,3); + R0(b,c,d,e,a,4); R0(a,b,c,d,e,5); R0(e,a,b,c,d,6); R0(d,e,a,b,c,7); + R0(c,d,e,a,b,8); R0(b,c,d,e,a,9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[]. + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} + +// SHA1Init - Initialize new context. +void SHA1Init(SHA1_CTX* context) { + // SHA1 initialization constants. + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +// Run your data through this. +void SHA1Update(SHA1_CTX* context, const uint8* data, size_t input_len) { + size_t i = 0; + +#ifdef VERBOSE + SHAPrintContext(context, "before"); +#endif + + // Compute number of bytes mod 64. + size_t index = (context->count[0] >> 3) & 63; + + // Update number of bits. + // TODO: Use uint64 instead of 2 uint32 for count. + // count[0] has low 29 bits for byte count + 3 pad 0's making 32 bits for + // bit count. + // Add bit count to low uint32 + context->count[0] += static_cast(input_len << 3); + if (context->count[0] < static_cast(input_len << 3)) { + ++context->count[1]; // if overlow (carry), add one to high word + } + context->count[1] += static_cast(input_len >> 29); + if ((index + input_len) > 63) { + i = 64 - index; + memcpy(&context->buffer[index], data, i); + SHA1Transform(context->state, context->buffer); + for (; i + 63 < input_len; i += 64) { + SHA1Transform(context->state, data + i); + } + index = 0; + } + memcpy(&context->buffer[index], &data[i], input_len - i); + +#ifdef VERBOSE + SHAPrintContext(context, "after "); +#endif +} + +// Add padding and return the message digest. +void SHA1Final(SHA1_CTX* context, uint8 digest[SHA1_DIGEST_SIZE]) { + uint8 finalcount[8]; + for (int i = 0; i < 8; ++i) { + // Endian independent + finalcount[i] = static_cast( + (context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8) ) & 255); + } + SHA1Update(context, reinterpret_cast("\200"), 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, reinterpret_cast("\0"), 1); + } + SHA1Update(context, finalcount, 8); // Should cause a SHA1Transform(). + for (int i = 0; i < SHA1_DIGEST_SIZE; ++i) { + digest[i] = static_cast( + (context->state[i >> 2] >> ((3 - (i & 3)) * 8) ) & 255); + } + + // Wipe variables. + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(finalcount, 0, 8); // SWR + +#ifdef SHA1HANDSOFF // Make SHA1Transform overwrite its own static vars. + SHA1Transform(context->state, context->buffer); +#endif +} diff --git a/webrtc/base/sha1.h b/webrtc/base/sha1.h new file mode 100644 index 000000000..b19c6592e --- /dev/null +++ b/webrtc/base/sha1.h @@ -0,0 +1,28 @@ +/* + * SHA-1 in C + * By Steve Reid + * 100% Public Domain + * +*/ + +// Ported to C++, Google style and uses basictypes.h + +#ifndef WEBRTC_BASE_SHA1_H_ +#define WEBRTC_BASE_SHA1_H_ + +#include "webrtc/base/basictypes.h" + +struct SHA1_CTX { + uint32 state[5]; + // TODO: Change bit count to uint64. + uint32 count[2]; // Bit count of input. + uint8 buffer[64]; +}; + +#define SHA1_DIGEST_SIZE 20 + +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const uint8* data, size_t len); +void SHA1Final(SHA1_CTX* context, uint8 digest[SHA1_DIGEST_SIZE]); + +#endif // WEBRTC_BASE_SHA1_H_ diff --git a/webrtc/base/sha1digest.h b/webrtc/base/sha1digest.h new file mode 100644 index 000000000..fb4c53e6e --- /dev/null +++ b/webrtc/base/sha1digest.h @@ -0,0 +1,47 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SHA1DIGEST_H_ +#define WEBRTC_BASE_SHA1DIGEST_H_ + +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/sha1.h" + +namespace rtc { + +// A simple wrapper for our SHA-1 implementation. +class Sha1Digest : public MessageDigest { + public: + enum { kSize = SHA1_DIGEST_SIZE }; + Sha1Digest() { + SHA1Init(&ctx_); + } + virtual size_t Size() const { + return kSize; + } + virtual void Update(const void* buf, size_t len) { + SHA1Update(&ctx_, static_cast(buf), len); + } + virtual size_t Finish(void* buf, size_t len) { + if (len < kSize) { + return 0; + } + SHA1Final(&ctx_, static_cast(buf)); + SHA1Init(&ctx_); // Reset for next use. + return kSize; + } + + private: + SHA1_CTX ctx_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SHA1DIGEST_H_ diff --git a/webrtc/base/sha1digest_unittest.cc b/webrtc/base/sha1digest_unittest.cc new file mode 100644 index 000000000..d3c204387 --- /dev/null +++ b/webrtc/base/sha1digest_unittest.cc @@ -0,0 +1,82 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/sha1digest.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +std::string Sha1(const std::string& input) { + Sha1Digest sha1; + return ComputeDigest(&sha1, input); +} + +TEST(Sha1DigestTest, TestSize) { + Sha1Digest sha1; + EXPECT_EQ(20, static_cast(Sha1Digest::kSize)); + EXPECT_EQ(20U, sha1.Size()); +} + +TEST(Sha1DigestTest, TestBasic) { + // Test vectors from sha1.c. + EXPECT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709", Sha1("")); + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", Sha1("abc")); + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + Sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")); + std::string a_million_as(1000000, 'a'); + EXPECT_EQ("34aa973cd4c4daa4f61eeb2bdbad27316534016f", Sha1(a_million_as)); +} + +TEST(Sha1DigestTest, TestMultipleUpdates) { + Sha1Digest sha1; + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + char output[Sha1Digest::kSize]; + for (size_t i = 0; i < input.size(); ++i) { + sha1.Update(&input[i], 1); + } + EXPECT_EQ(sha1.Size(), sha1.Finish(output, sizeof(output))); + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + hex_encode(output, sizeof(output))); +} + +TEST(Sha1DigestTest, TestReuse) { + Sha1Digest sha1; + std::string input = "abc"; + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", + ComputeDigest(&sha1, input)); + input = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + ComputeDigest(&sha1, input)); +} + +TEST(Sha1DigestTest, TestBufferTooSmall) { + Sha1Digest sha1; + std::string input = "abcdefghijklmnopqrstuvwxyz"; + char output[Sha1Digest::kSize - 1]; + sha1.Update(input.c_str(), input.size()); + EXPECT_EQ(0U, sha1.Finish(output, sizeof(output))); +} + +TEST(Sha1DigestTest, TestBufferConst) { + Sha1Digest sha1; + const int kLongSize = 1000000; + std::string input(kLongSize, '\0'); + for (int i = 0; i < kLongSize; ++i) { + input[i] = static_cast(i); + } + sha1.Update(input.c_str(), input.size()); + for (int i = 0; i < kLongSize; ++i) { + EXPECT_EQ(static_cast(i), input[i]); + } +} + +} // namespace rtc diff --git a/webrtc/base/sharedexclusivelock.cc b/webrtc/base/sharedexclusivelock.cc new file mode 100644 index 000000000..9facf60ea --- /dev/null +++ b/webrtc/base/sharedexclusivelock.cc @@ -0,0 +1,44 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/sharedexclusivelock.h" + +namespace rtc { + +SharedExclusiveLock::SharedExclusiveLock() + : shared_count_is_zero_(true, true), + shared_count_(0) { +} + +void SharedExclusiveLock::LockExclusive() { + cs_exclusive_.Enter(); + shared_count_is_zero_.Wait(rtc::kForever); +} + +void SharedExclusiveLock::UnlockExclusive() { + cs_exclusive_.Leave(); +} + +void SharedExclusiveLock::LockShared() { + CritScope exclusive_scope(&cs_exclusive_); + CritScope shared_scope(&cs_shared_); + if (++shared_count_ == 1) { + shared_count_is_zero_.Reset(); + } +} + +void SharedExclusiveLock::UnlockShared() { + CritScope shared_scope(&cs_shared_); + if (--shared_count_ == 0) { + shared_count_is_zero_.Set(); + } +} + +} // namespace rtc diff --git a/webrtc/base/sharedexclusivelock.h b/webrtc/base/sharedexclusivelock.h new file mode 100644 index 000000000..f64d7cf50 --- /dev/null +++ b/webrtc/base/sharedexclusivelock.h @@ -0,0 +1,76 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SHAREDEXCLUSIVELOCK_H_ +#define WEBRTC_BASE_SHAREDEXCLUSIVELOCK_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/event.h" + +namespace rtc { + +// This class provides shared-exclusive lock. It can be used in cases like +// multiple-readers/single-writer model. +class SharedExclusiveLock { + public: + SharedExclusiveLock(); + + // Locking/unlocking methods. It is encouraged to use SharedScope or + // ExclusiveScope for protection. + void LockExclusive(); + void UnlockExclusive(); + void LockShared(); + void UnlockShared(); + + private: + rtc::CriticalSection cs_exclusive_; + rtc::CriticalSection cs_shared_; + rtc::Event shared_count_is_zero_; + int shared_count_; + + DISALLOW_COPY_AND_ASSIGN(SharedExclusiveLock); +}; + +class SharedScope { + public: + explicit SharedScope(SharedExclusiveLock* lock) : lock_(lock) { + lock_->LockShared(); + } + + ~SharedScope() { + lock_->UnlockShared(); + } + + private: + SharedExclusiveLock* lock_; + + DISALLOW_COPY_AND_ASSIGN(SharedScope); +}; + +class ExclusiveScope { + public: + explicit ExclusiveScope(SharedExclusiveLock* lock) : lock_(lock) { + lock_->LockExclusive(); + } + + ~ExclusiveScope() { + lock_->UnlockExclusive(); + } + + private: + SharedExclusiveLock* lock_; + + DISALLOW_COPY_AND_ASSIGN(ExclusiveScope); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SHAREDEXCLUSIVELOCK_H_ diff --git a/webrtc/base/sharedexclusivelock_unittest.cc b/webrtc/base/sharedexclusivelock_unittest.cc new file mode 100644 index 000000000..de1dbe812 --- /dev/null +++ b/webrtc/base/sharedexclusivelock_unittest.cc @@ -0,0 +1,217 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sharedexclusivelock.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +static const uint32 kMsgRead = 0; +static const uint32 kMsgWrite = 0; +static const int kNoWaitThresholdInMs = 10; +static const int kWaitThresholdInMs = 80; +static const int kProcessTimeInMs = 100; +static const int kProcessTimeoutInMs = 5000; + +class SharedExclusiveTask : public MessageHandler { + public: + SharedExclusiveTask(SharedExclusiveLock* shared_exclusive_lock, + int* value, + bool* done) + : shared_exclusive_lock_(shared_exclusive_lock), + waiting_time_in_ms_(0), + value_(value), + done_(done) { + worker_thread_.reset(new Thread()); + worker_thread_->Start(); + } + + int waiting_time_in_ms() const { return waiting_time_in_ms_; } + + protected: + scoped_ptr worker_thread_; + SharedExclusiveLock* shared_exclusive_lock_; + int waiting_time_in_ms_; + int* value_; + bool* done_; +}; + +class ReadTask : public SharedExclusiveTask { + public: + ReadTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done) + : SharedExclusiveTask(shared_exclusive_lock, value, done) { + } + + void PostRead(int* value) { + worker_thread_->Post(this, kMsgRead, new TypedMessageData(value)); + } + + private: + virtual void OnMessage(Message* message) { + ASSERT(rtc::Thread::Current() == worker_thread_.get()); + ASSERT(message != NULL); + ASSERT(message->message_id == kMsgRead); + + TypedMessageData* message_data = + static_cast*>(message->pdata); + + uint32 start_time = Time(); + { + SharedScope ss(shared_exclusive_lock_); + waiting_time_in_ms_ = TimeDiff(Time(), start_time); + + Thread::SleepMs(kProcessTimeInMs); + *message_data->data() = *value_; + *done_ = true; + } + delete message->pdata; + message->pdata = NULL; + } +}; + +class WriteTask : public SharedExclusiveTask { + public: + WriteTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done) + : SharedExclusiveTask(shared_exclusive_lock, value, done) { + } + + void PostWrite(int value) { + worker_thread_->Post(this, kMsgWrite, new TypedMessageData(value)); + } + + private: + virtual void OnMessage(Message* message) { + ASSERT(rtc::Thread::Current() == worker_thread_.get()); + ASSERT(message != NULL); + ASSERT(message->message_id == kMsgWrite); + + TypedMessageData* message_data = + static_cast*>(message->pdata); + + uint32 start_time = Time(); + { + ExclusiveScope es(shared_exclusive_lock_); + waiting_time_in_ms_ = TimeDiff(Time(), start_time); + + Thread::SleepMs(kProcessTimeInMs); + *value_ = message_data->data(); + *done_ = true; + } + delete message->pdata; + message->pdata = NULL; + } +}; + +// Unit test for SharedExclusiveLock. +class SharedExclusiveLockTest + : public testing::Test { + public: + SharedExclusiveLockTest() : value_(0) { + } + + virtual void SetUp() { + shared_exclusive_lock_.reset(new SharedExclusiveLock()); + } + + protected: + scoped_ptr shared_exclusive_lock_; + int value_; +}; + +TEST_F(SharedExclusiveLockTest, TestSharedShared) { + int value0, value1; + bool done0, done1; + ReadTask reader0(shared_exclusive_lock_.get(), &value_, &done0); + ReadTask reader1(shared_exclusive_lock_.get(), &value_, &done1); + + // Test shared locks can be shared without waiting. + { + SharedScope ss(shared_exclusive_lock_.get()); + value_ = 1; + done0 = false; + done1 = false; + reader0.PostRead(&value0); + reader1.PostRead(&value1); + Thread::SleepMs(kProcessTimeInMs); + } + + EXPECT_TRUE_WAIT(done0, kProcessTimeoutInMs); + EXPECT_EQ(1, value0); + EXPECT_LE(reader0.waiting_time_in_ms(), kNoWaitThresholdInMs); + EXPECT_TRUE_WAIT(done1, kProcessTimeoutInMs); + EXPECT_EQ(1, value1); + EXPECT_LE(reader1.waiting_time_in_ms(), kNoWaitThresholdInMs); +} + +TEST_F(SharedExclusiveLockTest, TestSharedExclusive) { + bool done; + WriteTask writer(shared_exclusive_lock_.get(), &value_, &done); + + // Test exclusive lock needs to wait for shared lock. + { + SharedScope ss(shared_exclusive_lock_.get()); + value_ = 1; + done = false; + writer.PostWrite(2); + Thread::SleepMs(kProcessTimeInMs); + EXPECT_EQ(1, value_); + } + + EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs); + EXPECT_EQ(2, value_); + EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs); +} + +TEST_F(SharedExclusiveLockTest, TestExclusiveShared) { + int value; + bool done; + ReadTask reader(shared_exclusive_lock_.get(), &value_, &done); + + // Test shared lock needs to wait for exclusive lock. + { + ExclusiveScope es(shared_exclusive_lock_.get()); + value_ = 1; + done = false; + reader.PostRead(&value); + Thread::SleepMs(kProcessTimeInMs); + value_ = 2; + } + + EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs); + EXPECT_EQ(2, value); + EXPECT_GE(reader.waiting_time_in_ms(), kWaitThresholdInMs); +} + +TEST_F(SharedExclusiveLockTest, TestExclusiveExclusive) { + bool done; + WriteTask writer(shared_exclusive_lock_.get(), &value_, &done); + + // Test exclusive lock needs to wait for exclusive lock. + { + ExclusiveScope es(shared_exclusive_lock_.get()); + value_ = 1; + done = false; + writer.PostWrite(2); + Thread::SleepMs(kProcessTimeInMs); + EXPECT_EQ(1, value_); + } + + EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs); + EXPECT_EQ(2, value_); + EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs); +} + +} // namespace rtc diff --git a/webrtc/base/signalthread.cc b/webrtc/base/signalthread.cc new file mode 100644 index 000000000..f95cb5fbc --- /dev/null +++ b/webrtc/base/signalthread.cc @@ -0,0 +1,149 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/signalthread.h" + +#include "webrtc/base/common.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// SignalThread +/////////////////////////////////////////////////////////////////////////////// + +SignalThread::SignalThread() + : main_(Thread::Current()), + worker_(this), + state_(kInit), + refcount_(1) { + main_->SignalQueueDestroyed.connect(this, + &SignalThread::OnMainThreadDestroyed); + worker_.SetName("SignalThread", this); +} + +SignalThread::~SignalThread() { + ASSERT(refcount_ == 0); +} + +bool SignalThread::SetName(const std::string& name, const void* obj) { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + ASSERT(kInit == state_); + return worker_.SetName(name, obj); +} + +bool SignalThread::SetPriority(ThreadPriority priority) { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + ASSERT(kInit == state_); + return worker_.SetPriority(priority); +} + +void SignalThread::Start() { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + if (kInit == state_ || kComplete == state_) { + state_ = kRunning; + OnWorkStart(); + worker_.Start(); + } else { + ASSERT(false); + } +} + +void SignalThread::Destroy(bool wait) { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + if ((kInit == state_) || (kComplete == state_)) { + refcount_--; + } else if (kRunning == state_ || kReleasing == state_) { + state_ = kStopping; + // OnWorkStop() must follow Quit(), so that when the thread wakes up due to + // OWS(), ContinueWork() will return false. + worker_.Quit(); + OnWorkStop(); + if (wait) { + // Release the thread's lock so that it can return from ::Run. + cs_.Leave(); + worker_.Stop(); + cs_.Enter(); + refcount_--; + } + } else { + ASSERT(false); + } +} + +void SignalThread::Release() { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + if (kComplete == state_) { + refcount_--; + } else if (kRunning == state_) { + state_ = kReleasing; + } else { + // if (kInit == state_) use Destroy() + ASSERT(false); + } +} + +bool SignalThread::ContinueWork() { + EnterExit ee(this); + ASSERT(worker_.IsCurrent()); + return worker_.ProcessMessages(0); +} + +void SignalThread::OnMessage(Message *msg) { + EnterExit ee(this); + if (ST_MSG_WORKER_DONE == msg->message_id) { + ASSERT(main_->IsCurrent()); + OnWorkDone(); + bool do_delete = false; + if (kRunning == state_) { + state_ = kComplete; + } else { + do_delete = true; + } + if (kStopping != state_) { + // Before signaling that the work is done, make sure that the worker + // thread actually is done. We got here because DoWork() finished and + // Run() posted the ST_MSG_WORKER_DONE message. This means the worker + // thread is about to go away anyway, but sometimes it doesn't actually + // finish before SignalWorkDone is processed, and for a reusable + // SignalThread this makes an assert in thread.cc fire. + // + // Calling Stop() on the worker ensures that the OS thread that underlies + // the worker will finish, and will be set to NULL, enabling us to call + // Start() again. + worker_.Stop(); + SignalWorkDone(this); + } + if (do_delete) { + refcount_--; + } + } +} + +void SignalThread::Run() { + DoWork(); + { + EnterExit ee(this); + if (main_) { + main_->Post(this, ST_MSG_WORKER_DONE); + } + } +} + +void SignalThread::OnMainThreadDestroyed() { + EnterExit ee(this); + main_ = NULL; +} + +} // namespace rtc diff --git a/webrtc/base/signalthread.h b/webrtc/base/signalthread.h new file mode 100644 index 000000000..a97bda1af --- /dev/null +++ b/webrtc/base/signalthread.h @@ -0,0 +1,156 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SIGNALTHREAD_H_ +#define WEBRTC_BASE_SIGNALTHREAD_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// SignalThread - Base class for worker threads. The main thread should call +// Start() to begin work, and then follow one of these models: +// Normal: Wait for SignalWorkDone, and then call Release to destroy. +// Cancellation: Call Release(true), to abort the worker thread. +// Fire-and-forget: Call Release(false), which allows the thread to run to +// completion, and then self-destruct without further notification. +// Periodic tasks: Wait for SignalWorkDone, then eventually call Start() +// again to repeat the task. When the instance isn't needed anymore, +// call Release. DoWork, OnWorkStart and OnWorkStop are called again, +// on a new thread. +// The subclass should override DoWork() to perform the background task. By +// periodically calling ContinueWork(), it can check for cancellation. +// OnWorkStart and OnWorkDone can be overridden to do pre- or post-work +// tasks in the context of the main thread. +/////////////////////////////////////////////////////////////////////////////// + +class SignalThread + : public sigslot::has_slots<>, + protected MessageHandler { + public: + SignalThread(); + + // Context: Main Thread. Call before Start to change the worker's name. + bool SetName(const std::string& name, const void* obj); + + // Context: Main Thread. Call before Start to change the worker's priority. + bool SetPriority(ThreadPriority priority); + + // Context: Main Thread. Call to begin the worker thread. + void Start(); + + // Context: Main Thread. If the worker thread is not running, deletes the + // object immediately. Otherwise, asks the worker thread to abort processing, + // and schedules the object to be deleted once the worker exits. + // SignalWorkDone will not be signalled. If wait is true, does not return + // until the thread is deleted. + void Destroy(bool wait); + + // Context: Main Thread. If the worker thread is complete, deletes the + // object immediately. Otherwise, schedules the object to be deleted once + // the worker thread completes. SignalWorkDone will be signalled. + void Release(); + + // Context: Main Thread. Signalled when work is complete. + sigslot::signal1 SignalWorkDone; + + enum { ST_MSG_WORKER_DONE, ST_MSG_FIRST_AVAILABLE }; + + protected: + virtual ~SignalThread(); + + Thread* worker() { return &worker_; } + + // Context: Main Thread. Subclass should override to do pre-work setup. + virtual void OnWorkStart() { } + + // Context: Worker Thread. Subclass should override to do work. + virtual void DoWork() = 0; + + // Context: Worker Thread. Subclass should call periodically to + // dispatch messages and determine if the thread should terminate. + bool ContinueWork(); + + // Context: Worker Thread. Subclass should override when extra work is + // needed to abort the worker thread. + virtual void OnWorkStop() { } + + // Context: Main Thread. Subclass should override to do post-work cleanup. + virtual void OnWorkDone() { } + + // Context: Any Thread. If subclass overrides, be sure to call the base + // implementation. Do not use (message_id < ST_MSG_FIRST_AVAILABLE) + virtual void OnMessage(Message *msg); + + private: + enum State { + kInit, // Initialized, but not started + kRunning, // Started and doing work + kReleasing, // Same as running, but to be deleted when work is done + kComplete, // Work is done + kStopping, // Work is being interrupted + }; + + class Worker : public Thread { + public: + explicit Worker(SignalThread* parent) : parent_(parent) {} + virtual ~Worker() { Stop(); } + virtual void Run() { parent_->Run(); } + + private: + SignalThread* parent_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Worker); + }; + + class EnterExit { + public: + explicit EnterExit(SignalThread* t) : t_(t) { + t_->cs_.Enter(); + // If refcount_ is zero then the object has already been deleted and we + // will be double-deleting it in ~EnterExit()! (shouldn't happen) + ASSERT(t_->refcount_ != 0); + ++t_->refcount_; + } + ~EnterExit() { + bool d = (0 == --t_->refcount_); + t_->cs_.Leave(); + if (d) + delete t_; + } + + private: + SignalThread* t_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(EnterExit); + }; + + void Run(); + void OnMainThreadDestroyed(); + + Thread* main_; + Worker worker_; + CriticalSection cs_; + State state_; + int refcount_; + + DISALLOW_COPY_AND_ASSIGN(SignalThread); +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SIGNALTHREAD_H_ diff --git a/webrtc/base/signalthread_unittest.cc b/webrtc/base/signalthread_unittest.cc new file mode 100644 index 000000000..4d3e0402e --- /dev/null +++ b/webrtc/base/signalthread_unittest.cc @@ -0,0 +1,198 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/signalthread.h" +#include "webrtc/base/thread.h" + +using namespace rtc; + +class SignalThreadTest : public testing::Test, public sigslot::has_slots<> { + public: + class SlowSignalThread : public SignalThread { + public: + SlowSignalThread(SignalThreadTest* harness) : harness_(harness) { + } + + virtual ~SlowSignalThread() { + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + ++harness_->thread_deleted_; + } + + const SignalThreadTest* harness() { return harness_; } + + protected: + virtual void OnWorkStart() { + ASSERT_TRUE(harness_ != NULL); + ++harness_->thread_started_; + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + EXPECT_FALSE(worker()->started()); // not started yet + } + + virtual void OnWorkStop() { + ++harness_->thread_stopped_; + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + EXPECT_TRUE(worker()->started()); // not stopped yet + } + + virtual void OnWorkDone() { + ++harness_->thread_done_; + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + EXPECT_TRUE(worker()->started()); // not stopped yet + } + + virtual void DoWork() { + EXPECT_NE(harness_->main_thread_, Thread::Current()); + EXPECT_EQ(worker(), Thread::Current()); + Thread::Current()->socketserver()->Wait(250, false); + } + + private: + SignalThreadTest* harness_; + DISALLOW_EVIL_CONSTRUCTORS(SlowSignalThread); + }; + + void OnWorkComplete(rtc::SignalThread* thread) { + SlowSignalThread* t = static_cast(thread); + EXPECT_EQ(t->harness(), this); + EXPECT_EQ(main_thread_, Thread::Current()); + + ++thread_completed_; + if (!called_release_) { + thread->Release(); + } + } + + virtual void SetUp() { + main_thread_ = Thread::Current(); + thread_ = new SlowSignalThread(this); + thread_->SignalWorkDone.connect(this, &SignalThreadTest::OnWorkComplete); + called_release_ = false; + thread_started_ = 0; + thread_done_ = 0; + thread_completed_ = 0; + thread_stopped_ = 0; + thread_deleted_ = 0; + } + + virtual void TearDown() { + } + + Thread* main_thread_; + SlowSignalThread* thread_; + bool called_release_; + + int thread_started_; + int thread_done_; + int thread_completed_; + int thread_stopped_; + int thread_deleted_; +}; + +class OwnerThread : public Thread, public sigslot::has_slots<> { + public: + explicit OwnerThread(SignalThreadTest* harness) + : harness_(harness), + has_run_(false) { + } + + virtual ~OwnerThread() { + Stop(); + } + + virtual void Run() { + SignalThreadTest::SlowSignalThread* signal_thread = + new SignalThreadTest::SlowSignalThread(harness_); + signal_thread->SignalWorkDone.connect(this, &OwnerThread::OnWorkDone); + signal_thread->Start(); + Thread::Current()->socketserver()->Wait(100, false); + signal_thread->Release(); + // Delete |signal_thread|. + signal_thread->Destroy(true); + has_run_ = true; + } + + bool has_run() { return has_run_; } + void OnWorkDone(SignalThread* signal_thread) { + FAIL() << " This shouldn't get called."; + } + + private: + SignalThreadTest* harness_; + bool has_run_; + DISALLOW_EVIL_CONSTRUCTORS(OwnerThread); +}; + +// Test for when the main thread goes away while the +// signal thread is still working. This may happen +// when shutting down the process. +TEST_F(SignalThreadTest, OwnerThreadGoesAway) { + { + scoped_ptr owner(new OwnerThread(this)); + main_thread_ = owner.get(); + owner->Start(); + while (!owner->has_run()) { + Thread::Current()->socketserver()->Wait(10, false); + } + } + // At this point the main thread has gone away. + // Give the SignalThread a little time to do its callback, + // which will crash if the signal thread doesn't handle + // this situation well. + Thread::Current()->socketserver()->Wait(500, false); +} + +#define EXPECT_STATE(started, done, completed, stopped, deleted) \ + EXPECT_EQ(started, thread_started_); \ + EXPECT_EQ(done, thread_done_); \ + EXPECT_EQ(completed, thread_completed_); \ + EXPECT_EQ(stopped, thread_stopped_); \ + EXPECT_EQ(deleted, thread_deleted_); + +TEST_F(SignalThreadTest, ThreadFinishes) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::SleepMs(500); + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 1, 1, 0, 1); +} + +TEST_F(SignalThreadTest, ReleasedThreadFinishes) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + thread_->Release(); + called_release_ = true; + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::SleepMs(500); + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 1, 1, 0, 1); +} + +TEST_F(SignalThreadTest, DestroyedThreadCleansUp) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + thread_->Destroy(true); + EXPECT_STATE(1, 0, 0, 1, 1); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 0, 0, 1, 1); +} + +TEST_F(SignalThreadTest, DeferredDestroyedThreadCleansUp) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + thread_->Destroy(false); + EXPECT_STATE(1, 0, 0, 1, 0); + Thread::SleepMs(500); + EXPECT_STATE(1, 0, 0, 1, 0); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 1, 0, 1, 1); +} diff --git a/webrtc/base/sigslot.h b/webrtc/base/sigslot.h new file mode 100644 index 000000000..990d2efb7 --- /dev/null +++ b/webrtc/base/sigslot.h @@ -0,0 +1,2850 @@ +// sigslot.h: Signal/Slot classes +// +// Written by Sarah Thompson (sarah@telergy.com) 2002. +// +// License: Public domain. You are free to use this code however you like, with the proviso that +// the author takes on no responsibility or liability for any use. +// +// QUICK DOCUMENTATION +// +// (see also the full documentation at http://sigslot.sourceforge.net/) +// +// #define switches +// SIGSLOT_PURE_ISO - Define this to force ISO C++ compliance. This also disables +// all of the thread safety support on platforms where it is +// available. +// +// SIGSLOT_USE_POSIX_THREADS - Force use of Posix threads when using a C++ compiler other than +// gcc on a platform that supports Posix threads. (When using gcc, +// this is the default - use SIGSLOT_PURE_ISO to disable this if +// necessary) +// +// SIGSLOT_DEFAULT_MT_POLICY - Where thread support is enabled, this defaults to multi_threaded_global. +// Otherwise, the default is single_threaded. #define this yourself to +// override the default. In pure ISO mode, anything other than +// single_threaded will cause a compiler error. +// +// PLATFORM NOTES +// +// Win32 - On Win32, the WEBRTC_WIN symbol must be #defined. Most mainstream +// compilers do this by default, but you may need to define it +// yourself if your build environment is less standard. This causes +// the Win32 thread support to be compiled in and used automatically. +// +// Unix/Linux/BSD, etc. - If you're using gcc, it is assumed that you have Posix threads +// available, so they are used automatically. You can override this +// (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using +// something other than gcc but still want to use Posix threads, you +// need to #define SIGSLOT_USE_POSIX_THREADS. +// +// ISO C++ - If none of the supported platforms are detected, or if +// SIGSLOT_PURE_ISO is defined, all multithreading support is turned off, +// along with any code that might cause a pure ISO C++ environment to +// complain. Before you ask, gcc -ansi -pedantic won't compile this +// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of +// errors that aren't really there. If you feel like investigating this, +// please contact the author. +// +// +// THREADING MODES +// +// single_threaded - Your program is assumed to be single threaded from the point of view +// of signal/slot usage (i.e. all objects using signals and slots are +// created and destroyed from a single thread). Behaviour if objects are +// destroyed concurrently is undefined (i.e. you'll get the occasional +// segmentation fault/memory exception). +// +// multi_threaded_global - Your program is assumed to be multi threaded. Objects using signals and +// slots can be safely created and destroyed from any thread, even when +// connections exist. In multi_threaded_global mode, this is achieved by a +// single global mutex (actually a critical section on Windows because they +// are faster). This option uses less OS resources, but results in more +// opportunities for contention, possibly resulting in more context switches +// than are strictly necessary. +// +// multi_threaded_local - Behaviour in this mode is essentially the same as multi_threaded_global, +// except that each signal, and each object that inherits has_slots, all +// have their own mutex/critical section. In practice, this means that +// mutex collisions (and hence context switches) only happen if they are +// absolutely essential. However, on some platforms, creating a lot of +// mutexes can slow down the whole OS, so use this option with care. +// +// USING THE LIBRARY +// +// See the full documentation at http://sigslot.sourceforge.net/ +// +// +// Libjingle specific: +// This file has been modified such that has_slots and signalx do not have to be +// using the same threading requirements. E.g. it is possible to connect a +// has_slots and signal0 or +// has_slots and signal0. +// If has_slots is single threaded the user must ensure that it is not trying +// to connect or disconnect to signalx concurrently or data race may occur. +// If signalx is single threaded the user must ensure that disconnect, connect +// or signal is not happening concurrently or data race may occur. + +#ifndef WEBRTC_BASE_SIGSLOT_H__ +#define WEBRTC_BASE_SIGSLOT_H__ + +#include +#include +#include + +// On our copy of sigslot.h, we set single threading as default. +#define SIGSLOT_DEFAULT_MT_POLICY single_threaded + +#if defined(SIGSLOT_PURE_ISO) || (!defined(WEBRTC_WIN) && !defined(__GNUG__) && !defined(SIGSLOT_USE_POSIX_THREADS)) +# define _SIGSLOT_SINGLE_THREADED +#elif defined(WEBRTC_WIN) +# define _SIGSLOT_HAS_WIN32_THREADS +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# include "webrtc/base/win32.h" +#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS) +# define _SIGSLOT_HAS_POSIX_THREADS +# include +#else +# define _SIGSLOT_SINGLE_THREADED +#endif + +#ifndef SIGSLOT_DEFAULT_MT_POLICY +# ifdef _SIGSLOT_SINGLE_THREADED +# define SIGSLOT_DEFAULT_MT_POLICY single_threaded +# else +# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local +# endif +#endif + +// TODO: change this namespace to rtc? +namespace sigslot { + + class single_threaded + { + public: + single_threaded() + { + ; + } + + virtual ~single_threaded() + { + ; + } + + virtual void lock() + { + ; + } + + virtual void unlock() + { + ; + } + }; + +#ifdef _SIGSLOT_HAS_WIN32_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + static bool isinitialised = false; + + if(!isinitialised) + { + InitializeCriticalSection(get_critsec()); + isinitialised = true; + } + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + EnterCriticalSection(get_critsec()); + } + + virtual void unlock() + { + LeaveCriticalSection(get_critsec()); + } + + private: + CRITICAL_SECTION* get_critsec() + { + static CRITICAL_SECTION g_critsec; + return &g_critsec; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + InitializeCriticalSection(&m_critsec); + } + + multi_threaded_local(const multi_threaded_local&) + { + InitializeCriticalSection(&m_critsec); + } + + virtual ~multi_threaded_local() + { + DeleteCriticalSection(&m_critsec); + } + + virtual void lock() + { + EnterCriticalSection(&m_critsec); + } + + virtual void unlock() + { + LeaveCriticalSection(&m_critsec); + } + + private: + CRITICAL_SECTION m_critsec; + }; +#endif // _SIGSLOT_HAS_WIN32_THREADS + +#ifdef _SIGSLOT_HAS_POSIX_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + pthread_mutex_init(get_mutex(), NULL); + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + pthread_mutex_lock(get_mutex()); + } + + virtual void unlock() + { + pthread_mutex_unlock(get_mutex()); + } + + private: + pthread_mutex_t* get_mutex() + { + static pthread_mutex_t g_mutex; + return &g_mutex; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + pthread_mutex_init(&m_mutex, NULL); + } + + multi_threaded_local(const multi_threaded_local&) + { + pthread_mutex_init(&m_mutex, NULL); + } + + virtual ~multi_threaded_local() + { + pthread_mutex_destroy(&m_mutex); + } + + virtual void lock() + { + pthread_mutex_lock(&m_mutex); + } + + virtual void unlock() + { + pthread_mutex_unlock(&m_mutex); + } + + private: + pthread_mutex_t m_mutex; + }; +#endif // _SIGSLOT_HAS_POSIX_THREADS + + template + class lock_block + { + public: + mt_policy *m_mutex; + + lock_block(mt_policy *mtx) + : m_mutex(mtx) + { + m_mutex->lock(); + } + + ~lock_block() + { + m_mutex->unlock(); + } + }; + + class has_slots_interface; + + template + class _connection_base0 + { + public: + virtual ~_connection_base0() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit() = 0; + virtual _connection_base0* clone() = 0; + virtual _connection_base0* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base1 + { + public: + virtual ~_connection_base1() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type) = 0; + virtual _connection_base1* clone() = 0; + virtual _connection_base1* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base2 + { + public: + virtual ~_connection_base2() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type) = 0; + virtual _connection_base2* clone() = 0; + virtual _connection_base2* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base3 + { + public: + virtual ~_connection_base3() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type) = 0; + virtual _connection_base3* clone() = 0; + virtual _connection_base3* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base4 + { + public: + virtual ~_connection_base4() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0; + virtual _connection_base4* clone() = 0; + virtual _connection_base4* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base5 + { + public: + virtual ~_connection_base5() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type) = 0; + virtual _connection_base5* clone() = 0; + virtual _connection_base5* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base6 + { + public: + virtual ~_connection_base6() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type) = 0; + virtual _connection_base6* clone() = 0; + virtual _connection_base6* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base7 + { + public: + virtual ~_connection_base7() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type) = 0; + virtual _connection_base7* clone() = 0; + virtual _connection_base7* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base8 + { + public: + virtual ~_connection_base8() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type, arg8_type) = 0; + virtual _connection_base8* clone() = 0; + virtual _connection_base8* duplicate(has_slots_interface* pnewdest) = 0; + }; + + class _signal_base_interface + { + public: + virtual void slot_disconnect(has_slots_interface* pslot) = 0; + virtual void slot_duplicate(const has_slots_interface* poldslot, has_slots_interface* pnewslot) = 0; + }; + + template + class _signal_base : public _signal_base_interface, public mt_policy + { + }; + + class has_slots_interface + { + public: + has_slots_interface() + { + ; + } + + virtual void signal_connect(_signal_base_interface* sender) = 0; + + virtual void signal_disconnect(_signal_base_interface* sender) = 0; + + virtual ~has_slots_interface() + { + } + + virtual void disconnect_all() = 0; + }; + + template + class has_slots : public has_slots_interface, public mt_policy + { + private: + typedef std::set<_signal_base_interface*> sender_set; + typedef sender_set::const_iterator const_iterator; + + public: + has_slots() + { + ; + } + + has_slots(const has_slots& hs) + { + lock_block lock(this); + const_iterator it = hs.m_senders.begin(); + const_iterator itEnd = hs.m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_duplicate(&hs, this); + m_senders.insert(*it); + ++it; + } + } + + void signal_connect(_signal_base_interface* sender) + { + lock_block lock(this); + m_senders.insert(sender); + } + + void signal_disconnect(_signal_base_interface* sender) + { + lock_block lock(this); + m_senders.erase(sender); + } + + virtual ~has_slots() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_senders.begin(); + const_iterator itEnd = m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_disconnect(this); + ++it; + } + + m_senders.erase(m_senders.begin(), m_senders.end()); + } + + private: + sender_set m_senders; + }; + + template + class _signal_base0 : public _signal_base + { + public: + typedef std::list<_connection_base0 *> connections_list; + + _signal_base0() + { + ; + } + + _signal_base0(const _signal_base0& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + ~_signal_base0() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base1 : public _signal_base + { + public: + typedef std::list<_connection_base1 *> connections_list; + + _signal_base1() + { + ; + } + + _signal_base1(const _signal_base1& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base1() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base2 : public _signal_base + { + public: + typedef std::list<_connection_base2 *> + connections_list; + + _signal_base2() + { + ; + } + + _signal_base2(const _signal_base2& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base2() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base3 : public _signal_base + { + public: + typedef std::list<_connection_base3 *> + connections_list; + + _signal_base3() + { + ; + } + + _signal_base3(const _signal_base3& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base3() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base4 : public _signal_base + { + public: + typedef std::list<_connection_base4 *> connections_list; + + _signal_base4() + { + ; + } + + _signal_base4(const _signal_base4& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base4() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base5 : public _signal_base + { + public: + typedef std::list<_connection_base5 *> connections_list; + + _signal_base5() + { + ; + } + + _signal_base5(const _signal_base5& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base5() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base6 : public _signal_base + { + public: + typedef std::list<_connection_base6 *> connections_list; + + _signal_base6() + { + ; + } + + _signal_base6(const _signal_base6& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base6() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base7 : public _signal_base + { + public: + typedef std::list<_connection_base7 *> connections_list; + + _signal_base7() + { + ; + } + + _signal_base7(const _signal_base7& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base7() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base8 : public _signal_base + { + public: + typedef std::list<_connection_base8 *> + connections_list; + + _signal_base8() + { + ; + } + + _signal_base8(const _signal_base8& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base8() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + + template + class _connection0 : public _connection_base0 + { + public: + _connection0() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection0(dest_type* pobject, void (dest_type::*pmemfun)()) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection0() + { + } + + virtual _connection_base0* clone() + { + return new _connection0(*this); + } + + virtual _connection_base0* duplicate(has_slots_interface* pnewdest) + { + return new _connection0((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit() + { + (m_pobject->*m_pmemfun)(); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(); + }; + + template + class _connection1 : public _connection_base1 + { + public: + _connection1() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection1() + { + } + + virtual _connection_base1* clone() + { + return new _connection1(*this); + } + + virtual _connection_base1* duplicate(has_slots_interface* pnewdest) + { + return new _connection1((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1) + { + (m_pobject->*m_pmemfun)(a1); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type); + }; + + template + class _connection2 : public _connection_base2 + { + public: + _connection2() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection2() + { + } + + virtual _connection_base2* clone() + { + return new _connection2(*this); + } + + virtual _connection_base2* duplicate(has_slots_interface* pnewdest) + { + return new _connection2((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2) + { + (m_pobject->*m_pmemfun)(a1, a2); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type); + }; + + template + class _connection3 : public _connection_base3 + { + public: + _connection3() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection3() + { + } + + virtual _connection_base3* clone() + { + return new _connection3(*this); + } + + virtual _connection_base3* duplicate(has_slots_interface* pnewdest) + { + return new _connection3((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + (m_pobject->*m_pmemfun)(a1, a2, a3); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type); + }; + + template + class _connection4 : public _connection_base4 + { + public: + _connection4() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection4() + { + } + + virtual _connection_base4* clone() + { + return new _connection4(*this); + } + + virtual _connection_base4* duplicate(has_slots_interface* pnewdest) + { + return new _connection4((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, + arg4_type a4) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, + arg4_type); + }; + + template + class _connection5 : public _connection_base5 + { + public: + _connection5() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection5() + { + } + + virtual _connection_base5* clone() + { + return new _connection5(*this); + } + + virtual _connection_base5* duplicate(has_slots_interface* pnewdest) + { + return new _connection5((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type); + }; + + template + class _connection6 : public _connection_base6 + { + public: + _connection6() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection6() + { + } + + virtual _connection_base6* clone() + { + return new _connection6(*this); + } + + virtual _connection_base6* duplicate(has_slots_interface* pnewdest) + { + return new _connection6((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type); + }; + + template + class _connection7 : public _connection_base7 + { + public: + _connection7() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection7() + { + } + + virtual _connection_base7* clone() + { + return new _connection7(*this); + } + + virtual _connection_base7* duplicate(has_slots_interface* pnewdest) + { + return new _connection7((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type); + }; + + template + class _connection8 : public _connection_base8 + { + public: + _connection8() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection8() + { + } + + virtual _connection_base8* clone() + { + return new _connection8(*this); + } + + virtual _connection_base8* duplicate(has_slots_interface* pnewdest) + { + return new _connection8((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type, arg8_type); + }; + + template + class signal0 : public _signal_base0 + { + public: + typedef _signal_base0 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal0() + { + ; + } + + signal0(const signal0& s) + : _signal_base0(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)()) + { + lock_block lock(this); + _connection0* conn = + new _connection0(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + + void operator()() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + }; + + template + class signal1 : public _signal_base1 + { + public: + typedef _signal_base1 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal1() + { + ; + } + + signal1(const signal1& s) + : _signal_base1(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type)) + { + lock_block lock(this); + _connection1* conn = + new _connection1(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + + void operator()(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + }; + + template + class signal2 : public _signal_base2 + { + public: + typedef _signal_base2 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal2() + { + ; + } + + signal2(const signal2& s) + : _signal_base2(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type)) + { + lock_block lock(this); + _connection2* conn = new + _connection2(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + }; + + template + class signal3 : public _signal_base3 + { + public: + typedef _signal_base3 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal3() + { + ; + } + + signal3(const signal3& s) + : _signal_base3(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + lock_block lock(this); + _connection3* conn = + new _connection3(pclass, + pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + }; + + template + class signal4 : public _signal_base4 + { + public: + typedef _signal_base4 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal4() + { + ; + } + + signal4(const signal4& s) + : _signal_base4(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + lock_block lock(this); + _connection4* + conn = new _connection4(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + }; + + template + class signal5 : public _signal_base5 + { + public: + typedef _signal_base5 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal5() + { + ; + } + + signal5(const signal5& s) + : _signal_base5(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + lock_block lock(this); + _connection5* conn = new _connection5(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + }; + + + template + class signal6 : public _signal_base6 + { + public: + typedef _signal_base6 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal6() + { + ; + } + + signal6(const signal6& s) + : _signal_base6(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + lock_block lock(this); + _connection6* conn = + new _connection6(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + }; + + template + class signal7 : public _signal_base7 + { + public: + typedef _signal_base7 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal7() + { + ; + } + + signal7(const signal7& s) + : _signal_base7(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type)) + { + lock_block lock(this); + _connection7* conn = + new _connection7(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + }; + + template + class signal8 : public _signal_base8 + { + public: + typedef _signal_base8 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal8() + { + ; + } + + signal8(const signal8& s) + : _signal_base8(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + lock_block lock(this); + _connection8* conn = + new _connection8(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + }; + +}; // namespace sigslot + +#endif // WEBRTC_BASE_SIGSLOT_H__ diff --git a/webrtc/base/sigslot_unittest.cc b/webrtc/base/sigslot_unittest.cc new file mode 100644 index 000000000..4d3041d13 --- /dev/null +++ b/webrtc/base/sigslot_unittest.cc @@ -0,0 +1,250 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/sigslot.h" + +#include "webrtc/base/gunit.h" + +// This function, when passed a has_slots or signalx, will break the build if +// its threading requirement is not single threaded +static bool TemplateIsST(const sigslot::single_threaded* p) { + return true; +} +// This function, when passed a has_slots or signalx, will break the build if +// its threading requirement is not multi threaded +static bool TemplateIsMT(const sigslot::multi_threaded_local* p) { + return true; +} + +class SigslotDefault : public testing::Test, public sigslot::has_slots<> { + protected: + sigslot::signal0<> signal_; +}; + +template +class SigslotReceiver : public sigslot::has_slots { + public: + SigslotReceiver() : signal_(NULL), signal_count_(0) { + } + ~SigslotReceiver() { + } + + void Connect(sigslot::signal0* signal) { + if (!signal) return; + Disconnect(); + signal_ = signal; + signal->connect(this, + &SigslotReceiver::OnSignal); + } + void Disconnect() { + if (!signal_) return; + signal_->disconnect(this); + signal_ = NULL; + } + void OnSignal() { + ++signal_count_; + } + int signal_count() { return signal_count_; } + + private: + sigslot::signal0* signal_; + int signal_count_; +}; + +template +class SigslotSlotTest : public testing::Test { + protected: + SigslotSlotTest() { + mt_signal_policy mt_policy; + TemplateIsMT(&mt_policy); + } + + virtual void SetUp() { + Connect(); + } + virtual void TearDown() { + Disconnect(); + } + + void Disconnect() { + st_receiver_.Disconnect(); + mt_receiver_.Disconnect(); + } + + void Connect() { + st_receiver_.Connect(&SignalSTLoopback); + mt_receiver_.Connect(&SignalMTLoopback); + } + + int st_loop_back_count() { return st_receiver_.signal_count(); } + int mt_loop_back_count() { return mt_receiver_.signal_count(); } + + sigslot::signal0<> SignalSTLoopback; + SigslotReceiver st_receiver_; + sigslot::signal0 SignalMTLoopback; + SigslotReceiver mt_receiver_; +}; + +typedef SigslotSlotTest<> SigslotSTSlotTest; +typedef SigslotSlotTest SigslotMTSlotTest; + +class multi_threaded_local_fake : public sigslot::multi_threaded_local { + public: + multi_threaded_local_fake() : lock_count_(0), unlock_count_(0) { + } + + virtual void lock() { + ++lock_count_; + } + virtual void unlock() { + ++unlock_count_; + } + + int lock_count() { return lock_count_; } + + bool InCriticalSection() { return lock_count_ != unlock_count_; } + + protected: + int lock_count_; + int unlock_count_; +}; + +typedef SigslotSlotTest SigslotMTLockBase; + +class SigslotMTLockTest : public SigslotMTLockBase { + protected: + SigslotMTLockTest() {} + + virtual void SetUp() { + EXPECT_EQ(0, SlotLockCount()); + SigslotMTLockBase::SetUp(); + // Connects to two signals (ST and MT). However, + // SlotLockCount() only gets the count for the + // MT signal (there are two separate SigslotReceiver which + // keep track of their own count). + EXPECT_EQ(1, SlotLockCount()); + } + virtual void TearDown() { + const int previous_lock_count = SlotLockCount(); + SigslotMTLockBase::TearDown(); + // Disconnects from two signals. Note analogous to SetUp(). + EXPECT_EQ(previous_lock_count + 1, SlotLockCount()); + } + + int SlotLockCount() { return mt_receiver_.lock_count(); } + void Signal() { SignalMTLoopback(); } + int SignalLockCount() { return SignalMTLoopback.lock_count(); } + int signal_count() { return mt_loop_back_count(); } + bool InCriticalSection() { return SignalMTLoopback.InCriticalSection(); } +}; + +// This test will always succeed. However, if the default template instantiation +// changes from single threaded to multi threaded it will break the build here. +TEST_F(SigslotDefault, DefaultIsST) { + EXPECT_TRUE(TemplateIsST(this)); + EXPECT_TRUE(TemplateIsST(&signal_)); +} + +// ST slot, ST signal +TEST_F(SigslotSTSlotTest, STLoopbackTest) { + SignalSTLoopback(); + EXPECT_EQ(1, st_loop_back_count()); + EXPECT_EQ(0, mt_loop_back_count()); +} + +// ST slot, MT signal +TEST_F(SigslotSTSlotTest, MTLoopbackTest) { + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(0, st_loop_back_count()); +} + +// ST slot, both ST and MT (separate) signal +TEST_F(SigslotSTSlotTest, AllLoopbackTest) { + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(1, st_loop_back_count()); +} + +TEST_F(SigslotSTSlotTest, Reconnect) { + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(1, st_loop_back_count()); + Disconnect(); + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(1, st_loop_back_count()); + Connect(); + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(2, mt_loop_back_count()); + EXPECT_EQ(2, st_loop_back_count()); +} + +// MT slot, ST signal +TEST_F(SigslotMTSlotTest, STLoopbackTest) { + SignalSTLoopback(); + EXPECT_EQ(1, st_loop_back_count()); + EXPECT_EQ(0, mt_loop_back_count()); +} + +// MT slot, MT signal +TEST_F(SigslotMTSlotTest, MTLoopbackTest) { + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(0, st_loop_back_count()); +} + +// MT slot, both ST and MT (separate) signal +TEST_F(SigslotMTSlotTest, AllLoopbackTest) { + SignalMTLoopback(); + SignalSTLoopback(); + EXPECT_EQ(1, st_loop_back_count()); + EXPECT_EQ(1, mt_loop_back_count()); +} + +// Test that locks are acquired and released correctly. +TEST_F(SigslotMTLockTest, LockSanity) { + const int lock_count = SignalLockCount(); + Signal(); + EXPECT_FALSE(InCriticalSection()); + EXPECT_EQ(lock_count + 1, SignalLockCount()); + EXPECT_EQ(1, signal_count()); +} + +// Destroy signal and slot in different orders. +TEST(DestructionOrder, SignalFirst) { + sigslot::signal0<>* signal = new sigslot::signal0<>; + SigslotReceiver<>* receiver = new SigslotReceiver<>(); + receiver->Connect(signal); + (*signal)(); + EXPECT_EQ(1, receiver->signal_count()); + delete signal; + delete receiver; +} + +TEST(DestructionOrder, SlotFirst) { + sigslot::signal0<>* signal = new sigslot::signal0<>; + SigslotReceiver<>* receiver = new SigslotReceiver<>(); + receiver->Connect(signal); + (*signal)(); + EXPECT_EQ(1, receiver->signal_count()); + + delete receiver; + (*signal)(); + delete signal; +} diff --git a/webrtc/base/sigslotrepeater.h b/webrtc/base/sigslotrepeater.h new file mode 100644 index 000000000..d1c891e02 --- /dev/null +++ b/webrtc/base/sigslotrepeater.h @@ -0,0 +1,94 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SIGSLOTREPEATER_H__ +#define WEBRTC_BASE_SIGSLOTREPEATER_H__ + +// repeaters are both signals and slots, which are designed as intermediate +// pass-throughs for signals and slots which don't know about each other (for +// modularity or encapsulation). This eliminates the need to declare a signal +// handler whose sole purpose is to fire another signal. The repeater connects +// to the originating signal using the 'repeat' method. When the repeated +// signal fires, the repeater will also fire. + +#include "webrtc/base/sigslot.h" + +namespace sigslot { + + template + class repeater0 : public signal0, + public has_slots + { + public: + typedef signal0 base_type; + typedef repeater0 this_type; + + repeater0() { } + repeater0(const this_type& s) : base_type(s) { } + + void reemit() { signal0::emit(); } + void repeat(base_type &s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + + template + class repeater1 : public signal1, + public has_slots + { + public: + typedef signal1 base_type; + typedef repeater1 this_type; + + repeater1() { } + repeater1(const this_type& s) : base_type(s) { } + + void reemit(arg1_type a1) { signal1::emit(a1); } + void repeat(base_type& s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + + template + class repeater2 : public signal2, + public has_slots + { + public: + typedef signal2 base_type; + typedef repeater2 this_type; + + repeater2() { } + repeater2(const this_type& s) : base_type(s) { } + + void reemit(arg1_type a1, arg2_type a2) { signal2::emit(a1,a2); } + void repeat(base_type& s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + + template + class repeater3 : public signal3, + public has_slots + { + public: + typedef signal3 base_type; + typedef repeater3 this_type; + + repeater3() { } + repeater3(const this_type& s) : base_type(s) { } + + void reemit(arg1_type a1, arg2_type a2, arg3_type a3) { + signal3::emit(a1,a2,a3); + } + void repeat(base_type& s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + +} // namespace sigslot + +#endif // WEBRTC_BASE_SIGSLOTREPEATER_H__ diff --git a/webrtc/base/socket.h b/webrtc/base/socket.h new file mode 100644 index 000000000..725bd45d1 --- /dev/null +++ b/webrtc/base/socket.h @@ -0,0 +1,188 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKET_H__ +#define WEBRTC_BASE_SOCKET_H__ + +#include + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#include +#define SOCKET_EACCES EACCES +#endif + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/socketaddress.h" + +// Rather than converting errors into a private namespace, +// Reuse the POSIX socket api errors. Note this depends on +// Win32 compatibility. + +#if defined(WEBRTC_WIN) +#undef EWOULDBLOCK // Remove errno.h's definition for each macro below. +#define EWOULDBLOCK WSAEWOULDBLOCK +#undef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#undef EALREADY +#define EALREADY WSAEALREADY +#undef ENOTSOCK +#define ENOTSOCK WSAENOTSOCK +#undef EDESTADDRREQ +#define EDESTADDRREQ WSAEDESTADDRREQ +#undef EMSGSIZE +#define EMSGSIZE WSAEMSGSIZE +#undef EPROTOTYPE +#define EPROTOTYPE WSAEPROTOTYPE +#undef ENOPROTOOPT +#define ENOPROTOOPT WSAENOPROTOOPT +#undef EPROTONOSUPPORT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#undef ESOCKTNOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#undef EOPNOTSUPP +#define EOPNOTSUPP WSAEOPNOTSUPP +#undef EPFNOSUPPORT +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#undef EAFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#undef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE +#undef EADDRNOTAVAIL +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#undef ENETDOWN +#define ENETDOWN WSAENETDOWN +#undef ENETUNREACH +#define ENETUNREACH WSAENETUNREACH +#undef ENETRESET +#define ENETRESET WSAENETRESET +#undef ECONNABORTED +#define ECONNABORTED WSAECONNABORTED +#undef ECONNRESET +#define ECONNRESET WSAECONNRESET +#undef ENOBUFS +#define ENOBUFS WSAENOBUFS +#undef EISCONN +#define EISCONN WSAEISCONN +#undef ENOTCONN +#define ENOTCONN WSAENOTCONN +#undef ESHUTDOWN +#define ESHUTDOWN WSAESHUTDOWN +#undef ETOOMANYREFS +#define ETOOMANYREFS WSAETOOMANYREFS +#undef ETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT +#undef ECONNREFUSED +#define ECONNREFUSED WSAECONNREFUSED +#undef ELOOP +#define ELOOP WSAELOOP +#undef ENAMETOOLONG +#define ENAMETOOLONG WSAENAMETOOLONG +#undef EHOSTDOWN +#define EHOSTDOWN WSAEHOSTDOWN +#undef EHOSTUNREACH +#define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENOTEMPTY +#define ENOTEMPTY WSAENOTEMPTY +#undef EPROCLIM +#define EPROCLIM WSAEPROCLIM +#undef EUSERS +#define EUSERS WSAEUSERS +#undef EDQUOT +#define EDQUOT WSAEDQUOT +#undef ESTALE +#define ESTALE WSAESTALE +#undef EREMOTE +#define EREMOTE WSAEREMOTE +#undef EACCES +#define SOCKET_EACCES WSAEACCES +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) +#define closesocket(s) close(s) +#endif // WEBRTC_POSIX + +namespace rtc { + +inline bool IsBlockingError(int e) { + return (e == EWOULDBLOCK) || (e == EAGAIN) || (e == EINPROGRESS); +} + +// General interface for the socket implementations of various networks. The +// methods match those of normal UNIX sockets very closely. +class Socket { + public: + virtual ~Socket() {} + + // Returns the address to which the socket is bound. If the socket is not + // bound, then the any-address is returned. + virtual SocketAddress GetLocalAddress() const = 0; + + // Returns the address to which the socket is connected. If the socket is + // not connected, then the any-address is returned. + virtual SocketAddress GetRemoteAddress() const = 0; + + virtual int Bind(const SocketAddress& addr) = 0; + virtual int Connect(const SocketAddress& addr) = 0; + virtual int Send(const void *pv, size_t cb) = 0; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0; + virtual int Recv(void *pv, size_t cb) = 0; + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) = 0; + virtual int Listen(int backlog) = 0; + virtual Socket *Accept(SocketAddress *paddr) = 0; + virtual int Close() = 0; + virtual int GetError() const = 0; + virtual void SetError(int error) = 0; + inline bool IsBlocking() const { return IsBlockingError(GetError()); } + + enum ConnState { + CS_CLOSED, + CS_CONNECTING, + CS_CONNECTED + }; + virtual ConnState GetState() const = 0; + + // Fills in the given uint16 with the current estimate of the MTU along the + // path to the address to which this socket is connected. NOTE: This method + // can block for up to 10 seconds on Windows. + virtual int EstimateMTU(uint16* mtu) = 0; + + enum Option { + OPT_DONTFRAGMENT, + OPT_RCVBUF, // receive buffer size + OPT_SNDBUF, // send buffer size + OPT_NODELAY, // whether Nagle algorithm is enabled + OPT_IPV6_V6ONLY, // Whether the socket is IPv6 only. + OPT_DSCP, // DSCP code + OPT_RTP_SENDTIME_EXTN_ID, // This is a non-traditional socket option param. + // This is specific to libjingle and will be used + // if SendTime option is needed at socket level. + }; + virtual int GetOption(Option opt, int* value) = 0; + virtual int SetOption(Option opt, int value) = 0; + + protected: + Socket() {} + + private: + DISALLOW_EVIL_CONSTRUCTORS(Socket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKET_H__ diff --git a/webrtc/base/socket_unittest.cc b/webrtc/base/socket_unittest.cc new file mode 100644 index 000000000..6104eda4e --- /dev/null +++ b/webrtc/base/socket_unittest.cc @@ -0,0 +1,1012 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/socket_unittest.h" + +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#define MAYBE_SKIP_IPV6 \ + if (!HasIPv6Enabled()) { \ + LOG(LS_INFO) << "No IPv6... skipping"; \ + return; \ + } + + +void SocketTest::TestConnectIPv4() { + ConnectInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectIPv6() { + MAYBE_SKIP_IPV6; + ConnectInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWithDnsLookupIPv4() { + ConnectWithDnsLookupInternal(kIPv4Loopback, "localhost"); +} + +void SocketTest::TestConnectWithDnsLookupIPv6() { + // TODO: Enable this when DNS resolution supports IPv6. + LOG(LS_INFO) << "Skipping IPv6 DNS test"; + // ConnectWithDnsLookupInternal(kIPv6Loopback, "localhost6"); +} + +void SocketTest::TestConnectFailIPv4() { + ConnectFailInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectFailIPv6() { + MAYBE_SKIP_IPV6; + ConnectFailInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWithDnsLookupFailIPv4() { + ConnectWithDnsLookupFailInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectWithDnsLookupFailIPv6() { + MAYBE_SKIP_IPV6; + ConnectWithDnsLookupFailInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWithClosedSocketIPv4() { + ConnectWithClosedSocketInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectWithClosedSocketIPv6() { + MAYBE_SKIP_IPV6; + ConnectWithClosedSocketInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWhileNotClosedIPv4() { + ConnectWhileNotClosedInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectWhileNotClosedIPv6() { + MAYBE_SKIP_IPV6; + ConnectWhileNotClosedInternal(kIPv6Loopback); +} + +void SocketTest::TestServerCloseDuringConnectIPv4() { + ServerCloseDuringConnectInternal(kIPv4Loopback); +} + +void SocketTest::TestServerCloseDuringConnectIPv6() { + MAYBE_SKIP_IPV6; + ServerCloseDuringConnectInternal(kIPv6Loopback); +} + +void SocketTest::TestClientCloseDuringConnectIPv4() { + ClientCloseDuringConnectInternal(kIPv4Loopback); +} + +void SocketTest::TestClientCloseDuringConnectIPv6() { + MAYBE_SKIP_IPV6; + ClientCloseDuringConnectInternal(kIPv6Loopback); +} + +void SocketTest::TestServerCloseIPv4() { + ServerCloseInternal(kIPv4Loopback); +} + +void SocketTest::TestServerCloseIPv6() { + MAYBE_SKIP_IPV6; + ServerCloseInternal(kIPv6Loopback); +} + +void SocketTest::TestCloseInClosedCallbackIPv4() { + CloseInClosedCallbackInternal(kIPv4Loopback); +} + +void SocketTest::TestCloseInClosedCallbackIPv6() { + MAYBE_SKIP_IPV6; + CloseInClosedCallbackInternal(kIPv6Loopback); +} + +void SocketTest::TestSocketServerWaitIPv4() { + SocketServerWaitInternal(kIPv4Loopback); +} + +void SocketTest::TestSocketServerWaitIPv6() { + MAYBE_SKIP_IPV6; + SocketServerWaitInternal(kIPv6Loopback); +} + +void SocketTest::TestTcpIPv4() { + TcpInternal(kIPv4Loopback); +} + +void SocketTest::TestTcpIPv6() { + MAYBE_SKIP_IPV6; + TcpInternal(kIPv6Loopback); +} + +void SocketTest::TestSingleFlowControlCallbackIPv4() { + SingleFlowControlCallbackInternal(kIPv4Loopback); +} + +void SocketTest::TestSingleFlowControlCallbackIPv6() { + MAYBE_SKIP_IPV6; + SingleFlowControlCallbackInternal(kIPv6Loopback); +} + +void SocketTest::TestUdpIPv4() { + UdpInternal(kIPv4Loopback); +} + +void SocketTest::TestUdpIPv6() { + MAYBE_SKIP_IPV6; + UdpInternal(kIPv6Loopback); +} + +void SocketTest::TestUdpReadyToSendIPv4() { +#if !defined(WEBRTC_MAC) + // TODO(ronghuawu): Enable this test on mac/ios. + UdpReadyToSend(kIPv4Loopback); +#endif +} + +void SocketTest::TestUdpReadyToSendIPv6() { +#if defined(WEBRTC_WIN) + // TODO(ronghuawu): Enable this test (currently flakey) on mac and linux. + MAYBE_SKIP_IPV6; + UdpReadyToSend(kIPv6Loopback); +#endif +} + +void SocketTest::TestGetSetOptionsIPv4() { + GetSetOptionsInternal(kIPv4Loopback); +} + +void SocketTest::TestGetSetOptionsIPv6() { + MAYBE_SKIP_IPV6; + GetSetOptionsInternal(kIPv6Loopback); +} + +// For unbound sockets, GetLocalAddress / GetRemoteAddress return AF_UNSPEC +// values on Windows, but an empty address of the same family on Linux/MacOS X. +bool IsUnspecOrEmptyIP(const IPAddress& address) { +#if !defined(WEBRTC_WIN) + return IPIsAny(address); +#else + return address.family() == AF_UNSPEC; +#endif +} + +void SocketTest::ConnectInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client(ss_->CreateAsyncSocket(loopback.family(), + SOCK_STREAM)); + sink.Monitor(client.get()); + EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState()); + EXPECT_PRED1(IsUnspecOrEmptyIP, client->GetLocalAddress().ipaddr()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(AsyncSocket::CS_CONNECTING, server->GetState()); + + // Ensure no pending server connections, since we haven't done anything yet. + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_TRUE(accept_addr.IsNil()); + + // Attempt connect to listening socket. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + EXPECT_FALSE(client->GetLocalAddress().IsNil()); + EXPECT_NE(server->GetLocalAddress(), client->GetLocalAddress()); + + // Client is connecting, outcome not yet determined. + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + + // Server has pending connection, accept it. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + EXPECT_FALSE(accept_addr.IsNil()); + EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr); + + // Connected from server perspective, check the addresses are correct. + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + + // Connected from client perspective, check the addresses are correct. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); +} + +void SocketTest::ConnectWithDnsLookupInternal(const IPAddress& loopback, + const std::string& host) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connect to listening socket. + SocketAddress dns_addr(server->GetLocalAddress()); + dns_addr.SetIP(host); + EXPECT_EQ(0, client->Connect(dns_addr)); + // TODO: Bind when doing DNS lookup. + //EXPECT_NE(kEmptyAddr, client->GetLocalAddress()); // Implicit Bind + + // Client is connecting, outcome not yet determined. + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + + // Server has pending connection, accept it. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + EXPECT_FALSE(accept_addr.IsNil()); + EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr); + + // Connected from server perspective, check the addresses are correct. + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + + // Connected from client perspective, check the addresses are correct. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); +} + +void SocketTest::ConnectFailInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server, but don't listen yet. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + + // Attempt connect to a non-existent socket. + // We don't connect to the server socket created above, since on + // MacOS it takes about 75 seconds to get back an error! + SocketAddress bogus_addr(loopback, 65535); + EXPECT_EQ(0, client->Connect(bogus_addr)); + + // Wait for connection to fail (ECONNREFUSED). + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + EXPECT_TRUE(client->GetRemoteAddress().IsNil()); + + // Should be no pending server connections. + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_EQ(IPAddress(), accept_addr.ipaddr()); +} + +void SocketTest::ConnectWithDnsLookupFailInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server, but don't listen yet. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + + // Attempt connect to a non-existent host. + // We don't connect to the server socket created above, since on + // MacOS it takes about 75 seconds to get back an error! + SocketAddress bogus_dns_addr("not-a-real-hostname", 65535); + EXPECT_EQ(0, client->Connect(bogus_dns_addr)); + + // Wait for connection to fail (EHOSTNOTFOUND). + bool dns_lookup_finished = false; + WAIT_(client->GetState() == AsyncSocket::CS_CLOSED, kTimeout, + dns_lookup_finished); + if (!dns_lookup_finished) { + LOG(LS_WARNING) << "Skipping test; DNS resolution took longer than 5 " + << "seconds."; + return; + } + + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + EXPECT_TRUE(client->GetRemoteAddress().IsNil()); + // Should be no pending server connections. + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_TRUE(accept_addr.IsNil()); +} + +void SocketTest::ConnectWithClosedSocketInternal(const IPAddress& loopback) { + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Create a client and put in to CS_CLOSED state. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + EXPECT_EQ(0, client->Close()); + EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState()); + + // Connect() should reinitialize the socket, and put it in to CS_CONNECTING. + EXPECT_EQ(0, client->Connect(SocketAddress(server->GetLocalAddress()))); + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); +} + +void SocketTest::ConnectWhileNotClosedInternal(const IPAddress& loopback) { + // Create server and listen. + testing::StreamSink sink; + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + // Create client, connect. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + EXPECT_EQ(0, client->Connect(SocketAddress(server->GetLocalAddress()))); + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); + // Try to connect again. Should fail, but not interfere with original attempt. + EXPECT_EQ(SOCKET_ERROR, + client->Connect(SocketAddress(server->GetLocalAddress()))); + + // Accept the original connection. + SocketAddress accept_addr; + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + EXPECT_FALSE(accept_addr.IsNil()); + + // Check the states and addresses. + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + + // Try to connect again, to an unresolved hostname. + // Shouldn't break anything. + EXPECT_EQ(SOCKET_ERROR, + client->Connect(SocketAddress("localhost", + server->GetLocalAddress().port()))); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState()); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); +} + +void SocketTest::ServerCloseDuringConnectInternal(const IPAddress& loopback) { + testing::StreamSink sink; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connect to listening socket. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Close down the server while the socket is in the accept queue. + EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout); + server->Close(); + + // This should fail the connection for the client. Clean up. + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + client->Close(); +} + +void SocketTest::ClientCloseDuringConnectInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connect to listening socket. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Close down the client while the socket is in the accept queue. + EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout); + client->Close(); + + // The connection should still be able to be accepted. + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + + // The accepted socket should then close (possibly with err, timing-related) + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, accepted->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(accepted.get(), testing::SSE_CLOSE) || + sink.Check(accepted.get(), testing::SSE_ERROR)); + + // The client should not get a close event. + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); +} + +void SocketTest::ServerCloseInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Send data to the client, and then close the connection. + EXPECT_EQ(1, accepted->Send("a", 1)); + accepted->Close(); + EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState()); + + // Expect that the client is notified, and has not yet closed. + EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState()); + + // Ensure the data can be read. + char buffer[10]; + EXPECT_EQ(1, client->Recv(buffer, sizeof(buffer))); + EXPECT_EQ('a', buffer[0]); + + // Now we should close, but the remote address will remain. + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_FALSE(client->GetRemoteAddress().IsAnyIP()); + + // The closer should not get a close signal. + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_CLOSE)); + EXPECT_TRUE(accepted->GetRemoteAddress().IsNil()); + + // And the closee should only get a single signal. + Thread::Current()->ProcessMessages(0); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + + // Close down the client and ensure all is good. + client->Close(); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_TRUE(client->GetRemoteAddress().IsNil()); +} + +class SocketCloser : public sigslot::has_slots<> { + public: + void OnClose(AsyncSocket* socket, int error) { + socket->Close(); // Deleting here would blow up the vector of handlers + // for the socket's signal. + } +}; + +void SocketTest::CloseInClosedCallbackInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketCloser closer; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + client->SignalCloseEvent.connect(&closer, &SocketCloser::OnClose); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Send data to the client, and then close the connection. + accepted->Close(); + EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState()); + + // Expect that the client is notified, and has not yet closed. + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState()); + + // Now we should be closed and invalidated + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_TRUE(Socket::CS_CLOSED == client->GetState()); +} + +class Sleeper : public MessageHandler { + public: + Sleeper() {} + void OnMessage(Message* msg) { + Thread::Current()->SleepMs(500); + } +}; + +void SocketTest::SocketServerWaitInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create & connect server and client sockets. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + + // Do an i/o operation, triggering an eventual callback. + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ)); + char buf[1024] = {0}; + + EXPECT_EQ(1024, client->Send(buf, 1024)); + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ)); + + // Shouldn't signal when blocked in a thread Send, where process_io is false. + scoped_ptr thread(new Thread()); + thread->Start(); + Sleeper sleeper; + TypedMessageData data(client.get()); + thread->Send(&sleeper, 0, &data); + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ)); + + // But should signal when process_io is true. + EXPECT_TRUE_WAIT((sink.Check(accepted.get(), testing::SSE_READ)), kTimeout); + EXPECT_LT(0, accepted->Recv(buf, 1024)); +} + +void SocketTest::TcpInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create test data. + const size_t kDataSize = 1024 * 1024; + scoped_ptr send_buffer(new char[kDataSize]); + scoped_ptr recv_buffer(new char[kDataSize]); + size_t send_pos = 0, recv_pos = 0; + for (size_t i = 0; i < kDataSize; ++i) { + send_buffer[i] = static_cast(i % 256); + recv_buffer[i] = 0; + } + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Send and receive a bunch of data. + bool send_waiting_for_writability = false; + bool send_expect_success = true; + bool recv_waiting_for_readability = true; + bool recv_expect_success = false; + int data_in_flight = 0; + while (recv_pos < kDataSize) { + // Send as much as we can if we've been cleared to send. + while (!send_waiting_for_writability && send_pos < kDataSize) { + int tosend = static_cast(kDataSize - send_pos); + int sent = accepted->Send(send_buffer.get() + send_pos, tosend); + if (send_expect_success) { + // The first Send() after connecting or getting writability should + // succeed and send some data. + EXPECT_GT(sent, 0); + send_expect_success = false; + } + if (sent >= 0) { + EXPECT_LE(sent, tosend); + send_pos += sent; + data_in_flight += sent; + } else { + ASSERT_TRUE(accepted->IsBlocking()); + send_waiting_for_writability = true; + } + } + + // Read all the sent data. + while (data_in_flight > 0) { + if (recv_waiting_for_readability) { + // Wait until data is available. + EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout); + recv_waiting_for_readability = false; + recv_expect_success = true; + } + + // Receive as much as we can get in a single recv call. + int rcvd = client->Recv(recv_buffer.get() + recv_pos, + kDataSize - recv_pos); + + if (recv_expect_success) { + // The first Recv() after getting readability should succeed and receive + // some data. + // TODO: The following line is disabled due to flakey pulse + // builds. Re-enable if/when possible. + // EXPECT_GT(rcvd, 0); + recv_expect_success = false; + } + if (rcvd >= 0) { + EXPECT_LE(rcvd, data_in_flight); + recv_pos += rcvd; + data_in_flight -= rcvd; + } else { + ASSERT_TRUE(client->IsBlocking()); + recv_waiting_for_readability = true; + } + } + + // Once all that we've sent has been rcvd, expect to be able to send again. + if (send_waiting_for_writability) { + EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), + kTimeout); + send_waiting_for_writability = false; + send_expect_success = true; + } + } + + // The received data matches the sent data. + EXPECT_EQ(kDataSize, send_pos); + EXPECT_EQ(kDataSize, recv_pos); + EXPECT_EQ(0, memcmp(recv_buffer.get(), send_buffer.get(), kDataSize)); + + // Close down. + accepted->Close(); + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE)); + client->Close(); +} + +void SocketTest::SingleFlowControlCallbackInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Expect a writable callback from the connect. + EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), kTimeout); + + // Fill the socket buffer. + char buf[1024 * 16] = {0}; + int sends = 0; + while (++sends && accepted->Send(&buf, ARRAY_SIZE(buf)) != -1) {} + EXPECT_TRUE(accepted->IsBlocking()); + + // Wait until data is available. + EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout); + + // Pull data. + for (int i = 0; i < sends; ++i) { + client->Recv(buf, ARRAY_SIZE(buf)); + } + + // Expect at least one additional writable callback. + EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), kTimeout); + + // Adding data in response to the writeable callback shouldn't cause infinite + // callbacks. + int extras = 0; + for (int i = 0; i < 100; ++i) { + accepted->Send(&buf, ARRAY_SIZE(buf)); + rtc::Thread::Current()->ProcessMessages(1); + if (sink.Check(accepted.get(), testing::SSE_WRITE)) { + extras++; + } + } + EXPECT_LT(extras, 2); + + // Close down. + accepted->Close(); + client->Close(); +} + +void SocketTest::UdpInternal(const IPAddress& loopback) { + SocketAddress empty = EmptySocketAddressWithFamily(loopback.family()); + // Test basic bind and connect behavior. + AsyncSocket* socket = + ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM); + EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState()); + EXPECT_EQ(0, socket->Bind(SocketAddress(loopback, 0))); + SocketAddress addr1 = socket->GetLocalAddress(); + EXPECT_EQ(0, socket->Connect(addr1)); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, socket->GetState()); + socket->Close(); + EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState()); + delete socket; + + // Test send/receive behavior. + scoped_ptr client1( + new TestClient(AsyncUDPSocket::Create(ss_, addr1))); + scoped_ptr client2( + new TestClient(AsyncUDPSocket::Create(ss_, empty))); + + SocketAddress addr2; + EXPECT_EQ(3, client2->SendTo("foo", 3, addr1)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr2)); + + SocketAddress addr3; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr2)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr3)); + EXPECT_EQ(addr3, addr1); + // TODO: figure out what the intent is here + for (int i = 0; i < 10; ++i) { + client2.reset(new TestClient(AsyncUDPSocket::Create(ss_, empty))); + + SocketAddress addr4; + EXPECT_EQ(3, client2->SendTo("foo", 3, addr1)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr4)); + EXPECT_EQ(addr4.ipaddr(), addr2.ipaddr()); + + SocketAddress addr5; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr4)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr5)); + EXPECT_EQ(addr5, addr1); + + addr2 = addr4; + } +} + +void SocketTest::UdpReadyToSend(const IPAddress& loopback) { + SocketAddress empty = EmptySocketAddressWithFamily(loopback.family()); + // RFC 5737 - The blocks 192.0.2.0/24 (TEST-NET-1) ... are provided for use in + // documentation. + // RFC 3849 - 2001:DB8::/32 as a documentation-only prefix. + std::string dest = (loopback.family() == AF_INET6) ? + "2001:db8::1" : "192.0.2.0"; + SocketAddress test_addr(dest, 2345); + + // Test send + scoped_ptr client( + new TestClient(AsyncUDPSocket::Create(ss_, empty))); + int test_packet_size = 1200; + rtc::scoped_ptr test_packet(new char[test_packet_size]); + // Init the test packet just to avoid memcheck warning. + memset(test_packet.get(), 0, test_packet_size); + // Set the send buffer size to the same size as the test packet to have a + // better chance to get EWOULDBLOCK. + int send_buffer_size = test_packet_size; +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + send_buffer_size /= 2; +#endif + client->SetOption(rtc::Socket::OPT_SNDBUF, send_buffer_size); + + int error = 0; + uint32 start_ms = Time(); + int sent_packet_num = 0; + int expected_error = EWOULDBLOCK; + while (start_ms + kTimeout > Time()) { + int ret = client->SendTo(test_packet.get(), test_packet_size, test_addr); + ++sent_packet_num; + if (ret != test_packet_size) { + error = client->GetError(); + if (error == expected_error) { + LOG(LS_INFO) << "Got expected error code after sending " + << sent_packet_num << " packets."; + break; + } + } + } + EXPECT_EQ(expected_error, error); + EXPECT_FALSE(client->ready_to_send()); + EXPECT_TRUE_WAIT(client->ready_to_send(), kTimeout); + LOG(LS_INFO) << "Got SignalReadyToSend"; +} + +void SocketTest::GetSetOptionsInternal(const IPAddress& loopback) { + rtc::scoped_ptr socket( + ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM)); + socket->Bind(SocketAddress(loopback, 0)); + + // Check SNDBUF/RCVBUF. + const int desired_size = 12345; +#if defined(WEBRTC_LINUX) + // Yes, really. It's in the kernel source. + const int expected_size = desired_size * 2; +#else // !WEBRTC_LINUX + const int expected_size = desired_size; +#endif // !WEBRTC_LINUX + int recv_size = 0; + int send_size = 0; + // get the initial sizes + ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size)); + ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size)); + // set our desired sizes + ASSERT_NE(-1, socket->SetOption(Socket::OPT_RCVBUF, desired_size)); + ASSERT_NE(-1, socket->SetOption(Socket::OPT_SNDBUF, desired_size)); + // get the sizes again + ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size)); + ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size)); + // make sure they are right + ASSERT_EQ(expected_size, recv_size); + ASSERT_EQ(expected_size, send_size); + + // Check that we can't set NODELAY on a UDP socket. + int current_nd, desired_nd = 1; + ASSERT_EQ(-1, socket->GetOption(Socket::OPT_NODELAY, ¤t_nd)); + ASSERT_EQ(-1, socket->SetOption(Socket::OPT_NODELAY, desired_nd)); + + // Skip the esimate MTU test for IPv6 for now. + if (loopback.family() != AF_INET6) { + // Try estimating MTU. + rtc::scoped_ptr + mtu_socket( + ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM)); + mtu_socket->Bind(SocketAddress(loopback, 0)); + uint16 mtu; + // should fail until we connect + ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu)); + mtu_socket->Connect(SocketAddress(loopback, 0)); +#if defined(WEBRTC_WIN) + // now it should succeed + ASSERT_NE(-1, mtu_socket->EstimateMTU(&mtu)); + ASSERT_GE(mtu, 1492); // should be at least the 1492 "plateau" on localhost +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + // except on WEBRTC_MAC && !WEBRTC_IOS, where it's not yet implemented + ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu)); +#else + // and the behavior seems unpredictable on Linux, + // failing on the build machine + // but succeeding on my Ubiquity instance. +#endif + } +} + +} // namespace rtc diff --git a/webrtc/base/socket_unittest.h b/webrtc/base/socket_unittest.h new file mode 100644 index 000000000..d368afb3f --- /dev/null +++ b/webrtc/base/socket_unittest.h @@ -0,0 +1,88 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKET_UNITTEST_H_ +#define WEBRTC_BASE_SOCKET_UNITTEST_H_ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Generic socket tests, to be used when testing individual socketservers. +// Derive your specific test class from SocketTest, install your +// socketserver, and call the SocketTest test methods. +class SocketTest : public testing::Test { + protected: + SocketTest() : ss_(NULL), kIPv4Loopback(INADDR_LOOPBACK), + kIPv6Loopback(in6addr_loopback) {} + virtual void SetUp() { ss_ = Thread::Current()->socketserver(); } + void TestConnectIPv4(); + void TestConnectIPv6(); + void TestConnectWithDnsLookupIPv4(); + void TestConnectWithDnsLookupIPv6(); + void TestConnectFailIPv4(); + void TestConnectFailIPv6(); + void TestConnectWithDnsLookupFailIPv4(); + void TestConnectWithDnsLookupFailIPv6(); + void TestConnectWithClosedSocketIPv4(); + void TestConnectWithClosedSocketIPv6(); + void TestConnectWhileNotClosedIPv4(); + void TestConnectWhileNotClosedIPv6(); + void TestServerCloseDuringConnectIPv4(); + void TestServerCloseDuringConnectIPv6(); + void TestClientCloseDuringConnectIPv4(); + void TestClientCloseDuringConnectIPv6(); + void TestServerCloseIPv4(); + void TestServerCloseIPv6(); + void TestCloseInClosedCallbackIPv4(); + void TestCloseInClosedCallbackIPv6(); + void TestSocketServerWaitIPv4(); + void TestSocketServerWaitIPv6(); + void TestTcpIPv4(); + void TestTcpIPv6(); + void TestSingleFlowControlCallbackIPv4(); + void TestSingleFlowControlCallbackIPv6(); + void TestUdpIPv4(); + void TestUdpIPv6(); + void TestUdpReadyToSendIPv4(); + void TestUdpReadyToSendIPv6(); + void TestGetSetOptionsIPv4(); + void TestGetSetOptionsIPv6(); + + private: + void ConnectInternal(const IPAddress& loopback); + void ConnectWithDnsLookupInternal(const IPAddress& loopback, + const std::string& host); + void ConnectFailInternal(const IPAddress& loopback); + + void ConnectWithDnsLookupFailInternal(const IPAddress& loopback); + void ConnectWithClosedSocketInternal(const IPAddress& loopback); + void ConnectWhileNotClosedInternal(const IPAddress& loopback); + void ServerCloseDuringConnectInternal(const IPAddress& loopback); + void ClientCloseDuringConnectInternal(const IPAddress& loopback); + void ServerCloseInternal(const IPAddress& loopback); + void CloseInClosedCallbackInternal(const IPAddress& loopback); + void SocketServerWaitInternal(const IPAddress& loopback); + void TcpInternal(const IPAddress& loopback); + void SingleFlowControlCallbackInternal(const IPAddress& loopback); + void UdpInternal(const IPAddress& loopback); + void UdpReadyToSend(const IPAddress& loopback); + void GetSetOptionsInternal(const IPAddress& loopback); + + static const int kTimeout = 5000; // ms + SocketServer* ss_; + const IPAddress kIPv4Loopback; + const IPAddress kIPv6Loopback; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKET_UNITTEST_H_ diff --git a/webrtc/base/socketadapters.cc b/webrtc/base/socketadapters.cc new file mode 100644 index 000000000..1cdd1bcbe --- /dev/null +++ b/webrtc/base/socketadapters.cc @@ -0,0 +1,893 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include +#include + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#define SECURITY_WIN32 +#include +#endif + +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/common.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/sec_buffer.h" +#endif // WEBRTC_WIN + +namespace rtc { + +BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t size) + : AsyncSocketAdapter(socket), buffer_size_(size), + data_len_(0), buffering_(false) { + buffer_ = new char[buffer_size_]; +} + +BufferedReadAdapter::~BufferedReadAdapter() { + delete [] buffer_; +} + +int BufferedReadAdapter::Send(const void *pv, size_t cb) { + if (buffering_) { + // TODO: Spoof error better; Signal Writeable + socket_->SetError(EWOULDBLOCK); + return -1; + } + return AsyncSocketAdapter::Send(pv, cb); +} + +int BufferedReadAdapter::Recv(void *pv, size_t cb) { + if (buffering_) { + socket_->SetError(EWOULDBLOCK); + return -1; + } + + size_t read = 0; + + if (data_len_) { + read = _min(cb, data_len_); + memcpy(pv, buffer_, read); + data_len_ -= read; + if (data_len_ > 0) { + memmove(buffer_, buffer_ + read, data_len_); + } + pv = static_cast(pv) + read; + cb -= read; + } + + // FIX: If cb == 0, we won't generate another read event + + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res < 0) + return res; + + return res + static_cast(read); +} + +void BufferedReadAdapter::BufferInput(bool on) { + buffering_ = on; +} + +void BufferedReadAdapter::OnReadEvent(AsyncSocket * socket) { + ASSERT(socket == socket_); + + if (!buffering_) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (data_len_ >= buffer_size_) { + LOG(INFO) << "Input buffer overflow"; + ASSERT(false); + data_len_ = 0; + } + + int len = socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + LOG_ERR(INFO) << "Recv"; + return; + } + + data_len_ += len; + + ProcessInput(buffer_, &data_len_); +} + +/////////////////////////////////////////////////////////////////////////////// + +// This is a SSL v2 CLIENT_HELLO message. +// TODO: Should this have a session id? The response doesn't have a +// certificate, so the hello should have a session id. +static const uint8 kSslClientHello[] = { + 0x80, 0x46, // msg len + 0x01, // CLIENT_HELLO + 0x03, 0x01, // SSL 3.1 + 0x00, 0x2d, // ciphersuite len + 0x00, 0x00, // session id len + 0x00, 0x10, // challenge len + 0x01, 0x00, 0x80, 0x03, 0x00, 0x80, 0x07, 0x00, 0xc0, // ciphersuites + 0x06, 0x00, 0x40, 0x02, 0x00, 0x80, 0x04, 0x00, 0x80, // + 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x0a, // + 0x00, 0xfe, 0xfe, 0x00, 0x00, 0x09, 0x00, 0x00, 0x64, // + 0x00, 0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, // + 0x1f, 0x17, 0x0c, 0xa6, 0x2f, 0x00, 0x78, 0xfc, // challenge + 0x46, 0x55, 0x2e, 0xb1, 0x83, 0x39, 0xf1, 0xea // +}; + +// This is a TLSv1 SERVER_HELLO message. +static const uint8 kSslServerHello[] = { + 0x16, // handshake message + 0x03, 0x01, // SSL 3.1 + 0x00, 0x4a, // message len + 0x02, // SERVER_HELLO + 0x00, 0x00, 0x46, // handshake len + 0x03, 0x01, // SSL 3.1 + 0x42, 0x85, 0x45, 0xa7, 0x27, 0xa9, 0x5d, 0xa0, // server random + 0xb3, 0xc5, 0xe7, 0x53, 0xda, 0x48, 0x2b, 0x3f, // + 0xc6, 0x5a, 0xca, 0x89, 0xc1, 0x58, 0x52, 0xa1, // + 0x78, 0x3c, 0x5b, 0x17, 0x46, 0x00, 0x85, 0x3f, // + 0x20, // session id len + 0x0e, 0xd3, 0x06, 0x72, 0x5b, 0x5b, 0x1b, 0x5f, // session id + 0x15, 0xac, 0x13, 0xf9, 0x88, 0x53, 0x9d, 0x9b, // + 0xe8, 0x3d, 0x7b, 0x0c, 0x30, 0x32, 0x6e, 0x38, // + 0x4d, 0xa2, 0x75, 0x57, 0x41, 0x6c, 0x34, 0x5c, // + 0x00, 0x04, // RSA/RC4-128/MD5 + 0x00 // null compression +}; + +AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket) + : BufferedReadAdapter(socket, 1024) { +} + +int AsyncSSLSocket::Connect(const SocketAddress& addr) { + // Begin buffering before we connect, so that there isn't a race condition + // between potential senders and receiving the OnConnectEvent signal + BufferInput(true); + return BufferedReadAdapter::Connect(addr); +} + +void AsyncSSLSocket::OnConnectEvent(AsyncSocket * socket) { + ASSERT(socket == socket_); + // TODO: we could buffer output too... + VERIFY(sizeof(kSslClientHello) == + DirectSend(kSslClientHello, sizeof(kSslClientHello))); +} + +void AsyncSSLSocket::ProcessInput(char* data, size_t* len) { + if (*len < sizeof(kSslServerHello)) + return; + + if (memcmp(kSslServerHello, data, sizeof(kSslServerHello)) != 0) { + Close(); + SignalCloseEvent(this, 0); // TODO: error code? + return; + } + + *len -= sizeof(kSslServerHello); + if (*len > 0) { + memmove(data, data + sizeof(kSslServerHello), *len); + } + + bool remainder = (*len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); +} + +AsyncSSLServerSocket::AsyncSSLServerSocket(AsyncSocket* socket) + : BufferedReadAdapter(socket, 1024) { + BufferInput(true); +} + +void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) { + // We only accept client hello messages. + if (*len < sizeof(kSslClientHello)) { + return; + } + + if (memcmp(kSslClientHello, data, sizeof(kSslClientHello)) != 0) { + Close(); + SignalCloseEvent(this, 0); + return; + } + + *len -= sizeof(kSslClientHello); + + // Clients should not send more data until the handshake is completed. + ASSERT(*len == 0); + + // Send a server hello back to the client. + DirectSend(kSslServerHello, sizeof(kSslServerHello)); + + // Handshake completed for us, redirect input to our parent. + BufferInput(false); +} + +/////////////////////////////////////////////////////////////////////////////// + +AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket, + const std::string& user_agent, + const SocketAddress& proxy, + const std::string& username, + const CryptString& password) + : BufferedReadAdapter(socket, 1024), proxy_(proxy), agent_(user_agent), + user_(username), pass_(password), force_connect_(false), state_(PS_ERROR), + context_(0) { +} + +AsyncHttpsProxySocket::~AsyncHttpsProxySocket() { + delete context_; +} + +int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) { + int ret; + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect(" + << proxy_.ToSensitiveString() << ")"; + dest_ = addr; + state_ = PS_INIT; + if (ShouldIssueConnect()) { + BufferInput(true); + } + ret = BufferedReadAdapter::Connect(proxy_); + // TODO: Set state_ appropriately if Connect fails. + return ret; +} + +SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncHttpsProxySocket::Close() { + headers_.clear(); + state_ = PS_ERROR; + dest_.Clear(); + delete context_; + context_ = NULL; + return BufferedReadAdapter::Close(); +} + +Socket::ConnState AsyncHttpsProxySocket::GetState() const { + if (state_ < PS_TUNNEL) { + return CS_CONNECTING; + } else if (state_ == PS_TUNNEL) { + return CS_CONNECTED; + } else { + return CS_CLOSED; + } +} + +void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent"; + if (!ShouldIssueConnect()) { + state_ = PS_TUNNEL; + BufferedReadAdapter::OnConnectEvent(socket); + return; + } + SendRequest(); +} + +void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")"; + if ((state_ == PS_WAIT_CLOSE) && (err == 0)) { + state_ = PS_ERROR; + Connect(dest_); + } else { + BufferedReadAdapter::OnCloseEvent(socket, err); + } +} + +void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) { + size_t start = 0; + for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) { + if (state_ == PS_SKIP_BODY) { + size_t consume = _min(*len - pos, content_length_); + pos += consume; + start = pos; + content_length_ -= consume; + if (content_length_ == 0) { + EndResponse(); + } + continue; + } + + if (data[pos++] != '\n') + continue; + + size_t len = pos - start - 1; + if ((len > 0) && (data[start + len - 1] == '\r')) + --len; + + data[start + len] = 0; + ProcessLine(data + start, len); + start = pos; + } + + *len -= start; + if (*len > 0) { + memmove(data, data + start, *len); + } + + if (state_ != PS_TUNNEL) + return; + + bool remainder = (*len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +bool AsyncHttpsProxySocket::ShouldIssueConnect() const { + // TODO: Think about whether a more sophisticated test + // than dest port == 80 is needed. + return force_connect_ || (dest_.port() != 80); +} + +void AsyncHttpsProxySocket::SendRequest() { + std::stringstream ss; + ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n"; + ss << "User-Agent: " << agent_ << "\r\n"; + ss << "Host: " << dest_.HostAsURIString() << "\r\n"; + ss << "Content-Length: 0\r\n"; + ss << "Proxy-Connection: Keep-Alive\r\n"; + ss << headers_; + ss << "\r\n"; + std::string str = ss.str(); + DirectSend(str.c_str(), str.size()); + state_ = PS_LEADER; + expect_close_ = true; + content_length_ = 0; + headers_.clear(); + + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str; +} + +void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data; + + if (len == 0) { + if (state_ == PS_TUNNEL_HEADERS) { + state_ = PS_TUNNEL; + } else if (state_ == PS_ERROR_HEADERS) { + Error(defer_error_); + return; + } else if (state_ == PS_SKIP_HEADERS) { + if (content_length_) { + state_ = PS_SKIP_BODY; + } else { + EndResponse(); + return; + } + } else { + static bool report = false; + if (!unknown_mechanisms_.empty() && !report) { + report = true; + std::string msg( + "Unable to connect to the Google Talk service due to an incompatibility " + "with your proxy.\r\nPlease help us resolve this issue by submitting the " + "following information to us using our technical issue submission form " + "at:\r\n\r\n" + "http://www.google.com/support/talk/bin/request.py\r\n\r\n" + "We apologize for the inconvenience.\r\n\r\n" + "Information to submit to Google: " + ); + //std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: "); + msg.append(unknown_mechanisms_); +#if defined(WEBRTC_WIN) + MessageBoxA(0, msg.c_str(), "Oops!", MB_OK); +#endif +#if defined(WEBRTC_POSIX) + // TODO: Raise a signal so the UI can be separated. + LOG(LS_ERROR) << "Oops!\n\n" << msg; +#endif + } + // Unexpected end of headers + Error(0); + return; + } + } else if (state_ == PS_LEADER) { + unsigned int code; + if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) { + Error(0); + return; + } + switch (code) { + case 200: + // connection good! + state_ = PS_TUNNEL_HEADERS; + return; +#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407) +#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ +#endif + case 407: // HTTP_STATUS_PROXY_AUTH_REQ + state_ = PS_AUTHENTICATE; + return; + default: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + return; + } + } else if ((state_ == PS_AUTHENTICATE) + && (_strnicmp(data, "Proxy-Authenticate:", 19) == 0)) { + std::string response, auth_method; + switch (HttpAuthenticate(data + 19, len - 19, + proxy_, "CONNECT", "/", + user_, pass_, context_, response, auth_method)) { + case HAR_IGNORE: + LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method; + if (!unknown_mechanisms_.empty()) + unknown_mechanisms_.append(", "); + unknown_mechanisms_.append(auth_method); + break; + case HAR_RESPONSE: + headers_ = "Proxy-Authorization: "; + headers_.append(response); + headers_.append("\r\n"); + state_ = PS_SKIP_HEADERS; + unknown_mechanisms_.clear(); + break; + case HAR_CREDENTIALS: + defer_error_ = SOCKET_EACCES; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + case HAR_ERROR: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + } + } else if (_strnicmp(data, "Content-Length:", 15) == 0) { + content_length_ = strtoul(data + 15, 0, 0); + } else if (_strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) { + expect_close_ = false; + /* + } else if (_strnicmp(data, "Connection: close", 17) == 0) { + expect_close_ = true; + */ + } +} + +void AsyncHttpsProxySocket::EndResponse() { + if (!expect_close_) { + SendRequest(); + return; + } + + // No point in waiting for the server to close... let's close now + // TODO: Refactor out PS_WAIT_CLOSE + state_ = PS_WAIT_CLOSE; + BufferedReadAdapter::Close(); + OnCloseEvent(this, 0); +} + +void AsyncHttpsProxySocket::Error(int error) { + BufferInput(false); + Close(); + SetError(error); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket, + const SocketAddress& proxy, + const std::string& username, + const CryptString& password) + : BufferedReadAdapter(socket, 1024), state_(SS_ERROR), proxy_(proxy), + user_(username), pass_(password) { +} + +int AsyncSocksProxySocket::Connect(const SocketAddress& addr) { + int ret; + dest_ = addr; + state_ = SS_INIT; + BufferInput(true); + ret = BufferedReadAdapter::Connect(proxy_); + // TODO: Set state_ appropriately if Connect fails. + return ret; +} + +SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncSocksProxySocket::Close() { + state_ = SS_ERROR; + dest_.Clear(); + return BufferedReadAdapter::Close(); +} + +Socket::ConnState AsyncSocksProxySocket::GetState() const { + if (state_ < SS_TUNNEL) { + return CS_CONNECTING; + } else if (state_ == SS_TUNNEL) { + return CS_CONNECTED; + } else { + return CS_CLOSED; + } +} + +void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) { + SendHello(); +} + +void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) { + ASSERT(state_ < SS_TUNNEL); + + ByteBuffer response(data, *len); + + if (state_ == SS_HELLO) { + uint8 ver, method; + if (!response.ReadUInt8(&ver) || + !response.ReadUInt8(&method)) + return; + + if (ver != 5) { + Error(0); + return; + } + + if (method == 0) { + SendConnect(); + } else if (method == 2) { + SendAuth(); + } else { + Error(0); + return; + } + } else if (state_ == SS_AUTH) { + uint8 ver, status; + if (!response.ReadUInt8(&ver) || + !response.ReadUInt8(&status)) + return; + + if ((ver != 1) || (status != 0)) { + Error(SOCKET_EACCES); + return; + } + + SendConnect(); + } else if (state_ == SS_CONNECT) { + uint8 ver, rep, rsv, atyp; + if (!response.ReadUInt8(&ver) || + !response.ReadUInt8(&rep) || + !response.ReadUInt8(&rsv) || + !response.ReadUInt8(&atyp)) + return; + + if ((ver != 5) || (rep != 0)) { + Error(0); + return; + } + + uint16 port; + if (atyp == 1) { + uint32 addr; + if (!response.ReadUInt32(&addr) || + !response.ReadUInt16(&port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 3) { + uint8 len; + std::string addr; + if (!response.ReadUInt8(&len) || + !response.ReadString(&addr, len) || + !response.ReadUInt16(&port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 4) { + std::string addr; + if (!response.ReadString(&addr, 16) || + !response.ReadUInt16(&port)) + return; + LOG(LS_VERBOSE) << "Bound on :" << port; + } else { + Error(0); + return; + } + + state_ = SS_TUNNEL; + } + + // Consume parsed data + *len = response.Length(); + memcpy(data, response.Data(), *len); + + if (state_ != SS_TUNNEL) + return; + + bool remainder = (*len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +void AsyncSocksProxySocket::SendHello() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + if (user_.empty()) { + request.WriteUInt8(1); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + } else { + request.WriteUInt8(2); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + request.WriteUInt8(2); // Username/Password + } + DirectSend(request.Data(), request.Length()); + state_ = SS_HELLO; +} + +void AsyncSocksProxySocket::SendAuth() { + ByteBuffer request; + request.WriteUInt8(1); // Negotiation Version + request.WriteUInt8(static_cast(user_.size())); + request.WriteString(user_); // Username + request.WriteUInt8(static_cast(pass_.GetLength())); + size_t len = pass_.GetLength() + 1; + char * sensitive = new char[len]; + pass_.CopyTo(sensitive, true); + request.WriteString(sensitive); // Password + memset(sensitive, 0, len); + delete [] sensitive; + DirectSend(request.Data(), request.Length()); + state_ = SS_AUTH; +} + +void AsyncSocksProxySocket::SendConnect() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + request.WriteUInt8(1); // CONNECT + request.WriteUInt8(0); // Reserved + if (dest_.IsUnresolved()) { + std::string hostname = dest_.hostname(); + request.WriteUInt8(3); // DOMAINNAME + request.WriteUInt8(static_cast(hostname.size())); + request.WriteString(hostname); // Destination Hostname + } else { + request.WriteUInt8(1); // IPV4 + request.WriteUInt32(dest_.ip()); // Destination IP + } + request.WriteUInt16(dest_.port()); // Destination Port + DirectSend(request.Data(), request.Length()); + state_ = SS_CONNECT; +} + +void AsyncSocksProxySocket::Error(int error) { + state_ = SS_ERROR; + BufferInput(false); + Close(); + SetError(SOCKET_EACCES); + SignalCloseEvent(this, error); +} + +AsyncSocksProxyServerSocket::AsyncSocksProxyServerSocket(AsyncSocket* socket) + : AsyncProxyServerSocket(socket, kBufferSize), state_(SS_HELLO) { + BufferInput(true); +} + +void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) { + // TODO: See if the whole message has arrived + ASSERT(state_ < SS_CONNECT_PENDING); + + ByteBuffer response(data, *len); + if (state_ == SS_HELLO) { + HandleHello(&response); + } else if (state_ == SS_AUTH) { + HandleAuth(&response); + } else if (state_ == SS_CONNECT) { + HandleConnect(&response); + } + + // Consume parsed data + *len = response.Length(); + memcpy(data, response.Data(), *len); +} + +void AsyncSocksProxyServerSocket::DirectSend(const ByteBuffer& buf) { + BufferedReadAdapter::DirectSend(buf.Data(), buf.Length()); +} + +void AsyncSocksProxyServerSocket::HandleHello(ByteBuffer* request) { + uint8 ver, num_methods; + if (!request->ReadUInt8(&ver) || + !request->ReadUInt8(&num_methods)) { + Error(0); + return; + } + + if (ver != 5) { + Error(0); + return; + } + + // Handle either no-auth (0) or user/pass auth (2) + uint8 method = 0xFF; + if (num_methods > 0 && !request->ReadUInt8(&method)) { + Error(0); + return; + } + + // TODO: Ask the server which method to use. + SendHelloReply(method); + if (method == 0) { + state_ = SS_CONNECT; + } else if (method == 2) { + state_ = SS_AUTH; + } else { + state_ = SS_ERROR; + } +} + +void AsyncSocksProxyServerSocket::SendHelloReply(int method) { + ByteBuffer response; + response.WriteUInt8(5); // Socks Version + response.WriteUInt8(method); // Auth method + DirectSend(response); +} + +void AsyncSocksProxyServerSocket::HandleAuth(ByteBuffer* request) { + uint8 ver, user_len, pass_len; + std::string user, pass; + if (!request->ReadUInt8(&ver) || + !request->ReadUInt8(&user_len) || + !request->ReadString(&user, user_len) || + !request->ReadUInt8(&pass_len) || + !request->ReadString(&pass, pass_len)) { + Error(0); + return; + } + + // TODO: Allow for checking of credentials. + SendAuthReply(0); + state_ = SS_CONNECT; +} + +void AsyncSocksProxyServerSocket::SendAuthReply(int result) { + ByteBuffer response; + response.WriteUInt8(1); // Negotiation Version + response.WriteUInt8(result); + DirectSend(response); +} + +void AsyncSocksProxyServerSocket::HandleConnect(ByteBuffer* request) { + uint8 ver, command, reserved, addr_type; + uint32 ip; + uint16 port; + if (!request->ReadUInt8(&ver) || + !request->ReadUInt8(&command) || + !request->ReadUInt8(&reserved) || + !request->ReadUInt8(&addr_type) || + !request->ReadUInt32(&ip) || + !request->ReadUInt16(&port)) { + Error(0); + return; + } + + if (ver != 5 || command != 1 || + reserved != 0 || addr_type != 1) { + Error(0); + return; + } + + SignalConnectRequest(this, SocketAddress(ip, port)); + state_ = SS_CONNECT_PENDING; +} + +void AsyncSocksProxyServerSocket::SendConnectResult(int result, + const SocketAddress& addr) { + if (state_ != SS_CONNECT_PENDING) + return; + + ByteBuffer response; + response.WriteUInt8(5); // Socks version + response.WriteUInt8((result != 0)); // 0x01 is generic error + response.WriteUInt8(0); // reserved + response.WriteUInt8(1); // IPv4 address + response.WriteUInt32(addr.ip()); + response.WriteUInt16(addr.port()); + DirectSend(response); + BufferInput(false); + state_ = SS_TUNNEL; +} + +void AsyncSocksProxyServerSocket::Error(int error) { + state_ = SS_ERROR; + BufferInput(false); + Close(); + SetError(SOCKET_EACCES); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +LoggingSocketAdapter::LoggingSocketAdapter(AsyncSocket* socket, + LoggingSeverity level, + const char * label, bool hex_mode) + : AsyncSocketAdapter(socket), level_(level), hex_mode_(hex_mode) { + label_.append("["); + label_.append(label); + label_.append("]"); +} + +int LoggingSocketAdapter::Send(const void *pv, size_t cb) { + int res = AsyncSocketAdapter::Send(pv, cb); + if (res > 0) + LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::SendTo(const void *pv, size_t cb, + const SocketAddress& addr) { + int res = AsyncSocketAdapter::SendTo(pv, cb, addr); + if (res > 0) + LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::Recv(void *pv, size_t cb) { + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res > 0) + LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + if (res > 0) + LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::Close() { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed locally"; + return socket_->Close(); +} + +void LoggingSocketAdapter::OnConnectEvent(AsyncSocket * socket) { + LOG_V(level_) << label_ << " Connected"; + AsyncSocketAdapter::OnConnectEvent(socket); +} + +void LoggingSocketAdapter::OnCloseEvent(AsyncSocket * socket, int err) { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed with error: " << err; + AsyncSocketAdapter::OnCloseEvent(socket, err); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/socketadapters.h b/webrtc/base/socketadapters.h new file mode 100644 index 000000000..3292df289 --- /dev/null +++ b/webrtc/base/socketadapters.h @@ -0,0 +1,244 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKETADAPTERS_H_ +#define WEBRTC_BASE_SOCKETADAPTERS_H_ + +#include +#include + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +struct HttpAuthContext; +class ByteBuffer; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that can buffer and process data internally, +// as in the case of connecting to a proxy, where you must speak the proxy +// protocol before commencing normal socket behavior. +class BufferedReadAdapter : public AsyncSocketAdapter { + public: + BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size); + virtual ~BufferedReadAdapter(); + + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + + protected: + int DirectSend(const void* pv, size_t cb) { + return AsyncSocketAdapter::Send(pv, cb); + } + + void BufferInput(bool on = true); + virtual void ProcessInput(char* data, size_t* len) = 0; + + virtual void OnReadEvent(AsyncSocket * socket); + + private: + char * buffer_; + size_t buffer_size_, data_len_; + bool buffering_; + DISALLOW_EVIL_CONSTRUCTORS(BufferedReadAdapter); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Interface for implementing proxy server sockets. +class AsyncProxyServerSocket : public BufferedReadAdapter { + public: + AsyncProxyServerSocket(AsyncSocket* socket, size_t buffer_size) + : BufferedReadAdapter(socket, buffer_size) {} + sigslot::signal2 SignalConnectRequest; + virtual void SendConnectResult(int err, const SocketAddress& addr) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that performs the client side of a +// fake SSL handshake. Used for "ssltcp" P2P functionality. +class AsyncSSLSocket : public BufferedReadAdapter { + public: + explicit AsyncSSLSocket(AsyncSocket* socket); + + virtual int Connect(const SocketAddress& addr); + + protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void ProcessInput(char* data, size_t* len); + DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLSocket); +}; + +// Implements a socket adapter that performs the server side of a +// fake SSL handshake. Used when implementing a relay server that does "ssltcp". +class AsyncSSLServerSocket : public BufferedReadAdapter { + public: + explicit AsyncSSLServerSocket(AsyncSocket* socket); + + protected: + virtual void ProcessInput(char* data, size_t* len); + DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLServerSocket); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that speaks the HTTP/S proxy protocol. +class AsyncHttpsProxySocket : public BufferedReadAdapter { + public: + AsyncHttpsProxySocket(AsyncSocket* socket, const std::string& user_agent, + const SocketAddress& proxy, + const std::string& username, const CryptString& password); + virtual ~AsyncHttpsProxySocket(); + + // If connect is forced, the adapter will always issue an HTTP CONNECT to the + // target address. Otherwise, it will connect only if the destination port + // is not port 80. + void SetForceConnect(bool force) { force_connect_ = force; } + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + virtual int Close(); + virtual ConnState GetState() const; + + protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void OnCloseEvent(AsyncSocket* socket, int err); + virtual void ProcessInput(char* data, size_t* len); + + bool ShouldIssueConnect() const; + void SendRequest(); + void ProcessLine(char* data, size_t len); + void EndResponse(); + void Error(int error); + + private: + SocketAddress proxy_, dest_; + std::string agent_, user_, headers_; + CryptString pass_; + bool force_connect_; + size_t content_length_; + int defer_error_; + bool expect_close_; + enum ProxyState { + PS_INIT, PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS, + PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR + } state_; + HttpAuthContext * context_; + std::string unknown_mechanisms_; + DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxySocket); +}; + +/* TODO: Implement this. +class AsyncHttpsProxyServerSocket : public AsyncProxyServerSocket { + public: + explicit AsyncHttpsProxyServerSocket(AsyncSocket* socket); + + private: + virtual void ProcessInput(char * data, size_t& len); + void Error(int error); + DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxyServerSocket); +}; +*/ + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that speaks the SOCKS proxy protocol. +class AsyncSocksProxySocket : public BufferedReadAdapter { + public: + AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const CryptString& password); + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + virtual int Close(); + virtual ConnState GetState() const; + + protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void ProcessInput(char* data, size_t* len); + + void SendHello(); + void SendConnect(); + void SendAuth(); + void Error(int error); + + private: + enum State { + SS_INIT, SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR + }; + State state_; + SocketAddress proxy_, dest_; + std::string user_; + CryptString pass_; + DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxySocket); +}; + +// Implements a proxy server socket for the SOCKS protocol. +class AsyncSocksProxyServerSocket : public AsyncProxyServerSocket { + public: + explicit AsyncSocksProxyServerSocket(AsyncSocket* socket); + + private: + virtual void ProcessInput(char* data, size_t* len); + void DirectSend(const ByteBuffer& buf); + + void HandleHello(ByteBuffer* request); + void SendHelloReply(int method); + void HandleAuth(ByteBuffer* request); + void SendAuthReply(int result); + void HandleConnect(ByteBuffer* request); + virtual void SendConnectResult(int result, const SocketAddress& addr); + + void Error(int error); + + static const int kBufferSize = 1024; + enum State { + SS_HELLO, SS_AUTH, SS_CONNECT, SS_CONNECT_PENDING, SS_TUNNEL, SS_ERROR + }; + State state_; + DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxyServerSocket); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that logs everything that it sends and receives. +class LoggingSocketAdapter : public AsyncSocketAdapter { + public: + LoggingSocketAdapter(AsyncSocket* socket, LoggingSeverity level, + const char * label, bool hex_mode = false); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + virtual int Recv(void *pv, size_t cb); + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr); + virtual int Close(); + + protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + + private: + LoggingSeverity level_; + std::string label_; + bool hex_mode_; + LogMultilineState lms_; + DISALLOW_EVIL_CONSTRUCTORS(LoggingSocketAdapter); +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETADAPTERS_H_ diff --git a/webrtc/base/socketaddress.cc b/webrtc/base/socketaddress.cc new file mode 100644 index 000000000..47ddd0400 --- /dev/null +++ b/webrtc/base/socketaddress.cc @@ -0,0 +1,383 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/socketaddress.h" + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#if defined(OPENBSD) +#include +#endif +#if !defined(__native_client__) +#include +#endif +#include +#include +#include +#endif + +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +SocketAddress::SocketAddress() { + Clear(); +} + +SocketAddress::SocketAddress(const std::string& hostname, int port) { + SetIP(hostname); + SetPort(port); +} + +SocketAddress::SocketAddress(uint32 ip_as_host_order_integer, int port) { + SetIP(IPAddress(ip_as_host_order_integer)); + SetPort(port); +} + +SocketAddress::SocketAddress(const IPAddress& ip, int port) { + SetIP(ip); + SetPort(port); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + this->operator=(addr); +} + +void SocketAddress::Clear() { + hostname_.clear(); + literal_ = false; + ip_ = IPAddress(); + port_ = 0; + scope_id_ = 0; +} + +bool SocketAddress::IsNil() const { + return hostname_.empty() && IPIsUnspec(ip_) && 0 == port_; +} + +bool SocketAddress::IsComplete() const { + return (!IPIsAny(ip_)) && (0 != port_); +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& addr) { + hostname_ = addr.hostname_; + ip_ = addr.ip_; + port_ = addr.port_; + literal_ = addr.literal_; + scope_id_ = addr.scope_id_; + return *this; +} + +void SocketAddress::SetIP(uint32 ip_as_host_order_integer) { + hostname_.clear(); + literal_ = false; + ip_ = IPAddress(ip_as_host_order_integer); + scope_id_ = 0; +} + +void SocketAddress::SetIP(const IPAddress& ip) { + hostname_.clear(); + literal_ = false; + ip_ = ip; + scope_id_ = 0; +} + +void SocketAddress::SetIP(const std::string& hostname) { + hostname_ = hostname; + literal_ = IPFromString(hostname, &ip_); + if (!literal_) { + ip_ = IPAddress(); + } + scope_id_ = 0; +} + +void SocketAddress::SetResolvedIP(uint32 ip_as_host_order_integer) { + ip_ = IPAddress(ip_as_host_order_integer); + scope_id_ = 0; +} + +void SocketAddress::SetResolvedIP(const IPAddress& ip) { + ip_ = ip; + scope_id_ = 0; +} + +void SocketAddress::SetPort(int port) { + ASSERT((0 <= port) && (port < 65536)); + port_ = port; +} + +uint32 SocketAddress::ip() const { + return ip_.v4AddressAsHostOrderInteger(); +} + +const IPAddress& SocketAddress::ipaddr() const { + return ip_; +} + +uint16 SocketAddress::port() const { + return port_; +} + +std::string SocketAddress::HostAsURIString() const { + // If the hostname was a literal IP string, it may need to have square + // brackets added (for SocketAddress::ToString()). + if (!literal_ && !hostname_.empty()) + return hostname_; + if (ip_.family() == AF_INET6) { + return "[" + ip_.ToString() + "]"; + } else { + return ip_.ToString(); + } +} + +std::string SocketAddress::HostAsSensitiveURIString() const { + // If the hostname was a literal IP string, it may need to have square + // brackets added (for SocketAddress::ToString()). + if (!literal_ && !hostname_.empty()) + return hostname_; + if (ip_.family() == AF_INET6) { + return "[" + ip_.ToSensitiveString() + "]"; + } else { + return ip_.ToSensitiveString(); + } +} + +std::string SocketAddress::PortAsString() const { + std::ostringstream ost; + ost << port_; + return ost.str(); +} + +std::string SocketAddress::ToString() const { + std::ostringstream ost; + ost << *this; + return ost.str(); +} + +std::string SocketAddress::ToSensitiveString() const { + std::ostringstream ost; + ost << HostAsSensitiveURIString() << ":" << port(); + return ost.str(); +} + +bool SocketAddress::FromString(const std::string& str) { + if (str.at(0) == '[') { + std::string::size_type closebracket = str.rfind(']'); + if (closebracket != std::string::npos) { + std::string::size_type colon = str.find(':', closebracket); + if (colon != std::string::npos && colon > closebracket) { + SetPort(strtoul(str.substr(colon + 1).c_str(), NULL, 10)); + SetIP(str.substr(1, closebracket - 1)); + } else { + return false; + } + } + } else { + std::string::size_type pos = str.find(':'); + if (std::string::npos == pos) + return false; + SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10)); + SetIP(str.substr(0, pos)); + } + return true; +} + +std::ostream& operator<<(std::ostream& os, const SocketAddress& addr) { + os << addr.HostAsURIString() << ":" << addr.port(); + return os; +} + +bool SocketAddress::IsAnyIP() const { + return IPIsAny(ip_); +} + +bool SocketAddress::IsLoopbackIP() const { + return IPIsLoopback(ip_) || (IPIsAny(ip_) && + 0 == strcmp(hostname_.c_str(), "localhost")); +} + +bool SocketAddress::IsPrivateIP() const { + return IPIsPrivate(ip_); +} + +bool SocketAddress::IsUnresolvedIP() const { + return IPIsUnspec(ip_) && !literal_ && !hostname_.empty(); +} + +bool SocketAddress::operator==(const SocketAddress& addr) const { + return EqualIPs(addr) && EqualPorts(addr); +} + +bool SocketAddress::operator<(const SocketAddress& addr) const { + if (ip_ < addr.ip_) + return true; + else if (addr.ip_ < ip_) + return false; + + // We only check hostnames if both IPs are zero. This matches EqualIPs() + if (addr.IsAnyIP()) { + if (hostname_ < addr.hostname_) + return true; + else if (addr.hostname_ < hostname_) + return false; + } + + return port_ < addr.port_; +} + +bool SocketAddress::EqualIPs(const SocketAddress& addr) const { + return (ip_ == addr.ip_) && + ((!IPIsAny(ip_)) || (hostname_ == addr.hostname_)); +} + +bool SocketAddress::EqualPorts(const SocketAddress& addr) const { + return (port_ == addr.port_); +} + +size_t SocketAddress::Hash() const { + size_t h = 0; + h ^= HashIP(ip_); + h ^= port_ | (port_ << 16); + return h; +} + +void SocketAddress::ToSockAddr(sockaddr_in* saddr) const { + memset(saddr, 0, sizeof(*saddr)); + if (ip_.family() != AF_INET) { + saddr->sin_family = AF_UNSPEC; + return; + } + saddr->sin_family = AF_INET; + saddr->sin_port = HostToNetwork16(port_); + if (IPIsAny(ip_)) { + saddr->sin_addr.s_addr = INADDR_ANY; + } else { + saddr->sin_addr = ip_.ipv4_address(); + } +} + +bool SocketAddress::FromSockAddr(const sockaddr_in& saddr) { + if (saddr.sin_family != AF_INET) + return false; + SetIP(NetworkToHost32(saddr.sin_addr.s_addr)); + SetPort(NetworkToHost16(saddr.sin_port)); + literal_ = false; + return true; +} + +static size_t ToSockAddrStorageHelper(sockaddr_storage* addr, + IPAddress ip, int port, int scope_id) { + memset(addr, 0, sizeof(sockaddr_storage)); + addr->ss_family = ip.family(); + if (addr->ss_family == AF_INET6) { + sockaddr_in6* saddr = reinterpret_cast(addr); + saddr->sin6_addr = ip.ipv6_address(); + saddr->sin6_port = HostToNetwork16(port); + saddr->sin6_scope_id = scope_id; + return sizeof(sockaddr_in6); + } else if (addr->ss_family == AF_INET) { + sockaddr_in* saddr = reinterpret_cast(addr); + saddr->sin_addr = ip.ipv4_address(); + saddr->sin_port = HostToNetwork16(port); + return sizeof(sockaddr_in); + } + return 0; +} + +size_t SocketAddress::ToDualStackSockAddrStorage(sockaddr_storage *addr) const { + return ToSockAddrStorageHelper(addr, ip_.AsIPv6Address(), port_, scope_id_); +} + +size_t SocketAddress::ToSockAddrStorage(sockaddr_storage* addr) const { + return ToSockAddrStorageHelper(addr, ip_, port_, scope_id_); +} + +std::string SocketAddress::IPToString(uint32 ip_as_host_order_integer) { + return IPAddress(ip_as_host_order_integer).ToString(); +} + +std::string IPToSensitiveString(uint32 ip_as_host_order_integer) { + return IPAddress(ip_as_host_order_integer).ToSensitiveString(); +} + +bool SocketAddress::StringToIP(const std::string& hostname, uint32* ip) { + in_addr addr; + if (rtc::inet_pton(AF_INET, hostname.c_str(), &addr) == 0) + return false; + *ip = NetworkToHost32(addr.s_addr); + return true; +} + +bool SocketAddress::StringToIP(const std::string& hostname, IPAddress* ip) { + in_addr addr4; + if (rtc::inet_pton(AF_INET, hostname.c_str(), &addr4) > 0) { + if (ip) { + *ip = IPAddress(addr4); + } + return true; + } + + in6_addr addr6; + if (rtc::inet_pton(AF_INET6, hostname.c_str(), &addr6) > 0) { + if (ip) { + *ip = IPAddress(addr6); + } + return true; + } + return false; +} + +uint32 SocketAddress::StringToIP(const std::string& hostname) { + uint32 ip = 0; + StringToIP(hostname, &ip); + return ip; +} + +bool SocketAddressFromSockAddrStorage(const sockaddr_storage& addr, + SocketAddress* out) { + if (!out) { + return false; + } + if (addr.ss_family == AF_INET) { + const sockaddr_in* saddr = reinterpret_cast(&addr); + *out = SocketAddress(IPAddress(saddr->sin_addr), + NetworkToHost16(saddr->sin_port)); + return true; + } else if (addr.ss_family == AF_INET6) { + const sockaddr_in6* saddr = reinterpret_cast(&addr); + *out = SocketAddress(IPAddress(saddr->sin6_addr), + NetworkToHost16(saddr->sin6_port)); + out->SetScopeID(saddr->sin6_scope_id); + return true; + } + return false; +} + +SocketAddress EmptySocketAddressWithFamily(int family) { + if (family == AF_INET) { + return SocketAddress(IPAddress(INADDR_ANY), 0); + } else if (family == AF_INET6) { + return SocketAddress(IPAddress(in6addr_any), 0); + } + return SocketAddress(); +} + +} // namespace rtc diff --git a/webrtc/base/socketaddress.h b/webrtc/base/socketaddress.h new file mode 100644 index 000000000..f8256fc62 --- /dev/null +++ b/webrtc/base/socketaddress.h @@ -0,0 +1,214 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKETADDRESS_H_ +#define WEBRTC_BASE_SOCKETADDRESS_H_ + +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/ipaddress.h" + +#undef SetPort + +struct sockaddr_in; +struct sockaddr_storage; + +namespace rtc { + +// Records an IP address and port. +class SocketAddress { + public: + // Creates a nil address. + SocketAddress(); + + // Creates the address with the given host and port. Host may be a + // literal IP string or a hostname to be resolved later. + SocketAddress(const std::string& hostname, int port); + + // Creates the address with the given IP and port. + // IP is given as an integer in host byte order. V4 only, to be deprecated. + SocketAddress(uint32 ip_as_host_order_integer, int port); + + // Creates the address with the given IP and port. + SocketAddress(const IPAddress& ip, int port); + + // Creates a copy of the given address. + SocketAddress(const SocketAddress& addr); + + // Resets to the nil address. + void Clear(); + + // Determines if this is a nil address (empty hostname, any IP, null port) + bool IsNil() const; + + // Returns true if ip and port are set. + bool IsComplete() const; + + // Replaces our address with the given one. + SocketAddress& operator=(const SocketAddress& addr); + + // Changes the IP of this address to the given one, and clears the hostname + // IP is given as an integer in host byte order. V4 only, to be deprecated.. + void SetIP(uint32 ip_as_host_order_integer); + + // Changes the IP of this address to the given one, and clears the hostname. + void SetIP(const IPAddress& ip); + + // Changes the hostname of this address to the given one. + // Does not resolve the address; use Resolve to do so. + void SetIP(const std::string& hostname); + + // Sets the IP address while retaining the hostname. Useful for bypassing + // DNS for a pre-resolved IP. + // IP is given as an integer in host byte order. V4 only, to be deprecated. + void SetResolvedIP(uint32 ip_as_host_order_integer); + + // Sets the IP address while retaining the hostname. Useful for bypassing + // DNS for a pre-resolved IP. + void SetResolvedIP(const IPAddress& ip); + + // Changes the port of this address to the given one. + void SetPort(int port); + + // Returns the hostname. + const std::string& hostname() const { return hostname_; } + + // Returns the IP address as a host byte order integer. + // Returns 0 for non-v4 addresses. + uint32 ip() const; + + const IPAddress& ipaddr() const; + + int family() const {return ip_.family(); } + + // Returns the port part of this address. + uint16 port() const; + + // Returns the scope ID associated with this address. Scope IDs are a + // necessary addition to IPv6 link-local addresses, with different network + // interfaces having different scope-ids for their link-local addresses. + // IPv4 address do not have scope_ids and sockaddr_in structures do not have + // a field for them. + int scope_id() const {return scope_id_; } + void SetScopeID(int id) { scope_id_ = id; } + + // Returns the 'host' portion of the address (hostname or IP) in a form + // suitable for use in a URI. If both IP and hostname are present, hostname + // is preferred. IPv6 addresses are enclosed in square brackets ('[' and ']'). + std::string HostAsURIString() const; + + // Same as HostAsURIString but anonymizes IP addresses by hiding the last + // part. + std::string HostAsSensitiveURIString() const; + + // Returns the port as a string. + std::string PortAsString() const; + + // Returns hostname:port or [hostname]:port. + std::string ToString() const; + + // Same as ToString but anonymizes it by hiding the last part. + std::string ToSensitiveString() const; + + // Parses hostname:port and [hostname]:port. + bool FromString(const std::string& str); + + friend std::ostream& operator<<(std::ostream& os, const SocketAddress& addr); + + // Determines whether this represents a missing / any IP address. + // That is, 0.0.0.0 or ::. + // Hostname and/or port may be set. + bool IsAnyIP() const; + inline bool IsAny() const { return IsAnyIP(); } // deprecated + + // Determines whether the IP address refers to a loopback address. + // For v4 addresses this means the address is in the range 127.0.0.0/8. + // For v6 addresses this means the address is ::1. + bool IsLoopbackIP() const; + + // Determines whether the IP address is in one of the private ranges: + // For v4: 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12. + // For v6: FE80::/16 and ::1. + bool IsPrivateIP() const; + + // Determines whether the hostname has been resolved to an IP. + bool IsUnresolvedIP() const; + inline bool IsUnresolved() const { return IsUnresolvedIP(); } // deprecated + + // Determines whether this address is identical to the given one. + bool operator ==(const SocketAddress& addr) const; + inline bool operator !=(const SocketAddress& addr) const { + return !this->operator ==(addr); + } + + // Compares based on IP and then port. + bool operator <(const SocketAddress& addr) const; + + // Determines whether this address has the same IP as the one given. + bool EqualIPs(const SocketAddress& addr) const; + + // Determines whether this address has the same port as the one given. + bool EqualPorts(const SocketAddress& addr) const; + + // Hashes this address into a small number. + size_t Hash() const; + + // Write this address to a sockaddr_in. + // If IPv6, will zero out the sockaddr_in and sets family to AF_UNSPEC. + void ToSockAddr(sockaddr_in* saddr) const; + + // Read this address from a sockaddr_in. + bool FromSockAddr(const sockaddr_in& saddr); + + // Read and write the address to/from a sockaddr_storage. + // Dual stack version always sets family to AF_INET6, and maps v4 addresses. + // The other version doesn't map, and outputs an AF_INET address for + // v4 or mapped addresses, and AF_INET6 addresses for others. + // Returns the size of the sockaddr_in or sockaddr_in6 structure that is + // written to the sockaddr_storage, or zero on failure. + size_t ToDualStackSockAddrStorage(sockaddr_storage* saddr) const; + size_t ToSockAddrStorage(sockaddr_storage* saddr) const; + + // Converts the IP address given in 'compact form' into dotted form. + // IP is given as an integer in host byte order. V4 only, to be deprecated. + // TODO: Deprecate this. + static std::string IPToString(uint32 ip_as_host_order_integer); + + // Same as IPToString but anonymizes it by hiding the last part. + // TODO: Deprecate this. + static std::string IPToSensitiveString(uint32 ip_as_host_order_integer); + + // Converts the IP address given in dotted form into compact form. + // Only dotted names (A.B.C.D) are converted. + // Output integer is returned in host byte order. + // TODO: Deprecate, replace wth agnostic versions. + static bool StringToIP(const std::string& str, uint32* ip); + static uint32 StringToIP(const std::string& str); + + // Converts the IP address given in printable form into an IPAddress. + static bool StringToIP(const std::string& str, IPAddress* ip); + + private: + std::string hostname_; + IPAddress ip_; + uint16 port_; + int scope_id_; + bool literal_; // Indicates that 'hostname_' contains a literal IP string. +}; + +bool SocketAddressFromSockAddrStorage(const sockaddr_storage& saddr, + SocketAddress* out); +SocketAddress EmptySocketAddressWithFamily(int family); + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETADDRESS_H_ diff --git a/webrtc/base/socketaddress_unittest.cc b/webrtc/base/socketaddress_unittest.cc new file mode 100644 index 000000000..6166183fe --- /dev/null +++ b/webrtc/base/socketaddress_unittest.cc @@ -0,0 +1,335 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_POSIX) +#include // for sockaddr_in +#endif + +#include "webrtc/base/gunit.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ipaddress.h" + +namespace rtc { + +const in6_addr kTestV6Addr = { { {0x20, 0x01, 0x0d, 0xb8, + 0x10, 0x20, 0x30, 0x40, + 0x50, 0x60, 0x70, 0x80, + 0x90, 0xA0, 0xB0, 0xC0} } }; +const in6_addr kMappedV4Addr = { { {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, + 0x01, 0x02, 0x03, 0x04} } }; +const std::string kTestV6AddrString = "2001:db8:1020:3040:5060:7080:90a0:b0c0"; +const std::string kTestV6AddrAnonymizedString = "2001:db8:1020::"; +const std::string kTestV6AddrFullString = + "[2001:db8:1020:3040:5060:7080:90a0:b0c0]:5678"; +const std::string kTestV6AddrFullAnonymizedString = "[2001:db8:1020::]:5678"; + +TEST(SocketAddressTest, TestDefaultCtor) { + SocketAddress addr; + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(0, addr.port()); + EXPECT_EQ("", addr.hostname()); +} + +TEST(SocketAddressTest, TestIPPortCtor) { + SocketAddress addr(IPAddress(0x01020304), 5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestIPv4StringPortCtor) { + SocketAddress addr("1.2.3.4", 5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestIPv6StringPortCtor) { + SocketAddress addr2(kTestV6AddrString, 1234); + IPAddress tocheck(kTestV6Addr); + + EXPECT_FALSE(addr2.IsUnresolvedIP()); + EXPECT_EQ(tocheck, addr2.ipaddr()); + EXPECT_EQ(1234, addr2.port()); + EXPECT_EQ(kTestV6AddrString, addr2.hostname()); + EXPECT_EQ("[" + kTestV6AddrString + "]:1234", addr2.ToString()); +} + +TEST(SocketAddressTest, TestSpecialStringPortCtor) { + // inet_addr doesn't handle this address properly. + SocketAddress addr("255.255.255.255", 5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0xFFFFFFFFU), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("255.255.255.255", addr.hostname()); + EXPECT_EQ("255.255.255.255:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestHostnamePortCtor) { + SocketAddress addr("a.b.com", 5678); + EXPECT_TRUE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestCopyCtor) { + SocketAddress from("1.2.3.4", 5678); + SocketAddress addr(from); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestAssign) { + SocketAddress from("1.2.3.4", 5678); + SocketAddress addr(IPAddress(0x88888888), 9999); + addr = from; + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestSetIPPort) { + SocketAddress addr(IPAddress(0x88888888), 9999); + addr.SetIP(IPAddress(0x01020304)); + addr.SetPort(5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestSetIPFromString) { + SocketAddress addr(IPAddress(0x88888888), 9999); + addr.SetIP("1.2.3.4"); + addr.SetPort(5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestSetIPFromHostname) { + SocketAddress addr(IPAddress(0x88888888), 9999); + addr.SetIP("a.b.com"); + addr.SetPort(5678); + EXPECT_TRUE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); + addr.SetResolvedIP(IPAddress(0x01020304)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestFromIPv4String) { + SocketAddress addr; + EXPECT_TRUE(addr.FromString("1.2.3.4:5678")); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestFromIPv6String) { + SocketAddress addr; + EXPECT_TRUE(addr.FromString(kTestV6AddrFullString)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ(kTestV6AddrString, addr.hostname()); + EXPECT_EQ(kTestV6AddrFullString, addr.ToString()); +} + +TEST(SocketAddressTest, TestFromHostname) { + SocketAddress addr; + EXPECT_TRUE(addr.FromString("a.b.com:5678")); + EXPECT_TRUE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestToFromSockAddr) { + SocketAddress from("1.2.3.4", 5678), addr; + sockaddr_in addr_in; + from.ToSockAddr(&addr_in); + EXPECT_TRUE(addr.FromSockAddr(addr_in)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestToFromSockAddrStorage) { + SocketAddress from("1.2.3.4", 5678), addr; + sockaddr_storage addr_storage; + from.ToSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); + + addr.Clear(); + from.ToDualStackSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(kMappedV4Addr), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("[::ffff:1.2.3.4]:5678", addr.ToString()); + + addr.Clear(); + memset(&addr_storage, 0, sizeof(sockaddr_storage)); + from = SocketAddress(kTestV6AddrString, 5678); + from.SetScopeID(6); + from.ToSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ(kTestV6AddrFullString, addr.ToString()); + EXPECT_EQ(6, addr.scope_id()); + + addr.Clear(); + from.ToDualStackSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ(kTestV6AddrFullString, addr.ToString()); + EXPECT_EQ(6, addr.scope_id()); + + addr = from; + addr_storage.ss_family = AF_UNSPEC; + EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_EQ(from, addr); + + EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, NULL)); +} + +bool AreEqual(const SocketAddress& addr1, + const SocketAddress& addr2) { + return addr1 == addr2 && addr2 == addr1 && + !(addr1 != addr2) && !(addr2 != addr1); +} + +bool AreUnequal(const SocketAddress& addr1, + const SocketAddress& addr2) { + return !(addr1 == addr2) && !(addr2 == addr1) && + addr1 != addr2 && addr2 != addr1; +} + +TEST(SocketAddressTest, TestEqualityOperators) { + SocketAddress addr1("1.2.3.4", 5678); + SocketAddress addr2("1.2.3.4", 5678); + EXPECT_PRED2(AreEqual, addr1, addr2); + + addr2 = SocketAddress("0.0.0.1", 5678); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr2 = SocketAddress("1.2.3.4", 1234); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr2 = SocketAddress(kTestV6AddrString, 5678); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr1 = SocketAddress(kTestV6AddrString, 5678); + EXPECT_PRED2(AreEqual, addr1, addr2); + + addr2 = SocketAddress(kTestV6AddrString, 1234); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr2 = SocketAddress("fe80::1", 5678); + EXPECT_PRED2(AreUnequal, addr1, addr2); +} + +bool IsLessThan(const SocketAddress& addr1, + const SocketAddress& addr2) { + return addr1 < addr2 && + !(addr2 < addr1) && + !(addr1 == addr2); +} + +TEST(SocketAddressTest, TestComparisonOperator) { + SocketAddress addr1("1.2.3.4", 5678); + SocketAddress addr2("1.2.3.4", 5678); + + EXPECT_FALSE(addr1 < addr2); + EXPECT_FALSE(addr2 < addr1); + + addr2 = SocketAddress("1.2.3.4", 5679); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr2 = SocketAddress("2.2.3.4", 49152); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr2 = SocketAddress(kTestV6AddrString, 5678); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr1 = SocketAddress("fe80::1", 5678); + EXPECT_PRED2(IsLessThan, addr2, addr1); + + addr2 = SocketAddress("fe80::1", 5679); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr2 = SocketAddress("fe80::1", 5678); + EXPECT_FALSE(addr1 < addr2); + EXPECT_FALSE(addr2 < addr1); +} + +TEST(SocketAddressTest, TestToSensitiveString) { + SocketAddress addr_v4("1.2.3.4", 5678); + EXPECT_EQ("1.2.3.4", addr_v4.HostAsURIString()); + EXPECT_EQ("1.2.3.4:5678", addr_v4.ToString()); + EXPECT_EQ("1.2.3.4", addr_v4.HostAsSensitiveURIString()); + EXPECT_EQ("1.2.3.4:5678", addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ("1.2.3.x", addr_v4.HostAsSensitiveURIString()); + EXPECT_EQ("1.2.3.x:5678", addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); + + SocketAddress addr_v6(kTestV6AddrString, 5678); + EXPECT_EQ("[" + kTestV6AddrString + "]", addr_v6.HostAsURIString()); + EXPECT_EQ(kTestV6AddrFullString, addr_v6.ToString()); + EXPECT_EQ("[" + kTestV6AddrString + "]", addr_v6.HostAsSensitiveURIString()); + EXPECT_EQ(kTestV6AddrFullString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ("[" + kTestV6AddrAnonymizedString + "]", + addr_v6.HostAsSensitiveURIString()); + EXPECT_EQ(kTestV6AddrFullAnonymizedString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); +} + +} // namespace rtc diff --git a/webrtc/base/socketaddresspair.cc b/webrtc/base/socketaddresspair.cc new file mode 100644 index 000000000..dfa8b25a0 --- /dev/null +++ b/webrtc/base/socketaddresspair.cc @@ -0,0 +1,41 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/socketaddresspair.h" + +namespace rtc { + +SocketAddressPair::SocketAddressPair( + const SocketAddress& src, const SocketAddress& dest) + : src_(src), dest_(dest) { +} + + +bool SocketAddressPair::operator ==(const SocketAddressPair& p) const { + return (src_ == p.src_) && (dest_ == p.dest_); +} + +bool SocketAddressPair::operator <(const SocketAddressPair& p) const { + if (src_ < p.src_) + return true; + if (p.src_ < src_) + return false; + if (dest_ < p.dest_) + return true; + if (p.dest_ < dest_) + return false; + return false; +} + +size_t SocketAddressPair::Hash() const { + return src_.Hash() ^ dest_.Hash(); +} + +} // namespace rtc diff --git a/webrtc/base/socketaddresspair.h b/webrtc/base/socketaddresspair.h new file mode 100644 index 000000000..73a627f10 --- /dev/null +++ b/webrtc/base/socketaddresspair.h @@ -0,0 +1,41 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKETADDRESSPAIR_H__ +#define WEBRTC_BASE_SOCKETADDRESSPAIR_H__ + +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +// Records a pair (source,destination) of socket addresses. The two addresses +// identify a connection between two machines. (For UDP, this "connection" is +// not maintained explicitly in a socket.) +class SocketAddressPair { +public: + SocketAddressPair() {} + SocketAddressPair(const SocketAddress& srs, const SocketAddress& dest); + + const SocketAddress& source() const { return src_; } + const SocketAddress& destination() const { return dest_; } + + bool operator ==(const SocketAddressPair& r) const; + bool operator <(const SocketAddressPair& r) const; + + size_t Hash() const; + +private: + SocketAddress src_; + SocketAddress dest_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETADDRESSPAIR_H__ diff --git a/webrtc/base/socketfactory.h b/webrtc/base/socketfactory.h new file mode 100644 index 000000000..fe0f32bdb --- /dev/null +++ b/webrtc/base/socketfactory.h @@ -0,0 +1,38 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKETFACTORY_H__ +#define WEBRTC_BASE_SOCKETFACTORY_H__ + +#include "webrtc/base/socket.h" +#include "webrtc/base/asyncsocket.h" + +namespace rtc { + +class SocketFactory { +public: + virtual ~SocketFactory() {} + + // Returns a new socket for blocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + // TODO: C++ inheritance rules mean that all users must have both + // CreateSocket(int) and CreateSocket(int,int). Will remove CreateSocket(int) + // (and CreateAsyncSocket(int) when all callers are changed. + virtual Socket* CreateSocket(int type) = 0; + virtual Socket* CreateSocket(int family, int type) = 0; + // Returns a new socket for nonblocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual AsyncSocket* CreateAsyncSocket(int type) = 0; + virtual AsyncSocket* CreateAsyncSocket(int family, int type) = 0; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETFACTORY_H__ diff --git a/webrtc/base/socketpool.cc b/webrtc/base/socketpool.cc new file mode 100644 index 000000000..8e61cc313 --- /dev/null +++ b/webrtc/base/socketpool.cc @@ -0,0 +1,280 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socketfactory.h" +#include "webrtc/base/socketpool.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// StreamCache - Caches a set of open streams, defers creation to a separate +// StreamPool. +/////////////////////////////////////////////////////////////////////////////// + +StreamCache::StreamCache(StreamPool* pool) : pool_(pool) { +} + +StreamCache::~StreamCache() { + for (ConnectedList::iterator it = active_.begin(); it != active_.end(); + ++it) { + delete it->second; + } + for (ConnectedList::iterator it = cached_.begin(); it != cached_.end(); + ++it) { + delete it->second; + } +} + +StreamInterface* StreamCache::RequestConnectedStream( + const SocketAddress& remote, int* err) { + LOG_F(LS_VERBOSE) << "(" << remote << ")"; + for (ConnectedList::iterator it = cached_.begin(); it != cached_.end(); + ++it) { + if (remote == it->first) { + it->second->SignalEvent.disconnect(this); + // Move from cached_ to active_ + active_.push_front(*it); + cached_.erase(it); + if (err) + *err = 0; + LOG_F(LS_VERBOSE) << "Providing cached stream"; + return active_.front().second; + } + } + if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) { + // We track active streams so that we can remember their address + active_.push_front(ConnectedStream(remote, stream)); + LOG_F(LS_VERBOSE) << "Providing new stream"; + return active_.front().second; + } + return NULL; +} + +void StreamCache::ReturnConnectedStream(StreamInterface* stream) { + for (ConnectedList::iterator it = active_.begin(); it != active_.end(); + ++it) { + if (stream == it->second) { + LOG_F(LS_VERBOSE) << "(" << it->first << ")"; + if (stream->GetState() == SS_CLOSED) { + // Return closed streams + LOG_F(LS_VERBOSE) << "Returning closed stream"; + pool_->ReturnConnectedStream(it->second); + } else { + // Monitor open streams + stream->SignalEvent.connect(this, &StreamCache::OnStreamEvent); + LOG_F(LS_VERBOSE) << "Caching stream"; + cached_.push_front(*it); + } + active_.erase(it); + return; + } + } + ASSERT(false); +} + +void StreamCache::OnStreamEvent(StreamInterface* stream, int events, int err) { + if ((events & SE_CLOSE) == 0) { + LOG_F(LS_WARNING) << "(" << events << ", " << err + << ") received non-close event"; + return; + } + for (ConnectedList::iterator it = cached_.begin(); it != cached_.end(); + ++it) { + if (stream == it->second) { + LOG_F(LS_VERBOSE) << "(" << it->first << ")"; + // We don't cache closed streams, so return it. + it->second->SignalEvent.disconnect(this); + LOG_F(LS_VERBOSE) << "Returning closed stream"; + pool_->ReturnConnectedStream(it->second); + cached_.erase(it); + return; + } + } + ASSERT(false); +} + +////////////////////////////////////////////////////////////////////// +// NewSocketPool +////////////////////////////////////////////////////////////////////// + +NewSocketPool::NewSocketPool(SocketFactory* factory) : factory_(factory) { +} + +NewSocketPool::~NewSocketPool() { +} + +StreamInterface* +NewSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) { + AsyncSocket* socket = + factory_->CreateAsyncSocket(remote.family(), SOCK_STREAM); + if (!socket) { + if (err) + *err = -1; + return NULL; + } + if ((socket->Connect(remote) != 0) && !socket->IsBlocking()) { + if (err) + *err = socket->GetError(); + delete socket; + return NULL; + } + if (err) + *err = 0; + return new SocketStream(socket); +} + +void +NewSocketPool::ReturnConnectedStream(StreamInterface* stream) { + Thread::Current()->Dispose(stream); +} + +////////////////////////////////////////////////////////////////////// +// ReuseSocketPool +////////////////////////////////////////////////////////////////////// + +ReuseSocketPool::ReuseSocketPool(SocketFactory* factory) +: factory_(factory), stream_(NULL), checked_out_(false) { +} + +ReuseSocketPool::~ReuseSocketPool() { + ASSERT(!checked_out_); + delete stream_; +} + +StreamInterface* +ReuseSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) { + // Only one socket can be used from this "pool" at a time + ASSERT(!checked_out_); + if (!stream_) { + LOG_F(LS_VERBOSE) << "Creating new socket"; + int family = remote.family(); + // TODO: Deal with this when we/I clean up DNS resolution. + if (remote.IsUnresolvedIP()) { + family = AF_INET; + } + AsyncSocket* socket = + factory_->CreateAsyncSocket(family, SOCK_STREAM); + if (!socket) { + if (err) + *err = -1; + return NULL; + } + stream_ = new SocketStream(socket); + } + if ((stream_->GetState() == SS_OPEN) && (remote == remote_)) { + LOG_F(LS_VERBOSE) << "Reusing connection to: " << remote_; + } else { + remote_ = remote; + stream_->Close(); + if ((stream_->GetSocket()->Connect(remote_) != 0) + && !stream_->GetSocket()->IsBlocking()) { + if (err) + *err = stream_->GetSocket()->GetError(); + return NULL; + } else { + LOG_F(LS_VERBOSE) << "Opening connection to: " << remote_; + } + } + stream_->SignalEvent.disconnect(this); + checked_out_ = true; + if (err) + *err = 0; + return stream_; +} + +void +ReuseSocketPool::ReturnConnectedStream(StreamInterface* stream) { + ASSERT(stream == stream_); + ASSERT(checked_out_); + checked_out_ = false; + // Until the socket is reused, monitor it to determine if it closes. + stream_->SignalEvent.connect(this, &ReuseSocketPool::OnStreamEvent); +} + +void +ReuseSocketPool::OnStreamEvent(StreamInterface* stream, int events, int err) { + ASSERT(stream == stream_); + ASSERT(!checked_out_); + + // If the stream was written to and then immediately returned to us then + // we may get a writable notification for it, which we should ignore. + if (events == SE_WRITE) { + LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly writable: ignoring"; + return; + } + + // If the peer sent data, we can't process it, so drop the connection. + // If the socket has closed, clean it up. + // In either case, we'll reconnect it the next time it is used. + ASSERT(0 != (events & (SE_READ|SE_CLOSE))); + if (0 != (events & SE_CLOSE)) { + LOG_F(LS_VERBOSE) << "Connection closed with error: " << err; + } else { + LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly readable: closing"; + } + stream_->Close(); +} + +/////////////////////////////////////////////////////////////////////////////// +// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached +// LoggingAdapters. +/////////////////////////////////////////////////////////////////////////////// + +LoggingPoolAdapter::LoggingPoolAdapter( + StreamPool* pool, LoggingSeverity level, const std::string& label, + bool binary_mode) + : pool_(pool), level_(level), label_(label), binary_mode_(binary_mode) { +} + +LoggingPoolAdapter::~LoggingPoolAdapter() { + for (StreamList::iterator it = recycle_bin_.begin(); + it != recycle_bin_.end(); ++it) { + delete *it; + } +} + +StreamInterface* LoggingPoolAdapter::RequestConnectedStream( + const SocketAddress& remote, int* err) { + if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) { + ASSERT(SS_CLOSED != stream->GetState()); + std::stringstream ss; + ss << label_ << "(0x" << std::setfill('0') << std::hex << std::setw(8) + << stream << ")"; + LOG_V(level_) << ss.str() + << ((SS_OPEN == stream->GetState()) ? " Connected" + : " Connecting") + << " to " << remote; + if (recycle_bin_.empty()) { + return new LoggingAdapter(stream, level_, ss.str(), binary_mode_); + } + LoggingAdapter* logging = recycle_bin_.front(); + recycle_bin_.pop_front(); + logging->set_label(ss.str()); + logging->Attach(stream); + return logging; + } + return NULL; +} + +void LoggingPoolAdapter::ReturnConnectedStream(StreamInterface* stream) { + LoggingAdapter* logging = static_cast(stream); + pool_->ReturnConnectedStream(logging->Detach()); + recycle_bin_.push_back(logging); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/socketpool.h b/webrtc/base/socketpool.h new file mode 100644 index 000000000..7bcaa062d --- /dev/null +++ b/webrtc/base/socketpool.h @@ -0,0 +1,143 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKETPOOL_H_ +#define WEBRTC_BASE_SOCKETPOOL_H_ + +#include +#include +#include "webrtc/base/logging.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +class AsyncSocket; +class LoggingAdapter; +class SocketFactory; +class SocketStream; +class StreamInterface; + +////////////////////////////////////////////////////////////////////// +// StreamPool +////////////////////////////////////////////////////////////////////// + +class StreamPool { +public: + virtual ~StreamPool() { } + + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err) = 0; + virtual void ReturnConnectedStream(StreamInterface* stream) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamCache - Caches a set of open streams, defers creation/destruction to +// the supplied StreamPool. +/////////////////////////////////////////////////////////////////////////////// + +class StreamCache : public StreamPool, public sigslot::has_slots<> { +public: + StreamCache(StreamPool* pool); + virtual ~StreamCache(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + typedef std::pair ConnectedStream; + typedef std::list ConnectedList; + + void OnStreamEvent(StreamInterface* stream, int events, int err); + + // We delegate stream creation and deletion to this pool. + StreamPool* pool_; + // Streams that are in use (returned from RequestConnectedStream). + ConnectedList active_; + // Streams which were returned to us, but are still open. + ConnectedList cached_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// NewSocketPool +// Creates a new stream on every request +/////////////////////////////////////////////////////////////////////////////// + +class NewSocketPool : public StreamPool { +public: + NewSocketPool(SocketFactory* factory); + virtual ~NewSocketPool(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + SocketFactory* factory_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ReuseSocketPool +// Maintains a single socket at a time, and will reuse it without closing if +// the destination address is the same. +/////////////////////////////////////////////////////////////////////////////// + +class ReuseSocketPool : public StreamPool, public sigslot::has_slots<> { +public: + ReuseSocketPool(SocketFactory* factory); + virtual ~ReuseSocketPool(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + void OnStreamEvent(StreamInterface* stream, int events, int err); + + SocketFactory* factory_; + SocketStream* stream_; + SocketAddress remote_; + bool checked_out_; // Whether the stream is currently checked out +}; + +/////////////////////////////////////////////////////////////////////////////// +// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached +// LoggingAdapters. +/////////////////////////////////////////////////////////////////////////////// + +class LoggingPoolAdapter : public StreamPool { +public: + LoggingPoolAdapter(StreamPool* pool, LoggingSeverity level, + const std::string& label, bool binary_mode); + virtual ~LoggingPoolAdapter(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + StreamPool* pool_; + LoggingSeverity level_; + std::string label_; + bool binary_mode_; + typedef std::deque StreamList; + StreamList recycle_bin_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETPOOL_H_ diff --git a/webrtc/base/socketserver.h b/webrtc/base/socketserver.h new file mode 100644 index 000000000..467105a6a --- /dev/null +++ b/webrtc/base/socketserver.h @@ -0,0 +1,44 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKETSERVER_H_ +#define WEBRTC_BASE_SOCKETSERVER_H_ + +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +class MessageQueue; + +// Provides the ability to wait for activity on a set of sockets. The Thread +// class provides a nice wrapper on a socket server. +// +// The server is also a socket factory. The sockets it creates will be +// notified of asynchronous I/O from this server's Wait method. +class SocketServer : public SocketFactory { + public: + // When the socket server is installed into a Thread, this function is + // called to allow the socket server to use the thread's message queue for + // any messaging that it might need to perform. + virtual void SetMessageQueue(MessageQueue* queue) {} + + // Sleeps until: + // 1) cms milliseconds have elapsed (unless cms == kForever) + // 2) WakeUp() is called + // While sleeping, I/O is performed if process_io is true. + virtual bool Wait(int cms, bool process_io) = 0; + + // Causes the current wait (if one is in progress) to wake up. + virtual void WakeUp() = 0; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETSERVER_H_ diff --git a/webrtc/base/socketstream.cc b/webrtc/base/socketstream.cc new file mode 100644 index 000000000..b0acf94c5 --- /dev/null +++ b/webrtc/base/socketstream.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/socketstream.h" + +namespace rtc { + +SocketStream::SocketStream(AsyncSocket* socket) : socket_(NULL) { + Attach(socket); +} + +SocketStream::~SocketStream() { + delete socket_; +} + +void SocketStream::Attach(AsyncSocket* socket) { + if (socket_) + delete socket_; + socket_ = socket; + if (socket_) { + socket_->SignalConnectEvent.connect(this, &SocketStream::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &SocketStream::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &SocketStream::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &SocketStream::OnCloseEvent); + } +} + +AsyncSocket* SocketStream::Detach() { + AsyncSocket* socket = socket_; + if (socket_) { + socket_->SignalConnectEvent.disconnect(this); + socket_->SignalReadEvent.disconnect(this); + socket_->SignalWriteEvent.disconnect(this); + socket_->SignalCloseEvent.disconnect(this); + socket_ = NULL; + } + return socket; +} + +StreamState SocketStream::GetState() const { + ASSERT(socket_ != NULL); + switch (socket_->GetState()) { + case Socket::CS_CONNECTED: + return SS_OPEN; + case Socket::CS_CONNECTING: + return SS_OPENING; + case Socket::CS_CLOSED: + default: + return SS_CLOSED; + } +} + +StreamResult SocketStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + ASSERT(socket_ != NULL); + int result = socket_->Recv(buffer, buffer_len); + if (result < 0) { + if (socket_->IsBlocking()) + return SR_BLOCK; + if (error) + *error = socket_->GetError(); + return SR_ERROR; + } + if ((result > 0) || (buffer_len == 0)) { + if (read) + *read = result; + return SR_SUCCESS; + } + return SR_EOS; +} + +StreamResult SocketStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + ASSERT(socket_ != NULL); + int result = socket_->Send(data, data_len); + if (result < 0) { + if (socket_->IsBlocking()) + return SR_BLOCK; + if (error) + *error = socket_->GetError(); + return SR_ERROR; + } + if (written) + *written = result; + return SR_SUCCESS; +} + +void SocketStream::Close() { + ASSERT(socket_ != NULL); + socket_->Close(); +} + +void SocketStream::OnConnectEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_OPEN | SE_READ | SE_WRITE, 0); +} + +void SocketStream::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_READ, 0); +} + +void SocketStream::OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_WRITE, 0); +} + +void SocketStream::OnCloseEvent(AsyncSocket* socket, int err) { + ASSERT(socket == socket_); + SignalEvent(this, SE_CLOSE, err); +} + + +} // namespace rtc diff --git a/webrtc/base/socketstream.h b/webrtc/base/socketstream.h new file mode 100644 index 000000000..ce9939b0f --- /dev/null +++ b/webrtc/base/socketstream.h @@ -0,0 +1,57 @@ +/* + * Copyright 2005 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SOCKETSTREAM_H_ +#define WEBRTC_BASE_SOCKETSTREAM_H_ + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class SocketStream : public StreamInterface, public sigslot::has_slots<> { + public: + explicit SocketStream(AsyncSocket* socket); + virtual ~SocketStream(); + + void Attach(AsyncSocket* socket); + AsyncSocket* Detach(); + + AsyncSocket* GetSocket() { return socket_; } + + virtual StreamState GetState() const; + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + virtual void Close(); + + private: + void OnConnectEvent(AsyncSocket* socket); + void OnReadEvent(AsyncSocket* socket); + void OnWriteEvent(AsyncSocket* socket); + void OnCloseEvent(AsyncSocket* socket, int err); + + AsyncSocket* socket_; + + DISALLOW_EVIL_CONSTRUCTORS(SocketStream); +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETSTREAM_H_ diff --git a/webrtc/base/ssladapter.cc b/webrtc/base/ssladapter.cc new file mode 100644 index 000000000..d83a2779e --- /dev/null +++ b/webrtc/base/ssladapter.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/ssladapter.h" + +#include "webrtc/base/sslconfig.h" + +#if SSL_USE_SCHANNEL + +#include "schanneladapter.h" + +#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL + +#include "openssladapter.h" + +#elif SSL_USE_NSS // && !SSL_USE_CHANNEL && !SSL_USE_OPENSSL + +#include "nssstreamadapter.h" + +#endif // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +SSLAdapter* +SSLAdapter::Create(AsyncSocket* socket) { +#if SSL_USE_SCHANNEL + return new SChannelAdapter(socket); +#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL + return new OpenSSLAdapter(socket); +#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL + delete socket; + return NULL; +#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL +} + +/////////////////////////////////////////////////////////////////////////////// + +#if SSL_USE_OPENSSL + +bool InitializeSSL(VerificationCallback callback) { + return OpenSSLAdapter::InitializeSSL(callback); +} + +bool InitializeSSLThread() { + return OpenSSLAdapter::InitializeSSLThread(); +} + +bool CleanupSSL() { + return OpenSSLAdapter::CleanupSSL(); +} + +#elif SSL_USE_NSS // !SSL_USE_OPENSSL + +bool InitializeSSL(VerificationCallback callback) { + return NSSContext::InitializeSSL(callback); +} + +bool InitializeSSLThread() { + return NSSContext::InitializeSSLThread(); +} + +bool CleanupSSL() { + return NSSContext::CleanupSSL(); +} + +#else // !SSL_USE_OPENSSL && !SSL_USE_NSS + +bool InitializeSSL(VerificationCallback callback) { + return true; +} + +bool InitializeSSLThread() { + return true; +} + +bool CleanupSSL() { + return true; +} + +#endif // !SSL_USE_OPENSSL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/ssladapter.h b/webrtc/base/ssladapter.h new file mode 100644 index 000000000..87b993ffb --- /dev/null +++ b/webrtc/base/ssladapter.h @@ -0,0 +1,61 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SSLADAPTER_H_ +#define WEBRTC_BASE_SSLADAPTER_H_ + +#include "webrtc/base/asyncsocket.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class SSLAdapter : public AsyncSocketAdapter { + public: + explicit SSLAdapter(AsyncSocket* socket) + : AsyncSocketAdapter(socket), ignore_bad_cert_(false) { } + + bool ignore_bad_cert() const { return ignore_bad_cert_; } + void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; } + + // StartSSL returns 0 if successful. + // If StartSSL is called while the socket is closed or connecting, the SSL + // negotiation will begin as soon as the socket connects. + virtual int StartSSL(const char* hostname, bool restartable) = 0; + + // Create the default SSL adapter for this platform. On failure, returns NULL + // and deletes |socket|. Otherwise, the returned SSLAdapter takes ownership + // of |socket|. + static SSLAdapter* Create(AsyncSocket* socket); + + private: + // If true, the server certificate need not match the configured hostname. + bool ignore_bad_cert_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +typedef bool (*VerificationCallback)(void* cert); + +// Call this on the main thread, before using SSL. +// Call CleanupSSLThread when finished with SSL. +bool InitializeSSL(VerificationCallback callback = NULL); + +// Call to initialize additional threads. +bool InitializeSSLThread(); + +// Call to cleanup additional threads, and also the main thread. +bool CleanupSSL(); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLADAPTER_H_ diff --git a/webrtc/base/sslconfig.h b/webrtc/base/sslconfig.h new file mode 100644 index 000000000..d824ab062 --- /dev/null +++ b/webrtc/base/sslconfig.h @@ -0,0 +1,33 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SSLCONFIG_H_ +#define WEBRTC_BASE_SSLCONFIG_H_ + +// If no preference has been indicated, default to SChannel on Windows and +// OpenSSL everywhere else, if it is available. +#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL) && \ + !defined(SSL_USE_NSS) +#if defined(WEBRTC_WIN) + +#define SSL_USE_SCHANNEL 1 + +#else // defined(WEBRTC_WIN) + +#if defined(HAVE_OPENSSL_SSL_H) +#define SSL_USE_OPENSSL 1 +#elif defined(HAVE_NSS_SSL_H) +#define SSL_USE_NSS 1 +#endif + +#endif // !defined(WEBRTC_WIN) +#endif + +#endif // WEBRTC_BASE_SSLCONFIG_H_ diff --git a/webrtc/base/sslfingerprint.cc b/webrtc/base/sslfingerprint.cc new file mode 100644 index 000000000..1419243c8 --- /dev/null +++ b/webrtc/base/sslfingerprint.cc @@ -0,0 +1,96 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/sslfingerprint.h" + +#include +#include + +#include "webrtc/base/helpers.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +SSLFingerprint* SSLFingerprint::Create( + const std::string& algorithm, const rtc::SSLIdentity* identity) { + if (!identity) { + return NULL; + } + + return Create(algorithm, &(identity->certificate())); +} + +SSLFingerprint* SSLFingerprint::Create( + const std::string& algorithm, const rtc::SSLCertificate* cert) { + uint8 digest_val[64]; + size_t digest_len; + bool ret = cert->ComputeDigest( + algorithm, digest_val, sizeof(digest_val), &digest_len); + if (!ret) { + return NULL; + } + + return new SSLFingerprint(algorithm, digest_val, digest_len); +} + +SSLFingerprint* SSLFingerprint::CreateFromRfc4572( + const std::string& algorithm, const std::string& fingerprint) { + if (algorithm.empty() || !rtc::IsFips180DigestAlgorithm(algorithm)) + return NULL; + + if (fingerprint.empty()) + return NULL; + + size_t value_len; + char value[rtc::MessageDigest::kMaxSize]; + value_len = rtc::hex_decode_with_delimiter(value, sizeof(value), + fingerprint.c_str(), + fingerprint.length(), + ':'); + if (!value_len) + return NULL; + + return new SSLFingerprint(algorithm, + reinterpret_cast(value), + value_len); +} + +SSLFingerprint::SSLFingerprint( + const std::string& algorithm, const uint8* digest_in, size_t digest_len) + : algorithm(algorithm) { + digest.SetData(digest_in, digest_len); +} + +SSLFingerprint::SSLFingerprint(const SSLFingerprint& from) + : algorithm(from.algorithm), digest(from.digest) {} + +bool SSLFingerprint::operator==(const SSLFingerprint& other) const { + return algorithm == other.algorithm && + digest == other.digest; +} + +std::string SSLFingerprint::GetRfc4572Fingerprint() const { + std::string fingerprint = + rtc::hex_encode_with_delimiter( + digest.data(), digest.length(), ':'); + std::transform(fingerprint.begin(), fingerprint.end(), + fingerprint.begin(), ::toupper); + return fingerprint; +} + +std::string SSLFingerprint::ToString() { + std::string fp_str = algorithm; + fp_str.append(" "); + fp_str.append(GetRfc4572Fingerprint()); + return fp_str; +} + +} // namespace rtc diff --git a/webrtc/base/sslfingerprint.h b/webrtc/base/sslfingerprint.h new file mode 100644 index 000000000..a63b3dd87 --- /dev/null +++ b/webrtc/base/sslfingerprint.h @@ -0,0 +1,50 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SSLFINGERPRINT_H_ +#define WEBRTC_BASE_SSLFINGERPRINT_H_ + +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +class SSLCertificate; + +struct SSLFingerprint { + static SSLFingerprint* Create(const std::string& algorithm, + const rtc::SSLIdentity* identity); + + static SSLFingerprint* Create(const std::string& algorithm, + const rtc::SSLCertificate* cert); + + static SSLFingerprint* CreateFromRfc4572(const std::string& algorithm, + const std::string& fingerprint); + + SSLFingerprint(const std::string& algorithm, const uint8* digest_in, + size_t digest_len); + + SSLFingerprint(const SSLFingerprint& from); + + bool operator==(const SSLFingerprint& other) const; + + std::string GetRfc4572Fingerprint() const; + + std::string ToString(); + + std::string algorithm; + rtc::Buffer digest; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLFINGERPRINT_H_ diff --git a/webrtc/base/sslidentity.cc b/webrtc/base/sslidentity.cc new file mode 100644 index 000000000..00085740d --- /dev/null +++ b/webrtc/base/sslidentity.cc @@ -0,0 +1,154 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Handling of certificates and keypairs for SSLStreamAdapter's peer mode. +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/sslidentity.h" + +#include + +#include "webrtc/base/base64.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/sslconfig.h" + +#if SSL_USE_SCHANNEL + +#elif SSL_USE_OPENSSL // !SSL_USE_SCHANNEL + +#include "webrtc/base/opensslidentity.h" + +#elif SSL_USE_NSS // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + +#include "webrtc/base/nssidentity.h" + +#endif // SSL_USE_SCHANNEL + +namespace rtc { + +const char kPemTypeCertificate[] = "CERTIFICATE"; +const char kPemTypeRsaPrivateKey[] = "RSA PRIVATE KEY"; + +bool SSLIdentity::PemToDer(const std::string& pem_type, + const std::string& pem_string, + std::string* der) { + // Find the inner body. We need this to fulfill the contract of + // returning pem_length. + size_t header = pem_string.find("-----BEGIN " + pem_type + "-----"); + if (header == std::string::npos) + return false; + + size_t body = pem_string.find("\n", header); + if (body == std::string::npos) + return false; + + size_t trailer = pem_string.find("-----END " + pem_type + "-----"); + if (trailer == std::string::npos) + return false; + + std::string inner = pem_string.substr(body + 1, trailer - (body + 1)); + + *der = Base64::Decode(inner, Base64::DO_PARSE_WHITE | + Base64::DO_PAD_ANY | + Base64::DO_TERM_BUFFER); + return true; +} + +std::string SSLIdentity::DerToPem(const std::string& pem_type, + const unsigned char* data, + size_t length) { + std::stringstream result; + + result << "-----BEGIN " << pem_type << "-----\n"; + + std::string b64_encoded; + Base64::EncodeFromArray(data, length, &b64_encoded); + + // Divide the Base-64 encoded data into 64-character chunks, as per + // 4.3.2.4 of RFC 1421. + static const size_t kChunkSize = 64; + size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize; + for (size_t i = 0, chunk_offset = 0; i < chunks; + ++i, chunk_offset += kChunkSize) { + result << b64_encoded.substr(chunk_offset, kChunkSize); + result << "\n"; + } + + result << "-----END " << pem_type << "-----\n"; + + return result.str(); +} + +#if SSL_USE_SCHANNEL + +SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) { + return NULL; +} + +SSLIdentity* SSLIdentity::Generate(const std::string& common_name) { + return NULL; +} + +SSLIdentity* GenerateForTest(const SSLIdentityParams& params) { + return NULL; +} + +SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + return NULL; +} + +#elif SSL_USE_OPENSSL // !SSL_USE_SCHANNEL + +SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) { + return OpenSSLCertificate::FromPEMString(pem_string); +} + +SSLIdentity* SSLIdentity::Generate(const std::string& common_name) { + return OpenSSLIdentity::Generate(common_name); +} + +SSLIdentity* SSLIdentity::GenerateForTest(const SSLIdentityParams& params) { + return OpenSSLIdentity::GenerateForTest(params); +} + +SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + return OpenSSLIdentity::FromPEMStrings(private_key, certificate); +} + +#elif SSL_USE_NSS // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL + +SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) { + return NSSCertificate::FromPEMString(pem_string); +} + +SSLIdentity* SSLIdentity::Generate(const std::string& common_name) { + return NSSIdentity::Generate(common_name); +} + +SSLIdentity* SSLIdentity::GenerateForTest(const SSLIdentityParams& params) { + return NSSIdentity::GenerateForTest(params); +} + +SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + return NSSIdentity::FromPEMStrings(private_key, certificate); +} + +#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS + +#error "No SSL implementation" + +#endif // SSL_USE_SCHANNEL + +} // namespace rtc diff --git a/webrtc/base/sslidentity.h b/webrtc/base/sslidentity.h new file mode 100644 index 000000000..a0f32fd3b --- /dev/null +++ b/webrtc/base/sslidentity.h @@ -0,0 +1,172 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Handling of certificates and keypairs for SSLStreamAdapter's peer mode. + +#ifndef WEBRTC_BASE_SSLIDENTITY_H_ +#define WEBRTC_BASE_SSLIDENTITY_H_ + +#include +#include +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/messagedigest.h" + +namespace rtc { + +// Forward declaration due to circular dependency with SSLCertificate. +class SSLCertChain; + +// Abstract interface overridden by SSL library specific +// implementations. + +// A somewhat opaque type used to encapsulate a certificate. +// Wraps the SSL library's notion of a certificate, with reference counting. +// The SSLCertificate object is pretty much immutable once created. +// (The OpenSSL implementation only does reference counting and +// possibly caching of intermediate results.) +class SSLCertificate { + public: + // Parses and build a certificate from a PEM encoded string. + // Returns NULL on failure. + // The length of the string representation of the certificate is + // stored in *pem_length if it is non-NULL, and only if + // parsing was successful. + // Caller is responsible for freeing the returned object. + static SSLCertificate* FromPEMString(const std::string& pem_string); + virtual ~SSLCertificate() {} + + // Returns a new SSLCertificate object instance wrapping the same + // underlying certificate, including its chain if present. + // Caller is responsible for freeing the returned object. + virtual SSLCertificate* GetReference() const = 0; + + // Provides the cert chain, or returns false. The caller owns the chain. + // The chain includes a copy of each certificate, excluding the leaf. + virtual bool GetChain(SSLCertChain** chain) const = 0; + + // Returns a PEM encoded string representation of the certificate. + virtual std::string ToPEMString() const = 0; + + // Provides a DER encoded binary representation of the certificate. + virtual void ToDER(Buffer* der_buffer) const = 0; + + // Gets the name of the digest algorithm that was used to compute this + // certificate's signature. + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const = 0; + + // Compute the digest of the certificate given algorithm + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const = 0; +}; + +// SSLCertChain is a simple wrapper for a vector of SSLCertificates. It serves +// primarily to ensure proper memory management (especially deletion) of the +// SSLCertificate pointers. +class SSLCertChain { + public: + // These constructors copy the provided SSLCertificate(s), so the caller + // retains ownership. + explicit SSLCertChain(const std::vector& certs) { + ASSERT(!certs.empty()); + certs_.resize(certs.size()); + std::transform(certs.begin(), certs.end(), certs_.begin(), DupCert); + } + explicit SSLCertChain(const SSLCertificate* cert) { + certs_.push_back(cert->GetReference()); + } + + ~SSLCertChain() { + std::for_each(certs_.begin(), certs_.end(), DeleteCert); + } + + // Vector access methods. + size_t GetSize() const { return certs_.size(); } + + // Returns a temporary reference, only valid until the chain is destroyed. + const SSLCertificate& Get(size_t pos) const { return *(certs_[pos]); } + + // Returns a new SSLCertChain object instance wrapping the same underlying + // certificate chain. Caller is responsible for freeing the returned object. + SSLCertChain* Copy() const { + return new SSLCertChain(certs_); + } + + private: + // Helper function for duplicating a vector of certificates. + static SSLCertificate* DupCert(const SSLCertificate* cert) { + return cert->GetReference(); + } + + // Helper function for deleting a vector of certificates. + static void DeleteCert(SSLCertificate* cert) { delete cert; } + + std::vector certs_; + + DISALLOW_COPY_AND_ASSIGN(SSLCertChain); +}; + +// Parameters for generating an identity for testing. If common_name is +// non-empty, it will be used for the certificate's subject and issuer name, +// otherwise a random string will be used. |not_before| and |not_after| are +// offsets to the current time in number of seconds. +struct SSLIdentityParams { + std::string common_name; + int not_before; // in seconds. + int not_after; // in seconds. +}; + +// Our identity in an SSL negotiation: a keypair and certificate (both +// with the same public key). +// This too is pretty much immutable once created. +class SSLIdentity { + public: + // Generates an identity (keypair and self-signed certificate). If + // common_name is non-empty, it will be used for the certificate's + // subject and issuer name, otherwise a random string will be used. + // Returns NULL on failure. + // Caller is responsible for freeing the returned object. + static SSLIdentity* Generate(const std::string& common_name); + + // Generates an identity with the specified validity period. + static SSLIdentity* GenerateForTest(const SSLIdentityParams& params); + + // Construct an identity from a private key and a certificate. + static SSLIdentity* FromPEMStrings(const std::string& private_key, + const std::string& certificate); + + virtual ~SSLIdentity() {} + + // Returns a new SSLIdentity object instance wrapping the same + // identity information. + // Caller is responsible for freeing the returned object. + virtual SSLIdentity* GetReference() const = 0; + + // Returns a temporary reference to the certificate. + virtual const SSLCertificate& certificate() const = 0; + + // Helpers for parsing converting between PEM and DER format. + static bool PemToDer(const std::string& pem_type, + const std::string& pem_string, + std::string* der); + static std::string DerToPem(const std::string& pem_type, + const unsigned char* data, + size_t length); +}; + +extern const char kPemTypeCertificate[]; +extern const char kPemTypeRsaPrivateKey[]; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLIDENTITY_H_ diff --git a/webrtc/base/sslidentity_unittest.cc b/webrtc/base/sslidentity_unittest.cc new file mode 100644 index 000000000..1486bebb5 --- /dev/null +++ b/webrtc/base/sslidentity_unittest.cc @@ -0,0 +1,208 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslidentity.h" + +using rtc::SSLIdentity; + +const char kTestCertificate[] = "-----BEGIN CERTIFICATE-----\n" + "MIIB6TCCAVICAQYwDQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCQVUxEzARBgNV\n" + "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRswGQYD\n" + "VQQDExJUZXN0IENBICgxMDI0IGJpdCkwHhcNMDAxMDE2MjIzMTAzWhcNMDMwMTE0\n" + "MjIzMTAzWjBjMQswCQYDVQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFuZDEaMBgG\n" + "A1UEChMRQ3J5cHRTb2Z0IFB0eSBMdGQxIzAhBgNVBAMTGlNlcnZlciB0ZXN0IGNl\n" + "cnQgKDUxMiBiaXQpMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ+zw4Qnlf8SMVIP\n" + "Fe9GEcStgOY2Ww/dgNdhjeD8ckUJNP5VZkVDTGiXav6ooKXfX3j/7tdkuD8Ey2//\n" + "Kv7+ue0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCT0grFQeZaqYb5EYfk20XixZV4\n" + "GmyAbXMftG1Eo7qGiMhYzRwGNWxEYojf5PZkYZXvSqZ/ZXHXa4g59jK/rJNnaVGM\n" + "k+xIX8mxQvlV0n5O9PIha5BX5teZnkHKgL8aKKLKW1BK7YTngsfSzzaeame5iKfz\n" + "itAE+OjGF+PFKbwX8Q==\n" + "-----END CERTIFICATE-----\n"; + +const unsigned char kTestCertSha1[] = {0xA6, 0xC8, 0x59, 0xEA, + 0xC3, 0x7E, 0x6D, 0x33, + 0xCF, 0xE2, 0x69, 0x9D, + 0x74, 0xE6, 0xF6, 0x8A, + 0x9E, 0x47, 0xA7, 0xCA}; + +class SSLIdentityTest : public testing::Test { + public: + SSLIdentityTest() : + identity1_(), identity2_() { + } + + ~SSLIdentityTest() { + } + + static void SetUpTestCase() { + rtc::InitializeSSL(); + } + + static void TearDownTestCase() { + rtc::CleanupSSL(); + } + + virtual void SetUp() { + identity1_.reset(SSLIdentity::Generate("test1")); + identity2_.reset(SSLIdentity::Generate("test2")); + + ASSERT_TRUE(identity1_); + ASSERT_TRUE(identity2_); + + test_cert_.reset( + rtc::SSLCertificate::FromPEMString(kTestCertificate)); + ASSERT_TRUE(test_cert_); + } + + void TestGetSignatureDigestAlgorithm() { + std::string digest_algorithm; + // Both NSSIdentity::Generate and OpenSSLIdentity::Generate are + // hard-coded to generate RSA-SHA1 certificates. + ASSERT_TRUE(identity1_->certificate().GetSignatureDigestAlgorithm( + &digest_algorithm)); + ASSERT_EQ(rtc::DIGEST_SHA_1, digest_algorithm); + ASSERT_TRUE(identity2_->certificate().GetSignatureDigestAlgorithm( + &digest_algorithm)); + ASSERT_EQ(rtc::DIGEST_SHA_1, digest_algorithm); + + // The test certificate has an MD5-based signature. + ASSERT_TRUE(test_cert_->GetSignatureDigestAlgorithm(&digest_algorithm)); + ASSERT_EQ(rtc::DIGEST_MD5, digest_algorithm); + } + + void TestDigest(const std::string &algorithm, size_t expected_len, + const unsigned char *expected_digest = NULL) { + unsigned char digest1[64]; + unsigned char digest1b[64]; + unsigned char digest2[64]; + size_t digest1_len; + size_t digest1b_len; + size_t digest2_len; + bool rv; + + rv = identity1_->certificate().ComputeDigest(algorithm, + digest1, sizeof(digest1), + &digest1_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest1_len); + + rv = identity1_->certificate().ComputeDigest(algorithm, + digest1b, sizeof(digest1b), + &digest1b_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest1b_len); + EXPECT_EQ(0, memcmp(digest1, digest1b, expected_len)); + + + rv = identity2_->certificate().ComputeDigest(algorithm, + digest2, sizeof(digest2), + &digest2_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest2_len); + EXPECT_NE(0, memcmp(digest1, digest2, expected_len)); + + // If we have an expected hash for the test cert, check it. + if (expected_digest) { + unsigned char digest3[64]; + size_t digest3_len; + + rv = test_cert_->ComputeDigest(algorithm, digest3, sizeof(digest3), + &digest3_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest3_len); + EXPECT_EQ(0, memcmp(digest3, expected_digest, expected_len)); + } + } + + private: + rtc::scoped_ptr identity1_; + rtc::scoped_ptr identity2_; + rtc::scoped_ptr test_cert_; +}; + +TEST_F(SSLIdentityTest, DigestSHA1) { + TestDigest(rtc::DIGEST_SHA_1, 20, kTestCertSha1); +} + +// HASH_AlgSHA224 is not supported in the chromium linux build. +#if SSL_USE_NSS +TEST_F(SSLIdentityTest, DISABLED_DigestSHA224) { +#else +TEST_F(SSLIdentityTest, DigestSHA224) { +#endif + TestDigest(rtc::DIGEST_SHA_224, 28); +} + +TEST_F(SSLIdentityTest, DigestSHA256) { + TestDigest(rtc::DIGEST_SHA_256, 32); +} + +TEST_F(SSLIdentityTest, DigestSHA384) { + TestDigest(rtc::DIGEST_SHA_384, 48); +} + +TEST_F(SSLIdentityTest, DigestSHA512) { + TestDigest(rtc::DIGEST_SHA_512, 64); +} + +TEST_F(SSLIdentityTest, FromPEMStrings) { + static const char kRSA_PRIVATE_KEY_PEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAECgYAvgOs4FJcgvp+TuREx7YtiYVsH\n" + "mwQPTum2z/8VzWGwR8BBHBvIpVe1MbD/Y4seyI2aco/7UaisatSgJhsU46/9Y4fq\n" + "2TwXH9QANf4at4d9n/R6rzwpAJOpgwZgKvdQjkfrKTtgLV+/dawvpxUYkRH4JZM1\n" + "CVGukMfKNrSVH4Ap4QJBAOJmGV1ASPnB4r4nc99at7JuIJmd7fmuVUwUgYi4XgaR\n" + "WhScBsgYwZ/JoywdyZJgnbcrTDuVcWG56B3vXbhdpMsCQQDf9zeJrjnPZ3Cqm79y\n" + "kdqANep0uwZciiNiWxsQrCHztywOvbFhdp8iYVFG9EK8DMY41Y5TxUwsHD+67zao\n" + "ZNqJAkEA1suLUP/GvL8IwuRneQd2tWDqqRQ/Td3qq03hP7e77XtF/buya3Ghclo5\n" + "54czUR89QyVfJEC6278nzA7n2h1uVQJAcG6mztNL6ja/dKZjYZye2CY44QjSlLo0\n" + "MTgTSjdfg/28fFn2Jjtqf9Pi/X+50LWI/RcYMC2no606wRk9kyOuIQJBAK6VSAim\n" + "1pOEjsYQn0X5KEIrz1G3bfCbB848Ime3U2/FWlCHMr6ch8kCZ5d1WUeJD3LbwMNG\n" + "UCXiYxSsu20QNVw=\n" + "-----END RSA PRIVATE KEY-----\n"; + + static const char kCERT_PEM[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIBmTCCAQKgAwIBAgIEbzBSAjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZX\n" + "ZWJSVEMwHhcNMTQwMTAyMTgyNDQ3WhcNMTQwMjAxMTgyNDQ3WjARMQ8wDQYDVQQD\n" + "EwZXZWJSVEMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAUflI\n" + "VUe5Krqf5RVa5C3u/UTAOAUJBiDS3VANTCLBxjuMsvqOG0WvaYWP3HYPgrz0jXK2\n" + "LJE/mGw3MyFHEqi81jh95J+ypl6xKW6Rm8jKLR87gUvCaVYn/Z4/P3AqcQTB7wOv\n" + "UD0A8qfhfDM+LK6rPAnCsVN0NRDY3jvd6rzix9M=\n" + "-----END CERTIFICATE-----\n"; + + rtc::scoped_ptr identity( + SSLIdentity::FromPEMStrings(kRSA_PRIVATE_KEY_PEM, kCERT_PEM)); + EXPECT_TRUE(identity); + EXPECT_EQ(kCERT_PEM, identity->certificate().ToPEMString()); +} + +TEST_F(SSLIdentityTest, PemDerConversion) { + std::string der; + EXPECT_TRUE(SSLIdentity::PemToDer("CERTIFICATE", kTestCertificate, &der)); + + EXPECT_EQ(kTestCertificate, SSLIdentity::DerToPem( + "CERTIFICATE", + reinterpret_cast(der.data()), der.length())); +} + +TEST_F(SSLIdentityTest, GetSignatureDigestAlgorithm) { + TestGetSignatureDigestAlgorithm(); +} diff --git a/webrtc/base/sslroots.h b/webrtc/base/sslroots.h new file mode 100644 index 000000000..0f983cd60 --- /dev/null +++ b/webrtc/base/sslroots.h @@ -0,0 +1,4930 @@ +// This file is the root certificates in C form that are needed to connect to +// Google. + +// It was generated with the following command line: +// > python //depot/googleclient/talk/tools/generate_sslroots.py +// //depot/google3/security/cacerts/for_connecting_to_google/roots.pem + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root */ + + +const unsigned char AddTrust_External_Root_certificate[1082]={ +0x30,0x82,0x04,0x36,0x30,0x82,0x03,0x1E,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,0x13,0x1D,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C, +0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x22,0x30,0x20, +0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20, +0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74, +0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30,0x31,0x30,0x34,0x38,0x33,0x38, +0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31,0x30,0x34,0x38,0x33,0x38,0x5A, +0x30,0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31, +0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75, +0x73,0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,0x13,0x1D, +0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61, +0x6C,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x22,0x30, +0x20,0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74, +0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F, +0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01, +0x01,0x00,0xB7,0xF7,0x1A,0x33,0xE6,0xF2,0x00,0x04,0x2D,0x39,0xE0,0x4E,0x5B,0xED, +0x1F,0xBC,0x6C,0x0F,0xCD,0xB5,0xFA,0x23,0xB6,0xCE,0xDE,0x9B,0x11,0x33,0x97,0xA4, +0x29,0x4C,0x7D,0x93,0x9F,0xBD,0x4A,0xBC,0x93,0xED,0x03,0x1A,0xE3,0x8F,0xCF,0xE5, +0x6D,0x50,0x5A,0xD6,0x97,0x29,0x94,0x5A,0x80,0xB0,0x49,0x7A,0xDB,0x2E,0x95,0xFD, +0xB8,0xCA,0xBF,0x37,0x38,0x2D,0x1E,0x3E,0x91,0x41,0xAD,0x70,0x56,0xC7,0xF0,0x4F, +0x3F,0xE8,0x32,0x9E,0x74,0xCA,0xC8,0x90,0x54,0xE9,0xC6,0x5F,0x0F,0x78,0x9D,0x9A, +0x40,0x3C,0x0E,0xAC,0x61,0xAA,0x5E,0x14,0x8F,0x9E,0x87,0xA1,0x6A,0x50,0xDC,0xD7, +0x9A,0x4E,0xAF,0x05,0xB3,0xA6,0x71,0x94,0x9C,0x71,0xB3,0x50,0x60,0x0A,0xC7,0x13, +0x9D,0x38,0x07,0x86,0x02,0xA8,0xE9,0xA8,0x69,0x26,0x18,0x90,0xAB,0x4C,0xB0,0x4F, +0x23,0xAB,0x3A,0x4F,0x84,0xD8,0xDF,0xCE,0x9F,0xE1,0x69,0x6F,0xBB,0xD7,0x42,0xD7, +0x6B,0x44,0xE4,0xC7,0xAD,0xEE,0x6D,0x41,0x5F,0x72,0x5A,0x71,0x08,0x37,0xB3,0x79, +0x65,0xA4,0x59,0xA0,0x94,0x37,0xF7,0x00,0x2F,0x0D,0xC2,0x92,0x72,0xDA,0xD0,0x38, +0x72,0xDB,0x14,0xA8,0x45,0xC4,0x5D,0x2A,0x7D,0xB7,0xB4,0xD6,0xC4,0xEE,0xAC,0xCD, +0x13,0x44,0xB7,0xC9,0x2B,0xDD,0x43,0x00,0x25,0xFA,0x61,0xB9,0x69,0x6A,0x58,0x23, +0x11,0xB7,0xA7,0x33,0x8F,0x56,0x75,0x59,0xF5,0xCD,0x29,0xD7,0x46,0xB7,0x0A,0x2B, +0x65,0xB6,0xD3,0x42,0x6F,0x15,0xB2,0xB8,0x7B,0xFB,0xEF,0xE9,0x5D,0x53,0xD5,0x34, +0x5A,0x27,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xDC,0x30,0x81,0xD9,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xAD,0xBD,0x98,0x7A,0x34,0xB4,0x26,0xF7, +0xFA,0xC4,0x26,0x54,0xEF,0x03,0xBD,0xE0,0x24,0xCB,0x54,0x1A,0x30,0x0B,0x06,0x03, +0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13, +0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x99,0x06,0x03,0x55, +0x1D,0x23,0x04,0x81,0x91,0x30,0x81,0x8E,0x80,0x14,0xAD,0xBD,0x98,0x7A,0x34,0xB4, +0x26,0xF7,0xFA,0xC4,0x26,0x54,0xEF,0x03,0xBD,0xE0,0x24,0xCB,0x54,0x1A,0xA1,0x73, +0xA4,0x71,0x30,0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53, +0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54, +0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B, +0x13,0x1D,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72, +0x6E,0x61,0x6C,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31, +0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75, +0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52, +0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xB0,0x9B,0xE0,0x85,0x25,0xC2, +0xD6,0x23,0xE2,0x0F,0x96,0x06,0x92,0x9D,0x41,0x98,0x9C,0xD9,0x84,0x79,0x81,0xD9, +0x1E,0x5B,0x14,0x07,0x23,0x36,0x65,0x8F,0xB0,0xD8,0x77,0xBB,0xAC,0x41,0x6C,0x47, +0x60,0x83,0x51,0xB0,0xF9,0x32,0x3D,0xE7,0xFC,0xF6,0x26,0x13,0xC7,0x80,0x16,0xA5, +0xBF,0x5A,0xFC,0x87,0xCF,0x78,0x79,0x89,0x21,0x9A,0xE2,0x4C,0x07,0x0A,0x86,0x35, +0xBC,0xF2,0xDE,0x51,0xC4,0xD2,0x96,0xB7,0xDC,0x7E,0x4E,0xEE,0x70,0xFD,0x1C,0x39, +0xEB,0x0C,0x02,0x51,0x14,0x2D,0x8E,0xBD,0x16,0xE0,0xC1,0xDF,0x46,0x75,0xE7,0x24, +0xAD,0xEC,0xF4,0x42,0xB4,0x85,0x93,0x70,0x10,0x67,0xBA,0x9D,0x06,0x35,0x4A,0x18, +0xD3,0x2B,0x7A,0xCC,0x51,0x42,0xA1,0x7A,0x63,0xD1,0xE6,0xBB,0xA1,0xC5,0x2B,0xC2, +0x36,0xBE,0x13,0x0D,0xE6,0xBD,0x63,0x7E,0x79,0x7B,0xA7,0x09,0x0D,0x40,0xAB,0x6A, +0xDD,0x8F,0x8A,0xC3,0xF6,0xF6,0x8C,0x1A,0x42,0x05,0x51,0xD4,0x45,0xF5,0x9F,0xA7, +0x62,0x21,0x68,0x15,0x20,0x43,0x3C,0x99,0xE7,0x7C,0xBD,0x24,0xD8,0xA9,0x91,0x17, +0x73,0x88,0x3F,0x56,0x1B,0x31,0x38,0x18,0xB4,0x71,0x0F,0x9A,0xCD,0xC8,0x0E,0x9E, +0x8E,0x2E,0x1B,0xE1,0x8C,0x98,0x83,0xCB,0x1F,0x31,0xF1,0x44,0x4C,0xC6,0x04,0x73, +0x49,0x76,0x60,0x0F,0xC7,0xF8,0xBD,0x17,0x80,0x6B,0x2E,0xE9,0xCC,0x4C,0x0E,0x5A, +0x9A,0x79,0x0F,0x20,0x0A,0x2E,0xD5,0x9E,0x63,0x26,0x1E,0x55,0x92,0x94,0xD8,0x82, +0x17,0x5A,0x7B,0xD0,0xBC,0xC7,0x8F,0x4E,0x86,0x04, +}; + + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Class 1 CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Class 1 CA Root */ + + +const unsigned char AddTrust_Low_Value_Services_Root_certificate[1052]={ +0x30,0x82,0x04,0x18,0x30,0x82,0x03,0x00,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x43, +0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30, +0x31,0x30,0x33,0x38,0x33,0x31,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31, +0x30,0x33,0x38,0x33,0x31,0x5A,0x30,0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B, +0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06, +0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54, +0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03, +0x55,0x04,0x03,0x13,0x18,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x31,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01, +0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0x96,0x96, +0xD4,0x21,0x49,0x60,0xE2,0x6B,0xE8,0x41,0x07,0x0C,0xDE,0xC4,0xE0,0xDC,0x13,0x23, +0xCD,0xC1,0x35,0xC7,0xFB,0xD6,0x4E,0x11,0x0A,0x67,0x5E,0xF5,0x06,0x5B,0x6B,0xA5, +0x08,0x3B,0x5B,0x29,0x16,0x3A,0xE7,0x87,0xB2,0x34,0x06,0xC5,0xBC,0x05,0xA5,0x03, +0x7C,0x82,0xCB,0x29,0x10,0xAE,0xE1,0x88,0x81,0xBD,0xD6,0x9E,0xD3,0xFE,0x2D,0x56, +0xC1,0x15,0xCE,0xE3,0x26,0x9D,0x15,0x2E,0x10,0xFB,0x06,0x8F,0x30,0x04,0xDE,0xA7, +0xB4,0x63,0xB4,0xFF,0xB1,0x9C,0xAE,0x3C,0xAF,0x77,0xB6,0x56,0xC5,0xB5,0xAB,0xA2, +0xE9,0x69,0x3A,0x3D,0x0E,0x33,0x79,0x32,0x3F,0x70,0x82,0x92,0x99,0x61,0x6D,0x8D, +0x30,0x08,0x8F,0x71,0x3F,0xA6,0x48,0x57,0x19,0xF8,0x25,0xDC,0x4B,0x66,0x5C,0xA5, +0x74,0x8F,0x98,0xAE,0xC8,0xF9,0xC0,0x06,0x22,0xE7,0xAC,0x73,0xDF,0xA5,0x2E,0xFB, +0x52,0xDC,0xB1,0x15,0x65,0x20,0xFA,0x35,0x66,0x69,0xDE,0xDF,0x2C,0xF1,0x6E,0xBC, +0x30,0xDB,0x2C,0x24,0x12,0xDB,0xEB,0x35,0x35,0x68,0x90,0xCB,0x00,0xB0,0x97,0x21, +0x3D,0x74,0x21,0x23,0x65,0x34,0x2B,0xBB,0x78,0x59,0xA3,0xD6,0xE1,0x76,0x39,0x9A, +0xA4,0x49,0x8E,0x8C,0x74,0xAF,0x6E,0xA4,0x9A,0xA3,0xD9,0x9B,0xD2,0x38,0x5C,0x9B, +0xA2,0x18,0xCC,0x75,0x23,0x84,0xBE,0xEB,0xE2,0x4D,0x33,0x71,0x8E,0x1A,0xF0,0xC2, +0xF8,0xC7,0x1D,0xA2,0xAD,0x03,0x97,0x2C,0xF8,0xCF,0x25,0xC6,0xF6,0xB8,0x24,0x31, +0xB1,0x63,0x5D,0x92,0x7F,0x63,0xF0,0x25,0xC9,0x53,0x2E,0x1F,0xBF,0x4D,0x02,0x03, +0x01,0x00,0x01,0xA3,0x81,0xD2,0x30,0x81,0xCF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E, +0x04,0x16,0x04,0x14,0x95,0xB1,0xB4,0xF0,0x94,0xB6,0xBD,0xC7,0xDA,0xD1,0x11,0x09, +0x21,0xBE,0xC1,0xAF,0x49,0xFD,0x10,0x7B,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04, +0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x8F,0x06,0x03,0x55,0x1D,0x23,0x04,0x81, +0x87,0x30,0x81,0x84,0x80,0x14,0x95,0xB1,0xB4,0xF0,0x94,0xB6,0xBD,0xC7,0xDA,0xD1, +0x11,0x09,0x21,0xBE,0xC1,0xAF,0x49,0xFD,0x10,0x7B,0xA1,0x69,0xA4,0x67,0x30,0x65, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30, +0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74, +0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F, +0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x41,0x64,0x64, +0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x43,0x41, +0x20,0x52,0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x2C,0x6D,0x64,0x1B, +0x1F,0xCD,0x0D,0xDD,0xB9,0x01,0xFA,0x96,0x63,0x34,0x32,0x48,0x47,0x99,0xAE,0x97, +0xED,0xFD,0x72,0x16,0xA6,0x73,0x47,0x5A,0xF4,0xEB,0xDD,0xE9,0xF5,0xD6,0xFB,0x45, +0xCC,0x29,0x89,0x44,0x5D,0xBF,0x46,0x39,0x3D,0xE8,0xEE,0xBC,0x4D,0x54,0x86,0x1E, +0x1D,0x6C,0xE3,0x17,0x27,0x43,0xE1,0x89,0x56,0x2B,0xA9,0x6F,0x72,0x4E,0x49,0x33, +0xE3,0x72,0x7C,0x2A,0x23,0x9A,0xBC,0x3E,0xFF,0x28,0x2A,0xED,0xA3,0xFF,0x1C,0x23, +0xBA,0x43,0x57,0x09,0x67,0x4D,0x4B,0x62,0x06,0x2D,0xF8,0xFF,0x6C,0x9D,0x60,0x1E, +0xD8,0x1C,0x4B,0x7D,0xB5,0x31,0x2F,0xD9,0xD0,0x7C,0x5D,0xF8,0xDE,0x6B,0x83,0x18, +0x78,0x37,0x57,0x2F,0xE8,0x33,0x07,0x67,0xDF,0x1E,0xC7,0x6B,0x2A,0x95,0x76,0xAE, +0x8F,0x57,0xA3,0xF0,0xF4,0x52,0xB4,0xA9,0x53,0x08,0xCF,0xE0,0x4F,0xD3,0x7A,0x53, +0x8B,0xFD,0xBB,0x1C,0x56,0x36,0xF2,0xFE,0xB2,0xB6,0xE5,0x76,0xBB,0xD5,0x22,0x65, +0xA7,0x3F,0xFE,0xD1,0x66,0xAD,0x0B,0xBC,0x6B,0x99,0x86,0xEF,0x3F,0x7D,0xF3,0x18, +0x32,0xCA,0x7B,0xC6,0xE3,0xAB,0x64,0x46,0x95,0xF8,0x26,0x69,0xD9,0x55,0x83,0x7B, +0x2C,0x96,0x07,0xFF,0x59,0x2C,0x44,0xA3,0xC6,0xE5,0xE9,0xA9,0xDC,0xA1,0x63,0x80, +0x5A,0x21,0x5E,0x21,0xCF,0x53,0x54,0xF0,0xBA,0x6F,0x89,0xDB,0xA8,0xAA,0x95,0xCF, +0x8B,0xE3,0x71,0xCC,0x1E,0x1B,0x20,0x44,0x08,0xC0,0x7A,0xB6,0x40,0xFD,0xC4,0xE4, +0x35,0xE1,0x1D,0x16,0x1C,0xD0,0xBC,0x2B,0x8E,0xD6,0x71,0xD9, +}; + + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Public CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Public CA Root */ + + +const unsigned char AddTrust_Public_Services_Root_certificate[1049]={ +0x30,0x82,0x04,0x15,0x30,0x82,0x02,0xFD,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x64,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x43,0x41, +0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30,0x31, +0x30,0x34,0x31,0x35,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31,0x30, +0x34,0x31,0x35,0x30,0x5A,0x30,0x64,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03, +0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54, +0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x20,0x30,0x1E,0x06,0x03,0x55, +0x04,0x03,0x13,0x17,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x75,0x62, +0x6C,0x69,0x63,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xE9,0x1A,0x30,0x8F, +0x83,0x88,0x14,0xC1,0x20,0xD8,0x3C,0x9B,0x8F,0x1B,0x7E,0x03,0x74,0xBB,0xDA,0x69, +0xD3,0x46,0xA5,0xF8,0x8E,0xC2,0x0C,0x11,0x90,0x51,0xA5,0x2F,0x66,0x54,0x40,0x55, +0xEA,0xDB,0x1F,0x4A,0x56,0xEE,0x9F,0x23,0x6E,0xF4,0x39,0xCB,0xA1,0xB9,0x6F,0xF2, +0x7E,0xF9,0x5D,0x87,0x26,0x61,0x9E,0x1C,0xF8,0xE2,0xEC,0xA6,0x81,0xF8,0x21,0xC5, +0x24,0xCC,0x11,0x0C,0x3F,0xDB,0x26,0x72,0x7A,0xC7,0x01,0x97,0x07,0x17,0xF9,0xD7, +0x18,0x2C,0x30,0x7D,0x0E,0x7A,0x1E,0x62,0x1E,0xC6,0x4B,0xC0,0xFD,0x7D,0x62,0x77, +0xD3,0x44,0x1E,0x27,0xF6,0x3F,0x4B,0x44,0xB3,0xB7,0x38,0xD9,0x39,0x1F,0x60,0xD5, +0x51,0x92,0x73,0x03,0xB4,0x00,0x69,0xE3,0xF3,0x14,0x4E,0xEE,0xD1,0xDC,0x09,0xCF, +0x77,0x34,0x46,0x50,0xB0,0xF8,0x11,0xF2,0xFE,0x38,0x79,0xF7,0x07,0x39,0xFE,0x51, +0x92,0x97,0x0B,0x5B,0x08,0x5F,0x34,0x86,0x01,0xAD,0x88,0x97,0xEB,0x66,0xCD,0x5E, +0xD1,0xFF,0xDC,0x7D,0xF2,0x84,0xDA,0xBA,0x77,0xAD,0xDC,0x80,0x08,0xC7,0xA7,0x87, +0xD6,0x55,0x9F,0x97,0x6A,0xE8,0xC8,0x11,0x64,0xBA,0xE7,0x19,0x29,0x3F,0x11,0xB3, +0x78,0x90,0x84,0x20,0x52,0x5B,0x11,0xEF,0x78,0xD0,0x83,0xF6,0xD5,0x48,0x90,0xD0, +0x30,0x1C,0xCF,0x80,0xF9,0x60,0xFE,0x79,0xE4,0x88,0xF2,0xDD,0x00,0xEB,0x94,0x45, +0xEB,0x65,0x94,0x69,0x40,0xBA,0xC0,0xD5,0xB4,0xB8,0xBA,0x7D,0x04,0x11,0xA8,0xEB, +0x31,0x05,0x96,0x94,0x4E,0x58,0x21,0x8E,0x9F,0xD0,0x60,0xFD,0x02,0x03,0x01,0x00, +0x01,0xA3,0x81,0xD1,0x30,0x81,0xCE,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0x81,0x3E,0x37,0xD8,0x92,0xB0,0x1F,0x77,0x9F,0x5C,0xB4,0xAB,0x73,0xAA, +0xE7,0xF6,0x34,0x60,0x2F,0xFA,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03, +0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x81,0x8E,0x06,0x03,0x55,0x1D,0x23,0x04,0x81,0x86,0x30, +0x81,0x83,0x80,0x14,0x81,0x3E,0x37,0xD8,0x92,0xB0,0x1F,0x77,0x9F,0x5C,0xB4,0xAB, +0x73,0xAA,0xE7,0xF6,0x34,0x60,0x2F,0xFA,0xA1,0x68,0xA4,0x66,0x30,0x64,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41, +0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54, +0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B, +0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x41,0x64,0x64,0x54,0x72, +0x75,0x73,0x74,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x43,0x41,0x20,0x52,0x6F, +0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x03,0xF7,0x15,0x4A,0xF8,0x24,0xDA, +0x23,0x56,0x16,0x93,0x76,0xDD,0x36,0x28,0xB9,0xAE,0x1B,0xB8,0xC3,0xF1,0x64,0xBA, +0x20,0x18,0x78,0x95,0x29,0x27,0x57,0x05,0xBC,0x7C,0x2A,0xF4,0xB9,0x51,0x55,0xDA, +0x87,0x02,0xDE,0x0F,0x16,0x17,0x31,0xF8,0xAA,0x79,0x2E,0x09,0x13,0xBB,0xAF,0xB2, +0x20,0x19,0x12,0xE5,0x93,0xF9,0x4B,0xF9,0x83,0xE8,0x44,0xD5,0xB2,0x41,0x25,0xBF, +0x88,0x75,0x6F,0xFF,0x10,0xFC,0x4A,0x54,0xD0,0x5F,0xF0,0xFA,0xEF,0x36,0x73,0x7D, +0x1B,0x36,0x45,0xC6,0x21,0x6D,0xB4,0x15,0xB8,0x4E,0xCF,0x9C,0x5C,0xA5,0x3D,0x5A, +0x00,0x8E,0x06,0xE3,0x3C,0x6B,0x32,0x7B,0xF2,0x9F,0xF0,0xB6,0xFD,0xDF,0xF0,0x28, +0x18,0x48,0xF0,0xC6,0xBC,0xD0,0xBF,0x34,0x80,0x96,0xC2,0x4A,0xB1,0x6D,0x8E,0xC7, +0x90,0x45,0xDE,0x2F,0x67,0xAC,0x45,0x04,0xA3,0x7A,0xDC,0x55,0x92,0xC9,0x47,0x66, +0xD8,0x1A,0x8C,0xC7,0xED,0x9C,0x4E,0x9A,0xE0,0x12,0xBB,0xB5,0x6A,0x4C,0x84,0xE1, +0xE1,0x22,0x0D,0x87,0x00,0x64,0xFE,0x8C,0x7D,0x62,0x39,0x65,0xA6,0xEF,0x42,0xB6, +0x80,0x25,0x12,0x61,0x01,0xA8,0x24,0x13,0x70,0x00,0x11,0x26,0x5F,0xFA,0x35,0x50, +0xC5,0x48,0xCC,0x06,0x47,0xE8,0x27,0xD8,0x70,0x8D,0x5F,0x64,0xE6,0xA1,0x44,0x26, +0x5E,0x22,0xEC,0x92,0xCD,0xFF,0x42,0x9A,0x44,0x21,0x6D,0x5C,0xC5,0xE3,0x22,0x1D, +0x5F,0x47,0x12,0xE7,0xCE,0x5F,0x5D,0xFA,0xD8,0xAA,0xB1,0x33,0x2D,0xD9,0x76,0xF2, +0x4E,0x3A,0x33,0x0C,0x2B,0xB3,0x2D,0x90,0x06, +}; + + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Qualified CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Qualified CA Root */ + + +const unsigned char AddTrust_Qualified_Certificates_Root_certificate[1058]={ +0x30,0x82,0x04,0x1E,0x30,0x82,0x03,0x06,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x67,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x51,0x75,0x61,0x6C,0x69,0x66,0x69,0x65,0x64, +0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35, +0x33,0x30,0x31,0x30,0x34,0x34,0x35,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33, +0x30,0x31,0x30,0x34,0x34,0x35,0x30,0x5A,0x30,0x67,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30, +0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74, +0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x23,0x30,0x21, +0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20, +0x51,0x75,0x61,0x6C,0x69,0x66,0x69,0x65,0x64,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F, +0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01, +0x01,0x00,0xE4,0x1E,0x9A,0xFE,0xDC,0x09,0x5A,0x87,0xA4,0x9F,0x47,0xBE,0x11,0x5F, +0xAF,0x84,0x34,0xDB,0x62,0x3C,0x79,0x78,0xB7,0xE9,0x30,0xB5,0xEC,0x0C,0x1C,0x2A, +0xC4,0x16,0xFF,0xE0,0xEC,0x71,0xEB,0x8A,0xF5,0x11,0x6E,0xED,0x4F,0x0D,0x91,0xD2, +0x12,0x18,0x2D,0x49,0x15,0x01,0xC2,0xA4,0x22,0x13,0xC7,0x11,0x64,0xFF,0x22,0x12, +0x9A,0xB9,0x8E,0x5C,0x2F,0x08,0xCF,0x71,0x6A,0xB3,0x67,0x01,0x59,0xF1,0x5D,0x46, +0xF3,0xB0,0x78,0xA5,0xF6,0x0E,0x42,0x7A,0xE3,0x7F,0x1B,0xCC,0xD0,0xF0,0xB7,0x28, +0xFD,0x2A,0xEA,0x9E,0xB3,0xB0,0xB9,0x04,0xAA,0xFD,0xF6,0xC7,0xB4,0xB1,0xB8,0x2A, +0xA0,0xFB,0x58,0xF1,0x19,0xA0,0x6F,0x70,0x25,0x7E,0x3E,0x69,0x4A,0x7F,0x0F,0x22, +0xD8,0xEF,0xAD,0x08,0x11,0x9A,0x29,0x99,0xE1,0xAA,0x44,0x45,0x9A,0x12,0x5E,0x3E, +0x9D,0x6D,0x52,0xFC,0xE7,0xA0,0x3D,0x68,0x2F,0xF0,0x4B,0x70,0x7C,0x13,0x38,0xAD, +0xBC,0x15,0x25,0xF1,0xD6,0xCE,0xAB,0xA2,0xC0,0x31,0xD6,0x2F,0x9F,0xE0,0xFF,0x14, +0x59,0xFC,0x84,0x93,0xD9,0x87,0x7C,0x4C,0x54,0x13,0xEB,0x9F,0xD1,0x2D,0x11,0xF8, +0x18,0x3A,0x3A,0xDE,0x25,0xD9,0xF7,0xD3,0x40,0xED,0xA4,0x06,0x12,0xC4,0x3B,0xE1, +0x91,0xC1,0x56,0x35,0xF0,0x14,0xDC,0x65,0x36,0x09,0x6E,0xAB,0xA4,0x07,0xC7,0x35, +0xD1,0xC2,0x03,0x33,0x36,0x5B,0x75,0x26,0x6D,0x42,0xF1,0x12,0x6B,0x43,0x6F,0x4B, +0x71,0x94,0xFA,0x34,0x1D,0xED,0x13,0x6E,0xCA,0x80,0x7F,0x98,0x2F,0x6C,0xB9,0x65, +0xD8,0xE9,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xD4,0x30,0x81,0xD1,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x39,0x95,0x8B,0x62,0x8B,0x5C,0xC9,0xD4, +0x80,0xBA,0x58,0x0F,0x97,0x3F,0x15,0x08,0x43,0xCC,0x98,0xA7,0x30,0x0B,0x06,0x03, +0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13, +0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x91,0x06,0x03,0x55, +0x1D,0x23,0x04,0x81,0x89,0x30,0x81,0x86,0x80,0x14,0x39,0x95,0x8B,0x62,0x8B,0x5C, +0xC9,0xD4,0x80,0xBA,0x58,0x0F,0x97,0x3F,0x15,0x08,0x43,0xCC,0x98,0xA7,0xA1,0x6B, +0xA4,0x69,0x30,0x67,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53, +0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54, +0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B, +0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E, +0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13, +0x1A,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x51,0x75,0x61,0x6C,0x69,0x66, +0x69,0x65,0x64,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01, +0x01,0x00,0x19,0xAB,0x75,0xEA,0xF8,0x8B,0x65,0x61,0x95,0x13,0xBA,0x69,0x04,0xEF, +0x86,0xCA,0x13,0xA0,0xC7,0xAA,0x4F,0x64,0x1B,0x3F,0x18,0xF6,0xA8,0x2D,0x2C,0x55, +0x8F,0x05,0xB7,0x30,0xEA,0x42,0x6A,0x1D,0xC0,0x25,0x51,0x2D,0xA7,0xBF,0x0C,0xB3, +0xED,0xEF,0x08,0x7F,0x6C,0x3C,0x46,0x1A,0xEA,0x18,0x43,0xDF,0x76,0xCC,0xF9,0x66, +0x86,0x9C,0x2C,0x68,0xF5,0xE9,0x17,0xF8,0x31,0xB3,0x18,0xC4,0xD6,0x48,0x7D,0x23, +0x4C,0x68,0xC1,0x7E,0xBB,0x01,0x14,0x6F,0xC5,0xD9,0x6E,0xDE,0xBB,0x04,0x42,0x6A, +0xF8,0xF6,0x5C,0x7D,0xE5,0xDA,0xFA,0x87,0xEB,0x0D,0x35,0x52,0x67,0xD0,0x9E,0x97, +0x76,0x05,0x93,0x3F,0x95,0xC7,0x01,0xE6,0x69,0x55,0x38,0x7F,0x10,0x61,0x99,0xC9, +0xE3,0x5F,0xA6,0xCA,0x3E,0x82,0x63,0x48,0xAA,0xE2,0x08,0x48,0x3E,0xAA,0xF2,0xB2, +0x85,0x62,0xA6,0xB4,0xA7,0xD9,0xBD,0x37,0x9C,0x68,0xB5,0x2D,0x56,0x7D,0xB0,0xB7, +0x3F,0xA0,0xB1,0x07,0xD6,0xE9,0x4F,0xDC,0xDE,0x45,0x71,0x30,0x32,0x7F,0x1B,0x2E, +0x09,0xF9,0xBF,0x52,0xA1,0xEE,0xC2,0x80,0x3E,0x06,0x5C,0x2E,0x55,0x40,0xC1,0x1B, +0xF5,0x70,0x45,0xB0,0xDC,0x5D,0xFA,0xF6,0x72,0x5A,0x77,0xD2,0x63,0xCD,0xCF,0x58, +0x89,0x00,0x42,0x63,0x3F,0x79,0x39,0xD0,0x44,0xB0,0x82,0x6E,0x41,0x19,0xE8,0xDD, +0xE0,0xC1,0x88,0x5A,0xD1,0x1E,0x71,0x93,0x1F,0x24,0x30,0x74,0xE5,0x1E,0xA8,0xDE, +0x3C,0x27,0x37,0x7F,0x83,0xAE,0x9E,0x77,0xCF,0xF0,0x30,0xB1,0xFF,0x4B,0x99,0xE8, +0xC6,0xA1, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Commercial */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Commercial */ + + +const unsigned char AffirmTrust_Commercial_certificate[848]={ +0x30,0x82,0x03,0x4C,0x30,0x82,0x02,0x34,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x77, +0x77,0x06,0x27,0x26,0xA9,0xB1,0x7C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B, +0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1F,0x30,0x1D,0x06, +0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69,0x61,0x6C,0x30,0x1E,0x17,0x0D, +0x31,0x30,0x30,0x31,0x32,0x39,0x31,0x34,0x30,0x36,0x30,0x36,0x5A,0x17,0x0D,0x33, +0x30,0x31,0x32,0x33,0x31,0x31,0x34,0x30,0x36,0x30,0x36,0x5A,0x30,0x44,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06, +0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69, +0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69, +0x61,0x6C,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xF6,0x1B,0x4F,0x67,0x07,0x2B,0xA1,0x15,0xF5,0x06,0x22,0xCB,0x1F, +0x01,0xB2,0xE3,0x73,0x45,0x06,0x44,0x49,0x2C,0xBB,0x49,0x25,0x14,0xD6,0xCE,0xC3, +0xB7,0xAB,0x2C,0x4F,0xC6,0x41,0x32,0x94,0x57,0xFA,0x12,0xA7,0x5B,0x0E,0xE2,0x8F, +0x1F,0x1E,0x86,0x19,0xA7,0xAA,0xB5,0x2D,0xB9,0x5F,0x0D,0x8A,0xC2,0xAF,0x85,0x35, +0x79,0x32,0x2D,0xBB,0x1C,0x62,0x37,0xF2,0xB1,0x5B,0x4A,0x3D,0xCA,0xCD,0x71,0x5F, +0xE9,0x42,0xBE,0x94,0xE8,0xC8,0xDE,0xF9,0x22,0x48,0x64,0xC6,0xE5,0xAB,0xC6,0x2B, +0x6D,0xAD,0x05,0xF0,0xFA,0xD5,0x0B,0xCF,0x9A,0xE5,0xF0,0x50,0xA4,0x8B,0x3B,0x47, +0xA5,0x23,0x5B,0x7A,0x7A,0xF8,0x33,0x3F,0xB8,0xEF,0x99,0x97,0xE3,0x20,0xC1,0xD6, +0x28,0x89,0xCF,0x94,0xFB,0xB9,0x45,0xED,0xE3,0x40,0x17,0x11,0xD4,0x74,0xF0,0x0B, +0x31,0xE2,0x2B,0x26,0x6A,0x9B,0x4C,0x57,0xAE,0xAC,0x20,0x3E,0xBA,0x45,0x7A,0x05, +0xF3,0xBD,0x9B,0x69,0x15,0xAE,0x7D,0x4E,0x20,0x63,0xC4,0x35,0x76,0x3A,0x07,0x02, +0xC9,0x37,0xFD,0xC7,0x47,0xEE,0xE8,0xF1,0x76,0x1D,0x73,0x15,0xF2,0x97,0xA4,0xB5, +0xC8,0x7A,0x79,0xD9,0x42,0xAA,0x2B,0x7F,0x5C,0xFE,0xCE,0x26,0x4F,0xA3,0x66,0x81, +0x35,0xAF,0x44,0xBA,0x54,0x1E,0x1C,0x30,0x32,0x65,0x9D,0xE6,0x3C,0x93,0x5E,0x50, +0x4E,0x7A,0xE3,0x3A,0xD4,0x6E,0xCC,0x1A,0xFB,0xF9,0xD2,0x37,0xAE,0x24,0x2A,0xAB, +0x57,0x03,0x22,0x28,0x0D,0x49,0x75,0x7F,0xB7,0x28,0xDA,0x75,0xBF,0x8E,0xE3,0xDC, +0x0E,0x79,0x31,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9D,0x93,0xC6,0x53,0x8B,0x5E,0xCA,0xAF,0x3F, +0x9F,0x1E,0x0F,0xE5,0x99,0x95,0xBC,0x24,0xF6,0x94,0x8F,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x58,0xAC,0xF4,0x04,0x0E,0xCD,0xC0,0x0D,0xFF,0x0A,0xFD,0xD4,0xBA,0x16,0x5F,0x29, +0xBD,0x7B,0x68,0x99,0x58,0x49,0xD2,0xB4,0x1D,0x37,0x4D,0x7F,0x27,0x7D,0x46,0x06, +0x5D,0x43,0xC6,0x86,0x2E,0x3E,0x73,0xB2,0x26,0x7D,0x4F,0x93,0xA9,0xB6,0xC4,0x2A, +0x9A,0xAB,0x21,0x97,0x14,0xB1,0xDE,0x8C,0xD3,0xAB,0x89,0x15,0xD8,0x6B,0x24,0xD4, +0xF1,0x16,0xAE,0xD8,0xA4,0x5C,0xD4,0x7F,0x51,0x8E,0xED,0x18,0x01,0xB1,0x93,0x63, +0xBD,0xBC,0xF8,0x61,0x80,0x9A,0x9E,0xB1,0xCE,0x42,0x70,0xE2,0xA9,0x7D,0x06,0x25, +0x7D,0x27,0xA1,0xFE,0x6F,0xEC,0xB3,0x1E,0x24,0xDA,0xE3,0x4B,0x55,0x1A,0x00,0x3B, +0x35,0xB4,0x3B,0xD9,0xD7,0x5D,0x30,0xFD,0x81,0x13,0x89,0xF2,0xC2,0x06,0x2B,0xED, +0x67,0xC4,0x8E,0xC9,0x43,0xB2,0x5C,0x6B,0x15,0x89,0x02,0xBC,0x62,0xFC,0x4E,0xF2, +0xB5,0x33,0xAA,0xB2,0x6F,0xD3,0x0A,0xA2,0x50,0xE3,0xF6,0x3B,0xE8,0x2E,0x44,0xC2, +0xDB,0x66,0x38,0xA9,0x33,0x56,0x48,0xF1,0x6D,0x1B,0x33,0x8D,0x0D,0x8C,0x3F,0x60, +0x37,0x9D,0xD3,0xCA,0x6D,0x7E,0x34,0x7E,0x0D,0x9F,0x72,0x76,0x8B,0x1B,0x9F,0x72, +0xFD,0x52,0x35,0x41,0x45,0x02,0x96,0x2F,0x1C,0xB2,0x9A,0x73,0x49,0x21,0xB1,0x49, +0x47,0x45,0x47,0xB4,0xEF,0x6A,0x34,0x11,0xC9,0x4D,0x9A,0xCC,0x59,0xB7,0xD6,0x02, +0x9E,0x5A,0x4E,0x65,0xB5,0x94,0xAE,0x1B,0xDF,0x29,0xB0,0x16,0xF1,0xBF,0x00,0x9E, +0x07,0x3A,0x17,0x64,0xB5,0x04,0xB5,0x23,0x21,0x99,0x0A,0x95,0x3B,0x97,0x7C,0xEF, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Networking */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Networking */ + + +const unsigned char AffirmTrust_Networking_certificate[848]={ +0x30,0x82,0x03,0x4C,0x30,0x82,0x02,0x34,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x7C, +0x4F,0x04,0x39,0x1C,0xD4,0x99,0x2D,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B, +0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1F,0x30,0x1D,0x06, +0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x69,0x6E,0x67,0x30,0x1E,0x17,0x0D, +0x31,0x30,0x30,0x31,0x32,0x39,0x31,0x34,0x30,0x38,0x32,0x34,0x5A,0x17,0x0D,0x33, +0x30,0x31,0x32,0x33,0x31,0x31,0x34,0x30,0x38,0x32,0x34,0x5A,0x30,0x44,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06, +0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69, +0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x69, +0x6E,0x67,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xB4,0x84,0xCC,0x33,0x17,0x2E,0x6B,0x94,0x6C,0x6B,0x61,0x52,0xA0, +0xEB,0xA3,0xCF,0x79,0x94,0x4C,0xE5,0x94,0x80,0x99,0xCB,0x55,0x64,0x44,0x65,0x8F, +0x67,0x64,0xE2,0x06,0xE3,0x5C,0x37,0x49,0xF6,0x2F,0x9B,0x84,0x84,0x1E,0x2D,0xF2, +0x60,0x9D,0x30,0x4E,0xCC,0x84,0x85,0xE2,0x2C,0xCF,0x1E,0x9E,0xFE,0x36,0xAB,0x33, +0x77,0x35,0x44,0xD8,0x35,0x96,0x1A,0x3D,0x36,0xE8,0x7A,0x0E,0xD8,0xD5,0x47,0xA1, +0x6A,0x69,0x8B,0xD9,0xFC,0xBB,0x3A,0xAE,0x79,0x5A,0xD5,0xF4,0xD6,0x71,0xBB,0x9A, +0x90,0x23,0x6B,0x9A,0xB7,0x88,0x74,0x87,0x0C,0x1E,0x5F,0xB9,0x9E,0x2D,0xFA,0xAB, +0x53,0x2B,0xDC,0xBB,0x76,0x3E,0x93,0x4C,0x08,0x08,0x8C,0x1E,0xA2,0x23,0x1C,0xD4, +0x6A,0xAD,0x22,0xBA,0x99,0x01,0x2E,0x6D,0x65,0xCB,0xBE,0x24,0x66,0x55,0x24,0x4B, +0x40,0x44,0xB1,0x1B,0xD7,0xE1,0xC2,0x85,0xC0,0xDE,0x10,0x3F,0x3D,0xED,0xB8,0xFC, +0xF1,0xF1,0x23,0x53,0xDC,0xBF,0x65,0x97,0x6F,0xD9,0xF9,0x40,0x71,0x8D,0x7D,0xBD, +0x95,0xD4,0xCE,0xBE,0xA0,0x5E,0x27,0x23,0xDE,0xFD,0xA6,0xD0,0x26,0x0E,0x00,0x29, +0xEB,0x3C,0x46,0xF0,0x3D,0x60,0xBF,0x3F,0x50,0xD2,0xDC,0x26,0x41,0x51,0x9E,0x14, +0x37,0x42,0x04,0xA3,0x70,0x57,0xA8,0x1B,0x87,0xED,0x2D,0xFA,0x7B,0xEE,0x8C,0x0A, +0xE3,0xA9,0x66,0x89,0x19,0xCB,0x41,0xF9,0xDD,0x44,0x36,0x61,0xCF,0xE2,0x77,0x46, +0xC8,0x7D,0xF6,0xF4,0x92,0x81,0x36,0xFD,0xDB,0x34,0xF1,0x72,0x7E,0xF3,0x0C,0x16, +0xBD,0xB4,0x15,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x07,0x1F,0xD2,0xE7,0x9C,0xDA,0xC2,0x6E,0xA2, +0x40,0xB4,0xB0,0x7A,0x50,0x10,0x50,0x74,0xC4,0xC8,0xBD,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x89,0x57,0xB2,0x16,0x7A,0xA8,0xC2,0xFD,0xD6,0xD9,0x9B,0x9B,0x34,0xC2,0x9C,0xB4, +0x32,0x14,0x4D,0xA7,0xA4,0xDF,0xEC,0xBE,0xA7,0xBE,0xF8,0x43,0xDB,0x91,0x37,0xCE, +0xB4,0x32,0x2E,0x50,0x55,0x1A,0x35,0x4E,0x76,0x43,0x71,0x20,0xEF,0x93,0x77,0x4E, +0x15,0x70,0x2E,0x87,0xC3,0xC1,0x1D,0x6D,0xDC,0xCB,0xB5,0x27,0xD4,0x2C,0x56,0xD1, +0x52,0x53,0x3A,0x44,0xD2,0x73,0xC8,0xC4,0x1B,0x05,0x65,0x5A,0x62,0x92,0x9C,0xEE, +0x41,0x8D,0x31,0xDB,0xE7,0x34,0xEA,0x59,0x21,0xD5,0x01,0x7A,0xD7,0x64,0xB8,0x64, +0x39,0xCD,0xC9,0xED,0xAF,0xED,0x4B,0x03,0x48,0xA7,0xA0,0x99,0x01,0x80,0xDC,0x65, +0xA3,0x36,0xAE,0x65,0x59,0x48,0x4F,0x82,0x4B,0xC8,0x65,0xF1,0x57,0x1D,0xE5,0x59, +0x2E,0x0A,0x3F,0x6C,0xD8,0xD1,0xF5,0xE5,0x09,0xB4,0x6C,0x54,0x00,0x0A,0xE0,0x15, +0x4D,0x87,0x75,0x6D,0xB7,0x58,0x96,0x5A,0xDD,0x6D,0xD2,0x00,0xA0,0xF4,0x9B,0x48, +0xBE,0xC3,0x37,0xA4,0xBA,0x36,0xE0,0x7C,0x87,0x85,0x97,0x1A,0x15,0xA2,0xDE,0x2E, +0xA2,0x5B,0xBD,0xAF,0x18,0xF9,0x90,0x50,0xCD,0x70,0x59,0xF8,0x27,0x67,0x47,0xCB, +0xC7,0xA0,0x07,0x3A,0x7D,0xD1,0x2C,0x5D,0x6C,0x19,0x3A,0x66,0xB5,0x7D,0xFD,0x91, +0x6F,0x82,0xB1,0xBE,0x08,0x93,0xDB,0x14,0x47,0xF1,0xA2,0x37,0xC7,0x45,0x9E,0x3C, +0xC7,0x77,0xAF,0x64,0xA8,0x93,0xDF,0xF6,0x69,0x83,0x82,0x60,0xF2,0x49,0x42,0x34, +0xED,0x5A,0x00,0x54,0x85,0x1C,0x16,0x36,0x92,0x0C,0x5C,0xFA,0xA6,0xAD,0xBF,0xDB, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Premium */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Premium */ + + +const unsigned char AffirmTrust_Premium_certificate[1354]={ +0x30,0x82,0x05,0x46,0x30,0x82,0x03,0x2E,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x6D, +0x8C,0x14,0x46,0xB1,0xA6,0x0A,0xEE,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x0C,0x05,0x00,0x30,0x41,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B, +0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x03,0x0C,0x13,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x30,0x1E,0x17,0x0D,0x31,0x30,0x30, +0x31,0x32,0x39,0x31,0x34,0x31,0x30,0x33,0x36,0x5A,0x17,0x0D,0x34,0x30,0x31,0x32, +0x33,0x31,0x31,0x34,0x31,0x30,0x33,0x36,0x5A,0x30,0x41,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04, +0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x03,0x0C,0x13,0x41,0x66,0x66,0x69,0x72,0x6D,0x54, +0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x30,0x82,0x02,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xC4,0x12,0xDF, +0xA9,0x5F,0xFE,0x41,0xDD,0xDD,0xF5,0x9F,0x8A,0xE3,0xF6,0xAC,0xE1,0x3C,0x78,0x9A, +0xBC,0xD8,0xF0,0x7F,0x7A,0xA0,0x33,0x2A,0xDC,0x8D,0x20,0x5B,0xAE,0x2D,0x6F,0xE7, +0x93,0xD9,0x36,0x70,0x6A,0x68,0xCF,0x8E,0x51,0xA3,0x85,0x5B,0x67,0x04,0xA0,0x10, +0x24,0x6F,0x5D,0x28,0x82,0xC1,0x97,0x57,0xD8,0x48,0x29,0x13,0xB6,0xE1,0xBE,0x91, +0x4D,0xDF,0x85,0x0C,0x53,0x18,0x9A,0x1E,0x24,0xA2,0x4F,0x8F,0xF0,0xA2,0x85,0x0B, +0xCB,0xF4,0x29,0x7F,0xD2,0xA4,0x58,0xEE,0x26,0x4D,0xC9,0xAA,0xA8,0x7B,0x9A,0xD9, +0xFA,0x38,0xDE,0x44,0x57,0x15,0xE5,0xF8,0x8C,0xC8,0xD9,0x48,0xE2,0x0D,0x16,0x27, +0x1D,0x1E,0xC8,0x83,0x85,0x25,0xB7,0xBA,0xAA,0x55,0x41,0xCC,0x03,0x22,0x4B,0x2D, +0x91,0x8D,0x8B,0xE6,0x89,0xAF,0x66,0xC7,0xE9,0xFF,0x2B,0xE9,0x3C,0xAC,0xDA,0xD2, +0xB3,0xC3,0xE1,0x68,0x9C,0x89,0xF8,0x7A,0x00,0x56,0xDE,0xF4,0x55,0x95,0x6C,0xFB, +0xBA,0x64,0xDD,0x62,0x8B,0xDF,0x0B,0x77,0x32,0xEB,0x62,0xCC,0x26,0x9A,0x9B,0xBB, +0xAA,0x62,0x83,0x4C,0xB4,0x06,0x7A,0x30,0xC8,0x29,0xBF,0xED,0x06,0x4D,0x97,0xB9, +0x1C,0xC4,0x31,0x2B,0xD5,0x5F,0xBC,0x53,0x12,0x17,0x9C,0x99,0x57,0x29,0x66,0x77, +0x61,0x21,0x31,0x07,0x2E,0x25,0x49,0x9D,0x18,0xF2,0xEE,0xF3,0x2B,0x71,0x8C,0xB5, +0xBA,0x39,0x07,0x49,0x77,0xFC,0xEF,0x2E,0x92,0x90,0x05,0x8D,0x2D,0x2F,0x77,0x7B, +0xEF,0x43,0xBF,0x35,0xBB,0x9A,0xD8,0xF9,0x73,0xA7,0x2C,0xF2,0xD0,0x57,0xEE,0x28, +0x4E,0x26,0x5F,0x8F,0x90,0x68,0x09,0x2F,0xB8,0xF8,0xDC,0x06,0xE9,0x2E,0x9A,0x3E, +0x51,0xA7,0xD1,0x22,0xC4,0x0A,0xA7,0x38,0x48,0x6C,0xB3,0xF9,0xFF,0x7D,0xAB,0x86, +0x57,0xE3,0xBA,0xD6,0x85,0x78,0x77,0xBA,0x43,0xEA,0x48,0x7F,0xF6,0xD8,0xBE,0x23, +0x6D,0x1E,0xBF,0xD1,0x36,0x6C,0x58,0x5C,0xF1,0xEE,0xA4,0x19,0x54,0x1A,0xF5,0x03, +0xD2,0x76,0xE6,0xE1,0x8C,0xBD,0x3C,0xB3,0xD3,0x48,0x4B,0xE2,0xC8,0xF8,0x7F,0x92, +0xA8,0x76,0x46,0x9C,0x42,0x65,0x3E,0xA4,0x1E,0xC1,0x07,0x03,0x5A,0x46,0x2D,0xB8, +0x97,0xF3,0xB7,0xD5,0xB2,0x55,0x21,0xEF,0xBA,0xDC,0x4C,0x00,0x97,0xFB,0x14,0x95, +0x27,0x33,0xBF,0xE8,0x43,0x47,0x46,0xD2,0x08,0x99,0x16,0x60,0x3B,0x9A,0x7E,0xD2, +0xE6,0xED,0x38,0xEA,0xEC,0x01,0x1E,0x3C,0x48,0x56,0x49,0x09,0xC7,0x4C,0x37,0x00, +0x9E,0x88,0x0E,0xC0,0x73,0xE1,0x6F,0x66,0xE9,0x72,0x47,0x30,0x3E,0x10,0xE5,0x0B, +0x03,0xC9,0x9A,0x42,0x00,0x6C,0xC5,0x94,0x7E,0x61,0xC4,0x8A,0xDF,0x7F,0x82,0x1A, +0x0B,0x59,0xC4,0x59,0x32,0x77,0xB3,0xBC,0x60,0x69,0x56,0x39,0xFD,0xB4,0x06,0x7B, +0x2C,0xD6,0x64,0x36,0xD9,0xBD,0x48,0xED,0x84,0x1F,0x7E,0xA5,0x22,0x8F,0x2A,0xB8, +0x42,0xF4,0x82,0xB7,0xD4,0x53,0x90,0x78,0x4E,0x2D,0x1A,0xFD,0x81,0x6F,0x44,0xD7, +0x3B,0x01,0x74,0x96,0x42,0xE0,0x00,0xE2,0x2E,0x6B,0xEA,0xC5,0xEE,0x72,0xAC,0xBB, +0xBF,0xFE,0xEA,0xAA,0xA8,0xF8,0xDC,0xF6,0xB2,0x79,0x8A,0xB6,0x67,0x02,0x03,0x01, +0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x9D,0xC0,0x67,0xA6,0x0C,0x22,0xD9,0x26,0xF5,0x45,0xAB,0xA6,0x65,0x52,0x11, +0x27,0xD8,0x45,0xAC,0x63,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x0C,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0xB3,0x57,0x4D,0x10,0x62,0x4E, +0x3A,0xE4,0xAC,0xEA,0xB8,0x1C,0xAF,0x32,0x23,0xC8,0xB3,0x49,0x5A,0x51,0x9C,0x76, +0x28,0x8D,0x79,0xAA,0x57,0x46,0x17,0xD5,0xF5,0x52,0xF6,0xB7,0x44,0xE8,0x08,0x44, +0xBF,0x18,0x84,0xD2,0x0B,0x80,0xCD,0xC5,0x12,0xFD,0x00,0x55,0x05,0x61,0x87,0x41, +0xDC,0xB5,0x24,0x9E,0x3C,0xC4,0xD8,0xC8,0xFB,0x70,0x9E,0x2F,0x78,0x96,0x83,0x20, +0x36,0xDE,0x7C,0x0F,0x69,0x13,0x88,0xA5,0x75,0x36,0x98,0x08,0xA6,0xC6,0xDF,0xAC, +0xCE,0xE3,0x58,0xD6,0xB7,0x3E,0xDE,0xBA,0xF3,0xEB,0x34,0x40,0xD8,0xA2,0x81,0xF5, +0x78,0x3F,0x2F,0xD5,0xA5,0xFC,0xD9,0xA2,0xD4,0x5E,0x04,0x0E,0x17,0xAD,0xFE,0x41, +0xF0,0xE5,0xB2,0x72,0xFA,0x44,0x82,0x33,0x42,0xE8,0x2D,0x58,0xF7,0x56,0x8C,0x62, +0x3F,0xBA,0x42,0xB0,0x9C,0x0C,0x5C,0x7E,0x2E,0x65,0x26,0x5C,0x53,0x4F,0x00,0xB2, +0x78,0x7E,0xA1,0x0D,0x99,0x2D,0x8D,0xB8,0x1D,0x8E,0xA2,0xC4,0xB0,0xFD,0x60,0xD0, +0x30,0xA4,0x8E,0xC8,0x04,0x62,0xA9,0xC4,0xED,0x35,0xDE,0x7A,0x97,0xED,0x0E,0x38, +0x5E,0x92,0x2F,0x93,0x70,0xA5,0xA9,0x9C,0x6F,0xA7,0x7D,0x13,0x1D,0x7E,0xC6,0x08, +0x48,0xB1,0x5E,0x67,0xEB,0x51,0x08,0x25,0xE9,0xE6,0x25,0x6B,0x52,0x29,0x91,0x9C, +0xD2,0x39,0x73,0x08,0x57,0xDE,0x99,0x06,0xB4,0x5B,0x9D,0x10,0x06,0xE1,0xC2,0x00, +0xA8,0xB8,0x1C,0x4A,0x02,0x0A,0x14,0xD0,0xC1,0x41,0xCA,0xFB,0x8C,0x35,0x21,0x7D, +0x82,0x38,0xF2,0xA9,0x54,0x91,0x19,0x35,0x93,0x94,0x6D,0x6A,0x3A,0xC5,0xB2,0xD0, +0xBB,0x89,0x86,0x93,0xE8,0x9B,0xC9,0x0F,0x3A,0xA7,0x7A,0xB8,0xA1,0xF0,0x78,0x46, +0xFA,0xFC,0x37,0x2F,0xE5,0x8A,0x84,0xF3,0xDF,0xFE,0x04,0xD9,0xA1,0x68,0xA0,0x2F, +0x24,0xE2,0x09,0x95,0x06,0xD5,0x95,0xCA,0xE1,0x24,0x96,0xEB,0x7C,0xF6,0x93,0x05, +0xBB,0xED,0x73,0xE9,0x2D,0xD1,0x75,0x39,0xD7,0xE7,0x24,0xDB,0xD8,0x4E,0x5F,0x43, +0x8F,0x9E,0xD0,0x14,0x39,0xBF,0x55,0x70,0x48,0x99,0x57,0x31,0xB4,0x9C,0xEE,0x4A, +0x98,0x03,0x96,0x30,0x1F,0x60,0x06,0xEE,0x1B,0x23,0xFE,0x81,0x60,0x23,0x1A,0x47, +0x62,0x85,0xA5,0xCC,0x19,0x34,0x80,0x6F,0xB3,0xAC,0x1A,0xE3,0x9F,0xF0,0x7B,0x48, +0xAD,0xD5,0x01,0xD9,0x67,0xB6,0xA9,0x72,0x93,0xEA,0x2D,0x66,0xB5,0xB2,0xB8,0xE4, +0x3D,0x3C,0xB2,0xEF,0x4C,0x8C,0xEA,0xEB,0x07,0xBF,0xAB,0x35,0x9A,0x55,0x86,0xBC, +0x18,0xA6,0xB5,0xA8,0x5E,0xB4,0x83,0x6C,0x6B,0x69,0x40,0xD3,0x9F,0xDC,0xF1,0xC3, +0x69,0x6B,0xB9,0xE1,0x6D,0x09,0xF4,0xF1,0xAA,0x50,0x76,0x0A,0x7A,0x7D,0x7A,0x17, +0xA1,0x55,0x96,0x42,0x99,0x31,0x09,0xDD,0x60,0x11,0x8D,0x05,0x30,0x7E,0xE6,0x8E, +0x46,0xD1,0x9D,0x14,0xDA,0xC7,0x17,0xE4,0x05,0x96,0x8C,0xC4,0x24,0xB5,0x1B,0xCF, +0x14,0x07,0xB2,0x40,0xF8,0xA3,0x9E,0x41,0x86,0xBC,0x04,0xD0,0x6B,0x96,0xC8,0x2A, +0x80,0x34,0xFD,0xBF,0xEF,0x06,0xA3,0xDD,0x58,0xC5,0x85,0x3D,0x3E,0x8F,0xFE,0x9E, +0x29,0xE0,0xB6,0xB8,0x09,0x68,0x19,0x1C,0x18,0x43, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Premium ECC */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Premium ECC */ + + +const unsigned char AffirmTrust_Premium_ECC_certificate[514]={ +0x30,0x82,0x01,0xFE,0x30,0x82,0x01,0x85,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x74, +0x97,0x25,0x8A,0xC7,0x3F,0x7A,0x54,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D, +0x04,0x03,0x03,0x30,0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66, +0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04, +0x03,0x0C,0x17,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x50, +0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x45,0x43,0x43,0x30,0x1E,0x17,0x0D,0x31,0x30, +0x30,0x31,0x32,0x39,0x31,0x34,0x32,0x30,0x32,0x34,0x5A,0x17,0x0D,0x34,0x30,0x31, +0x32,0x33,0x31,0x31,0x34,0x32,0x30,0x32,0x34,0x5A,0x30,0x45,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55, +0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31, +0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x0C,0x17,0x41,0x66,0x66,0x69,0x72,0x6D, +0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x45,0x43, +0x43,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05, +0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0x0D,0x30,0x5E,0x1B,0x15,0x9D,0x03, +0xD0,0xA1,0x79,0x35,0xB7,0x3A,0x3C,0x92,0x7A,0xCA,0x15,0x1C,0xCD,0x62,0xF3,0x9C, +0x26,0x5C,0x07,0x3D,0xE5,0x54,0xFA,0xA3,0xD6,0xCC,0x12,0xEA,0xF4,0x14,0x5F,0xE8, +0x8E,0x19,0xAB,0x2F,0x2E,0x48,0xE6,0xAC,0x18,0x43,0x78,0xAC,0xD0,0x37,0xC3,0xBD, +0xB2,0xCD,0x2C,0xE6,0x47,0xE2,0x1A,0xE6,0x63,0xB8,0x3D,0x2E,0x2F,0x78,0xC4,0x4F, +0xDB,0xF4,0x0F,0xA4,0x68,0x4C,0x55,0x72,0x6B,0x95,0x1D,0x4E,0x18,0x42,0x95,0x78, +0xCC,0x37,0x3C,0x91,0xE2,0x9B,0x65,0x2B,0x29,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9A,0xAF,0x29,0x7A,0xC0,0x11,0x35,0x35, +0x26,0x51,0x30,0x00,0xC3,0x6A,0xFE,0x40,0xD5,0xAE,0xD6,0x3C,0x30,0x0F,0x06,0x03, +0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06, +0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0A,0x06, +0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x67,0x00,0x30,0x64,0x02,0x30, +0x17,0x09,0xF3,0x87,0x88,0x50,0x5A,0xAF,0xC8,0xC0,0x42,0xBF,0x47,0x5F,0xF5,0x6C, +0x6A,0x86,0xE0,0xC4,0x27,0x74,0xE4,0x38,0x53,0xD7,0x05,0x7F,0x1B,0x34,0xE3,0xC6, +0x2F,0xB3,0xCA,0x09,0x3C,0x37,0x9D,0xD7,0xE7,0xB8,0x46,0xF1,0xFD,0xA1,0xE2,0x71, +0x02,0x30,0x42,0x59,0x87,0x43,0xD4,0x51,0xDF,0xBA,0xD3,0x09,0x32,0x5A,0xCE,0x88, +0x7E,0x57,0x3D,0x9C,0x5F,0x42,0x6B,0xF5,0x07,0x2D,0xB5,0xF0,0x82,0x93,0xF9,0x59, +0x6F,0xAE,0x64,0xFA,0x58,0xE5,0x8B,0x1E,0xE3,0x63,0xBE,0xB5,0x81,0xCD,0x6F,0x02, +0x8C,0x79, +}; + + +/* subject:/C=US/O=America Online Inc./CN=America Online Root Certification Authority 1 */ +/* issuer :/C=US/O=America Online Inc./CN=America Online Root Certification Authority 1 */ + + +const unsigned char America_Online_Root_Certification_Authority_1_certificate[936]={ +0x30,0x82,0x03,0xA4,0x30,0x82,0x02,0x8C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,0x65,0x72,0x69,0x63,0x61, +0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x36,0x30,0x34, +0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,0x20,0x4F, +0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x31,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x38,0x30,0x36, +0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x31,0x31,0x39,0x32,0x30,0x34, +0x33,0x30,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D, +0x65,0x72,0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72, +0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75, +0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x31,0x30,0x82,0x01,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F, +0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xA8,0x2F,0xE8,0xA4,0x69,0x06, +0x03,0x47,0xC3,0xE9,0x2A,0x98,0xFF,0x19,0xA2,0x70,0x9A,0xC6,0x50,0xB2,0x7E,0xA5, +0xDF,0x68,0x4D,0x1B,0x7C,0x0F,0xB6,0x97,0x68,0x7D,0x2D,0xA6,0x8B,0x97,0xE9,0x64, +0x86,0xC9,0xA3,0xEF,0xA0,0x86,0xBF,0x60,0x65,0x9C,0x4B,0x54,0x88,0xC2,0x48,0xC5, +0x4A,0x39,0xBF,0x14,0xE3,0x59,0x55,0xE5,0x19,0xB4,0x74,0xC8,0xB4,0x05,0x39,0x5C, +0x16,0xA5,0xE2,0x95,0x05,0xE0,0x12,0xAE,0x59,0x8B,0xA2,0x33,0x68,0x58,0x1C,0xA6, +0xD4,0x15,0xB7,0xD8,0x9F,0xD7,0xDC,0x71,0xAB,0x7E,0x9A,0xBF,0x9B,0x8E,0x33,0x0F, +0x22,0xFD,0x1F,0x2E,0xE7,0x07,0x36,0xEF,0x62,0x39,0xC5,0xDD,0xCB,0xBA,0x25,0x14, +0x23,0xDE,0x0C,0xC6,0x3D,0x3C,0xCE,0x82,0x08,0xE6,0x66,0x3E,0xDA,0x51,0x3B,0x16, +0x3A,0xA3,0x05,0x7F,0xA0,0xDC,0x87,0xD5,0x9C,0xFC,0x72,0xA9,0xA0,0x7D,0x78,0xE4, +0xB7,0x31,0x55,0x1E,0x65,0xBB,0xD4,0x61,0xB0,0x21,0x60,0xED,0x10,0x32,0x72,0xC5, +0x92,0x25,0x1E,0xF8,0x90,0x4A,0x18,0x78,0x47,0xDF,0x7E,0x30,0x37,0x3E,0x50,0x1B, +0xDB,0x1C,0xD3,0x6B,0x9A,0x86,0x53,0x07,0xB0,0xEF,0xAC,0x06,0x78,0xF8,0x84,0x99, +0xFE,0x21,0x8D,0x4C,0x80,0xB6,0x0C,0x82,0xF6,0x66,0x70,0x79,0x1A,0xD3,0x4F,0xA3, +0xCF,0xF1,0xCF,0x46,0xB0,0x4B,0x0F,0x3E,0xDD,0x88,0x62,0xB8,0x8C,0xA9,0x09,0x28, +0x3B,0x7A,0xC7,0x97,0xE1,0x1E,0xE5,0xF4,0x9F,0xC0,0xC0,0xAE,0x24,0xA0,0xC8,0xA1, +0xD9,0x0F,0xD6,0x7B,0x26,0x82,0x69,0x32,0x3D,0xA7,0x02,0x03,0x01,0x00,0x01,0xA3, +0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x00, +0xAD,0xD9,0xA3,0xF6,0x79,0xF6,0x6E,0x74,0xA9,0x7F,0x33,0x3D,0x81,0x17,0xD7,0x4C, +0xCF,0x33,0xDE,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0x00,0xAD,0xD9,0xA3,0xF6,0x79,0xF6,0x6E,0x74,0xA9,0x7F,0x33,0x3D,0x81,0x17,0xD7, +0x4C,0xCF,0x33,0xDE,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x7C,0x8A,0xD1,0x1F,0x18,0x37,0x82,0xE0, +0xB8,0xB0,0xA3,0xED,0x56,0x95,0xC8,0x62,0x61,0x9C,0x05,0xA2,0xCD,0xC2,0x62,0x26, +0x61,0xCD,0x10,0x16,0xD7,0xCC,0xB4,0x65,0x34,0xD0,0x11,0x8A,0xAD,0xA8,0xA9,0x05, +0x66,0xEF,0x74,0xF3,0x6D,0x5F,0x9D,0x99,0xAF,0xF6,0x8B,0xFB,0xEB,0x52,0xB2,0x05, +0x98,0xA2,0x6F,0x2A,0xC5,0x54,0xBD,0x25,0xBD,0x5F,0xAE,0xC8,0x86,0xEA,0x46,0x2C, +0xC1,0xB3,0xBD,0xC1,0xE9,0x49,0x70,0x18,0x16,0x97,0x08,0x13,0x8C,0x20,0xE0,0x1B, +0x2E,0x3A,0x47,0xCB,0x1E,0xE4,0x00,0x30,0x95,0x5B,0xF4,0x45,0xA3,0xC0,0x1A,0xB0, +0x01,0x4E,0xAB,0xBD,0xC0,0x23,0x6E,0x63,0x3F,0x80,0x4A,0xC5,0x07,0xED,0xDC,0xE2, +0x6F,0xC7,0xC1,0x62,0xF1,0xE3,0x72,0xD6,0x04,0xC8,0x74,0x67,0x0B,0xFA,0x88,0xAB, +0xA1,0x01,0xC8,0x6F,0xF0,0x14,0xAF,0xD2,0x99,0xCD,0x51,0x93,0x7E,0xED,0x2E,0x38, +0xC7,0xBD,0xCE,0x46,0x50,0x3D,0x72,0xE3,0x79,0x25,0x9D,0x9B,0x88,0x2B,0x10,0x20, +0xDD,0xA5,0xB8,0x32,0x9F,0x8D,0xE0,0x29,0xDF,0x21,0x74,0x86,0x82,0xDB,0x2F,0x82, +0x30,0xC6,0xC7,0x35,0x86,0xB3,0xF9,0x96,0x5F,0x46,0xDB,0x0C,0x45,0xFD,0xF3,0x50, +0xC3,0x6F,0xC6,0xC3,0x48,0xAD,0x46,0xA6,0xE1,0x27,0x47,0x0A,0x1D,0x0E,0x9B,0xB6, +0xC2,0x77,0x7F,0x63,0xF2,0xE0,0x7D,0x1A,0xBE,0xFC,0xE0,0xDF,0xD7,0xC7,0xA7,0x6C, +0xB0,0xF9,0xAE,0xBA,0x3C,0xFD,0x74,0xB4,0x11,0xE8,0x58,0x0D,0x80,0xBC,0xD3,0xA8, +0x80,0x3A,0x99,0xED,0x75,0xCC,0x46,0x7B, +}; + + +/* subject:/C=US/O=America Online Inc./CN=America Online Root Certification Authority 2 */ +/* issuer :/C=US/O=America Online Inc./CN=America Online Root Certification Authority 2 */ + + +const unsigned char America_Online_Root_Certification_Authority_2_certificate[1448]={ +0x30,0x82,0x05,0xA4,0x30,0x82,0x03,0x8C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,0x65,0x72,0x69,0x63,0x61, +0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x36,0x30,0x34, +0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,0x20,0x4F, +0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x38,0x30,0x36, +0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x30,0x39,0x32,0x39,0x31,0x34,0x30, +0x38,0x30,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D, +0x65,0x72,0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72, +0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75, +0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F, +0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xCC,0x41,0x45,0x1D,0xE9,0x3D, +0x4D,0x10,0xF6,0x8C,0xB1,0x41,0xC9,0xE0,0x5E,0xCB,0x0D,0xB7,0xBF,0x47,0x73,0xD3, +0xF0,0x55,0x4D,0xDD,0xC6,0x0C,0xFA,0xB1,0x66,0x05,0x6A,0xCD,0x78,0xB4,0xDC,0x02, +0xDB,0x4E,0x81,0xF3,0xD7,0xA7,0x7C,0x71,0xBC,0x75,0x63,0xA0,0x5D,0xE3,0x07,0x0C, +0x48,0xEC,0x25,0xC4,0x03,0x20,0xF4,0xFF,0x0E,0x3B,0x12,0xFF,0x9B,0x8D,0xE1,0xC6, +0xD5,0x1B,0xB4,0x6D,0x22,0xE3,0xB1,0xDB,0x7F,0x21,0x64,0xAF,0x86,0xBC,0x57,0x22, +0x2A,0xD6,0x47,0x81,0x57,0x44,0x82,0x56,0x53,0xBD,0x86,0x14,0x01,0x0B,0xFC,0x7F, +0x74,0xA4,0x5A,0xAE,0xF1,0xBA,0x11,0xB5,0x9B,0x58,0x5A,0x80,0xB4,0x37,0x78,0x09, +0x33,0x7C,0x32,0x47,0x03,0x5C,0xC4,0xA5,0x83,0x48,0xF4,0x57,0x56,0x6E,0x81,0x36, +0x27,0x18,0x4F,0xEC,0x9B,0x28,0xC2,0xD4,0xB4,0xD7,0x7C,0x0C,0x3E,0x0C,0x2B,0xDF, +0xCA,0x04,0xD7,0xC6,0x8E,0xEA,0x58,0x4E,0xA8,0xA4,0xA5,0x18,0x1C,0x6C,0x45,0x98, +0xA3,0x41,0xD1,0x2D,0xD2,0xC7,0x6D,0x8D,0x19,0xF1,0xAD,0x79,0xB7,0x81,0x3F,0xBD, +0x06,0x82,0x27,0x2D,0x10,0x58,0x05,0xB5,0x78,0x05,0xB9,0x2F,0xDB,0x0C,0x6B,0x90, +0x90,0x7E,0x14,0x59,0x38,0xBB,0x94,0x24,0x13,0xE5,0xD1,0x9D,0x14,0xDF,0xD3,0x82, +0x4D,0x46,0xF0,0x80,0x39,0x52,0x32,0x0F,0xE3,0x84,0xB2,0x7A,0x43,0xF2,0x5E,0xDE, +0x5F,0x3F,0x1D,0xDD,0xE3,0xB2,0x1B,0xA0,0xA1,0x2A,0x23,0x03,0x6E,0x2E,0x01,0x15, +0x87,0x5C,0xA6,0x75,0x75,0xC7,0x97,0x61,0xBE,0xDE,0x86,0xDC,0xD4,0x48,0xDB,0xBD, +0x2A,0xBF,0x4A,0x55,0xDA,0xE8,0x7D,0x50,0xFB,0xB4,0x80,0x17,0xB8,0x94,0xBF,0x01, +0x3D,0xEA,0xDA,0xBA,0x7C,0xE0,0x58,0x67,0x17,0xB9,0x58,0xE0,0x88,0x86,0x46,0x67, +0x6C,0x9D,0x10,0x47,0x58,0x32,0xD0,0x35,0x7C,0x79,0x2A,0x90,0xA2,0x5A,0x10,0x11, +0x23,0x35,0xAD,0x2F,0xCC,0xE4,0x4A,0x5B,0xA7,0xC8,0x27,0xF2,0x83,0xDE,0x5E,0xBB, +0x5E,0x77,0xE7,0xE8,0xA5,0x6E,0x63,0xC2,0x0D,0x5D,0x61,0xD0,0x8C,0xD2,0x6C,0x5A, +0x21,0x0E,0xCA,0x28,0xA3,0xCE,0x2A,0xE9,0x95,0xC7,0x48,0xCF,0x96,0x6F,0x1D,0x92, +0x25,0xC8,0xC6,0xC6,0xC1,0xC1,0x0C,0x05,0xAC,0x26,0xC4,0xD2,0x75,0xD2,0xE1,0x2A, +0x67,0xC0,0x3D,0x5B,0xA5,0x9A,0xEB,0xCF,0x7B,0x1A,0xA8,0x9D,0x14,0x45,0xE5,0x0F, +0xA0,0x9A,0x65,0xDE,0x2F,0x28,0xBD,0xCE,0x6F,0x94,0x66,0x83,0x48,0x29,0xD8,0xEA, +0x65,0x8C,0xAF,0x93,0xD9,0x64,0x9F,0x55,0x57,0x26,0xBF,0x6F,0xCB,0x37,0x31,0x99, +0xA3,0x60,0xBB,0x1C,0xAD,0x89,0x34,0x32,0x62,0xB8,0x43,0x21,0x06,0x72,0x0C,0xA1, +0x5C,0x6D,0x46,0xC5,0xFA,0x29,0xCF,0x30,0xDE,0x89,0xDC,0x71,0x5B,0xDD,0xB6,0x37, +0x3E,0xDF,0x50,0xF5,0xB8,0x07,0x25,0x26,0xE5,0xBC,0xB5,0xFE,0x3C,0x02,0xB3,0xB7, +0xF8,0xBE,0x43,0xC1,0x87,0x11,0x94,0x9E,0x23,0x6C,0x17,0x8A,0xB8,0x8A,0x27,0x0C, +0x54,0x47,0xF0,0xA9,0xB3,0xC0,0x80,0x8C,0xA0,0x27,0xEB,0x1D,0x19,0xE3,0x07,0x8E, +0x77,0x70,0xCA,0x2B,0xF4,0x7D,0x76,0xE0,0x78,0x67,0x02,0x03,0x01,0x00,0x01,0xA3, +0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4D, +0x45,0xC1,0x68,0x38,0xBB,0x73,0xA9,0x69,0xA1,0x20,0xE7,0xED,0xF5,0x22,0xA1,0x23, +0x14,0xD7,0x9E,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0x4D,0x45,0xC1,0x68,0x38,0xBB,0x73,0xA9,0x69,0xA1,0x20,0xE7,0xED,0xF5,0x22,0xA1, +0x23,0x14,0xD7,0x9E,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x67,0x6B,0x06,0xB9,0x5F,0x45,0x3B,0x2A, +0x4B,0x33,0xB3,0xE6,0x1B,0x6B,0x59,0x4E,0x22,0xCC,0xB9,0xB7,0xA4,0x25,0xC9,0xA7, +0xC4,0xF0,0x54,0x96,0x0B,0x64,0xF3,0xB1,0x58,0x4F,0x5E,0x51,0xFC,0xB2,0x97,0x7B, +0x27,0x65,0xC2,0xE5,0xCA,0xE7,0x0D,0x0C,0x25,0x7B,0x62,0xE3,0xFA,0x9F,0xB4,0x87, +0xB7,0x45,0x46,0xAF,0x83,0xA5,0x97,0x48,0x8C,0xA5,0xBD,0xF1,0x16,0x2B,0x9B,0x76, +0x2C,0x7A,0x35,0x60,0x6C,0x11,0x80,0x97,0xCC,0xA9,0x92,0x52,0xE6,0x2B,0xE6,0x69, +0xED,0xA9,0xF8,0x36,0x2D,0x2C,0x77,0xBF,0x61,0x48,0xD1,0x63,0x0B,0xB9,0x5B,0x52, +0xED,0x18,0xB0,0x43,0x42,0x22,0xA6,0xB1,0x77,0xAE,0xDE,0x69,0xC5,0xCD,0xC7,0x1C, +0xA1,0xB1,0xA5,0x1C,0x10,0xFB,0x18,0xBE,0x1A,0x70,0xDD,0xC1,0x92,0x4B,0xBE,0x29, +0x5A,0x9D,0x3F,0x35,0xBE,0xE5,0x7D,0x51,0xF8,0x55,0xE0,0x25,0x75,0x23,0x87,0x1E, +0x5C,0xDC,0xBA,0x9D,0xB0,0xAC,0xB3,0x69,0xDB,0x17,0x83,0xC9,0xF7,0xDE,0x0C,0xBC, +0x08,0xDC,0x91,0x9E,0xA8,0xD0,0xD7,0x15,0x37,0x73,0xA5,0x35,0xB8,0xFC,0x7E,0xC5, +0x44,0x40,0x06,0xC3,0xEB,0xF8,0x22,0x80,0x5C,0x47,0xCE,0x02,0xE3,0x11,0x9F,0x44, +0xFF,0xFD,0x9A,0x32,0xCC,0x7D,0x64,0x51,0x0E,0xEB,0x57,0x26,0x76,0x3A,0xE3,0x1E, +0x22,0x3C,0xC2,0xA6,0x36,0xDD,0x19,0xEF,0xA7,0xFC,0x12,0xF3,0x26,0xC0,0x59,0x31, +0x85,0x4C,0x9C,0xD8,0xCF,0xDF,0xA4,0xCC,0xCC,0x29,0x93,0xFF,0x94,0x6D,0x76,0x5C, +0x13,0x08,0x97,0xF2,0xED,0xA5,0x0B,0x4D,0xDD,0xE8,0xC9,0x68,0x0E,0x66,0xD3,0x00, +0x0E,0x33,0x12,0x5B,0xBC,0x95,0xE5,0x32,0x90,0xA8,0xB3,0xC6,0x6C,0x83,0xAD,0x77, +0xEE,0x8B,0x7E,0x7E,0xB1,0xA9,0xAB,0xD3,0xE1,0xF1,0xB6,0xC0,0xB1,0xEA,0x88,0xC0, +0xE7,0xD3,0x90,0xE9,0x28,0x92,0x94,0x7B,0x68,0x7B,0x97,0x2A,0x0A,0x67,0x2D,0x85, +0x02,0x38,0x10,0xE4,0x03,0x61,0xD4,0xDA,0x25,0x36,0xC7,0x08,0x58,0x2D,0xA1,0xA7, +0x51,0xAF,0x30,0x0A,0x49,0xF5,0xA6,0x69,0x87,0x07,0x2D,0x44,0x46,0x76,0x8E,0x2A, +0xE5,0x9A,0x3B,0xD7,0x18,0xA2,0xFC,0x9C,0x38,0x10,0xCC,0xC6,0x3B,0xD2,0xB5,0x17, +0x3A,0x6F,0xFD,0xAE,0x25,0xBD,0xF5,0x72,0x59,0x64,0xB1,0x74,0x2A,0x38,0x5F,0x18, +0x4C,0xDF,0xCF,0x71,0x04,0x5A,0x36,0xD4,0xBF,0x2F,0x99,0x9C,0xE8,0xD9,0xBA,0xB1, +0x95,0xE6,0x02,0x4B,0x21,0xA1,0x5B,0xD5,0xC1,0x4F,0x8F,0xAE,0x69,0x6D,0x53,0xDB, +0x01,0x93,0xB5,0x5C,0x1E,0x18,0xDD,0x64,0x5A,0xCA,0x18,0x28,0x3E,0x63,0x04,0x11, +0xFD,0x1C,0x8D,0x00,0x0F,0xB8,0x37,0xDF,0x67,0x8A,0x9D,0x66,0xA9,0x02,0x6A,0x91, +0xFF,0x13,0xCA,0x2F,0x5D,0x83,0xBC,0x87,0x93,0x6C,0xDC,0x24,0x51,0x16,0x04,0x25, +0x66,0xFA,0xB3,0xD9,0xC2,0xBA,0x29,0xBE,0x9A,0x48,0x38,0x82,0x99,0xF4,0xBF,0x3B, +0x4A,0x31,0x19,0xF9,0xBF,0x8E,0x21,0x33,0x14,0xCA,0x4F,0x54,0x5F,0xFB,0xCE,0xFB, +0x8F,0x71,0x7F,0xFD,0x5E,0x19,0xA0,0x0F,0x4B,0x91,0xB8,0xC4,0x54,0xBC,0x06,0xB0, +0x45,0x8F,0x26,0x91,0xA2,0x8E,0xFE,0xA9, +}; + + +/* subject:/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root */ +/* issuer :/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root */ + + +const unsigned char Baltimore_CyberTrust_Root_certificate[891]={ +0x30,0x82,0x03,0x77,0x30,0x82,0x02,0x5F,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x02, +0x00,0x00,0xB9,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49, +0x45,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x0A,0x13,0x09,0x42,0x61,0x6C,0x74, +0x69,0x6D,0x6F,0x72,0x65,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0B,0x13,0x0A, +0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x31,0x22,0x30,0x20,0x06,0x03, +0x55,0x04,0x03,0x13,0x19,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72,0x65,0x20,0x43, +0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E, +0x17,0x0D,0x30,0x30,0x30,0x35,0x31,0x32,0x31,0x38,0x34,0x36,0x30,0x30,0x5A,0x17, +0x0D,0x32,0x35,0x30,0x35,0x31,0x32,0x32,0x33,0x35,0x39,0x30,0x30,0x5A,0x30,0x5A, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x45,0x31,0x12,0x30, +0x10,0x06,0x03,0x55,0x04,0x0A,0x13,0x09,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72, +0x65,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0B,0x13,0x0A,0x43,0x79,0x62,0x65, +0x72,0x54,0x72,0x75,0x73,0x74,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x03,0x13, +0x19,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72,0x65,0x20,0x43,0x79,0x62,0x65,0x72, +0x54,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01, +0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xA3,0x04,0xBB,0x22,0xAB, +0x98,0x3D,0x57,0xE8,0x26,0x72,0x9A,0xB5,0x79,0xD4,0x29,0xE2,0xE1,0xE8,0x95,0x80, +0xB1,0xB0,0xE3,0x5B,0x8E,0x2B,0x29,0x9A,0x64,0xDF,0xA1,0x5D,0xED,0xB0,0x09,0x05, +0x6D,0xDB,0x28,0x2E,0xCE,0x62,0xA2,0x62,0xFE,0xB4,0x88,0xDA,0x12,0xEB,0x38,0xEB, +0x21,0x9D,0xC0,0x41,0x2B,0x01,0x52,0x7B,0x88,0x77,0xD3,0x1C,0x8F,0xC7,0xBA,0xB9, +0x88,0xB5,0x6A,0x09,0xE7,0x73,0xE8,0x11,0x40,0xA7,0xD1,0xCC,0xCA,0x62,0x8D,0x2D, +0xE5,0x8F,0x0B,0xA6,0x50,0xD2,0xA8,0x50,0xC3,0x28,0xEA,0xF5,0xAB,0x25,0x87,0x8A, +0x9A,0x96,0x1C,0xA9,0x67,0xB8,0x3F,0x0C,0xD5,0xF7,0xF9,0x52,0x13,0x2F,0xC2,0x1B, +0xD5,0x70,0x70,0xF0,0x8F,0xC0,0x12,0xCA,0x06,0xCB,0x9A,0xE1,0xD9,0xCA,0x33,0x7A, +0x77,0xD6,0xF8,0xEC,0xB9,0xF1,0x68,0x44,0x42,0x48,0x13,0xD2,0xC0,0xC2,0xA4,0xAE, +0x5E,0x60,0xFE,0xB6,0xA6,0x05,0xFC,0xB4,0xDD,0x07,0x59,0x02,0xD4,0x59,0x18,0x98, +0x63,0xF5,0xA5,0x63,0xE0,0x90,0x0C,0x7D,0x5D,0xB2,0x06,0x7A,0xF3,0x85,0xEA,0xEB, +0xD4,0x03,0xAE,0x5E,0x84,0x3E,0x5F,0xFF,0x15,0xED,0x69,0xBC,0xF9,0x39,0x36,0x72, +0x75,0xCF,0x77,0x52,0x4D,0xF3,0xC9,0x90,0x2C,0xB9,0x3D,0xE5,0xC9,0x23,0x53,0x3F, +0x1F,0x24,0x98,0x21,0x5C,0x07,0x99,0x29,0xBD,0xC6,0x3A,0xEC,0xE7,0x6E,0x86,0x3A, +0x6B,0x97,0x74,0x63,0x33,0xBD,0x68,0x18,0x31,0xF0,0x78,0x8D,0x76,0xBF,0xFC,0x9E, +0x8E,0x5D,0x2A,0x86,0xA7,0x4D,0x90,0xDC,0x27,0x1A,0x39,0x02,0x03,0x01,0x00,0x01, +0xA3,0x45,0x30,0x43,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xE5, +0x9D,0x59,0x30,0x82,0x47,0x58,0xCC,0xAC,0xFA,0x08,0x54,0x36,0x86,0x7B,0x3A,0xB5, +0x04,0x4D,0xF0,0x30,0x12,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x08,0x30, +0x06,0x01,0x01,0xFF,0x02,0x01,0x03,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01, +0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x85,0x0C,0x5D,0x8E,0xE4, +0x6F,0x51,0x68,0x42,0x05,0xA0,0xDD,0xBB,0x4F,0x27,0x25,0x84,0x03,0xBD,0xF7,0x64, +0xFD,0x2D,0xD7,0x30,0xE3,0xA4,0x10,0x17,0xEB,0xDA,0x29,0x29,0xB6,0x79,0x3F,0x76, +0xF6,0x19,0x13,0x23,0xB8,0x10,0x0A,0xF9,0x58,0xA4,0xD4,0x61,0x70,0xBD,0x04,0x61, +0x6A,0x12,0x8A,0x17,0xD5,0x0A,0xBD,0xC5,0xBC,0x30,0x7C,0xD6,0xE9,0x0C,0x25,0x8D, +0x86,0x40,0x4F,0xEC,0xCC,0xA3,0x7E,0x38,0xC6,0x37,0x11,0x4F,0xED,0xDD,0x68,0x31, +0x8E,0x4C,0xD2,0xB3,0x01,0x74,0xEE,0xBE,0x75,0x5E,0x07,0x48,0x1A,0x7F,0x70,0xFF, +0x16,0x5C,0x84,0xC0,0x79,0x85,0xB8,0x05,0xFD,0x7F,0xBE,0x65,0x11,0xA3,0x0F,0xC0, +0x02,0xB4,0xF8,0x52,0x37,0x39,0x04,0xD5,0xA9,0x31,0x7A,0x18,0xBF,0xA0,0x2A,0xF4, +0x12,0x99,0xF7,0xA3,0x45,0x82,0xE3,0x3C,0x5E,0xF5,0x9D,0x9E,0xB5,0xC8,0x9E,0x7C, +0x2E,0xC8,0xA4,0x9E,0x4E,0x08,0x14,0x4B,0x6D,0xFD,0x70,0x6D,0x6B,0x1A,0x63,0xBD, +0x64,0xE6,0x1F,0xB7,0xCE,0xF0,0xF2,0x9F,0x2E,0xBB,0x1B,0xB7,0xF2,0x50,0x88,0x73, +0x92,0xC2,0xE2,0xE3,0x16,0x8D,0x9A,0x32,0x02,0xAB,0x8E,0x18,0xDD,0xE9,0x10,0x11, +0xEE,0x7E,0x35,0xAB,0x90,0xAF,0x3E,0x30,0x94,0x7A,0xD0,0x33,0x3D,0xA7,0x65,0x0F, +0xF5,0xFC,0x8E,0x9E,0x62,0xCF,0x47,0x44,0x2C,0x01,0x5D,0xBB,0x1D,0xB5,0x32,0xD2, +0x47,0xD2,0x38,0x2E,0xD0,0xFE,0x81,0xDC,0x32,0x6A,0x1E,0xB5,0xEE,0x3C,0xD5,0xFC, +0xE7,0x81,0x1D,0x19,0xC3,0x24,0x42,0xEA,0x63,0x39,0xA9, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=AAA Certificate Services */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=AAA Certificate Services */ + + +const unsigned char Comodo_AAA_Services_root_certificate[1078]={ +0x30,0x82,0x04,0x32,0x30,0x82,0x03,0x1A,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x7B,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x21,0x30,0x1F,0x06,0x03,0x55, +0x04,0x03,0x0C,0x18,0x41,0x41,0x41,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,0x1E,0x17,0x0D, +0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32, +0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x7B,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06, +0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61, +0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04, +0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03, +0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43,0x41,0x20,0x4C, +0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x0C, +0x18,0x41,0x41,0x41,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65, +0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,0x82,0x01,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F, +0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xBE,0x40,0x9D,0xF4,0x6E,0xE1, +0xEA,0x76,0x87,0x1C,0x4D,0x45,0x44,0x8E,0xBE,0x46,0xC8,0x83,0x06,0x9D,0xC1,0x2A, +0xFE,0x18,0x1F,0x8E,0xE4,0x02,0xFA,0xF3,0xAB,0x5D,0x50,0x8A,0x16,0x31,0x0B,0x9A, +0x06,0xD0,0xC5,0x70,0x22,0xCD,0x49,0x2D,0x54,0x63,0xCC,0xB6,0x6E,0x68,0x46,0x0B, +0x53,0xEA,0xCB,0x4C,0x24,0xC0,0xBC,0x72,0x4E,0xEA,0xF1,0x15,0xAE,0xF4,0x54,0x9A, +0x12,0x0A,0xC3,0x7A,0xB2,0x33,0x60,0xE2,0xDA,0x89,0x55,0xF3,0x22,0x58,0xF3,0xDE, +0xDC,0xCF,0xEF,0x83,0x86,0xA2,0x8C,0x94,0x4F,0x9F,0x68,0xF2,0x98,0x90,0x46,0x84, +0x27,0xC7,0x76,0xBF,0xE3,0xCC,0x35,0x2C,0x8B,0x5E,0x07,0x64,0x65,0x82,0xC0,0x48, +0xB0,0xA8,0x91,0xF9,0x61,0x9F,0x76,0x20,0x50,0xA8,0x91,0xC7,0x66,0xB5,0xEB,0x78, +0x62,0x03,0x56,0xF0,0x8A,0x1A,0x13,0xEA,0x31,0xA3,0x1E,0xA0,0x99,0xFD,0x38,0xF6, +0xF6,0x27,0x32,0x58,0x6F,0x07,0xF5,0x6B,0xB8,0xFB,0x14,0x2B,0xAF,0xB7,0xAA,0xCC, +0xD6,0x63,0x5F,0x73,0x8C,0xDA,0x05,0x99,0xA8,0x38,0xA8,0xCB,0x17,0x78,0x36,0x51, +0xAC,0xE9,0x9E,0xF4,0x78,0x3A,0x8D,0xCF,0x0F,0xD9,0x42,0xE2,0x98,0x0C,0xAB,0x2F, +0x9F,0x0E,0x01,0xDE,0xEF,0x9F,0x99,0x49,0xF1,0x2D,0xDF,0xAC,0x74,0x4D,0x1B,0x98, +0xB5,0x47,0xC5,0xE5,0x29,0xD1,0xF9,0x90,0x18,0xC7,0x62,0x9C,0xBE,0x83,0xC7,0x26, +0x7B,0x3E,0x8A,0x25,0xC7,0xC0,0xDD,0x9D,0xE6,0x35,0x68,0x10,0x20,0x9D,0x8F,0xD8, +0xDE,0xD2,0xC3,0x84,0x9C,0x0D,0x5E,0xE8,0x2F,0xC9,0x02,0x03,0x01,0x00,0x01,0xA3, +0x81,0xC0,0x30,0x81,0xBD,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14, +0xA0,0x11,0x0A,0x23,0x3E,0x96,0xF1,0x07,0xEC,0xE2,0xAF,0x29,0xEF,0x82,0xA5,0x7F, +0xD0,0x30,0xA4,0xB4,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x7B,0x06,0x03,0x55,0x1D,0x1F,0x04,0x74,0x30,0x72, +0x30,0x38,0xA0,0x36,0xA0,0x34,0x86,0x32,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63, +0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F, +0x41,0x41,0x41,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65, +0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30,0x36,0xA0,0x34,0xA0,0x32, +0x86,0x30,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D, +0x6F,0x64,0x6F,0x2E,0x6E,0x65,0x74,0x2F,0x41,0x41,0x41,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63, +0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05, +0x00,0x03,0x82,0x01,0x01,0x00,0x08,0x56,0xFC,0x02,0xF0,0x9B,0xE8,0xFF,0xA4,0xFA, +0xD6,0x7B,0xC6,0x44,0x80,0xCE,0x4F,0xC4,0xC5,0xF6,0x00,0x58,0xCC,0xA6,0xB6,0xBC, +0x14,0x49,0x68,0x04,0x76,0xE8,0xE6,0xEE,0x5D,0xEC,0x02,0x0F,0x60,0xD6,0x8D,0x50, +0x18,0x4F,0x26,0x4E,0x01,0xE3,0xE6,0xB0,0xA5,0xEE,0xBF,0xBC,0x74,0x54,0x41,0xBF, +0xFD,0xFC,0x12,0xB8,0xC7,0x4F,0x5A,0xF4,0x89,0x60,0x05,0x7F,0x60,0xB7,0x05,0x4A, +0xF3,0xF6,0xF1,0xC2,0xBF,0xC4,0xB9,0x74,0x86,0xB6,0x2D,0x7D,0x6B,0xCC,0xD2,0xF3, +0x46,0xDD,0x2F,0xC6,0xE0,0x6A,0xC3,0xC3,0x34,0x03,0x2C,0x7D,0x96,0xDD,0x5A,0xC2, +0x0E,0xA7,0x0A,0x99,0xC1,0x05,0x8B,0xAB,0x0C,0x2F,0xF3,0x5C,0x3A,0xCF,0x6C,0x37, +0x55,0x09,0x87,0xDE,0x53,0x40,0x6C,0x58,0xEF,0xFC,0xB6,0xAB,0x65,0x6E,0x04,0xF6, +0x1B,0xDC,0x3C,0xE0,0x5A,0x15,0xC6,0x9E,0xD9,0xF1,0x59,0x48,0x30,0x21,0x65,0x03, +0x6C,0xEC,0xE9,0x21,0x73,0xEC,0x9B,0x03,0xA1,0xE0,0x37,0xAD,0xA0,0x15,0x18,0x8F, +0xFA,0xBA,0x02,0xCE,0xA7,0x2C,0xA9,0x10,0x13,0x2C,0xD4,0xE5,0x08,0x26,0xAB,0x22, +0x97,0x60,0xF8,0x90,0x5E,0x74,0xD4,0xA2,0x9A,0x53,0xBD,0xF2,0xA9,0x68,0xE0,0xA2, +0x6E,0xC2,0xD7,0x6C,0xB1,0xA3,0x0F,0x9E,0xBF,0xEB,0x68,0xE7,0x56,0xF2,0xAE,0xF2, +0xE3,0x2B,0x38,0x3A,0x09,0x81,0xB5,0x6B,0x85,0xD7,0xBE,0x2D,0xED,0x3F,0x1A,0xB7, +0xB2,0x63,0xE2,0xF5,0x62,0x2C,0x82,0xD4,0x6A,0x00,0x41,0x50,0xF1,0x39,0x83,0x9F, +0x95,0xE9,0x36,0x96,0x98,0x6E, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority */ + + +const unsigned char COMODO_Certification_Authority_certificate[1057]={ +0x30,0x82,0x04,0x1D,0x30,0x82,0x03,0x05,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x4E, +0x81,0x2D,0x8A,0x82,0x65,0xE0,0x0B,0x02,0xEE,0x3E,0x35,0x02,0x46,0xE5,0x3D,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x81,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x27,0x30,0x25,0x06,0x03,0x55, +0x04,0x03,0x13,0x1E,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x30,0x31,0x30,0x30,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35, +0x39,0x5A,0x30,0x81,0x81,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x47,0x42,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65, +0x61,0x74,0x65,0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72, +0x64,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F, +0x44,0x4F,0x20,0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x27,0x30, +0x25,0x06,0x03,0x55,0x04,0x03,0x13,0x1E,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xD0,0x40,0x8B,0x8B,0x72,0xE3,0x91,0x1B,0xF7, +0x51,0xC1,0x1B,0x54,0x04,0x98,0xD3,0xA9,0xBF,0xC1,0xE6,0x8A,0x5D,0x3B,0x87,0xFB, +0xBB,0x88,0xCE,0x0D,0xE3,0x2F,0x3F,0x06,0x96,0xF0,0xA2,0x29,0x50,0x99,0xAE,0xDB, +0x3B,0xA1,0x57,0xB0,0x74,0x51,0x71,0xCD,0xED,0x42,0x91,0x4D,0x41,0xFE,0xA9,0xC8, +0xD8,0x6A,0x86,0x77,0x44,0xBB,0x59,0x66,0x97,0x50,0x5E,0xB4,0xD4,0x2C,0x70,0x44, +0xCF,0xDA,0x37,0x95,0x42,0x69,0x3C,0x30,0xC4,0x71,0xB3,0x52,0xF0,0x21,0x4D,0xA1, +0xD8,0xBA,0x39,0x7C,0x1C,0x9E,0xA3,0x24,0x9D,0xF2,0x83,0x16,0x98,0xAA,0x16,0x7C, +0x43,0x9B,0x15,0x5B,0xB7,0xAE,0x34,0x91,0xFE,0xD4,0x62,0x26,0x18,0x46,0x9A,0x3F, +0xEB,0xC1,0xF9,0xF1,0x90,0x57,0xEB,0xAC,0x7A,0x0D,0x8B,0xDB,0x72,0x30,0x6A,0x66, +0xD5,0xE0,0x46,0xA3,0x70,0xDC,0x68,0xD9,0xFF,0x04,0x48,0x89,0x77,0xDE,0xB5,0xE9, +0xFB,0x67,0x6D,0x41,0xE9,0xBC,0x39,0xBD,0x32,0xD9,0x62,0x02,0xF1,0xB1,0xA8,0x3D, +0x6E,0x37,0x9C,0xE2,0x2F,0xE2,0xD3,0xA2,0x26,0x8B,0xC6,0xB8,0x55,0x43,0x88,0xE1, +0x23,0x3E,0xA5,0xD2,0x24,0x39,0x6A,0x47,0xAB,0x00,0xD4,0xA1,0xB3,0xA9,0x25,0xFE, +0x0D,0x3F,0xA7,0x1D,0xBA,0xD3,0x51,0xC1,0x0B,0xA4,0xDA,0xAC,0x38,0xEF,0x55,0x50, +0x24,0x05,0x65,0x46,0x93,0x34,0x4F,0x2D,0x8D,0xAD,0xC6,0xD4,0x21,0x19,0xD2,0x8E, +0xCA,0x05,0x61,0x71,0x07,0x73,0x47,0xE5,0x8A,0x19,0x12,0xBD,0x04,0x4D,0xCE,0x4E, +0x9C,0xA5,0x48,0xAC,0xBB,0x26,0xF7,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x8E,0x30, +0x81,0x8B,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x0B,0x58,0xE5, +0x8B,0xC6,0x4C,0x15,0x37,0xA4,0x40,0xA9,0x30,0xA9,0x21,0xBE,0x47,0x36,0x5A,0x56, +0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x49,0x06,0x03,0x55,0x1D,0x1F,0x04,0x42,0x30,0x40,0x30,0x3E,0xA0, +0x3C,0xA0,0x3A,0x86,0x38,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E, +0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x43,0x4F,0x4D, +0x4F,0x44,0x4F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x2E,0x63,0x72,0x6C,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01, +0x00,0x3E,0x98,0x9E,0x9B,0xF6,0x1B,0xE9,0xD7,0x39,0xB7,0x78,0xAE,0x1D,0x72,0x18, +0x49,0xD3,0x87,0xE4,0x43,0x82,0xEB,0x3F,0xC9,0xAA,0xF5,0xA8,0xB5,0xEF,0x55,0x7C, +0x21,0x52,0x65,0xF9,0xD5,0x0D,0xE1,0x6C,0xF4,0x3E,0x8C,0x93,0x73,0x91,0x2E,0x02, +0xC4,0x4E,0x07,0x71,0x6F,0xC0,0x8F,0x38,0x61,0x08,0xA8,0x1E,0x81,0x0A,0xC0,0x2F, +0x20,0x2F,0x41,0x8B,0x91,0xDC,0x48,0x45,0xBC,0xF1,0xC6,0xDE,0xBA,0x76,0x6B,0x33, +0xC8,0x00,0x2D,0x31,0x46,0x4C,0xED,0xE7,0x9D,0xCF,0x88,0x94,0xFF,0x33,0xC0,0x56, +0xE8,0x24,0x86,0x26,0xB8,0xD8,0x38,0x38,0xDF,0x2A,0x6B,0xDD,0x12,0xCC,0xC7,0x3F, +0x47,0x17,0x4C,0xA2,0xC2,0x06,0x96,0x09,0xD6,0xDB,0xFE,0x3F,0x3C,0x46,0x41,0xDF, +0x58,0xE2,0x56,0x0F,0x3C,0x3B,0xC1,0x1C,0x93,0x35,0xD9,0x38,0x52,0xAC,0xEE,0xC8, +0xEC,0x2E,0x30,0x4E,0x94,0x35,0xB4,0x24,0x1F,0x4B,0x78,0x69,0xDA,0xF2,0x02,0x38, +0xCC,0x95,0x52,0x93,0xF0,0x70,0x25,0x59,0x9C,0x20,0x67,0xC4,0xEE,0xF9,0x8B,0x57, +0x61,0xF4,0x92,0x76,0x7D,0x3F,0x84,0x8D,0x55,0xB7,0xE8,0xE5,0xAC,0xD5,0xF1,0xF5, +0x19,0x56,0xA6,0x5A,0xFB,0x90,0x1C,0xAF,0x93,0xEB,0xE5,0x1C,0xD4,0x67,0x97,0x5D, +0x04,0x0E,0xBE,0x0B,0x83,0xA6,0x17,0x83,0xB9,0x30,0x12,0xA0,0xC5,0x33,0x15,0x05, +0xB9,0x0D,0xFB,0xC7,0x05,0x76,0xE3,0xD8,0x4A,0x8D,0xFC,0x34,0x17,0xA3,0xC6,0x21, +0x28,0xBE,0x30,0x45,0x31,0x1E,0xC7,0x78,0xBE,0x58,0x61,0x38,0xAC,0x3B,0xE2,0x01, +0x65, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO ECC Certification Authority */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO ECC Certification Authority */ + + +const unsigned char COMODO_ECC_Certification_Authority_certificate[653]={ +0x30,0x82,0x02,0x89,0x30,0x82,0x02,0x0F,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x1F, +0x47,0xAF,0xAA,0x62,0x00,0x70,0x50,0x54,0x4C,0x01,0x9E,0x9B,0x63,0x99,0x2A,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x85,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06, +0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61, +0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04, +0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03, +0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,0x41,0x20,0x4C, +0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13, +0x22,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x45,0x43,0x43,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x38,0x30,0x33,0x30,0x36,0x30,0x30,0x30, +0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32,0x33,0x35,0x39, +0x35,0x39,0x5A,0x30,0x81,0x85,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72, +0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72, +0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F, +0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D, +0x4F,0x44,0x4F,0x20,0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x2B, +0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20, +0x45,0x43,0x43,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x76,0x30,0x10,0x06, +0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03, +0x62,0x00,0x04,0x03,0x47,0x7B,0x2F,0x75,0xC9,0x82,0x15,0x85,0xFB,0x75,0xE4,0x91, +0x16,0xD4,0xAB,0x62,0x99,0xF5,0x3E,0x52,0x0B,0x06,0xCE,0x41,0x00,0x7F,0x97,0xE1, +0x0A,0x24,0x3C,0x1D,0x01,0x04,0xEE,0x3D,0xD2,0x8D,0x09,0x97,0x0C,0xE0,0x75,0xE4, +0xFA,0xFB,0x77,0x8A,0x2A,0xF5,0x03,0x60,0x4B,0x36,0x8B,0x16,0x23,0x16,0xAD,0x09, +0x71,0xF4,0x4A,0xF4,0x28,0x50,0xB4,0xFE,0x88,0x1C,0x6E,0x3F,0x6C,0x2F,0x2F,0x09, +0x59,0x5B,0xA5,0x5B,0x0B,0x33,0x99,0xE2,0xC3,0x3D,0x89,0xF9,0x6A,0x2C,0xEF,0xB2, +0xD3,0x06,0xE9,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0x75,0x71,0xA7,0x19,0x48,0x19,0xBC,0x9D,0x9D,0xEA,0x41,0x47,0xDF,0x94, +0xC4,0x48,0x77,0x99,0xD3,0x79,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF, +0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D, +0x04,0x03,0x03,0x03,0x68,0x00,0x30,0x65,0x02,0x31,0x00,0xEF,0x03,0x5B,0x7A,0xAC, +0xB7,0x78,0x0A,0x72,0xB7,0x88,0xDF,0xFF,0xB5,0x46,0x14,0x09,0x0A,0xFA,0xA0,0xE6, +0x7D,0x08,0xC6,0x1A,0x87,0xBD,0x18,0xA8,0x73,0xBD,0x26,0xCA,0x60,0x0C,0x9D,0xCE, +0x99,0x9F,0xCF,0x5C,0x0F,0x30,0xE1,0xBE,0x14,0x31,0xEA,0x02,0x30,0x14,0xF4,0x93, +0x3C,0x49,0xA7,0x33,0x7A,0x90,0x46,0x47,0xB3,0x63,0x7D,0x13,0x9B,0x4E,0xB7,0x6F, +0x18,0x37,0x80,0x53,0xFE,0xDD,0x20,0xE0,0x35,0x9A,0x36,0xD1,0xC7,0x01,0xB9,0xE6, +0xDC,0xDD,0xF3,0xFF,0x1D,0x2C,0x3A,0x16,0x57,0xD9,0x92,0x39,0xD6, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Secure Certificate Services */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Secure Certificate Services */ + + +const unsigned char Comodo_Secure_Services_root_certificate[1091]={ +0x30,0x82,0x04,0x3F,0x30,0x82,0x03,0x27,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x7E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x03,0x0C,0x1B,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30, +0x1E,0x17,0x0D,0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A, +0x17,0x0D,0x32,0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30, +0x7E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x03,0x0C,0x1B,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30, +0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01, +0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00, +0xC0,0x71,0x33,0x82,0x8A,0xD0,0x70,0xEB,0x73,0x87,0x82,0x40,0xD5,0x1D,0xE4,0xCB, +0xC9,0x0E,0x42,0x90,0xF9,0xDE,0x34,0xB9,0xA1,0xBA,0x11,0xF4,0x25,0x85,0xF3,0xCC, +0x72,0x6D,0xF2,0x7B,0x97,0x6B,0xB3,0x07,0xF1,0x77,0x24,0x91,0x5F,0x25,0x8F,0xF6, +0x74,0x3D,0xE4,0x80,0xC2,0xF8,0x3C,0x0D,0xF3,0xBF,0x40,0xEA,0xF7,0xC8,0x52,0xD1, +0x72,0x6F,0xEF,0xC8,0xAB,0x41,0xB8,0x6E,0x2E,0x17,0x2A,0x95,0x69,0x0C,0xCD,0xD2, +0x1E,0x94,0x7B,0x2D,0x94,0x1D,0xAA,0x75,0xD7,0xB3,0x98,0xCB,0xAC,0xBC,0x64,0x53, +0x40,0xBC,0x8F,0xAC,0xAC,0x36,0xCB,0x5C,0xAD,0xBB,0xDD,0xE0,0x94,0x17,0xEC,0xD1, +0x5C,0xD0,0xBF,0xEF,0xA5,0x95,0xC9,0x90,0xC5,0xB0,0xAC,0xFB,0x1B,0x43,0xDF,0x7A, +0x08,0x5D,0xB7,0xB8,0xF2,0x40,0x1B,0x2B,0x27,0x9E,0x50,0xCE,0x5E,0x65,0x82,0x88, +0x8C,0x5E,0xD3,0x4E,0x0C,0x7A,0xEA,0x08,0x91,0xB6,0x36,0xAA,0x2B,0x42,0xFB,0xEA, +0xC2,0xA3,0x39,0xE5,0xDB,0x26,0x38,0xAD,0x8B,0x0A,0xEE,0x19,0x63,0xC7,0x1C,0x24, +0xDF,0x03,0x78,0xDA,0xE6,0xEA,0xC1,0x47,0x1A,0x0B,0x0B,0x46,0x09,0xDD,0x02,0xFC, +0xDE,0xCB,0x87,0x5F,0xD7,0x30,0x63,0x68,0xA1,0xAE,0xDC,0x32,0xA1,0xBA,0xBE,0xFE, +0x44,0xAB,0x68,0xB6,0xA5,0x17,0x15,0xFD,0xBD,0xD5,0xA7,0xA7,0x9A,0xE4,0x44,0x33, +0xE9,0x88,0x8E,0xFC,0xED,0x51,0xEB,0x93,0x71,0x4E,0xAD,0x01,0xE7,0x44,0x8E,0xAB, +0x2D,0xCB,0xA8,0xFE,0x01,0x49,0x48,0xF0,0xC0,0xDD,0xC7,0x68,0xD8,0x92,0xFE,0x3D, +0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xC7,0x30,0x81,0xC4,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0x3C,0xD8,0x93,0x88,0xC2,0xC0,0x82,0x09,0xCC,0x01, +0x99,0x06,0x93,0x20,0xE9,0x9E,0x70,0x09,0x63,0x4F,0x30,0x0E,0x06,0x03,0x55,0x1D, +0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x81,0x06,0x03, +0x55,0x1D,0x1F,0x04,0x7A,0x30,0x78,0x30,0x3B,0xA0,0x39,0xA0,0x37,0x86,0x35,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F, +0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x53,0x65,0x63,0x75,0x72,0x65,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73, +0x2E,0x63,0x72,0x6C,0x30,0x39,0xA0,0x37,0xA0,0x35,0x86,0x33,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x2E,0x6E,0x65, +0x74,0x2F,0x53,0x65,0x63,0x75,0x72,0x65,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x87,0x01,0x6D,0x23,0x1D,0x7E,0x5B,0x17,0x7D,0xC1,0x61,0x32,0xCF, +0x8F,0xE7,0xF3,0x8A,0x94,0x59,0x66,0xE0,0x9E,0x28,0xA8,0x5E,0xD3,0xB7,0xF4,0x34, +0xE6,0xAA,0x39,0xB2,0x97,0x16,0xC5,0x82,0x6F,0x32,0xA4,0xE9,0x8C,0xE7,0xAF,0xFD, +0xEF,0xC2,0xE8,0xB9,0x4B,0xAA,0xA3,0xF4,0xE6,0xDA,0x8D,0x65,0x21,0xFB,0xBA,0x80, +0xEB,0x26,0x28,0x85,0x1A,0xFE,0x39,0x8C,0xDE,0x5B,0x04,0x04,0xB4,0x54,0xF9,0xA3, +0x67,0x9E,0x41,0xFA,0x09,0x52,0xCC,0x05,0x48,0xA8,0xC9,0x3F,0x21,0x04,0x1E,0xCE, +0x48,0x6B,0xFC,0x85,0xE8,0xC2,0x7B,0xAF,0x7F,0xB7,0xCC,0xF8,0x5F,0x3A,0xFD,0x35, +0xC6,0x0D,0xEF,0x97,0xDC,0x4C,0xAB,0x11,0xE1,0x6B,0xCB,0x31,0xD1,0x6C,0xFB,0x48, +0x80,0xAB,0xDC,0x9C,0x37,0xB8,0x21,0x14,0x4B,0x0D,0x71,0x3D,0xEC,0x83,0x33,0x6E, +0xD1,0x6E,0x32,0x16,0xEC,0x98,0xC7,0x16,0x8B,0x59,0xA6,0x34,0xAB,0x05,0x57,0x2D, +0x93,0xF7,0xAA,0x13,0xCB,0xD2,0x13,0xE2,0xB7,0x2E,0x3B,0xCD,0x6B,0x50,0x17,0x09, +0x68,0x3E,0xB5,0x26,0x57,0xEE,0xB6,0xE0,0xB6,0xDD,0xB9,0x29,0x80,0x79,0x7D,0x8F, +0xA3,0xF0,0xA4,0x28,0xA4,0x15,0xC4,0x85,0xF4,0x27,0xD4,0x6B,0xBF,0xE5,0x5C,0xE4, +0x65,0x02,0x76,0x54,0xB4,0xE3,0x37,0x66,0x24,0xD3,0x19,0x61,0xC8,0x52,0x10,0xE5, +0x8B,0x37,0x9A,0xB9,0xA9,0xF9,0x1D,0xBF,0xEA,0x99,0x92,0x61,0x96,0xFF,0x01,0xCD, +0xA1,0x5F,0x0D,0xBC,0x71,0xBC,0x0E,0xAC,0x0B,0x1D,0x47,0x45,0x1D,0xC1,0xEC,0x7C, +0xEC,0xFD,0x29, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Trusted Certificate Services */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Trusted Certificate Services */ + + +const unsigned char Comodo_Trusted_Services_root_certificate[1095]={ +0x30,0x82,0x04,0x43,0x30,0x82,0x03,0x2B,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x7F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x25,0x30,0x23,0x06,0x03,0x55, +0x04,0x03,0x0C,0x1C,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73, +0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30, +0x5A,0x17,0x0D,0x32,0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A, +0x30,0x7F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31, +0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65, +0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E, +0x06,0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A, +0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20, +0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x25,0x30,0x23,0x06,0x03, +0x55,0x04,0x03,0x0C,0x1C,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65, +0x73,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01, +0x01,0x00,0xDF,0x71,0x6F,0x36,0x58,0x53,0x5A,0xF2,0x36,0x54,0x57,0x80,0xC4,0x74, +0x08,0x20,0xED,0x18,0x7F,0x2A,0x1D,0xE6,0x35,0x9A,0x1E,0x25,0xAC,0x9C,0xE5,0x96, +0x7E,0x72,0x52,0xA0,0x15,0x42,0xDB,0x59,0xDD,0x64,0x7A,0x1A,0xD0,0xB8,0x7B,0xDD, +0x39,0x15,0xBC,0x55,0x48,0xC4,0xED,0x3A,0x00,0xEA,0x31,0x11,0xBA,0xF2,0x71,0x74, +0x1A,0x67,0xB8,0xCF,0x33,0xCC,0xA8,0x31,0xAF,0xA3,0xE3,0xD7,0x7F,0xBF,0x33,0x2D, +0x4C,0x6A,0x3C,0xEC,0x8B,0xC3,0x92,0xD2,0x53,0x77,0x24,0x74,0x9C,0x07,0x6E,0x70, +0xFC,0xBD,0x0B,0x5B,0x76,0xBA,0x5F,0xF2,0xFF,0xD7,0x37,0x4B,0x4A,0x60,0x78,0xF7, +0xF0,0xFA,0xCA,0x70,0xB4,0xEA,0x59,0xAA,0xA3,0xCE,0x48,0x2F,0xA9,0xC3,0xB2,0x0B, +0x7E,0x17,0x72,0x16,0x0C,0xA6,0x07,0x0C,0x1B,0x38,0xCF,0xC9,0x62,0xB7,0x3F,0xA0, +0x93,0xA5,0x87,0x41,0xF2,0xB7,0x70,0x40,0x77,0xD8,0xBE,0x14,0x7C,0xE3,0xA8,0xC0, +0x7A,0x8E,0xE9,0x63,0x6A,0xD1,0x0F,0x9A,0xC6,0xD2,0xF4,0x8B,0x3A,0x14,0x04,0x56, +0xD4,0xED,0xB8,0xCC,0x6E,0xF5,0xFB,0xE2,0x2C,0x58,0xBD,0x7F,0x4F,0x6B,0x2B,0xF7, +0x60,0x24,0x58,0x24,0xCE,0x26,0xEF,0x34,0x91,0x3A,0xD5,0xE3,0x81,0xD0,0xB2,0xF0, +0x04,0x02,0xD7,0x5B,0xB7,0x3E,0x92,0xAC,0x6B,0x12,0x8A,0xF9,0xE4,0x05,0xB0,0x3B, +0x91,0x49,0x5C,0xB2,0xEB,0x53,0xEA,0xF8,0x9F,0x47,0x86,0xEE,0xBF,0x95,0xC0,0xC0, +0x06,0x9F,0xD2,0x5B,0x5E,0x11,0x1B,0xF4,0xC7,0x04,0x35,0x29,0xD2,0x55,0x5C,0xE4, +0xED,0xEB,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xC9,0x30,0x81,0xC6,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC5,0x7B,0x58,0xBD,0xED,0xDA,0x25,0x69, +0xD2,0xF7,0x59,0x16,0xA8,0xB3,0x32,0xC0,0x7B,0x27,0x5B,0xF4,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03, +0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x83, +0x06,0x03,0x55,0x1D,0x1F,0x04,0x7C,0x30,0x7A,0x30,0x3C,0xA0,0x3A,0xA0,0x38,0x86, +0x36,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F, +0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x54,0x72,0x75,0x73,0x74,0x65,0x64, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69, +0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30,0x3A,0xA0,0x38,0xA0,0x36,0x86,0x34,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F, +0x2E,0x6E,0x65,0x74,0x2F,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E, +0x63,0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xC8,0x93,0x81,0x3B,0x89,0xB4,0xAF,0xB8,0x84, +0x12,0x4C,0x8D,0xD2,0xF0,0xDB,0x70,0xBA,0x57,0x86,0x15,0x34,0x10,0xB9,0x2F,0x7F, +0x1E,0xB0,0xA8,0x89,0x60,0xA1,0x8A,0xC2,0x77,0x0C,0x50,0x4A,0x9B,0x00,0x8B,0xD8, +0x8B,0xF4,0x41,0xE2,0xD0,0x83,0x8A,0x4A,0x1C,0x14,0x06,0xB0,0xA3,0x68,0x05,0x70, +0x31,0x30,0xA7,0x53,0x9B,0x0E,0xE9,0x4A,0xA0,0x58,0x69,0x67,0x0E,0xAE,0x9D,0xF6, +0xA5,0x2C,0x41,0xBF,0x3C,0x06,0x6B,0xE4,0x59,0xCC,0x6D,0x10,0xF1,0x96,0x6F,0x1F, +0xDF,0xF4,0x04,0x02,0xA4,0x9F,0x45,0x3E,0xC8,0xD8,0xFA,0x36,0x46,0x44,0x50,0x3F, +0x82,0x97,0x91,0x1F,0x28,0xDB,0x18,0x11,0x8C,0x2A,0xE4,0x65,0x83,0x57,0x12,0x12, +0x8C,0x17,0x3F,0x94,0x36,0xFE,0x5D,0xB0,0xC0,0x04,0x77,0x13,0xB8,0xF4,0x15,0xD5, +0x3F,0x38,0xCC,0x94,0x3A,0x55,0xD0,0xAC,0x98,0xF5,0xBA,0x00,0x5F,0xE0,0x86,0x19, +0x81,0x78,0x2F,0x28,0xC0,0x7E,0xD3,0xCC,0x42,0x0A,0xF5,0xAE,0x50,0xA0,0xD1,0x3E, +0xC6,0xA1,0x71,0xEC,0x3F,0xA0,0x20,0x8C,0x66,0x3A,0x89,0xB4,0x8E,0xD4,0xD8,0xB1, +0x4D,0x25,0x47,0xEE,0x2F,0x88,0xC8,0xB5,0xE1,0x05,0x45,0xC0,0xBE,0x14,0x71,0xDE, +0x7A,0xFD,0x8E,0x7B,0x7D,0x4D,0x08,0x96,0xA5,0x12,0x73,0xF0,0x2D,0xCA,0x37,0x27, +0x74,0x12,0x27,0x4C,0xCB,0xB6,0x97,0xE9,0xD9,0xAE,0x08,0x6D,0x5A,0x39,0x40,0xDD, +0x05,0x47,0x75,0x6A,0x5A,0x21,0xB3,0xA3,0x18,0xCF,0x4E,0xF7,0x2E,0x57,0xB7,0x98, +0x70,0x5E,0xC8,0xC4,0x78,0xB0,0x62, +}; + + +/* subject:/O=Cybertrust, Inc/CN=Cybertrust Global Root */ +/* issuer :/O=Cybertrust, Inc/CN=Cybertrust Global Root */ + + +const unsigned char Cybertrust_Global_Root_certificate[933]={ +0x30,0x82,0x03,0xA1,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x0F,0x85,0xAA,0x2D,0x48,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x3B,0x31,0x18,0x30,0x16,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0F,0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74, +0x2C,0x20,0x49,0x6E,0x63,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16, +0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61, +0x6C,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x31,0x35, +0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x31,0x31,0x32,0x31,0x35,0x30, +0x38,0x30,0x30,0x30,0x30,0x5A,0x30,0x3B,0x31,0x18,0x30,0x16,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0F,0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2C,0x20,0x49, +0x6E,0x63,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,0x43,0x79,0x62, +0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52, +0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02, +0x82,0x01,0x01,0x00,0xF8,0xC8,0xBC,0xBD,0x14,0x50,0x66,0x13,0xFF,0xF0,0xD3,0x79, +0xEC,0x23,0xF2,0xB7,0x1A,0xC7,0x8E,0x85,0xF1,0x12,0x73,0xA6,0x19,0xAA,0x10,0xDB, +0x9C,0xA2,0x65,0x74,0x5A,0x77,0x3E,0x51,0x7D,0x56,0xF6,0xDC,0x23,0xB6,0xD4,0xED, +0x5F,0x58,0xB1,0x37,0x4D,0xD5,0x49,0x0E,0x6E,0xF5,0x6A,0x87,0xD6,0xD2,0x8C,0xD2, +0x27,0xC6,0xE2,0xFF,0x36,0x9F,0x98,0x65,0xA0,0x13,0x4E,0xC6,0x2A,0x64,0x9B,0xD5, +0x90,0x12,0xCF,0x14,0x06,0xF4,0x3B,0xE3,0xD4,0x28,0xBE,0xE8,0x0E,0xF8,0xAB,0x4E, +0x48,0x94,0x6D,0x8E,0x95,0x31,0x10,0x5C,0xED,0xA2,0x2D,0xBD,0xD5,0x3A,0x6D,0xB2, +0x1C,0xBB,0x60,0xC0,0x46,0x4B,0x01,0xF5,0x49,0xAE,0x7E,0x46,0x8A,0xD0,0x74,0x8D, +0xA1,0x0C,0x02,0xCE,0xEE,0xFC,0xE7,0x8F,0xB8,0x6B,0x66,0xF3,0x7F,0x44,0x00,0xBF, +0x66,0x25,0x14,0x2B,0xDD,0x10,0x30,0x1D,0x07,0x96,0x3F,0x4D,0xF6,0x6B,0xB8,0x8F, +0xB7,0x7B,0x0C,0xA5,0x38,0xEB,0xDE,0x47,0xDB,0xD5,0x5D,0x39,0xFC,0x88,0xA7,0xF3, +0xD7,0x2A,0x74,0xF1,0xE8,0x5A,0xA2,0x3B,0x9F,0x50,0xBA,0xA6,0x8C,0x45,0x35,0xC2, +0x50,0x65,0x95,0xDC,0x63,0x82,0xEF,0xDD,0xBF,0x77,0x4D,0x9C,0x62,0xC9,0x63,0x73, +0x16,0xD0,0x29,0x0F,0x49,0xA9,0x48,0xF0,0xB3,0xAA,0xB7,0x6C,0xC5,0xA7,0x30,0x39, +0x40,0x5D,0xAE,0xC4,0xE2,0x5D,0x26,0x53,0xF0,0xCE,0x1C,0x23,0x08,0x61,0xA8,0x94, +0x19,0xBA,0x04,0x62,0x40,0xEC,0x1F,0x38,0x70,0x77,0x12,0x06,0x71,0xA7,0x30,0x18, +0x5D,0x25,0x27,0xA5,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xA5,0x30,0x81,0xA2,0x30, +0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30, +0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xB6,0x08,0x7B,0x0D,0x7A, +0xCC,0xAC,0x20,0x4C,0x86,0x56,0x32,0x5E,0xCF,0xAB,0x6E,0x85,0x2D,0x70,0x57,0x30, +0x3F,0x06,0x03,0x55,0x1D,0x1F,0x04,0x38,0x30,0x36,0x30,0x34,0xA0,0x32,0xA0,0x30, +0x86,0x2E,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x32,0x2E,0x70,0x75, +0x62,0x6C,0x69,0x63,0x2D,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x63, +0x72,0x6C,0x2F,0x63,0x74,0x2F,0x63,0x74,0x72,0x6F,0x6F,0x74,0x2E,0x63,0x72,0x6C, +0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xB6,0x08,0x7B, +0x0D,0x7A,0xCC,0xAC,0x20,0x4C,0x86,0x56,0x32,0x5E,0xCF,0xAB,0x6E,0x85,0x2D,0x70, +0x57,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x82,0x01,0x01,0x00,0x56,0xEF,0x0A,0x23,0xA0,0x54,0x4E,0x95,0x97,0xC9,0xF8, +0x89,0xDA,0x45,0xC1,0xD4,0xA3,0x00,0x25,0xF4,0x1F,0x13,0xAB,0xB7,0xA3,0x85,0x58, +0x69,0xC2,0x30,0xAD,0xD8,0x15,0x8A,0x2D,0xE3,0xC9,0xCD,0x81,0x5A,0xF8,0x73,0x23, +0x5A,0xA7,0x7C,0x05,0xF3,0xFD,0x22,0x3B,0x0E,0xD1,0x06,0xC4,0xDB,0x36,0x4C,0x73, +0x04,0x8E,0xE5,0xB0,0x22,0xE4,0xC5,0xF3,0x2E,0xA5,0xD9,0x23,0xE3,0xB8,0x4E,0x4A, +0x20,0xA7,0x6E,0x02,0x24,0x9F,0x22,0x60,0x67,0x7B,0x8B,0x1D,0x72,0x09,0xC5,0x31, +0x5C,0xE9,0x79,0x9F,0x80,0x47,0x3D,0xAD,0xA1,0x0B,0x07,0x14,0x3D,0x47,0xFF,0x03, +0x69,0x1A,0x0C,0x0B,0x44,0xE7,0x63,0x25,0xA7,0x7F,0xB2,0xC9,0xB8,0x76,0x84,0xED, +0x23,0xF6,0x7D,0x07,0xAB,0x45,0x7E,0xD3,0xDF,0xB3,0xBF,0xE9,0x8A,0xB6,0xCD,0xA8, +0xA2,0x67,0x2B,0x52,0xD5,0xB7,0x65,0xF0,0x39,0x4C,0x63,0xA0,0x91,0x79,0x93,0x52, +0x0F,0x54,0xDD,0x83,0xBB,0x9F,0xD1,0x8F,0xA7,0x53,0x73,0xC3,0xCB,0xFF,0x30,0xEC, +0x7C,0x04,0xB8,0xD8,0x44,0x1F,0x93,0x5F,0x71,0x09,0x22,0xB7,0x6E,0x3E,0xEA,0x1C, +0x03,0x4E,0x9D,0x1A,0x20,0x61,0xFB,0x81,0x37,0xEC,0x5E,0xFC,0x0A,0x45,0xAB,0xD7, +0xE7,0x17,0x55,0xD0,0xA0,0xEA,0x60,0x9B,0xA6,0xF6,0xE3,0x8C,0x5B,0x29,0xC2,0x06, +0x60,0x14,0x9D,0x2D,0x97,0x4C,0xA9,0x93,0x15,0x9D,0x61,0xC4,0x01,0x5F,0x48,0xD6, +0x58,0xBD,0x56,0x31,0x12,0x4E,0x11,0xC8,0x21,0xE0,0xB3,0x11,0x91,0x65,0xDB,0xB4, +0xA6,0x88,0x38,0xCE,0x55, +}; + + +/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA */ +/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA */ + + +const unsigned char DigiCert_Assured_ID_Root_CA_certificate[955]={ +0x30,0x82,0x03,0xB7,0x30,0x82,0x02,0x9F,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x0C, +0xE7,0xE0,0xE5,0x17,0xD8,0x46,0xFE,0x8F,0xE5,0x60,0xFC,0x1B,0xF0,0x30,0x39,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x65, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30, +0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77, +0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31, +0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x44,0x69,0x67,0x69,0x43,0x65, +0x72,0x74,0x20,0x41,0x73,0x73,0x75,0x72,0x65,0x64,0x20,0x49,0x44,0x20,0x52,0x6F, +0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x31,0x30,0x30, +0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x31,0x31,0x31,0x31,0x30,0x30,0x30, +0x30,0x30,0x30,0x30,0x5A,0x30,0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44, +0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06, +0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13, +0x1B,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x41,0x73,0x73,0x75,0x72,0x65, +0x64,0x20,0x49,0x44,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAD,0x0E,0x15, +0xCE,0xE4,0x43,0x80,0x5C,0xB1,0x87,0xF3,0xB7,0x60,0xF9,0x71,0x12,0xA5,0xAE,0xDC, +0x26,0x94,0x88,0xAA,0xF4,0xCE,0xF5,0x20,0x39,0x28,0x58,0x60,0x0C,0xF8,0x80,0xDA, +0xA9,0x15,0x95,0x32,0x61,0x3C,0xB5,0xB1,0x28,0x84,0x8A,0x8A,0xDC,0x9F,0x0A,0x0C, +0x83,0x17,0x7A,0x8F,0x90,0xAC,0x8A,0xE7,0x79,0x53,0x5C,0x31,0x84,0x2A,0xF6,0x0F, +0x98,0x32,0x36,0x76,0xCC,0xDE,0xDD,0x3C,0xA8,0xA2,0xEF,0x6A,0xFB,0x21,0xF2,0x52, +0x61,0xDF,0x9F,0x20,0xD7,0x1F,0xE2,0xB1,0xD9,0xFE,0x18,0x64,0xD2,0x12,0x5B,0x5F, +0xF9,0x58,0x18,0x35,0xBC,0x47,0xCD,0xA1,0x36,0xF9,0x6B,0x7F,0xD4,0xB0,0x38,0x3E, +0xC1,0x1B,0xC3,0x8C,0x33,0xD9,0xD8,0x2F,0x18,0xFE,0x28,0x0F,0xB3,0xA7,0x83,0xD6, +0xC3,0x6E,0x44,0xC0,0x61,0x35,0x96,0x16,0xFE,0x59,0x9C,0x8B,0x76,0x6D,0xD7,0xF1, +0xA2,0x4B,0x0D,0x2B,0xFF,0x0B,0x72,0xDA,0x9E,0x60,0xD0,0x8E,0x90,0x35,0xC6,0x78, +0x55,0x87,0x20,0xA1,0xCF,0xE5,0x6D,0x0A,0xC8,0x49,0x7C,0x31,0x98,0x33,0x6C,0x22, +0xE9,0x87,0xD0,0x32,0x5A,0xA2,0xBA,0x13,0x82,0x11,0xED,0x39,0x17,0x9D,0x99,0x3A, +0x72,0xA1,0xE6,0xFA,0xA4,0xD9,0xD5,0x17,0x31,0x75,0xAE,0x85,0x7D,0x22,0xAE,0x3F, +0x01,0x46,0x86,0xF6,0x28,0x79,0xC8,0xB1,0xDA,0xE4,0x57,0x17,0xC4,0x7E,0x1C,0x0E, +0xB0,0xB4,0x92,0xA6,0x56,0xB3,0xBD,0xB2,0x97,0xED,0xAA,0xA7,0xF0,0xB7,0xC5,0xA8, +0x3F,0x95,0x16,0xD0,0xFF,0xA1,0x96,0xEB,0x08,0x5F,0x18,0x77,0x4F,0x02,0x03,0x01, +0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF, +0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0x45,0xEB,0xA2,0xAF,0xF4,0x92,0xCB,0x82,0x31,0x2D,0x51,0x8B,0xA7,0xA7, +0x21,0x9D,0xF3,0x6D,0xC8,0x0F,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30, +0x16,0x80,0x14,0x45,0xEB,0xA2,0xAF,0xF4,0x92,0xCB,0x82,0x31,0x2D,0x51,0x8B,0xA7, +0xA7,0x21,0x9D,0xF3,0x6D,0xC8,0x0F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xA2,0x0E,0xBC,0xDF,0xE2, +0xED,0xF0,0xE3,0x72,0x73,0x7A,0x64,0x94,0xBF,0xF7,0x72,0x66,0xD8,0x32,0xE4,0x42, +0x75,0x62,0xAE,0x87,0xEB,0xF2,0xD5,0xD9,0xDE,0x56,0xB3,0x9F,0xCC,0xCE,0x14,0x28, +0xB9,0x0D,0x97,0x60,0x5C,0x12,0x4C,0x58,0xE4,0xD3,0x3D,0x83,0x49,0x45,0x58,0x97, +0x35,0x69,0x1A,0xA8,0x47,0xEA,0x56,0xC6,0x79,0xAB,0x12,0xD8,0x67,0x81,0x84,0xDF, +0x7F,0x09,0x3C,0x94,0xE6,0xB8,0x26,0x2C,0x20,0xBD,0x3D,0xB3,0x28,0x89,0xF7,0x5F, +0xFF,0x22,0xE2,0x97,0x84,0x1F,0xE9,0x65,0xEF,0x87,0xE0,0xDF,0xC1,0x67,0x49,0xB3, +0x5D,0xEB,0xB2,0x09,0x2A,0xEB,0x26,0xED,0x78,0xBE,0x7D,0x3F,0x2B,0xF3,0xB7,0x26, +0x35,0x6D,0x5F,0x89,0x01,0xB6,0x49,0x5B,0x9F,0x01,0x05,0x9B,0xAB,0x3D,0x25,0xC1, +0xCC,0xB6,0x7F,0xC2,0xF1,0x6F,0x86,0xC6,0xFA,0x64,0x68,0xEB,0x81,0x2D,0x94,0xEB, +0x42,0xB7,0xFA,0x8C,0x1E,0xDD,0x62,0xF1,0xBE,0x50,0x67,0xB7,0x6C,0xBD,0xF3,0xF1, +0x1F,0x6B,0x0C,0x36,0x07,0x16,0x7F,0x37,0x7C,0xA9,0x5B,0x6D,0x7A,0xF1,0x12,0x46, +0x60,0x83,0xD7,0x27,0x04,0xBE,0x4B,0xCE,0x97,0xBE,0xC3,0x67,0x2A,0x68,0x11,0xDF, +0x80,0xE7,0x0C,0x33,0x66,0xBF,0x13,0x0D,0x14,0x6E,0xF3,0x7F,0x1F,0x63,0x10,0x1E, +0xFA,0x8D,0x1B,0x25,0x6D,0x6C,0x8F,0xA5,0xB7,0x61,0x01,0xB1,0xD2,0xA3,0x26,0xA1, +0x10,0x71,0x9D,0xAD,0xE2,0xC3,0xF9,0xC3,0x99,0x51,0xB7,0x2B,0x07,0x08,0xCE,0x2E, +0xE6,0x50,0xB2,0xA7,0xFA,0x0A,0x45,0x2F,0xA2,0xF0,0xF2, +}; + + +/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA */ +/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA */ + + +const unsigned char DigiCert_Global_Root_CA_certificate[947]={ +0x30,0x82,0x03,0xAF,0x30,0x82,0x02,0x97,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x08, +0x3B,0xE0,0x56,0x90,0x42,0x46,0xB1,0xA1,0x75,0x6A,0xC9,0x59,0x91,0xC7,0x4A,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x61, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30, +0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77, +0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31, +0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x44,0x69,0x67,0x69,0x43,0x65, +0x72,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43, +0x41,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30, +0x30,0x5A,0x17,0x0D,0x33,0x31,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30, +0x5A,0x30,0x61,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53, +0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43, +0x65,0x72,0x74,0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B, +0x13,0x10,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63, +0x6F,0x6D,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x44,0x69,0x67, +0x69,0x43,0x65,0x72,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F, +0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xE2,0x3B,0xE1,0x11,0x72,0xDE,0xA8,0xA4,0xD3,0xA3,0x57, +0xAA,0x50,0xA2,0x8F,0x0B,0x77,0x90,0xC9,0xA2,0xA5,0xEE,0x12,0xCE,0x96,0x5B,0x01, +0x09,0x20,0xCC,0x01,0x93,0xA7,0x4E,0x30,0xB7,0x53,0xF7,0x43,0xC4,0x69,0x00,0x57, +0x9D,0xE2,0x8D,0x22,0xDD,0x87,0x06,0x40,0x00,0x81,0x09,0xCE,0xCE,0x1B,0x83,0xBF, +0xDF,0xCD,0x3B,0x71,0x46,0xE2,0xD6,0x66,0xC7,0x05,0xB3,0x76,0x27,0x16,0x8F,0x7B, +0x9E,0x1E,0x95,0x7D,0xEE,0xB7,0x48,0xA3,0x08,0xDA,0xD6,0xAF,0x7A,0x0C,0x39,0x06, +0x65,0x7F,0x4A,0x5D,0x1F,0xBC,0x17,0xF8,0xAB,0xBE,0xEE,0x28,0xD7,0x74,0x7F,0x7A, +0x78,0x99,0x59,0x85,0x68,0x6E,0x5C,0x23,0x32,0x4B,0xBF,0x4E,0xC0,0xE8,0x5A,0x6D, +0xE3,0x70,0xBF,0x77,0x10,0xBF,0xFC,0x01,0xF6,0x85,0xD9,0xA8,0x44,0x10,0x58,0x32, +0xA9,0x75,0x18,0xD5,0xD1,0xA2,0xBE,0x47,0xE2,0x27,0x6A,0xF4,0x9A,0x33,0xF8,0x49, +0x08,0x60,0x8B,0xD4,0x5F,0xB4,0x3A,0x84,0xBF,0xA1,0xAA,0x4A,0x4C,0x7D,0x3E,0xCF, +0x4F,0x5F,0x6C,0x76,0x5E,0xA0,0x4B,0x37,0x91,0x9E,0xDC,0x22,0xE6,0x6D,0xCE,0x14, +0x1A,0x8E,0x6A,0xCB,0xFE,0xCD,0xB3,0x14,0x64,0x17,0xC7,0x5B,0x29,0x9E,0x32,0xBF, +0xF2,0xEE,0xFA,0xD3,0x0B,0x42,0xD4,0xAB,0xB7,0x41,0x32,0xDA,0x0C,0xD4,0xEF,0xF8, +0x81,0xD5,0xBB,0x8D,0x58,0x3F,0xB5,0x1B,0xE8,0x49,0x28,0xA2,0x70,0xDA,0x31,0x04, +0xDD,0xF7,0xB2,0x16,0xF2,0x4C,0x0A,0x4E,0x07,0xA8,0xED,0x4A,0x3D,0x5E,0xB5,0x7F, +0xA3,0x90,0xC3,0xAF,0x27,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F, +0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30, +0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x03,0xDE,0x50,0x35,0x56,0xD1, +0x4C,0xBB,0x66,0xF0,0xA3,0xE2,0x1B,0x1B,0xC3,0x97,0xB2,0x3D,0xD1,0x55,0x30,0x1F, +0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x03,0xDE,0x50,0x35,0x56, +0xD1,0x4C,0xBB,0x66,0xF0,0xA3,0xE2,0x1B,0x1B,0xC3,0x97,0xB2,0x3D,0xD1,0x55,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0xCB,0x9C,0x37,0xAA,0x48,0x13,0x12,0x0A,0xFA,0xDD,0x44,0x9C,0x4F, +0x52,0xB0,0xF4,0xDF,0xAE,0x04,0xF5,0x79,0x79,0x08,0xA3,0x24,0x18,0xFC,0x4B,0x2B, +0x84,0xC0,0x2D,0xB9,0xD5,0xC7,0xFE,0xF4,0xC1,0x1F,0x58,0xCB,0xB8,0x6D,0x9C,0x7A, +0x74,0xE7,0x98,0x29,0xAB,0x11,0xB5,0xE3,0x70,0xA0,0xA1,0xCD,0x4C,0x88,0x99,0x93, +0x8C,0x91,0x70,0xE2,0xAB,0x0F,0x1C,0xBE,0x93,0xA9,0xFF,0x63,0xD5,0xE4,0x07,0x60, +0xD3,0xA3,0xBF,0x9D,0x5B,0x09,0xF1,0xD5,0x8E,0xE3,0x53,0xF4,0x8E,0x63,0xFA,0x3F, +0xA7,0xDB,0xB4,0x66,0xDF,0x62,0x66,0xD6,0xD1,0x6E,0x41,0x8D,0xF2,0x2D,0xB5,0xEA, +0x77,0x4A,0x9F,0x9D,0x58,0xE2,0x2B,0x59,0xC0,0x40,0x23,0xED,0x2D,0x28,0x82,0x45, +0x3E,0x79,0x54,0x92,0x26,0x98,0xE0,0x80,0x48,0xA8,0x37,0xEF,0xF0,0xD6,0x79,0x60, +0x16,0xDE,0xAC,0xE8,0x0E,0xCD,0x6E,0xAC,0x44,0x17,0x38,0x2F,0x49,0xDA,0xE1,0x45, +0x3E,0x2A,0xB9,0x36,0x53,0xCF,0x3A,0x50,0x06,0xF7,0x2E,0xE8,0xC4,0x57,0x49,0x6C, +0x61,0x21,0x18,0xD5,0x04,0xAD,0x78,0x3C,0x2C,0x3A,0x80,0x6B,0xA7,0xEB,0xAF,0x15, +0x14,0xE9,0xD8,0x89,0xC1,0xB9,0x38,0x6C,0xE2,0x91,0x6C,0x8A,0xFF,0x64,0xB9,0x77, +0x25,0x57,0x30,0xC0,0x1B,0x24,0xA3,0xE1,0xDC,0xE9,0xDF,0x47,0x7C,0xB5,0xB4,0x24, +0x08,0x05,0x30,0xEC,0x2D,0xBD,0x0B,0xBF,0x45,0xBF,0x50,0xB9,0xA9,0xF3,0xEB,0x98, +0x01,0x12,0xAD,0xC8,0x88,0xC6,0x98,0x34,0x5F,0x8D,0x0A,0x3C,0xC6,0xE9,0xD5,0x95, +0x95,0x6D,0xDE, +}; + + +/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA */ +/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA */ + + +const unsigned char DigiCert_High_Assurance_EV_Root_CA_certificate[969]={ +0x30,0x82,0x03,0xC5,0x30,0x82,0x02,0xAD,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x02, +0xAC,0x5C,0x26,0x6A,0x0B,0x40,0x9B,0x8F,0x0B,0x79,0xF2,0xAE,0x46,0x25,0x77,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x6C, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30, +0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77, +0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31, +0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65, +0x72,0x74,0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63, +0x65,0x20,0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D, +0x30,0x36,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33, +0x31,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x30,0x6C,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49, +0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77, +0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x2B,0x30, +0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,0x65,0x20, +0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01, +0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC6,0xCC,0xE5,0x73,0xE6, +0xFB,0xD4,0xBB,0xE5,0x2D,0x2D,0x32,0xA6,0xDF,0xE5,0x81,0x3F,0xC9,0xCD,0x25,0x49, +0xB6,0x71,0x2A,0xC3,0xD5,0x94,0x34,0x67,0xA2,0x0A,0x1C,0xB0,0x5F,0x69,0xA6,0x40, +0xB1,0xC4,0xB7,0xB2,0x8F,0xD0,0x98,0xA4,0xA9,0x41,0x59,0x3A,0xD3,0xDC,0x94,0xD6, +0x3C,0xDB,0x74,0x38,0xA4,0x4A,0xCC,0x4D,0x25,0x82,0xF7,0x4A,0xA5,0x53,0x12,0x38, +0xEE,0xF3,0x49,0x6D,0x71,0x91,0x7E,0x63,0xB6,0xAB,0xA6,0x5F,0xC3,0xA4,0x84,0xF8, +0x4F,0x62,0x51,0xBE,0xF8,0xC5,0xEC,0xDB,0x38,0x92,0xE3,0x06,0xE5,0x08,0x91,0x0C, +0xC4,0x28,0x41,0x55,0xFB,0xCB,0x5A,0x89,0x15,0x7E,0x71,0xE8,0x35,0xBF,0x4D,0x72, +0x09,0x3D,0xBE,0x3A,0x38,0x50,0x5B,0x77,0x31,0x1B,0x8D,0xB3,0xC7,0x24,0x45,0x9A, +0xA7,0xAC,0x6D,0x00,0x14,0x5A,0x04,0xB7,0xBA,0x13,0xEB,0x51,0x0A,0x98,0x41,0x41, +0x22,0x4E,0x65,0x61,0x87,0x81,0x41,0x50,0xA6,0x79,0x5C,0x89,0xDE,0x19,0x4A,0x57, +0xD5,0x2E,0xE6,0x5D,0x1C,0x53,0x2C,0x7E,0x98,0xCD,0x1A,0x06,0x16,0xA4,0x68,0x73, +0xD0,0x34,0x04,0x13,0x5C,0xA1,0x71,0xD3,0x5A,0x7C,0x55,0xDB,0x5E,0x64,0xE1,0x37, +0x87,0x30,0x56,0x04,0xE5,0x11,0xB4,0x29,0x80,0x12,0xF1,0x79,0x39,0x88,0xA2,0x02, +0x11,0x7C,0x27,0x66,0xB7,0x88,0xB7,0x78,0xF2,0xCA,0x0A,0xA8,0x38,0xAB,0x0A,0x64, +0xC2,0xBF,0x66,0x5D,0x95,0x84,0xC1,0xA1,0x25,0x1E,0x87,0x5D,0x1A,0x50,0x0B,0x20, +0x12,0xCC,0x41,0xBB,0x6E,0x0B,0x51,0x38,0xB8,0x4B,0xCB,0x02,0x03,0x01,0x00,0x01, +0xA3,0x63,0x30,0x61,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14, +0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,0x02,0xEF, +0x63,0x64,0x2B,0xC3,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80, +0x14,0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,0x02, +0xEF,0x63,0x64,0x2B,0xC3,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x1C,0x1A,0x06,0x97,0xDC,0xD7,0x9C, +0x9F,0x3C,0x88,0x66,0x06,0x08,0x57,0x21,0xDB,0x21,0x47,0xF8,0x2A,0x67,0xAA,0xBF, +0x18,0x32,0x76,0x40,0x10,0x57,0xC1,0x8A,0xF3,0x7A,0xD9,0x11,0x65,0x8E,0x35,0xFA, +0x9E,0xFC,0x45,0xB5,0x9E,0xD9,0x4C,0x31,0x4B,0xB8,0x91,0xE8,0x43,0x2C,0x8E,0xB3, +0x78,0xCE,0xDB,0xE3,0x53,0x79,0x71,0xD6,0xE5,0x21,0x94,0x01,0xDA,0x55,0x87,0x9A, +0x24,0x64,0xF6,0x8A,0x66,0xCC,0xDE,0x9C,0x37,0xCD,0xA8,0x34,0xB1,0x69,0x9B,0x23, +0xC8,0x9E,0x78,0x22,0x2B,0x70,0x43,0xE3,0x55,0x47,0x31,0x61,0x19,0xEF,0x58,0xC5, +0x85,0x2F,0x4E,0x30,0xF6,0xA0,0x31,0x16,0x23,0xC8,0xE7,0xE2,0x65,0x16,0x33,0xCB, +0xBF,0x1A,0x1B,0xA0,0x3D,0xF8,0xCA,0x5E,0x8B,0x31,0x8B,0x60,0x08,0x89,0x2D,0x0C, +0x06,0x5C,0x52,0xB7,0xC4,0xF9,0x0A,0x98,0xD1,0x15,0x5F,0x9F,0x12,0xBE,0x7C,0x36, +0x63,0x38,0xBD,0x44,0xA4,0x7F,0xE4,0x26,0x2B,0x0A,0xC4,0x97,0x69,0x0D,0xE9,0x8C, +0xE2,0xC0,0x10,0x57,0xB8,0xC8,0x76,0x12,0x91,0x55,0xF2,0x48,0x69,0xD8,0xBC,0x2A, +0x02,0x5B,0x0F,0x44,0xD4,0x20,0x31,0xDB,0xF4,0xBA,0x70,0x26,0x5D,0x90,0x60,0x9E, +0xBC,0x4B,0x17,0x09,0x2F,0xB4,0xCB,0x1E,0x43,0x68,0xC9,0x07,0x27,0xC1,0xD2,0x5C, +0xF7,0xEA,0x21,0xB9,0x68,0x12,0x9C,0x3C,0x9C,0xBF,0x9E,0xFC,0x80,0x5C,0x9B,0x63, +0xCD,0xEC,0x47,0xAA,0x25,0x27,0x67,0xA0,0x37,0xF3,0x00,0x82,0x7D,0x54,0xD7,0xA9, +0xF8,0xE9,0x2E,0x13,0xA3,0x77,0xE8,0x1F,0x4A, +}; + + +/* subject:/O=Entrust.net/OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Certification Authority (2048) */ +/* issuer :/O=Entrust.net/OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Certification Authority (2048) */ + + +const unsigned char Entrust_net_Premium_2048_Secure_Server_CA_certificate[1120]={ +0x30,0x82,0x04,0x5C,0x30,0x82,0x03,0x44,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x38, +0x63,0xB9,0x66,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x81,0xB4,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B, +0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x40,0x30,0x3E,0x06, +0x03,0x55,0x04,0x0B,0x14,0x37,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73, +0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x5F,0x32,0x30,0x34,0x38,0x20,0x69, +0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28, +0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30, +0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39, +0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D, +0x69,0x74,0x65,0x64,0x31,0x33,0x30,0x31,0x06,0x03,0x55,0x04,0x03,0x13,0x2A,0x45, +0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x28,0x32,0x30,0x34,0x38,0x29,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31, +0x32,0x32,0x34,0x31,0x37,0x35,0x30,0x35,0x31,0x5A,0x17,0x0D,0x31,0x39,0x31,0x32, +0x32,0x34,0x31,0x38,0x32,0x30,0x35,0x31,0x5A,0x30,0x81,0xB4,0x31,0x14,0x30,0x12, +0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E, +0x65,0x74,0x31,0x40,0x30,0x3E,0x06,0x03,0x55,0x04,0x0B,0x14,0x37,0x77,0x77,0x77, +0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53, +0x5F,0x32,0x30,0x34,0x38,0x20,0x69,0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62,0x79, +0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C,0x69, +0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E, +0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x33,0x30,0x31,0x06, +0x03,0x55,0x04,0x03,0x13,0x2A,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65, +0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x28,0x32,0x30,0x34,0x38,0x29, +0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01, +0x00,0xAD,0x4D,0x4B,0xA9,0x12,0x86,0xB2,0xEA,0xA3,0x20,0x07,0x15,0x16,0x64,0x2A, +0x2B,0x4B,0xD1,0xBF,0x0B,0x4A,0x4D,0x8E,0xED,0x80,0x76,0xA5,0x67,0xB7,0x78,0x40, +0xC0,0x73,0x42,0xC8,0x68,0xC0,0xDB,0x53,0x2B,0xDD,0x5E,0xB8,0x76,0x98,0x35,0x93, +0x8B,0x1A,0x9D,0x7C,0x13,0x3A,0x0E,0x1F,0x5B,0xB7,0x1E,0xCF,0xE5,0x24,0x14,0x1E, +0xB1,0x81,0xA9,0x8D,0x7D,0xB8,0xCC,0x6B,0x4B,0x03,0xF1,0x02,0x0C,0xDC,0xAB,0xA5, +0x40,0x24,0x00,0x7F,0x74,0x94,0xA1,0x9D,0x08,0x29,0xB3,0x88,0x0B,0xF5,0x87,0x77, +0x9D,0x55,0xCD,0xE4,0xC3,0x7E,0xD7,0x6A,0x64,0xAB,0x85,0x14,0x86,0x95,0x5B,0x97, +0x32,0x50,0x6F,0x3D,0xC8,0xBA,0x66,0x0C,0xE3,0xFC,0xBD,0xB8,0x49,0xC1,0x76,0x89, +0x49,0x19,0xFD,0xC0,0xA8,0xBD,0x89,0xA3,0x67,0x2F,0xC6,0x9F,0xBC,0x71,0x19,0x60, +0xB8,0x2D,0xE9,0x2C,0xC9,0x90,0x76,0x66,0x7B,0x94,0xE2,0xAF,0x78,0xD6,0x65,0x53, +0x5D,0x3C,0xD6,0x9C,0xB2,0xCF,0x29,0x03,0xF9,0x2F,0xA4,0x50,0xB2,0xD4,0x48,0xCE, +0x05,0x32,0x55,0x8A,0xFD,0xB2,0x64,0x4C,0x0E,0xE4,0x98,0x07,0x75,0xDB,0x7F,0xDF, +0xB9,0x08,0x55,0x60,0x85,0x30,0x29,0xF9,0x7B,0x48,0xA4,0x69,0x86,0xE3,0x35,0x3F, +0x1E,0x86,0x5D,0x7A,0x7A,0x15,0xBD,0xEF,0x00,0x8E,0x15,0x22,0x54,0x17,0x00,0x90, +0x26,0x93,0xBC,0x0E,0x49,0x68,0x91,0xBF,0xF8,0x47,0xD3,0x9D,0x95,0x42,0xC1,0x0E, +0x4D,0xDF,0x6F,0x26,0xCF,0xC3,0x18,0x21,0x62,0x66,0x43,0x70,0xD6,0xD5,0xC0,0x07, +0xE1,0x02,0x03,0x01,0x00,0x01,0xA3,0x74,0x30,0x72,0x30,0x11,0x06,0x09,0x60,0x86, +0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x1F,0x06, +0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x55,0xE4,0x81,0xD1,0x11,0x80, +0xBE,0xD8,0x89,0xB9,0x08,0xA3,0x31,0xF9,0xA1,0x24,0x09,0x16,0xB9,0x70,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x55,0xE4,0x81,0xD1,0x11,0x80,0xBE, +0xD8,0x89,0xB9,0x08,0xA3,0x31,0xF9,0xA1,0x24,0x09,0x16,0xB9,0x70,0x30,0x1D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04,0x10,0x30,0x0E,0x1B,0x08, +0x56,0x35,0x2E,0x30,0x3A,0x34,0x2E,0x30,0x03,0x02,0x04,0x90,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x59,0x47,0xAC,0x21,0x84,0x8A,0x17,0xC9,0x9C,0x89,0x53,0x1E,0xBA,0x80,0x85,0x1A, +0xC6,0x3C,0x4E,0x3E,0xB1,0x9C,0xB6,0x7C,0xC6,0x92,0x5D,0x18,0x64,0x02,0xE3,0xD3, +0x06,0x08,0x11,0x61,0x7C,0x63,0xE3,0x2B,0x9D,0x31,0x03,0x70,0x76,0xD2,0xA3,0x28, +0xA0,0xF4,0xBB,0x9A,0x63,0x73,0xED,0x6D,0xE5,0x2A,0xDB,0xED,0x14,0xA9,0x2B,0xC6, +0x36,0x11,0xD0,0x2B,0xEB,0x07,0x8B,0xA5,0xDA,0x9E,0x5C,0x19,0x9D,0x56,0x12,0xF5, +0x54,0x29,0xC8,0x05,0xED,0xB2,0x12,0x2A,0x8D,0xF4,0x03,0x1B,0xFF,0xE7,0x92,0x10, +0x87,0xB0,0x3A,0xB5,0xC3,0x9D,0x05,0x37,0x12,0xA3,0xC7,0xF4,0x15,0xB9,0xD5,0xA4, +0x39,0x16,0x9B,0x53,0x3A,0x23,0x91,0xF1,0xA8,0x82,0xA2,0x6A,0x88,0x68,0xC1,0x79, +0x02,0x22,0xBC,0xAA,0xA6,0xD6,0xAE,0xDF,0xB0,0x14,0x5F,0xB8,0x87,0xD0,0xDD,0x7C, +0x7F,0x7B,0xFF,0xAF,0x1C,0xCF,0xE6,0xDB,0x07,0xAD,0x5E,0xDB,0x85,0x9D,0xD0,0x2B, +0x0D,0x33,0xDB,0x04,0xD1,0xE6,0x49,0x40,0x13,0x2B,0x76,0xFB,0x3E,0xE9,0x9C,0x89, +0x0F,0x15,0xCE,0x18,0xB0,0x85,0x78,0x21,0x4F,0x6B,0x4F,0x0E,0xFA,0x36,0x67,0xCD, +0x07,0xF2,0xFF,0x08,0xD0,0xE2,0xDE,0xD9,0xBF,0x2A,0xAF,0xB8,0x87,0x86,0x21,0x3C, +0x04,0xCA,0xB7,0x94,0x68,0x7F,0xCF,0x3C,0xE9,0x98,0xD7,0x38,0xFF,0xEC,0xC0,0xD9, +0x50,0xF0,0x2E,0x4B,0x58,0xAE,0x46,0x6F,0xD0,0x2E,0xC3,0x60,0xDA,0x72,0x55,0x72, +0xBD,0x4C,0x45,0x9E,0x61,0xBA,0xBF,0x84,0x81,0x92,0x03,0xD1,0xD2,0x69,0x7C,0xC5, +}; + + +/* subject:/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority */ +/* issuer :/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority */ + + +const unsigned char Entrust_net_Secure_Server_CA_certificate[1244]={ +0x30,0x82,0x04,0xD8,0x30,0x82,0x04,0x41,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x37, +0x4A,0xD2,0x43,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x81,0xC3,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B,0x30,0x39,0x06,0x03,0x55,0x04, +0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E, +0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62, +0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C, +0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C, +0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74, +0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x3A,0x30,0x38, +0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E, +0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x53,0x65,0x72,0x76,0x65,0x72, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x35, +0x32,0x35,0x31,0x36,0x30,0x39,0x34,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x35,0x32, +0x35,0x31,0x36,0x33,0x39,0x34,0x30,0x5A,0x30,0x81,0xC3,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B, +0x30,0x39,0x06,0x03,0x55,0x04,0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63, +0x6F,0x72,0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69, +0x6D,0x69,0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06, +0x03,0x55,0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45, +0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74, +0x65,0x64,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20, +0x53,0x65,0x72,0x76,0x65,0x72,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x81, +0x9D,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x81,0x8B,0x00,0x30,0x81,0x87,0x02,0x81,0x81,0x00,0xCD,0x28,0x83,0x34,0x54, +0x1B,0x89,0xF3,0x0F,0xAF,0x37,0x91,0x31,0xFF,0xAF,0x31,0x60,0xC9,0xA8,0xE8,0xB2, +0x10,0x68,0xED,0x9F,0xE7,0x93,0x36,0xF1,0x0A,0x64,0xBB,0x47,0xF5,0x04,0x17,0x3F, +0x23,0x47,0x4D,0xC5,0x27,0x19,0x81,0x26,0x0C,0x54,0x72,0x0D,0x88,0x2D,0xD9,0x1F, +0x9A,0x12,0x9F,0xBC,0xB3,0x71,0xD3,0x80,0x19,0x3F,0x47,0x66,0x7B,0x8C,0x35,0x28, +0xD2,0xB9,0x0A,0xDF,0x24,0xDA,0x9C,0xD6,0x50,0x79,0x81,0x7A,0x5A,0xD3,0x37,0xF7, +0xC2,0x4A,0xD8,0x29,0x92,0x26,0x64,0xD1,0xE4,0x98,0x6C,0x3A,0x00,0x8A,0xF5,0x34, +0x9B,0x65,0xF8,0xED,0xE3,0x10,0xFF,0xFD,0xB8,0x49,0x58,0xDC,0xA0,0xDE,0x82,0x39, +0x6B,0x81,0xB1,0x16,0x19,0x61,0xB9,0x54,0xB6,0xE6,0x43,0x02,0x01,0x03,0xA3,0x82, +0x01,0xD7,0x30,0x82,0x01,0xD3,0x30,0x11,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8, +0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x82,0x01,0x19,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x82,0x01,0x10,0x30,0x82,0x01,0x0C,0x30,0x81,0xDE,0xA0,0x81,0xDB, +0xA0,0x81,0xD8,0xA4,0x81,0xD5,0x30,0x81,0xD2,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13, +0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B,0x30,0x39, +0x06,0x03,0x55,0x04,0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75, +0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63,0x6F,0x72, +0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69, +0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64, +0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74,0x72,0x75, +0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x53,0x65, +0x72,0x76,0x65,0x72,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69, +0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x31,0x0D,0x30,0x0B, +0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x29,0xA0,0x27,0xA0, +0x25,0x86,0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x65,0x6E, +0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x52,0x4C,0x2F,0x6E,0x65, +0x74,0x31,0x2E,0x63,0x72,0x6C,0x30,0x2B,0x06,0x03,0x55,0x1D,0x10,0x04,0x24,0x30, +0x22,0x80,0x0F,0x31,0x39,0x39,0x39,0x30,0x35,0x32,0x35,0x31,0x36,0x30,0x39,0x34, +0x30,0x5A,0x81,0x0F,0x32,0x30,0x31,0x39,0x30,0x35,0x32,0x35,0x31,0x36,0x30,0x39, +0x34,0x30,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06, +0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xF0,0x17,0x62, +0x13,0x55,0x3D,0xB3,0xFF,0x0A,0x00,0x6B,0xFB,0x50,0x84,0x97,0xF3,0xED,0x62,0xD0, +0x1A,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xF0,0x17,0x62,0x13, +0x55,0x3D,0xB3,0xFF,0x0A,0x00,0x6B,0xFB,0x50,0x84,0x97,0xF3,0xED,0x62,0xD0,0x1A, +0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x19, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04,0x0C,0x30,0x0A,0x1B, +0x04,0x56,0x34,0x2E,0x30,0x03,0x02,0x04,0x90,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x90,0xDC,0x30,0x02, +0xFA,0x64,0x74,0xC2,0xA7,0x0A,0xA5,0x7C,0x21,0x8D,0x34,0x17,0xA8,0xFB,0x47,0x0E, +0xFF,0x25,0x7C,0x8D,0x13,0x0A,0xFB,0xE4,0x98,0xB5,0xEF,0x8C,0xF8,0xC5,0x10,0x0D, +0xF7,0x92,0xBE,0xF1,0xC3,0xD5,0xD5,0x95,0x6A,0x04,0xBB,0x2C,0xCE,0x26,0x36,0x65, +0xC8,0x31,0xC6,0xE7,0xEE,0x3F,0xE3,0x57,0x75,0x84,0x7A,0x11,0xEF,0x46,0x4F,0x18, +0xF4,0xD3,0x98,0xBB,0xA8,0x87,0x32,0xBA,0x72,0xF6,0x3C,0xE2,0x3D,0x9F,0xD7,0x1D, +0xD9,0xC3,0x60,0x43,0x8C,0x58,0x0E,0x22,0x96,0x2F,0x62,0xA3,0x2C,0x1F,0xBA,0xAD, +0x05,0xEF,0xAB,0x32,0x78,0x87,0xA0,0x54,0x73,0x19,0xB5,0x5C,0x05,0xF9,0x52,0x3E, +0x6D,0x2D,0x45,0x0B,0xF7,0x0A,0x93,0xEA,0xED,0x06,0xF9,0xB2, +}; + + +/* subject:/C=US/O=Entrust, Inc./OU=www.entrust.net/CPS is incorporated by reference/OU=(c) 2006 Entrust, Inc./CN=Entrust Root Certification Authority */ +/* issuer :/C=US/O=Entrust, Inc./OU=www.entrust.net/CPS is incorporated by reference/OU=(c) 2006 Entrust, Inc./CN=Entrust Root Certification Authority */ + + +const unsigned char Entrust_Root_Certification_Authority_certificate[1173]={ +0x30,0x82,0x04,0x91,0x30,0x82,0x03,0x79,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x45, +0x6B,0x50,0x54,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x81,0xB0,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03, +0x55,0x04,0x0B,0x13,0x30,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74, +0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x73,0x20,0x69,0x6E,0x63,0x6F, +0x72,0x70,0x6F,0x72,0x61,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x65, +0x72,0x65,0x6E,0x63,0x65,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16, +0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x03,0x13, +0x24,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65, +0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68, +0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x32,0x37,0x32, +0x30,0x32,0x33,0x34,0x32,0x5A,0x17,0x0D,0x32,0x36,0x31,0x31,0x32,0x37,0x32,0x30, +0x35,0x33,0x34,0x32,0x5A,0x30,0x81,0xB0,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D, +0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30, +0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72, +0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x73,0x20,0x69, +0x6E,0x63,0x6F,0x72,0x70,0x6F,0x72,0x61,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x72, +0x65,0x66,0x65,0x72,0x65,0x6E,0x63,0x65,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04, +0x0B,0x13,0x16,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x45,0x6E,0x74,0x72, +0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55, +0x04,0x03,0x13,0x24,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00, +0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xB6,0x95,0xB6,0x43,0x42,0xFA,0xC6, +0x6D,0x2A,0x6F,0x48,0xDF,0x94,0x4C,0x39,0x57,0x05,0xEE,0xC3,0x79,0x11,0x41,0x68, +0x36,0xED,0xEC,0xFE,0x9A,0x01,0x8F,0xA1,0x38,0x28,0xFC,0xF7,0x10,0x46,0x66,0x2E, +0x4D,0x1E,0x1A,0xB1,0x1A,0x4E,0xC6,0xD1,0xC0,0x95,0x88,0xB0,0xC9,0xFF,0x31,0x8B, +0x33,0x03,0xDB,0xB7,0x83,0x7B,0x3E,0x20,0x84,0x5E,0xED,0xB2,0x56,0x28,0xA7,0xF8, +0xE0,0xB9,0x40,0x71,0x37,0xC5,0xCB,0x47,0x0E,0x97,0x2A,0x68,0xC0,0x22,0x95,0x62, +0x15,0xDB,0x47,0xD9,0xF5,0xD0,0x2B,0xFF,0x82,0x4B,0xC9,0xAD,0x3E,0xDE,0x4C,0xDB, +0x90,0x80,0x50,0x3F,0x09,0x8A,0x84,0x00,0xEC,0x30,0x0A,0x3D,0x18,0xCD,0xFB,0xFD, +0x2A,0x59,0x9A,0x23,0x95,0x17,0x2C,0x45,0x9E,0x1F,0x6E,0x43,0x79,0x6D,0x0C,0x5C, +0x98,0xFE,0x48,0xA7,0xC5,0x23,0x47,0x5C,0x5E,0xFD,0x6E,0xE7,0x1E,0xB4,0xF6,0x68, +0x45,0xD1,0x86,0x83,0x5B,0xA2,0x8A,0x8D,0xB1,0xE3,0x29,0x80,0xFE,0x25,0x71,0x88, +0xAD,0xBE,0xBC,0x8F,0xAC,0x52,0x96,0x4B,0xAA,0x51,0x8D,0xE4,0x13,0x31,0x19,0xE8, +0x4E,0x4D,0x9F,0xDB,0xAC,0xB3,0x6A,0xD5,0xBC,0x39,0x54,0x71,0xCA,0x7A,0x7A,0x7F, +0x90,0xDD,0x7D,0x1D,0x80,0xD9,0x81,0xBB,0x59,0x26,0xC2,0x11,0xFE,0xE6,0x93,0xE2, +0xF7,0x80,0xE4,0x65,0xFB,0x34,0x37,0x0E,0x29,0x80,0x70,0x4D,0xAF,0x38,0x86,0x2E, +0x9E,0x7F,0x57,0xAF,0x9E,0x17,0xAE,0xEB,0x1C,0xCB,0x28,0x21,0x5F,0xB6,0x1C,0xD8, +0xE7,0xA2,0x04,0x22,0xF9,0xD3,0xDA,0xD8,0xCB,0x02,0x03,0x01,0x00,0x01,0xA3,0x81, +0xB0,0x30,0x81,0xAD,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x2B,0x06,0x03,0x55,0x1D,0x10,0x04,0x24,0x30,0x22, +0x80,0x0F,0x32,0x30,0x30,0x36,0x31,0x31,0x32,0x37,0x32,0x30,0x32,0x33,0x34,0x32, +0x5A,0x81,0x0F,0x32,0x30,0x32,0x36,0x31,0x31,0x32,0x37,0x32,0x30,0x35,0x33,0x34, +0x32,0x5A,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x68, +0x90,0xE4,0x67,0xA4,0xA6,0x53,0x80,0xC7,0x86,0x66,0xA4,0xF1,0xF7,0x4B,0x43,0xFB, +0x84,0xBD,0x6D,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x68,0x90, +0xE4,0x67,0xA4,0xA6,0x53,0x80,0xC7,0x86,0x66,0xA4,0xF1,0xF7,0x4B,0x43,0xFB,0x84, +0xBD,0x6D,0x30,0x1D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04, +0x10,0x30,0x0E,0x1B,0x08,0x56,0x37,0x2E,0x31,0x3A,0x34,0x2E,0x30,0x03,0x02,0x04, +0x90,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x82,0x01,0x01,0x00,0x93,0xD4,0x30,0xB0,0xD7,0x03,0x20,0x2A,0xD0,0xF9,0x63, +0xE8,0x91,0x0C,0x05,0x20,0xA9,0x5F,0x19,0xCA,0x7B,0x72,0x4E,0xD4,0xB1,0xDB,0xD0, +0x96,0xFB,0x54,0x5A,0x19,0x2C,0x0C,0x08,0xF7,0xB2,0xBC,0x85,0xA8,0x9D,0x7F,0x6D, +0x3B,0x52,0xB3,0x2A,0xDB,0xE7,0xD4,0x84,0x8C,0x63,0xF6,0x0F,0xCB,0x26,0x01,0x91, +0x50,0x6C,0xF4,0x5F,0x14,0xE2,0x93,0x74,0xC0,0x13,0x9E,0x30,0x3A,0x50,0xE3,0xB4, +0x60,0xC5,0x1C,0xF0,0x22,0x44,0x8D,0x71,0x47,0xAC,0xC8,0x1A,0xC9,0xE9,0x9B,0x9A, +0x00,0x60,0x13,0xFF,0x70,0x7E,0x5F,0x11,0x4D,0x49,0x1B,0xB3,0x15,0x52,0x7B,0xC9, +0x54,0xDA,0xBF,0x9D,0x95,0xAF,0x6B,0x9A,0xD8,0x9E,0xE9,0xF1,0xE4,0x43,0x8D,0xE2, +0x11,0x44,0x3A,0xBF,0xAF,0xBD,0x83,0x42,0x73,0x52,0x8B,0xAA,0xBB,0xA7,0x29,0xCF, +0xF5,0x64,0x1C,0x0A,0x4D,0xD1,0xBC,0xAA,0xAC,0x9F,0x2A,0xD0,0xFF,0x7F,0x7F,0xDA, +0x7D,0xEA,0xB1,0xED,0x30,0x25,0xC1,0x84,0xDA,0x34,0xD2,0x5B,0x78,0x83,0x56,0xEC, +0x9C,0x36,0xC3,0x26,0xE2,0x11,0xF6,0x67,0x49,0x1D,0x92,0xAB,0x8C,0xFB,0xEB,0xFF, +0x7A,0xEE,0x85,0x4A,0xA7,0x50,0x80,0xF0,0xA7,0x5C,0x4A,0x94,0x2E,0x5F,0x05,0x99, +0x3C,0x52,0x41,0xE0,0xCD,0xB4,0x63,0xCF,0x01,0x43,0xBA,0x9C,0x83,0xDC,0x8F,0x60, +0x3B,0xF3,0x5A,0xB4,0xB4,0x7B,0xAE,0xDA,0x0B,0x90,0x38,0x75,0xEF,0x81,0x1D,0x66, +0xD2,0xF7,0x57,0x70,0x36,0xB3,0xBF,0xFC,0x28,0xAF,0x71,0x25,0x85,0x5B,0x13,0xFE, +0x1E,0x7F,0x5A,0xB4,0x3C, +}; + + +/* subject:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority */ +/* issuer :/C=US/O=Equifax/OU=Equifax Secure Certificate Authority */ + + +const unsigned char Equifax_Secure_CA_certificate[804]={ +0x30,0x82,0x03,0x20,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x35, +0xDE,0xF4,0xCF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71, +0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x38,0x32,0x32,0x31,0x36,0x34,0x31, +0x35,0x31,0x5A,0x17,0x0D,0x31,0x38,0x30,0x38,0x32,0x32,0x31,0x36,0x34,0x31,0x35, +0x31,0x5A,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71, +0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xC1, +0x5D,0xB1,0x58,0x67,0x08,0x62,0xEE,0xA0,0x9A,0x2D,0x1F,0x08,0x6D,0x91,0x14,0x68, +0x98,0x0A,0x1E,0xFE,0xDA,0x04,0x6F,0x13,0x84,0x62,0x21,0xC3,0xD1,0x7C,0xCE,0x9F, +0x05,0xE0,0xB8,0x01,0xF0,0x4E,0x34,0xEC,0xE2,0x8A,0x95,0x04,0x64,0xAC,0xF1,0x6B, +0x53,0x5F,0x05,0xB3,0xCB,0x67,0x80,0xBF,0x42,0x02,0x8E,0xFE,0xDD,0x01,0x09,0xEC, +0xE1,0x00,0x14,0x4F,0xFC,0xFB,0xF0,0x0C,0xDD,0x43,0xBA,0x5B,0x2B,0xE1,0x1F,0x80, +0x70,0x99,0x15,0x57,0x93,0x16,0xF1,0x0F,0x97,0x6A,0xB7,0xC2,0x68,0x23,0x1C,0xCC, +0x4D,0x59,0x30,0xAC,0x51,0x1E,0x3B,0xAF,0x2B,0xD6,0xEE,0x63,0x45,0x7B,0xC5,0xD9, +0x5F,0x50,0xD2,0xE3,0x50,0x0F,0x3A,0x88,0xE7,0xBF,0x14,0xFD,0xE0,0xC7,0xB9,0x02, +0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x09,0x30,0x82,0x01,0x05,0x30,0x70,0x06,0x03, +0x55,0x1D,0x1F,0x04,0x69,0x30,0x67,0x30,0x65,0xA0,0x63,0xA0,0x61,0xA4,0x5F,0x30, +0x5D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x10, +0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71,0x75,0x69,0x66, +0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69,0x66, +0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x31, +0x0D,0x30,0x0B,0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x1A, +0x06,0x03,0x55,0x1D,0x10,0x04,0x13,0x30,0x11,0x81,0x0F,0x32,0x30,0x31,0x38,0x30, +0x38,0x32,0x32,0x31,0x36,0x34,0x31,0x35,0x31,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D, +0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18, +0x30,0x16,0x80,0x14,0x48,0xE6,0x68,0xF9,0x2B,0xD2,0xB2,0x95,0xD7,0x47,0xD8,0x23, +0x20,0x10,0x4F,0x33,0x98,0x90,0x9F,0xD4,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04, +0x16,0x04,0x14,0x48,0xE6,0x68,0xF9,0x2B,0xD2,0xB2,0x95,0xD7,0x47,0xD8,0x23,0x20, +0x10,0x4F,0x33,0x98,0x90,0x9F,0xD4,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x1A,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07, +0x41,0x00,0x04,0x0D,0x30,0x0B,0x1B,0x05,0x56,0x33,0x2E,0x30,0x63,0x03,0x02,0x06, +0xC0,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x81,0x81,0x00,0x58,0xCE,0x29,0xEA,0xFC,0xF7,0xDE,0xB5,0xCE,0x02,0xB9,0x17, +0xB5,0x85,0xD1,0xB9,0xE3,0xE0,0x95,0xCC,0x25,0x31,0x0D,0x00,0xA6,0x92,0x6E,0x7F, +0xB6,0x92,0x63,0x9E,0x50,0x95,0xD1,0x9A,0x6F,0xE4,0x11,0xDE,0x63,0x85,0x6E,0x98, +0xEE,0xA8,0xFF,0x5A,0xC8,0xD3,0x55,0xB2,0x66,0x71,0x57,0xDE,0xC0,0x21,0xEB,0x3D, +0x2A,0xA7,0x23,0x49,0x01,0x04,0x86,0x42,0x7B,0xFC,0xEE,0x7F,0xA2,0x16,0x52,0xB5, +0x67,0x67,0xD3,0x40,0xDB,0x3B,0x26,0x58,0xB2,0x28,0x77,0x3D,0xAE,0x14,0x77,0x61, +0xD6,0xFA,0x2A,0x66,0x27,0xA0,0x0D,0xFA,0xA7,0x73,0x5C,0xEA,0x70,0xF1,0x94,0x21, +0x65,0x44,0x5F,0xFA,0xFC,0xEF,0x29,0x68,0xA9,0xA2,0x87,0x79,0xEF,0x79,0xEF,0x4F, +0xAC,0x07,0x77,0x38, +}; + + +/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure eBusiness CA-1 */ +/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure eBusiness CA-1 */ + + +const unsigned char Equifax_Secure_eBusiness_CA_1_certificate[646]={ +0x30,0x82,0x02,0x82,0x30,0x82,0x01,0xEB,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x04, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x26,0x30,0x24, +0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53, +0x65,0x63,0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20, +0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x31,0x30,0x34, +0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x36,0x32,0x31,0x30,0x34,0x30, +0x30,0x30,0x30,0x5A,0x30,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71, +0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69, +0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30, +0x81,0x89,0x02,0x81,0x81,0x00,0xCE,0x2F,0x19,0xBC,0x17,0xB7,0x77,0xDE,0x93,0xA9, +0x5F,0x5A,0x0D,0x17,0x4F,0x34,0x1A,0x0C,0x98,0xF4,0x22,0xD9,0x59,0xD4,0xC4,0x68, +0x46,0xF0,0xB4,0x35,0xC5,0x85,0x03,0x20,0xC6,0xAF,0x45,0xA5,0x21,0x51,0x45,0x41, +0xEB,0x16,0x58,0x36,0x32,0x6F,0xE2,0x50,0x62,0x64,0xF9,0xFD,0x51,0x9C,0xAA,0x24, +0xD9,0xF4,0x9D,0x83,0x2A,0x87,0x0A,0x21,0xD3,0x12,0x38,0x34,0x6C,0x8D,0x00,0x6E, +0x5A,0xA0,0xD9,0x42,0xEE,0x1A,0x21,0x95,0xF9,0x52,0x4C,0x55,0x5A,0xC5,0x0F,0x38, +0x4F,0x46,0xFA,0x6D,0xF8,0x2E,0x35,0xD6,0x1D,0x7C,0xEB,0xE2,0xF0,0xB0,0x75,0x80, +0xC8,0xA9,0x13,0xAC,0xBE,0x88,0xEF,0x3A,0x6E,0xAB,0x5F,0x2A,0x38,0x62,0x02,0xB0, +0x12,0x7B,0xFE,0x8F,0xA6,0x03,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30, +0x11,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02, +0x00,0x07,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03, +0x01,0x01,0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0x4A,0x78,0x32,0x52,0x11,0xDB,0x59,0x16,0x36,0x5E,0xDF,0xC1,0x14,0x36,0x40,0x6A, +0x47,0x7C,0x4C,0xA1,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4A, +0x78,0x32,0x52,0x11,0xDB,0x59,0x16,0x36,0x5E,0xDF,0xC1,0x14,0x36,0x40,0x6A,0x47, +0x7C,0x4C,0xA1,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04, +0x05,0x00,0x03,0x81,0x81,0x00,0x75,0x5B,0xA8,0x9B,0x03,0x11,0xE6,0xE9,0x56,0x4C, +0xCD,0xF9,0xA9,0x4C,0xC0,0x0D,0x9A,0xF3,0xCC,0x65,0x69,0xE6,0x25,0x76,0xCC,0x59, +0xB7,0xD6,0x54,0xC3,0x1D,0xCD,0x99,0xAC,0x19,0xDD,0xB4,0x85,0xD5,0xE0,0x3D,0xFC, +0x62,0x20,0xA7,0x84,0x4B,0x58,0x65,0xF1,0xE2,0xF9,0x95,0x21,0x3F,0xF5,0xD4,0x7E, +0x58,0x1E,0x47,0x87,0x54,0x3E,0x58,0xA1,0xB5,0xB5,0xF8,0x2A,0xEF,0x71,0xE7,0xBC, +0xC3,0xF6,0xB1,0x49,0x46,0xE2,0xD7,0xA0,0x6B,0xE5,0x56,0x7A,0x9A,0x27,0x98,0x7C, +0x46,0x62,0x14,0xE7,0xC9,0xFC,0x6E,0x03,0x12,0x79,0x80,0x38,0x1D,0x48,0x82,0x8D, +0xFC,0x17,0xFE,0x2A,0x96,0x2B,0xB5,0x62,0xA6,0xA6,0x3D,0xBD,0x7F,0x92,0x59,0xCD, +0x5A,0x2A,0x82,0xB2,0x37,0x79, +}; + + +/* subject:/C=US/O=Equifax Secure/OU=Equifax Secure eBusiness CA-2 */ +/* issuer :/C=US/O=Equifax Secure/OU=Equifax Secure eBusiness CA-2 */ + + +const unsigned char Equifax_Secure_eBusiness_CA_2_certificate[804]={ +0x30,0x82,0x03,0x20,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x37, +0x70,0xCF,0xB5,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03, +0x55,0x04,0x0B,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63, +0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41, +0x2D,0x32,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x33,0x31,0x32,0x31,0x34, +0x34,0x35,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,0x33,0x31,0x32,0x31,0x34,0x34, +0x35,0x5A,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03, +0x55,0x04,0x0B,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63, +0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41, +0x2D,0x32,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xE4, +0x39,0x39,0x93,0x1E,0x52,0x06,0x1B,0x28,0x36,0xF8,0xB2,0xA3,0x29,0xC5,0xED,0x8E, +0xB2,0x11,0xBD,0xFE,0xEB,0xE7,0xB4,0x74,0xC2,0x8F,0xFF,0x05,0xE7,0xD9,0x9D,0x06, +0xBF,0x12,0xC8,0x3F,0x0E,0xF2,0xD6,0xD1,0x24,0xB2,0x11,0xDE,0xD1,0x73,0x09,0x8A, +0xD4,0xB1,0x2C,0x98,0x09,0x0D,0x1E,0x50,0x46,0xB2,0x83,0xA6,0x45,0x8D,0x62,0x68, +0xBB,0x85,0x1B,0x20,0x70,0x32,0xAA,0x40,0xCD,0xA6,0x96,0x5F,0xC4,0x71,0x37,0x3F, +0x04,0xF3,0xB7,0x41,0x24,0x39,0x07,0x1A,0x1E,0x2E,0x61,0x58,0xA0,0x12,0x0B,0xE5, +0xA5,0xDF,0xC5,0xAB,0xEA,0x37,0x71,0xCC,0x1C,0xC8,0x37,0x3A,0xB9,0x97,0x52,0xA7, +0xAC,0xC5,0x6A,0x24,0x94,0x4E,0x9C,0x7B,0xCF,0xC0,0x6A,0xD6,0xDF,0x21,0xBD,0x02, +0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x09,0x30,0x82,0x01,0x05,0x30,0x70,0x06,0x03, +0x55,0x1D,0x1F,0x04,0x69,0x30,0x67,0x30,0x65,0xA0,0x63,0xA0,0x61,0xA4,0x5F,0x30, +0x5D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B, +0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65, +0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x32,0x31, +0x0D,0x30,0x0B,0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x1A, +0x06,0x03,0x55,0x1D,0x10,0x04,0x13,0x30,0x11,0x81,0x0F,0x32,0x30,0x31,0x39,0x30, +0x36,0x32,0x33,0x31,0x32,0x31,0x34,0x34,0x35,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D, +0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18, +0x30,0x16,0x80,0x14,0x50,0x9E,0x0B,0xEA,0xAF,0x5E,0xB9,0x20,0x48,0xA6,0x50,0x6A, +0xCB,0xFD,0xD8,0x20,0x7A,0xA7,0x82,0x76,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04, +0x16,0x04,0x14,0x50,0x9E,0x0B,0xEA,0xAF,0x5E,0xB9,0x20,0x48,0xA6,0x50,0x6A,0xCB, +0xFD,0xD8,0x20,0x7A,0xA7,0x82,0x76,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x1A,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07, +0x41,0x00,0x04,0x0D,0x30,0x0B,0x1B,0x05,0x56,0x33,0x2E,0x30,0x63,0x03,0x02,0x06, +0xC0,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x81,0x81,0x00,0x0C,0x86,0x82,0xAD,0xE8,0x4E,0x1A,0xF5,0x8E,0x89,0x27,0xE2, +0x35,0x58,0x3D,0x29,0xB4,0x07,0x8F,0x36,0x50,0x95,0xBF,0x6E,0xC1,0x9E,0xEB,0xC4, +0x90,0xB2,0x85,0xA8,0xBB,0xB7,0x42,0xE0,0x0F,0x07,0x39,0xDF,0xFB,0x9E,0x90,0xB2, +0xD1,0xC1,0x3E,0x53,0x9F,0x03,0x44,0xB0,0x7E,0x4B,0xF4,0x6F,0xE4,0x7C,0x1F,0xE7, +0xE2,0xB1,0xE4,0xB8,0x9A,0xEF,0xC3,0xBD,0xCE,0xDE,0x0B,0x32,0x34,0xD9,0xDE,0x28, +0xED,0x33,0x6B,0xC4,0xD4,0xD7,0x3D,0x12,0x58,0xAB,0x7D,0x09,0x2D,0xCB,0x70,0xF5, +0x13,0x8A,0x94,0xA1,0x27,0xA4,0xD6,0x70,0xC5,0x6D,0x94,0xB5,0xC9,0x7D,0x9D,0xA0, +0xD2,0xC6,0x08,0x49,0xD9,0x66,0x9B,0xA6,0xD3,0xF4,0x0B,0xDC,0xC5,0x26,0x57,0xE1, +0x91,0x30,0xEA,0xCD, +}; + + +/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */ +/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */ + + +const unsigned char Equifax_Secure_Global_eBusiness_CA_certificate[660]={ +0x30,0x82,0x02,0x90,0x30,0x82,0x01,0xF9,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B, +0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53, +0x65,0x63,0x75,0x72,0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75, +0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39, +0x39,0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30, +0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x30,0x5A,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03, +0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63, +0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04, +0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72, +0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65, +0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89, +0x02,0x81,0x81,0x00,0xBA,0xE7,0x17,0x90,0x02,0x65,0xB1,0x34,0x55,0x3C,0x49,0xC2, +0x51,0xD5,0xDF,0xA7,0xD1,0x37,0x8F,0xD1,0xE7,0x81,0x73,0x41,0x52,0x60,0x9B,0x9D, +0xA1,0x17,0x26,0x78,0xAD,0xC7,0xB1,0xE8,0x26,0x94,0x32,0xB5,0xDE,0x33,0x8D,0x3A, +0x2F,0xDB,0xF2,0x9A,0x7A,0x5A,0x73,0x98,0xA3,0x5C,0xE9,0xFB,0x8A,0x73,0x1B,0x5C, +0xE7,0xC3,0xBF,0x80,0x6C,0xCD,0xA9,0xF4,0xD6,0x2B,0xC0,0xF7,0xF9,0x99,0xAA,0x63, +0xA2,0xB1,0x47,0x02,0x0F,0xD4,0xE4,0x51,0x3A,0x12,0x3C,0x6C,0x8A,0x5A,0x54,0x84, +0x70,0xDB,0xC1,0xC5,0x90,0xCF,0x72,0x45,0xCB,0xA8,0x59,0xC0,0xCD,0x33,0x9D,0x3F, +0xA3,0x96,0xEB,0x85,0x33,0x21,0x1C,0x3E,0x1E,0x3E,0x60,0x6E,0x76,0x9C,0x67,0x85, +0xC5,0xC8,0xC3,0x61,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30,0x11,0x06, +0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07, +0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01, +0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xBE,0xA8, +0xA0,0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B, +0x68,0x6C,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBE,0xA8,0xA0, +0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,0x68, +0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00, +0x03,0x81,0x81,0x00,0x30,0xE2,0x01,0x51,0xAA,0xC7,0xEA,0x5F,0xDA,0xB9,0xD0,0x65, +0x0F,0x30,0xD6,0x3E,0xDA,0x0D,0x14,0x49,0x6E,0x91,0x93,0x27,0x14,0x31,0xEF,0xC4, +0xF7,0x2D,0x45,0xF8,0xEC,0xC7,0xBF,0xA2,0x41,0x0D,0x23,0xB4,0x92,0xF9,0x19,0x00, +0x67,0xBD,0x01,0xAF,0xCD,0xE0,0x71,0xFC,0x5A,0xCF,0x64,0xC4,0xE0,0x96,0x98,0xD0, +0xA3,0x40,0xE2,0x01,0x8A,0xEF,0x27,0x07,0xF1,0x65,0x01,0x8A,0x44,0x2D,0x06,0x65, +0x75,0x52,0xC0,0x86,0x10,0x20,0x21,0x5F,0x6C,0x6B,0x0F,0x6C,0xAE,0x09,0x1C,0xAF, +0xF2,0xA2,0x18,0x34,0xC4,0x75,0xA4,0x73,0x1C,0xF1,0x8D,0xDC,0xEF,0xAD,0xF9,0xB3, +0x76,0xB4,0x92,0xBF,0xDC,0x95,0x10,0x1E,0xBE,0xCB,0xC8,0x3B,0x5A,0x84,0x60,0x19, +0x56,0x94,0xA9,0x55, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA */ + + +const unsigned char GeoTrust_Global_CA_certificate[856]={ +0x30,0x82,0x03,0x54,0x30,0x82,0x02,0x3C,0xA0,0x03,0x02,0x01,0x02,0x02,0x03,0x02, +0x34,0x56,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05, +0x00,0x30,0x42,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53, +0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72, +0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04, +0x03,0x13,0x12,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62, +0x61,0x6C,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x31,0x30, +0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x32,0x30,0x35,0x32,0x31,0x30,0x34, +0x30,0x30,0x30,0x30,0x5A,0x30,0x42,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47, +0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1B,0x30,0x19, +0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F, +0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDA,0xCC,0x18,0x63,0x30,0xFD, +0xF4,0x17,0x23,0x1A,0x56,0x7E,0x5B,0xDF,0x3C,0x6C,0x38,0xE4,0x71,0xB7,0x78,0x91, +0xD4,0xBC,0xA1,0xD8,0x4C,0xF8,0xA8,0x43,0xB6,0x03,0xE9,0x4D,0x21,0x07,0x08,0x88, +0xDA,0x58,0x2F,0x66,0x39,0x29,0xBD,0x05,0x78,0x8B,0x9D,0x38,0xE8,0x05,0xB7,0x6A, +0x7E,0x71,0xA4,0xE6,0xC4,0x60,0xA6,0xB0,0xEF,0x80,0xE4,0x89,0x28,0x0F,0x9E,0x25, +0xD6,0xED,0x83,0xF3,0xAD,0xA6,0x91,0xC7,0x98,0xC9,0x42,0x18,0x35,0x14,0x9D,0xAD, +0x98,0x46,0x92,0x2E,0x4F,0xCA,0xF1,0x87,0x43,0xC1,0x16,0x95,0x57,0x2D,0x50,0xEF, +0x89,0x2D,0x80,0x7A,0x57,0xAD,0xF2,0xEE,0x5F,0x6B,0xD2,0x00,0x8D,0xB9,0x14,0xF8, +0x14,0x15,0x35,0xD9,0xC0,0x46,0xA3,0x7B,0x72,0xC8,0x91,0xBF,0xC9,0x55,0x2B,0xCD, +0xD0,0x97,0x3E,0x9C,0x26,0x64,0xCC,0xDF,0xCE,0x83,0x19,0x71,0xCA,0x4E,0xE6,0xD4, +0xD5,0x7B,0xA9,0x19,0xCD,0x55,0xDE,0xC8,0xEC,0xD2,0x5E,0x38,0x53,0xE5,0x5C,0x4F, +0x8C,0x2D,0xFE,0x50,0x23,0x36,0xFC,0x66,0xE6,0xCB,0x8E,0xA4,0x39,0x19,0x00,0xB7, +0x95,0x02,0x39,0x91,0x0B,0x0E,0xFE,0x38,0x2E,0xD1,0x1D,0x05,0x9A,0xF6,0x4D,0x3E, +0x6F,0x0F,0x07,0x1D,0xAF,0x2C,0x1E,0x8F,0x60,0x39,0xE2,0xFA,0x36,0x53,0x13,0x39, +0xD4,0x5E,0x26,0x2B,0xDB,0x3D,0xA8,0x14,0xBD,0x32,0xEB,0x18,0x03,0x28,0x52,0x04, +0x71,0xE5,0xAB,0x33,0x3D,0xE1,0x38,0xBB,0x07,0x36,0x84,0x62,0x9C,0x79,0xEA,0x16, +0x30,0xF4,0x5F,0xC0,0x2B,0xE8,0x71,0x6B,0xE4,0xF9,0x02,0x03,0x01,0x00,0x01,0xA3, +0x53,0x30,0x51,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC0, +0x7A,0x98,0x68,0x8D,0x89,0xFB,0xAB,0x05,0x64,0x0C,0x11,0x7D,0xAA,0x7D,0x65,0xB8, +0xCA,0xCC,0x4E,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0xC0,0x7A,0x98,0x68,0x8D,0x89,0xFB,0xAB,0x05,0x64,0x0C,0x11,0x7D,0xAA,0x7D,0x65, +0xB8,0xCA,0xCC,0x4E,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x35,0xE3,0x29,0x6A,0xE5,0x2F,0x5D,0x54, +0x8E,0x29,0x50,0x94,0x9F,0x99,0x1A,0x14,0xE4,0x8F,0x78,0x2A,0x62,0x94,0xA2,0x27, +0x67,0x9E,0xD0,0xCF,0x1A,0x5E,0x47,0xE9,0xC1,0xB2,0xA4,0xCF,0xDD,0x41,0x1A,0x05, +0x4E,0x9B,0x4B,0xEE,0x4A,0x6F,0x55,0x52,0xB3,0x24,0xA1,0x37,0x0A,0xEB,0x64,0x76, +0x2A,0x2E,0x2C,0xF3,0xFD,0x3B,0x75,0x90,0xBF,0xFA,0x71,0xD8,0xC7,0x3D,0x37,0xD2, +0xB5,0x05,0x95,0x62,0xB9,0xA6,0xDE,0x89,0x3D,0x36,0x7B,0x38,0x77,0x48,0x97,0xAC, +0xA6,0x20,0x8F,0x2E,0xA6,0xC9,0x0C,0xC2,0xB2,0x99,0x45,0x00,0xC7,0xCE,0x11,0x51, +0x22,0x22,0xE0,0xA5,0xEA,0xB6,0x15,0x48,0x09,0x64,0xEA,0x5E,0x4F,0x74,0xF7,0x05, +0x3E,0xC7,0x8A,0x52,0x0C,0xDB,0x15,0xB4,0xBD,0x6D,0x9B,0xE5,0xC6,0xB1,0x54,0x68, +0xA9,0xE3,0x69,0x90,0xB6,0x9A,0xA5,0x0F,0xB8,0xB9,0x3F,0x20,0x7D,0xAE,0x4A,0xB5, +0xB8,0x9C,0xE4,0x1D,0xB6,0xAB,0xE6,0x94,0xA5,0xC1,0xC7,0x83,0xAD,0xDB,0xF5,0x27, +0x87,0x0E,0x04,0x6C,0xD5,0xFF,0xDD,0xA0,0x5D,0xED,0x87,0x52,0xB7,0x2B,0x15,0x02, +0xAE,0x39,0xA6,0x6A,0x74,0xE9,0xDA,0xC4,0xE7,0xBC,0x4D,0x34,0x1E,0xA9,0x5C,0x4D, +0x33,0x5F,0x92,0x09,0x2F,0x88,0x66,0x5D,0x77,0x97,0xC7,0x1D,0x76,0x13,0xA9,0xD5, +0xE5,0xF1,0x16,0x09,0x11,0x35,0xD5,0xAC,0xDB,0x24,0x71,0x70,0x2C,0x98,0x56,0x0B, +0xD9,0x17,0xB4,0xD1,0xE3,0x51,0x2B,0x5E,0x75,0xE8,0xD5,0xD0,0xDC,0x4F,0x34,0xED, +0xC2,0x05,0x66,0x80,0xA1,0xCB,0xE6,0x33, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA 2 */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA 2 */ + + +const unsigned char GeoTrust_Global_CA_2_certificate[874]={ +0x30,0x82,0x03,0x66,0x30,0x82,0x02,0x4E,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x03,0x13, +0x14,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C, +0x20,0x43,0x41,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33,0x30,0x34,0x30, +0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x33,0x30,0x34,0x30,0x35, +0x30,0x30,0x30,0x30,0x5A,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47, +0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1D,0x30,0x1B, +0x06,0x03,0x55,0x04,0x03,0x13,0x14,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x41,0x20,0x32,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xEF,0x3C,0x4D,0x40, +0x3D,0x10,0xDF,0x3B,0x53,0x00,0xE1,0x67,0xFE,0x94,0x60,0x15,0x3E,0x85,0x88,0xF1, +0x89,0x0D,0x90,0xC8,0x28,0x23,0x99,0x05,0xE8,0x2B,0x20,0x9D,0xC6,0xF3,0x60,0x46, +0xD8,0xC1,0xB2,0xD5,0x8C,0x31,0xD9,0xDC,0x20,0x79,0x24,0x81,0xBF,0x35,0x32,0xFC, +0x63,0x69,0xDB,0xB1,0x2A,0x6B,0xEE,0x21,0x58,0xF2,0x08,0xE9,0x78,0xCB,0x6F,0xCB, +0xFC,0x16,0x52,0xC8,0x91,0xC4,0xFF,0x3D,0x73,0xDE,0xB1,0x3E,0xA7,0xC2,0x7D,0x66, +0xC1,0xF5,0x7E,0x52,0x24,0x1A,0xE2,0xD5,0x67,0x91,0xD0,0x82,0x10,0xD7,0x78,0x4B, +0x4F,0x2B,0x42,0x39,0xBD,0x64,0x2D,0x40,0xA0,0xB0,0x10,0xD3,0x38,0x48,0x46,0x88, +0xA1,0x0C,0xBB,0x3A,0x33,0x2A,0x62,0x98,0xFB,0x00,0x9D,0x13,0x59,0x7F,0x6F,0x3B, +0x72,0xAA,0xEE,0xA6,0x0F,0x86,0xF9,0x05,0x61,0xEA,0x67,0x7F,0x0C,0x37,0x96,0x8B, +0xE6,0x69,0x16,0x47,0x11,0xC2,0x27,0x59,0x03,0xB3,0xA6,0x60,0xC2,0x21,0x40,0x56, +0xFA,0xA0,0xC7,0x7D,0x3A,0x13,0xE3,0xEC,0x57,0xC7,0xB3,0xD6,0xAE,0x9D,0x89,0x80, +0xF7,0x01,0xE7,0x2C,0xF6,0x96,0x2B,0x13,0x0D,0x79,0x2C,0xD9,0xC0,0xE4,0x86,0x7B, +0x4B,0x8C,0x0C,0x72,0x82,0x8A,0xFB,0x17,0xCD,0x00,0x6C,0x3A,0x13,0x3C,0xB0,0x84, +0x87,0x4B,0x16,0x7A,0x29,0xB2,0x4F,0xDB,0x1D,0xD4,0x0B,0xF3,0x66,0x37,0xBD,0xD8, +0xF6,0x57,0xBB,0x5E,0x24,0x7A,0xB8,0x3C,0x8B,0xB9,0xFA,0x92,0x1A,0x1A,0x84,0x9E, +0xD8,0x74,0x8F,0xAA,0x1B,0x7F,0x5E,0xF4,0xFE,0x45,0x22,0x21,0x02,0x03,0x01,0x00, +0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x71,0x38,0x36,0xF2,0x02,0x31,0x53,0x47,0x2B,0x6E,0xBA,0x65,0x46,0xA9,0x10, +0x15,0x58,0x20,0x05,0x09,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16, +0x80,0x14,0x71,0x38,0x36,0xF2,0x02,0x31,0x53,0x47,0x2B,0x6E,0xBA,0x65,0x46,0xA9, +0x10,0x15,0x58,0x20,0x05,0x09,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x03,0xF7,0xB5,0x2B,0xAB,0x5D, +0x10,0xFC,0x7B,0xB2,0xB2,0x5E,0xAC,0x9B,0x0E,0x7E,0x53,0x78,0x59,0x3E,0x42,0x04, +0xFE,0x75,0xA3,0xAD,0xAC,0x81,0x4E,0xD7,0x02,0x8B,0x5E,0xC4,0x2D,0xC8,0x52,0x76, +0xC7,0x2C,0x1F,0xFC,0x81,0x32,0x98,0xD1,0x4B,0xC6,0x92,0x93,0x33,0x35,0x31,0x2F, +0xFC,0xD8,0x1D,0x44,0xDD,0xE0,0x81,0x7F,0x9D,0xE9,0x8B,0xE1,0x64,0x91,0x62,0x0B, +0x39,0x08,0x8C,0xAC,0x74,0x9D,0x59,0xD9,0x7A,0x59,0x52,0x97,0x11,0xB9,0x16,0x7B, +0x6F,0x45,0xD3,0x96,0xD9,0x31,0x7D,0x02,0x36,0x0F,0x9C,0x3B,0x6E,0xCF,0x2C,0x0D, +0x03,0x46,0x45,0xEB,0xA0,0xF4,0x7F,0x48,0x44,0xC6,0x08,0x40,0xCC,0xDE,0x1B,0x70, +0xB5,0x29,0xAD,0xBA,0x8B,0x3B,0x34,0x65,0x75,0x1B,0x71,0x21,0x1D,0x2C,0x14,0x0A, +0xB0,0x96,0x95,0xB8,0xD6,0xEA,0xF2,0x65,0xFB,0x29,0xBA,0x4F,0xEA,0x91,0x93,0x74, +0x69,0xB6,0xF2,0xFF,0xE1,0x1A,0xD0,0x0C,0xD1,0x76,0x85,0xCB,0x8A,0x25,0xBD,0x97, +0x5E,0x2C,0x6F,0x15,0x99,0x26,0xE7,0xB6,0x29,0xFF,0x22,0xEC,0xC9,0x02,0xC7,0x56, +0x00,0xCD,0x49,0xB9,0xB3,0x6C,0x7B,0x53,0x04,0x1A,0xE2,0xA8,0xC9,0xAA,0x12,0x05, +0x23,0xC2,0xCE,0xE7,0xBB,0x04,0x02,0xCC,0xC0,0x47,0xA2,0xE4,0xC4,0x29,0x2F,0x5B, +0x45,0x57,0x89,0x51,0xEE,0x3C,0xEB,0x52,0x08,0xFF,0x07,0x35,0x1E,0x9F,0x35,0x6A, +0x47,0x4A,0x56,0x98,0xD1,0x5A,0x85,0x1F,0x8C,0xF5,0x22,0xBF,0xAB,0xCE,0x83,0xF3, +0xE2,0x22,0x29,0xAE,0x7D,0x83,0x40,0xA8,0xBA,0x6C, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Primary Certification Authority */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Primary Certification Authority */ + + +const unsigned char GeoTrust_Primary_Certification_Authority_certificate[896]={ +0x30,0x82,0x03,0x7C,0x30,0x82,0x02,0x64,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x18, +0xAC,0xB5,0x6A,0xFD,0x69,0xB6,0x15,0x3A,0x63,0x6C,0xAF,0xDA,0xFA,0xC4,0xA1,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x58, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30, +0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28, +0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31, +0x32,0x37,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31, +0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x58,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28,0x47,0x65,0x6F,0x54,0x72,0x75, +0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xBE,0xB8,0x15,0x7B,0xFF,0xD4,0x7C,0x7D,0x67,0xAD,0x83,0x64,0x7B, +0xC8,0x42,0x53,0x2D,0xDF,0xF6,0x84,0x08,0x20,0x61,0xD6,0x01,0x59,0x6A,0x9C,0x44, +0x11,0xAF,0xEF,0x76,0xFD,0x95,0x7E,0xCE,0x61,0x30,0xBB,0x7A,0x83,0x5F,0x02,0xBD, +0x01,0x66,0xCA,0xEE,0x15,0x8D,0x6F,0xA1,0x30,0x9C,0xBD,0xA1,0x85,0x9E,0x94,0x3A, +0xF3,0x56,0x88,0x00,0x31,0xCF,0xD8,0xEE,0x6A,0x96,0x02,0xD9,0xED,0x03,0x8C,0xFB, +0x75,0x6D,0xE7,0xEA,0xB8,0x55,0x16,0x05,0x16,0x9A,0xF4,0xE0,0x5E,0xB1,0x88,0xC0, +0x64,0x85,0x5C,0x15,0x4D,0x88,0xC7,0xB7,0xBA,0xE0,0x75,0xE9,0xAD,0x05,0x3D,0x9D, +0xC7,0x89,0x48,0xE0,0xBB,0x28,0xC8,0x03,0xE1,0x30,0x93,0x64,0x5E,0x52,0xC0,0x59, +0x70,0x22,0x35,0x57,0x88,0x8A,0xF1,0x95,0x0A,0x83,0xD7,0xBC,0x31,0x73,0x01,0x34, +0xED,0xEF,0x46,0x71,0xE0,0x6B,0x02,0xA8,0x35,0x72,0x6B,0x97,0x9B,0x66,0xE0,0xCB, +0x1C,0x79,0x5F,0xD8,0x1A,0x04,0x68,0x1E,0x47,0x02,0xE6,0x9D,0x60,0xE2,0x36,0x97, +0x01,0xDF,0xCE,0x35,0x92,0xDF,0xBE,0x67,0xC7,0x6D,0x77,0x59,0x3B,0x8F,0x9D,0xD6, +0x90,0x15,0x94,0xBC,0x42,0x34,0x10,0xC1,0x39,0xF9,0xB1,0x27,0x3E,0x7E,0xD6,0x8A, +0x75,0xC5,0xB2,0xAF,0x96,0xD3,0xA2,0xDE,0x9B,0xE4,0x98,0xBE,0x7D,0xE1,0xE9,0x81, +0xAD,0xB6,0x6F,0xFC,0xD7,0x0E,0xDA,0xE0,0x34,0xB0,0x0D,0x1A,0x77,0xE7,0xE3,0x08, +0x98,0xEF,0x58,0xFA,0x9C,0x84,0xB7,0x36,0xAF,0xC2,0xDF,0xAC,0xD2,0xF4,0x10,0x06, +0x70,0x71,0x35,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03, +0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06, +0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x2C,0xD5,0x50,0x41,0x97,0x15,0x8B,0xF0, +0x8F,0x36,0x61,0x5B,0x4A,0xFB,0x6B,0xD9,0x99,0xC9,0x33,0x92,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x5A,0x70,0x7F,0x2C,0xDD,0xB7,0x34,0x4F,0xF5,0x86,0x51,0xA9,0x26,0xBE,0x4B,0xB8, +0xAA,0xF1,0x71,0x0D,0xDC,0x61,0xC7,0xA0,0xEA,0x34,0x1E,0x7A,0x77,0x0F,0x04,0x35, +0xE8,0x27,0x8F,0x6C,0x90,0xBF,0x91,0x16,0x24,0x46,0x3E,0x4A,0x4E,0xCE,0x2B,0x16, +0xD5,0x0B,0x52,0x1D,0xFC,0x1F,0x67,0xA2,0x02,0x45,0x31,0x4F,0xCE,0xF3,0xFA,0x03, +0xA7,0x79,0x9D,0x53,0x6A,0xD9,0xDA,0x63,0x3A,0xF8,0x80,0xD7,0xD3,0x99,0xE1,0xA5, +0xE1,0xBE,0xD4,0x55,0x71,0x98,0x35,0x3A,0xBE,0x93,0xEA,0xAE,0xAD,0x42,0xB2,0x90, +0x6F,0xE0,0xFC,0x21,0x4D,0x35,0x63,0x33,0x89,0x49,0xD6,0x9B,0x4E,0xCA,0xC7,0xE7, +0x4E,0x09,0x00,0xF7,0xDA,0xC7,0xEF,0x99,0x62,0x99,0x77,0xB6,0x95,0x22,0x5E,0x8A, +0xA0,0xAB,0xF4,0xB8,0x78,0x98,0xCA,0x38,0x19,0x99,0xC9,0x72,0x9E,0x78,0xCD,0x4B, +0xAC,0xAF,0x19,0xA0,0x73,0x12,0x2D,0xFC,0xC2,0x41,0xBA,0x81,0x91,0xDA,0x16,0x5A, +0x31,0xB7,0xF9,0xB4,0x71,0x80,0x12,0x48,0x99,0x72,0x73,0x5A,0x59,0x53,0xC1,0x63, +0x52,0x33,0xED,0xA7,0xC9,0xD2,0x39,0x02,0x70,0xFA,0xE0,0xB1,0x42,0x66,0x29,0xAA, +0x9B,0x51,0xED,0x30,0x54,0x22,0x14,0x5F,0xD9,0xAB,0x1D,0xC1,0xE4,0x94,0xF0,0xF8, +0xF5,0x2B,0xF7,0xEA,0xCA,0x78,0x46,0xD6,0xB8,0x91,0xFD,0xA6,0x0D,0x2B,0x1A,0x14, +0x01,0x3E,0x80,0xF0,0x42,0xA0,0x95,0x07,0x5E,0x6D,0xCD,0xCC,0x4B,0xA4,0x45,0x8D, +0xAB,0x12,0xE8,0xB3,0xDE,0x5A,0xE5,0xA0,0x7C,0xE8,0x0F,0x22,0x1D,0x5A,0xE9,0x59, +}; + + +/* subject:/C=US/O=GeoTrust Inc./OU=(c) 2007 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G2 */ +/* issuer :/C=US/O=GeoTrust Inc./OU=(c) 2007 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G2 */ + + +const unsigned char GeoTrust_Primary_Certification_Authority___G2_certificate[690]={ +0x30,0x82,0x02,0xAE,0x30,0x82,0x02,0x35,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x3C, +0xB2,0xF4,0x48,0x0A,0x00,0xE2,0xFE,0xEB,0x24,0x3B,0x5E,0x60,0x3E,0xC3,0x6B,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x98,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49, +0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63, +0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F, +0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36, +0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66, +0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31,0x30,0x35, +0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32, +0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13, +0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39, +0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63,0x29,0x20,0x32,0x30,0x30, +0x37,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x20, +0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64, +0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36,0x30,0x34,0x06,0x03,0x55, +0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69, +0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69, +0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47, +0x32,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05, +0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0x15,0xB1,0xE8,0xFD,0x03,0x15,0x43, +0xE5,0xAC,0xEB,0x87,0x37,0x11,0x62,0xEF,0xD2,0x83,0x36,0x52,0x7D,0x45,0x57,0x0B, +0x4A,0x8D,0x7B,0x54,0x3B,0x3A,0x6E,0x5F,0x15,0x02,0xC0,0x50,0xA6,0xCF,0x25,0x2F, +0x7D,0xCA,0x48,0xB8,0xC7,0x50,0x63,0x1C,0x2A,0x21,0x08,0x7C,0x9A,0x36,0xD8,0x0B, +0xFE,0xD1,0x26,0xC5,0x58,0x31,0x30,0x28,0x25,0xF3,0x5D,0x5D,0xA3,0xB8,0xB6,0xA5, +0xB4,0x92,0xED,0x6C,0x2C,0x9F,0xEB,0xDD,0x43,0x89,0xA2,0x3C,0x4B,0x48,0x91,0x1D, +0x50,0xEC,0x26,0xDF,0xD6,0x60,0x2E,0xBD,0x21,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06, +0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x15,0x5F,0x35,0x57,0x51,0x55,0xFB, +0x25,0xB2,0xAD,0x03,0x69,0xFC,0x01,0xA3,0xFA,0xBE,0x11,0x55,0xD5,0x30,0x0A,0x06, +0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x67,0x00,0x30,0x64,0x02,0x30, +0x64,0x96,0x59,0xA6,0xE8,0x09,0xDE,0x8B,0xBA,0xFA,0x5A,0x88,0x88,0xF0,0x1F,0x91, +0xD3,0x46,0xA8,0xF2,0x4A,0x4C,0x02,0x63,0xFB,0x6C,0x5F,0x38,0xDB,0x2E,0x41,0x93, +0xA9,0x0E,0xE6,0x9D,0xDC,0x31,0x1C,0xB2,0xA0,0xA7,0x18,0x1C,0x79,0xE1,0xC7,0x36, +0x02,0x30,0x3A,0x56,0xAF,0x9A,0x74,0x6C,0xF6,0xFB,0x83,0xE0,0x33,0xD3,0x08,0x5F, +0xA1,0x9C,0xC2,0x5B,0x9F,0x46,0xD6,0xB6,0xCB,0x91,0x06,0x63,0xA2,0x06,0xE7,0x33, +0xAC,0x3E,0xA8,0x81,0x12,0xD0,0xCB,0xBA,0xD0,0x92,0x0B,0xB6,0x9E,0x96,0xAA,0x04, +0x0F,0x8A, +}; + + +/* subject:/C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3 */ +/* issuer :/C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3 */ + + +const unsigned char GeoTrust_Primary_Certification_Authority___G3_certificate[1026]={ +0x30,0x82,0x03,0xFE,0x30,0x82,0x02,0xE6,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x15, +0xAC,0x6E,0x94,0x19,0xB2,0x79,0x4B,0x41,0xF6,0x27,0xA9,0xC3,0x18,0x0F,0x1F,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81, +0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13, +0x30,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x47,0x65,0x6F,0x54,0x72,0x75, +0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75, +0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C, +0x79,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54, +0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F, +0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x30,0x38,0x30, +0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32, +0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55, +0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63,0x29,0x20, +0x32,0x30,0x30,0x38,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E, +0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69, +0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36,0x30,0x34, +0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xDC,0xE2,0x5E,0x62,0x58,0x1D,0x33,0x57,0x39,0x32,0x33, +0xFA,0xEB,0xCB,0x87,0x8C,0xA7,0xD4,0x4A,0xDD,0x06,0x88,0xEA,0x64,0x8E,0x31,0x98, +0xA5,0x38,0x90,0x1E,0x98,0xCF,0x2E,0x63,0x2B,0xF0,0x46,0xBC,0x44,0xB2,0x89,0xA1, +0xC0,0x28,0x0C,0x49,0x70,0x21,0x95,0x9F,0x64,0xC0,0xA6,0x93,0x12,0x02,0x65,0x26, +0x86,0xC6,0xA5,0x89,0xF0,0xFA,0xD7,0x84,0xA0,0x70,0xAF,0x4F,0x1A,0x97,0x3F,0x06, +0x44,0xD5,0xC9,0xEB,0x72,0x10,0x7D,0xE4,0x31,0x28,0xFB,0x1C,0x61,0xE6,0x28,0x07, +0x44,0x73,0x92,0x22,0x69,0xA7,0x03,0x88,0x6C,0x9D,0x63,0xC8,0x52,0xDA,0x98,0x27, +0xE7,0x08,0x4C,0x70,0x3E,0xB4,0xC9,0x12,0xC1,0xC5,0x67,0x83,0x5D,0x33,0xF3,0x03, +0x11,0xEC,0x6A,0xD0,0x53,0xE2,0xD1,0xBA,0x36,0x60,0x94,0x80,0xBB,0x61,0x63,0x6C, +0x5B,0x17,0x7E,0xDF,0x40,0x94,0x1E,0xAB,0x0D,0xC2,0x21,0x28,0x70,0x88,0xFF,0xD6, +0x26,0x6C,0x6C,0x60,0x04,0x25,0x4E,0x55,0x7E,0x7D,0xEF,0xBF,0x94,0x48,0xDE,0xB7, +0x1D,0xDD,0x70,0x8D,0x05,0x5F,0x88,0xA5,0x9B,0xF2,0xC2,0xEE,0xEA,0xD1,0x40,0x41, +0x6D,0x62,0x38,0x1D,0x56,0x06,0xC5,0x03,0x47,0x51,0x20,0x19,0xFC,0x7B,0x10,0x0B, +0x0E,0x62,0xAE,0x76,0x55,0xBF,0x5F,0x77,0xBE,0x3E,0x49,0x01,0x53,0x3D,0x98,0x25, +0x03,0x76,0x24,0x5A,0x1D,0xB4,0xDB,0x89,0xEA,0x79,0xE5,0xB6,0xB3,0x3B,0x3F,0xBA, +0x4C,0x28,0x41,0x7F,0x06,0xAC,0x6A,0x8E,0xC1,0xD0,0xF6,0x05,0x1D,0x7D,0xE6,0x42, +0x86,0xE3,0xA5,0xD5,0x47,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F, +0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30, +0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30, +0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC4,0x79,0xCA,0x8E,0xA1,0x4E, +0x03,0x1D,0x1C,0xDC,0x6B,0xDB,0x31,0x5B,0x94,0x3E,0x3F,0x30,0x7F,0x2D,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01, +0x01,0x00,0x2D,0xC5,0x13,0xCF,0x56,0x80,0x7B,0x7A,0x78,0xBD,0x9F,0xAE,0x2C,0x99, +0xE7,0xEF,0xDA,0xDF,0x94,0x5E,0x09,0x69,0xA7,0xE7,0x6E,0x68,0x8C,0xBD,0x72,0xBE, +0x47,0xA9,0x0E,0x97,0x12,0xB8,0x4A,0xF1,0x64,0xD3,0x39,0xDF,0x25,0x34,0xD4,0xC1, +0xCD,0x4E,0x81,0xF0,0x0F,0x04,0xC4,0x24,0xB3,0x34,0x96,0xC6,0xA6,0xAA,0x30,0xDF, +0x68,0x61,0x73,0xD7,0xF9,0x8E,0x85,0x89,0xEF,0x0E,0x5E,0x95,0x28,0x4A,0x2A,0x27, +0x8F,0x10,0x8E,0x2E,0x7C,0x86,0xC4,0x02,0x9E,0xDA,0x0C,0x77,0x65,0x0E,0x44,0x0D, +0x92,0xFD,0xFD,0xB3,0x16,0x36,0xFA,0x11,0x0D,0x1D,0x8C,0x0E,0x07,0x89,0x6A,0x29, +0x56,0xF7,0x72,0xF4,0xDD,0x15,0x9C,0x77,0x35,0x66,0x57,0xAB,0x13,0x53,0xD8,0x8E, +0xC1,0x40,0xC5,0xD7,0x13,0x16,0x5A,0x72,0xC7,0xB7,0x69,0x01,0xC4,0x7A,0xB1,0x83, +0x01,0x68,0x7D,0x8D,0x41,0xA1,0x94,0x18,0xC1,0x25,0x5C,0xFC,0xF0,0xFE,0x83,0x02, +0x87,0x7C,0x0D,0x0D,0xCF,0x2E,0x08,0x5C,0x4A,0x40,0x0D,0x3E,0xEC,0x81,0x61,0xE6, +0x24,0xDB,0xCA,0xE0,0x0E,0x2D,0x07,0xB2,0x3E,0x56,0xDC,0x8D,0xF5,0x41,0x85,0x07, +0x48,0x9B,0x0C,0x0B,0xCB,0x49,0x3F,0x7D,0xEC,0xB7,0xFD,0xCB,0x8D,0x67,0x89,0x1A, +0xAB,0xED,0xBB,0x1E,0xA3,0x00,0x08,0x08,0x17,0x2A,0x82,0x5C,0x31,0x5D,0x46,0x8A, +0x2D,0x0F,0x86,0x9B,0x74,0xD9,0x45,0xFB,0xD4,0x40,0xB1,0x7A,0xAA,0x68,0x2D,0x86, +0xB2,0x99,0x22,0xE1,0xC1,0x2B,0xC7,0x9C,0xF8,0xF3,0x5F,0xA8,0x82,0x12,0xEB,0x19, +0x11,0x2D, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA */ + + +const unsigned char GeoTrust_Universal_CA_certificate[1388]={ +0x30,0x82,0x05,0x68,0x30,0x82,0x03,0x50,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x03,0x13, +0x15,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72, +0x73,0x61,0x6C,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33,0x30,0x34, +0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x30,0x34,0x30, +0x35,0x30,0x30,0x30,0x30,0x5A,0x30,0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D, +0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1E,0x30, +0x1C,0x06,0x03,0x55,0x04,0x03,0x13,0x15,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74, +0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x30,0x82,0x02, +0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xA6,0x15, +0x55,0xA0,0xA3,0xC6,0xE0,0x1F,0x8C,0x9D,0x21,0x50,0xD7,0xC1,0xBE,0x2B,0x5B,0xB5, +0xA4,0x9E,0xA1,0xD9,0x72,0x58,0xBD,0x00,0x1B,0x4C,0xBF,0x61,0xC9,0x14,0x1D,0x45, +0x82,0xAB,0xC6,0x1D,0x80,0xD6,0x3D,0xEB,0x10,0x9C,0x3A,0xAF,0x6D,0x24,0xF8,0xBC, +0x71,0x01,0x9E,0x06,0xF5,0x7C,0x5F,0x1E,0xC1,0x0E,0x55,0xCA,0x83,0x9A,0x59,0x30, +0xAE,0x19,0xCB,0x30,0x48,0x95,0xED,0x22,0x37,0x8D,0xF4,0x4A,0x9A,0x72,0x66,0x3E, +0xAD,0x95,0xC0,0xE0,0x16,0x00,0xE0,0x10,0x1F,0x2B,0x31,0x0E,0xD7,0x94,0x54,0xD3, +0x42,0x33,0xA0,0x34,0x1D,0x1E,0x45,0x76,0xDD,0x4F,0xCA,0x18,0x37,0xEC,0x85,0x15, +0x7A,0x19,0x08,0xFC,0xD5,0xC7,0x9C,0xF0,0xF2,0xA9,0x2E,0x10,0xA9,0x92,0xE6,0x3D, +0x58,0x3D,0xA9,0x16,0x68,0x3C,0x2F,0x75,0x21,0x18,0x7F,0x28,0x77,0xA5,0xE1,0x61, +0x17,0xB7,0xA6,0xE9,0xF8,0x1E,0x99,0xDB,0x73,0x6E,0xF4,0x0A,0xA2,0x21,0x6C,0xEE, +0xDA,0xAA,0x85,0x92,0x66,0xAF,0xF6,0x7A,0x6B,0x82,0xDA,0xBA,0x22,0x08,0x35,0x0F, +0xCF,0x42,0xF1,0x35,0xFA,0x6A,0xEE,0x7E,0x2B,0x25,0xCC,0x3A,0x11,0xE4,0x6D,0xAF, +0x73,0xB2,0x76,0x1D,0xAD,0xD0,0xB2,0x78,0x67,0x1A,0xA4,0x39,0x1C,0x51,0x0B,0x67, +0x56,0x83,0xFD,0x38,0x5D,0x0D,0xCE,0xDD,0xF0,0xBB,0x2B,0x96,0x1F,0xDE,0x7B,0x32, +0x52,0xFD,0x1D,0xBB,0xB5,0x06,0xA1,0xB2,0x21,0x5E,0xA5,0xD6,0x95,0x68,0x7F,0xF0, +0x99,0x9E,0xDC,0x45,0x08,0x3E,0xE7,0xD2,0x09,0x0D,0x35,0x94,0xDD,0x80,0x4E,0x53, +0x97,0xD7,0xB5,0x09,0x44,0x20,0x64,0x16,0x17,0x03,0x02,0x4C,0x53,0x0D,0x68,0xDE, +0xD5,0xAA,0x72,0x4D,0x93,0x6D,0x82,0x0E,0xDB,0x9C,0xBD,0xCF,0xB4,0xF3,0x5C,0x5D, +0x54,0x7A,0x69,0x09,0x96,0xD6,0xDB,0x11,0xC1,0x8D,0x75,0xA8,0xB4,0xCF,0x39,0xC8, +0xCE,0x3C,0xBC,0x24,0x7C,0xE6,0x62,0xCA,0xE1,0xBD,0x7D,0xA7,0xBD,0x57,0x65,0x0B, +0xE4,0xFE,0x25,0xED,0xB6,0x69,0x10,0xDC,0x28,0x1A,0x46,0xBD,0x01,0x1D,0xD0,0x97, +0xB5,0xE1,0x98,0x3B,0xC0,0x37,0x64,0xD6,0x3D,0x94,0xEE,0x0B,0xE1,0xF5,0x28,0xAE, +0x0B,0x56,0xBF,0x71,0x8B,0x23,0x29,0x41,0x8E,0x86,0xC5,0x4B,0x52,0x7B,0xD8,0x71, +0xAB,0x1F,0x8A,0x15,0xA6,0x3B,0x83,0x5A,0xD7,0x58,0x01,0x51,0xC6,0x4C,0x41,0xD9, +0x7F,0xD8,0x41,0x67,0x72,0xA2,0x28,0xDF,0x60,0x83,0xA9,0x9E,0xC8,0x7B,0xFC,0x53, +0x73,0x72,0x59,0xF5,0x93,0x7A,0x17,0x76,0x0E,0xCE,0xF7,0xE5,0x5C,0xD9,0x0B,0x55, +0x34,0xA2,0xAA,0x5B,0xB5,0x6A,0x54,0xE7,0x13,0xCA,0x57,0xEC,0x97,0x6D,0xF4,0x5E, +0x06,0x2F,0x45,0x8B,0x58,0xD4,0x23,0x16,0x92,0xE4,0x16,0x6E,0x28,0x63,0x59,0x30, +0xDF,0x50,0x01,0x9C,0x63,0x89,0x1A,0x9F,0xDB,0x17,0x94,0x82,0x70,0x37,0xC3,0x24, +0x9E,0x9A,0x47,0xD6,0x5A,0xCA,0x4E,0xA8,0x69,0x89,0x72,0x1F,0x91,0x6C,0xDB,0x7E, +0x9E,0x1B,0xAD,0xC7,0x1F,0x73,0xDD,0x2C,0x4F,0x19,0x65,0xFD,0x7F,0x93,0x40,0x10, +0x2E,0xD2,0xF0,0xED,0x3C,0x9E,0x2E,0x28,0x3E,0x69,0x26,0x33,0xC5,0x7B,0x02,0x03, +0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01, +0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04, +0x16,0x04,0x14,0xDA,0xBB,0x2E,0xAA,0xB0,0x0C,0xB8,0x88,0x26,0x51,0x74,0x5C,0x6D, +0x03,0xD3,0xC0,0xD8,0x8F,0x7A,0xD6,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18, +0x30,0x16,0x80,0x14,0xDA,0xBB,0x2E,0xAA,0xB0,0x0C,0xB8,0x88,0x26,0x51,0x74,0x5C, +0x6D,0x03,0xD3,0xC0,0xD8,0x8F,0x7A,0xD6,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01, +0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x31,0x78,0xE6,0xC7, +0xB5,0xDF,0xB8,0x94,0x40,0xC9,0x71,0xC4,0xA8,0x35,0xEC,0x46,0x1D,0xC2,0x85,0xF3, +0x28,0x58,0x86,0xB0,0x0B,0xFC,0x8E,0xB2,0x39,0x8F,0x44,0x55,0xAB,0x64,0x84,0x5C, +0x69,0xA9,0xD0,0x9A,0x38,0x3C,0xFA,0xE5,0x1F,0x35,0xE5,0x44,0xE3,0x80,0x79,0x94, +0x68,0xA4,0xBB,0xC4,0x9F,0x3D,0xE1,0x34,0xCD,0x30,0x46,0x8B,0x54,0x2B,0x95,0xA5, +0xEF,0xF7,0x3F,0x99,0x84,0xFD,0x35,0xE6,0xCF,0x31,0xC6,0xDC,0x6A,0xBF,0xA7,0xD7, +0x23,0x08,0xE1,0x98,0x5E,0xC3,0x5A,0x08,0x76,0xA9,0xA6,0xAF,0x77,0x2F,0xB7,0x60, +0xBD,0x44,0x46,0x6A,0xEF,0x97,0xFF,0x73,0x95,0xC1,0x8E,0xE8,0x93,0xFB,0xFD,0x31, +0xB7,0xEC,0x57,0x11,0x11,0x45,0x9B,0x30,0xF1,0x1A,0x88,0x39,0xC1,0x4F,0x3C,0xA7, +0x00,0xD5,0xC7,0xFC,0xAB,0x6D,0x80,0x22,0x70,0xA5,0x0C,0xE0,0x5D,0x04,0x29,0x02, +0xFB,0xCB,0xA0,0x91,0xD1,0x7C,0xD6,0xC3,0x7E,0x50,0xD5,0x9D,0x58,0xBE,0x41,0x38, +0xEB,0xB9,0x75,0x3C,0x15,0xD9,0x9B,0xC9,0x4A,0x83,0x59,0xC0,0xDA,0x53,0xFD,0x33, +0xBB,0x36,0x18,0x9B,0x85,0x0F,0x15,0xDD,0xEE,0x2D,0xAC,0x76,0x93,0xB9,0xD9,0x01, +0x8D,0x48,0x10,0xA8,0xFB,0xF5,0x38,0x86,0xF1,0xDB,0x0A,0xC6,0xBD,0x84,0xA3,0x23, +0x41,0xDE,0xD6,0x77,0x6F,0x85,0xD4,0x85,0x1C,0x50,0xE0,0xAE,0x51,0x8A,0xBA,0x8D, +0x3E,0x76,0xE2,0xB9,0xCA,0x27,0xF2,0x5F,0x9F,0xEF,0x6E,0x59,0x0D,0x06,0xD8,0x2B, +0x17,0xA4,0xD2,0x7C,0x6B,0xBB,0x5F,0x14,0x1A,0x48,0x8F,0x1A,0x4C,0xE7,0xB3,0x47, +0x1C,0x8E,0x4C,0x45,0x2B,0x20,0xEE,0x48,0xDF,0xE7,0xDD,0x09,0x8E,0x18,0xA8,0xDA, +0x40,0x8D,0x92,0x26,0x11,0x53,0x61,0x73,0x5D,0xEB,0xBD,0xE7,0xC4,0x4D,0x29,0x37, +0x61,0xEB,0xAC,0x39,0x2D,0x67,0x2E,0x16,0xD6,0xF5,0x00,0x83,0x85,0xA1,0xCC,0x7F, +0x76,0xC4,0x7D,0xE4,0xB7,0x4B,0x66,0xEF,0x03,0x45,0x60,0x69,0xB6,0x0C,0x52,0x96, +0x92,0x84,0x5E,0xA6,0xA3,0xB5,0xA4,0x3E,0x2B,0xD9,0xCC,0xD8,0x1B,0x47,0xAA,0xF2, +0x44,0xDA,0x4F,0xF9,0x03,0xE8,0xF0,0x14,0xCB,0x3F,0xF3,0x83,0xDE,0xD0,0xC1,0x54, +0xE3,0xB7,0xE8,0x0A,0x37,0x4D,0x8B,0x20,0x59,0x03,0x30,0x19,0xA1,0x2C,0xC8,0xBD, +0x11,0x1F,0xDF,0xAE,0xC9,0x4A,0xC5,0xF3,0x27,0x66,0x66,0x86,0xAC,0x68,0x91,0xFF, +0xD9,0xE6,0x53,0x1C,0x0F,0x8B,0x5C,0x69,0x65,0x0A,0x26,0xC8,0x1E,0x34,0xC3,0x5D, +0x51,0x7B,0xD7,0xA9,0x9C,0x06,0xA1,0x36,0xDD,0xD5,0x89,0x94,0xBC,0xD9,0xE4,0x2D, +0x0C,0x5E,0x09,0x6C,0x08,0x97,0x7C,0xA3,0x3D,0x7C,0x93,0xFF,0x3F,0xA1,0x14,0xA7, +0xCF,0xB5,0x5D,0xEB,0xDB,0xDB,0x1C,0xC4,0x76,0xDF,0x88,0xB9,0xBD,0x45,0x05,0x95, +0x1B,0xAE,0xFC,0x46,0x6A,0x4C,0xAF,0x48,0xE3,0xCE,0xAE,0x0F,0xD2,0x7E,0xEB,0xE6, +0x6C,0x9C,0x4F,0x81,0x6A,0x7A,0x64,0xAC,0xBB,0x3E,0xD5,0xE7,0xCB,0x76,0x2E,0xC5, +0xA7,0x48,0xC1,0x5C,0x90,0x0F,0xCB,0xC8,0x3F,0xFA,0xE6,0x32,0xE1,0x8D,0x1B,0x6F, +0xA4,0xE6,0x8E,0xD8,0xF9,0x29,0x48,0x8A,0xCE,0x73,0xFE,0x2C, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA 2 */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA 2 */ + + +const unsigned char GeoTrust_Universal_CA_2_certificate[1392]={ +0x30,0x82,0x05,0x6C,0x30,0x82,0x03,0x54,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x47,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13, +0x17,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72, +0x73,0x61,0x6C,0x20,0x43,0x41,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33, +0x30,0x34,0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x30, +0x34,0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x30,0x47,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x47,0x65,0x6F,0x54,0x72,0x75, +0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x20, +0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02, +0x01,0x00,0xB3,0x54,0x52,0xC1,0xC9,0x3E,0xF2,0xD9,0xDC,0xB1,0x53,0x1A,0x59,0x29, +0xE7,0xB1,0xC3,0x45,0x28,0xE5,0xD7,0xD1,0xED,0xC5,0xC5,0x4B,0xA1,0xAA,0x74,0x7B, +0x57,0xAF,0x4A,0x26,0xFC,0xD8,0xF5,0x5E,0xA7,0x6E,0x19,0xDB,0x74,0x0C,0x4F,0x35, +0x5B,0x32,0x0B,0x01,0xE3,0xDB,0xEB,0x7A,0x77,0x35,0xEA,0xAA,0x5A,0xE0,0xD6,0xE8, +0xA1,0x57,0x94,0xF0,0x90,0xA3,0x74,0x56,0x94,0x44,0x30,0x03,0x1E,0x5C,0x4E,0x2B, +0x85,0x26,0x74,0x82,0x7A,0x0C,0x76,0xA0,0x6F,0x4D,0xCE,0x41,0x2D,0xA0,0x15,0x06, +0x14,0x5F,0xB7,0x42,0xCD,0x7B,0x8F,0x58,0x61,0x34,0xDC,0x2A,0x08,0xF9,0x2E,0xC3, +0x01,0xA6,0x22,0x44,0x1C,0x4C,0x07,0x82,0xE6,0x5B,0xCE,0xD0,0x4A,0x7C,0x04,0xD3, +0x19,0x73,0x27,0xF0,0xAA,0x98,0x7F,0x2E,0xAF,0x4E,0xEB,0x87,0x1E,0x24,0x77,0x6A, +0x5D,0xB6,0xE8,0x5B,0x45,0xBA,0xDC,0xC3,0xA1,0x05,0x6F,0x56,0x8E,0x8F,0x10,0x26, +0xA5,0x49,0xC3,0x2E,0xD7,0x41,0x87,0x22,0xE0,0x4F,0x86,0xCA,0x60,0xB5,0xEA,0xA1, +0x63,0xC0,0x01,0x97,0x10,0x79,0xBD,0x00,0x3C,0x12,0x6D,0x2B,0x15,0xB1,0xAC,0x4B, +0xB1,0xEE,0x18,0xB9,0x4E,0x96,0xDC,0xDC,0x76,0xFF,0x3B,0xBE,0xCF,0x5F,0x03,0xC0, +0xFC,0x3B,0xE8,0xBE,0x46,0x1B,0xFF,0xDA,0x40,0xC2,0x52,0xF7,0xFE,0xE3,0x3A,0xF7, +0x6A,0x77,0x35,0xD0,0xDA,0x8D,0xEB,0x5E,0x18,0x6A,0x31,0xC7,0x1E,0xBA,0x3C,0x1B, +0x28,0xD6,0x6B,0x54,0xC6,0xAA,0x5B,0xD7,0xA2,0x2C,0x1B,0x19,0xCC,0xA2,0x02,0xF6, +0x9B,0x59,0xBD,0x37,0x6B,0x86,0xB5,0x6D,0x82,0xBA,0xD8,0xEA,0xC9,0x56,0xBC,0xA9, +0x36,0x58,0xFD,0x3E,0x19,0xF3,0xED,0x0C,0x26,0xA9,0x93,0x38,0xF8,0x4F,0xC1,0x5D, +0x22,0x06,0xD0,0x97,0xEA,0xE1,0xAD,0xC6,0x55,0xE0,0x81,0x2B,0x28,0x83,0x3A,0xFA, +0xF4,0x7B,0x21,0x51,0x00,0xBE,0x52,0x38,0xCE,0xCD,0x66,0x79,0xA8,0xF4,0x81,0x56, +0xE2,0xD0,0x83,0x09,0x47,0x51,0x5B,0x50,0x6A,0xCF,0xDB,0x48,0x1A,0x5D,0x3E,0xF7, +0xCB,0xF6,0x65,0xF7,0x6C,0xF1,0x95,0xF8,0x02,0x3B,0x32,0x56,0x82,0x39,0x7A,0x5B, +0xBD,0x2F,0x89,0x1B,0xBF,0xA1,0xB4,0xE8,0xFF,0x7F,0x8D,0x8C,0xDF,0x03,0xF1,0x60, +0x4E,0x58,0x11,0x4C,0xEB,0xA3,0x3F,0x10,0x2B,0x83,0x9A,0x01,0x73,0xD9,0x94,0x6D, +0x84,0x00,0x27,0x66,0xAC,0xF0,0x70,0x40,0x09,0x42,0x92,0xAD,0x4F,0x93,0x0D,0x61, +0x09,0x51,0x24,0xD8,0x92,0xD5,0x0B,0x94,0x61,0xB2,0x87,0xB2,0xED,0xFF,0x9A,0x35, +0xFF,0x85,0x54,0xCA,0xED,0x44,0x43,0xAC,0x1B,0x3C,0x16,0x6B,0x48,0x4A,0x0A,0x1C, +0x40,0x88,0x1F,0x92,0xC2,0x0B,0x00,0x05,0xFF,0xF2,0xC8,0x02,0x4A,0xA4,0xAA,0xA9, +0xCC,0x99,0x96,0x9C,0x2F,0x58,0xE0,0x7D,0xE1,0xBE,0xBB,0x07,0xDC,0x5F,0x04,0x72, +0x5C,0x31,0x34,0xC3,0xEC,0x5F,0x2D,0xE0,0x3D,0x64,0x90,0x22,0xE6,0xD1,0xEC,0xB8, +0x2E,0xDD,0x59,0xAE,0xD9,0xA1,0x37,0xBF,0x54,0x35,0xDC,0x73,0x32,0x4F,0x8C,0x04, +0x1E,0x33,0xB2,0xC9,0x46,0xF1,0xD8,0x5C,0xC8,0x55,0x50,0xC9,0x68,0xBD,0xA8,0xBA, +0x36,0x09,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x76,0xF3,0x55,0xE1,0xFA,0xA4,0x36,0xFB,0xF0, +0x9F,0x5C,0x62,0x71,0xED,0x3C,0xF4,0x47,0x38,0x10,0x2B,0x30,0x1F,0x06,0x03,0x55, +0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x76,0xF3,0x55,0xE1,0xFA,0xA4,0x36,0xFB, +0xF0,0x9F,0x5C,0x62,0x71,0xED,0x3C,0xF4,0x47,0x38,0x10,0x2B,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00, +0x66,0xC1,0xC6,0x23,0xF3,0xD9,0xE0,0x2E,0x6E,0x5F,0xE8,0xCF,0xAE,0xB0,0xB0,0x25, +0x4D,0x2B,0xF8,0x3B,0x58,0x9B,0x40,0x24,0x37,0x5A,0xCB,0xAB,0x16,0x49,0xFF,0xB3, +0x75,0x79,0x33,0xA1,0x2F,0x6D,0x70,0x17,0x34,0x91,0xFE,0x67,0x7E,0x8F,0xEC,0x9B, +0xE5,0x5E,0x82,0xA9,0x55,0x1F,0x2F,0xDC,0xD4,0x51,0x07,0x12,0xFE,0xAC,0x16,0x3E, +0x2C,0x35,0xC6,0x63,0xFC,0xDC,0x10,0xEB,0x0D,0xA3,0xAA,0xD0,0x7C,0xCC,0xD1,0xD0, +0x2F,0x51,0x2E,0xC4,0x14,0x5A,0xDE,0xE8,0x19,0xE1,0x3E,0xC6,0xCC,0xA4,0x29,0xE7, +0x2E,0x84,0xAA,0x06,0x30,0x78,0x76,0x54,0x73,0x28,0x98,0x59,0x38,0xE0,0x00,0x0D, +0x62,0xD3,0x42,0x7D,0x21,0x9F,0xAE,0x3D,0x3A,0x8C,0xD5,0xFA,0x77,0x0D,0x18,0x2B, +0x16,0x0E,0x5F,0x36,0xE1,0xFC,0x2A,0xB5,0x30,0x24,0xCF,0xE0,0x63,0x0C,0x7B,0x58, +0x1A,0xFE,0x99,0xBA,0x42,0x12,0xB1,0x91,0xF4,0x7C,0x68,0xE2,0xC8,0xE8,0xAF,0x2C, +0xEA,0xC9,0x7E,0xAE,0xBB,0x2A,0x3D,0x0D,0x15,0xDC,0x34,0x95,0xB6,0x18,0x74,0xA8, +0x6A,0x0F,0xC7,0xB4,0xF4,0x13,0xC4,0xE4,0x5B,0xED,0x0A,0xD2,0xA4,0x97,0x4C,0x2A, +0xED,0x2F,0x6C,0x12,0x89,0x3D,0xF1,0x27,0x70,0xAA,0x6A,0x03,0x52,0x21,0x9F,0x40, +0xA8,0x67,0x50,0xF2,0xF3,0x5A,0x1F,0xDF,0xDF,0x23,0xF6,0xDC,0x78,0x4E,0xE6,0x98, +0x4F,0x55,0x3A,0x53,0xE3,0xEF,0xF2,0xF4,0x9F,0xC7,0x7C,0xD8,0x58,0xAF,0x29,0x22, +0x97,0xB8,0xE0,0xBD,0x91,0x2E,0xB0,0x76,0xEC,0x57,0x11,0xCF,0xEF,0x29,0x44,0xF3, +0xE9,0x85,0x7A,0x60,0x63,0xE4,0x5D,0x33,0x89,0x17,0xD9,0x31,0xAA,0xDA,0xD6,0xF3, +0x18,0x35,0x72,0xCF,0x87,0x2B,0x2F,0x63,0x23,0x84,0x5D,0x84,0x8C,0x3F,0x57,0xA0, +0x88,0xFC,0x99,0x91,0x28,0x26,0x69,0x99,0xD4,0x8F,0x97,0x44,0xBE,0x8E,0xD5,0x48, +0xB1,0xA4,0x28,0x29,0xF1,0x15,0xB4,0xE1,0xE5,0x9E,0xDD,0xF8,0x8F,0xA6,0x6F,0x26, +0xD7,0x09,0x3C,0x3A,0x1C,0x11,0x0E,0xA6,0x6C,0x37,0xF7,0xAD,0x44,0x87,0x2C,0x28, +0xC7,0xD8,0x74,0x82,0xB3,0xD0,0x6F,0x4A,0x57,0xBB,0x35,0x29,0x27,0xA0,0x8B,0xE8, +0x21,0xA7,0x87,0x64,0x36,0x5D,0xCC,0xD8,0x16,0xAC,0xC7,0xB2,0x27,0x40,0x92,0x55, +0x38,0x28,0x8D,0x51,0x6E,0xDD,0x14,0x67,0x53,0x6C,0x71,0x5C,0x26,0x84,0x4D,0x75, +0x5A,0xB6,0x7E,0x60,0x56,0xA9,0x4D,0xAD,0xFB,0x9B,0x1E,0x97,0xF3,0x0D,0xD9,0xD2, +0x97,0x54,0x77,0xDA,0x3D,0x12,0xB7,0xE0,0x1E,0xEF,0x08,0x06,0xAC,0xF9,0x85,0x87, +0xE9,0xA2,0xDC,0xAF,0x7E,0x18,0x12,0x83,0xFD,0x56,0x17,0x41,0x2E,0xD5,0x29,0x82, +0x7D,0x99,0xF4,0x31,0xF6,0x71,0xA9,0xCF,0x2C,0x01,0x27,0xA5,0x05,0xB9,0xAA,0xB2, +0x48,0x4E,0x2A,0xEF,0x9F,0x93,0x52,0x51,0x95,0x3C,0x52,0x73,0x8E,0x56,0x4C,0x17, +0x40,0xC0,0x09,0x28,0xE4,0x8B,0x6A,0x48,0x53,0xDB,0xEC,0xCD,0x55,0x55,0xF1,0xC6, +0xF8,0xE9,0xA2,0x2C,0x4C,0xA6,0xD1,0x26,0x5F,0x7E,0xAF,0x5A,0x4C,0xDA,0x1F,0xA6, +0xF2,0x1C,0x2C,0x7E,0xAE,0x02,0x16,0xD2,0x56,0xD0,0x2F,0x57,0x53,0x47,0xE8,0x92, +}; + + +/* subject:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA */ +/* issuer :/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA */ + + +const unsigned char GlobalSign_Root_CA_certificate[889]={ +0x30,0x82,0x03,0x75,0x30,0x82,0x02,0x5D,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x15,0x4B,0x5A,0xC3,0x94,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x57,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x42,0x45,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04, +0x0A,0x13,0x10,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x6E,0x76, +0x2D,0x73,0x61,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0B,0x13,0x07,0x52,0x6F, +0x6F,0x74,0x20,0x43,0x41,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12, +0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x41,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x39,0x30,0x31,0x31,0x32,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x32,0x38,0x30,0x31,0x32,0x38,0x31,0x32,0x30,0x30,0x30, +0x30,0x5A,0x30,0x57,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x42, +0x45,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0A,0x13,0x10,0x47,0x6C,0x6F,0x62, +0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x6E,0x76,0x2D,0x73,0x61,0x31,0x10,0x30,0x0E, +0x06,0x03,0x55,0x04,0x0B,0x13,0x07,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53, +0x69,0x67,0x6E,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDA,0x0E,0xE6,0x99, +0x8D,0xCE,0xA3,0xE3,0x4F,0x8A,0x7E,0xFB,0xF1,0x8B,0x83,0x25,0x6B,0xEA,0x48,0x1F, +0xF1,0x2A,0xB0,0xB9,0x95,0x11,0x04,0xBD,0xF0,0x63,0xD1,0xE2,0x67,0x66,0xCF,0x1C, +0xDD,0xCF,0x1B,0x48,0x2B,0xEE,0x8D,0x89,0x8E,0x9A,0xAF,0x29,0x80,0x65,0xAB,0xE9, +0xC7,0x2D,0x12,0xCB,0xAB,0x1C,0x4C,0x70,0x07,0xA1,0x3D,0x0A,0x30,0xCD,0x15,0x8D, +0x4F,0xF8,0xDD,0xD4,0x8C,0x50,0x15,0x1C,0xEF,0x50,0xEE,0xC4,0x2E,0xF7,0xFC,0xE9, +0x52,0xF2,0x91,0x7D,0xE0,0x6D,0xD5,0x35,0x30,0x8E,0x5E,0x43,0x73,0xF2,0x41,0xE9, +0xD5,0x6A,0xE3,0xB2,0x89,0x3A,0x56,0x39,0x38,0x6F,0x06,0x3C,0x88,0x69,0x5B,0x2A, +0x4D,0xC5,0xA7,0x54,0xB8,0x6C,0x89,0xCC,0x9B,0xF9,0x3C,0xCA,0xE5,0xFD,0x89,0xF5, +0x12,0x3C,0x92,0x78,0x96,0xD6,0xDC,0x74,0x6E,0x93,0x44,0x61,0xD1,0x8D,0xC7,0x46, +0xB2,0x75,0x0E,0x86,0xE8,0x19,0x8A,0xD5,0x6D,0x6C,0xD5,0x78,0x16,0x95,0xA2,0xE9, +0xC8,0x0A,0x38,0xEB,0xF2,0x24,0x13,0x4F,0x73,0x54,0x93,0x13,0x85,0x3A,0x1B,0xBC, +0x1E,0x34,0xB5,0x8B,0x05,0x8C,0xB9,0x77,0x8B,0xB1,0xDB,0x1F,0x20,0x91,0xAB,0x09, +0x53,0x6E,0x90,0xCE,0x7B,0x37,0x74,0xB9,0x70,0x47,0x91,0x22,0x51,0x63,0x16,0x79, +0xAE,0xB1,0xAE,0x41,0x26,0x08,0xC8,0x19,0x2B,0xD1,0x46,0xAA,0x48,0xD6,0x64,0x2A, +0xD7,0x83,0x34,0xFF,0x2C,0x2A,0xC1,0x6C,0x19,0x43,0x4A,0x07,0x85,0xE7,0xD3,0x7C, +0xF6,0x21,0x68,0xEF,0xEA,0xF2,0x52,0x9F,0x7F,0x93,0x90,0xCF,0x02,0x03,0x01,0x00, +0x01,0xA3,0x42,0x30,0x40,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04, +0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x60,0x7B,0x66,0x1A,0x45,0x0D,0x97,0xCA,0x89,0x50,0x2F,0x7D,0x04,0xCD,0x34, +0xA8,0xFF,0xFC,0xFD,0x4B,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xD6,0x73,0xE7,0x7C,0x4F,0x76,0xD0, +0x8D,0xBF,0xEC,0xBA,0xA2,0xBE,0x34,0xC5,0x28,0x32,0xB5,0x7C,0xFC,0x6C,0x9C,0x2C, +0x2B,0xBD,0x09,0x9E,0x53,0xBF,0x6B,0x5E,0xAA,0x11,0x48,0xB6,0xE5,0x08,0xA3,0xB3, +0xCA,0x3D,0x61,0x4D,0xD3,0x46,0x09,0xB3,0x3E,0xC3,0xA0,0xE3,0x63,0x55,0x1B,0xF2, +0xBA,0xEF,0xAD,0x39,0xE1,0x43,0xB9,0x38,0xA3,0xE6,0x2F,0x8A,0x26,0x3B,0xEF,0xA0, +0x50,0x56,0xF9,0xC6,0x0A,0xFD,0x38,0xCD,0xC4,0x0B,0x70,0x51,0x94,0x97,0x98,0x04, +0xDF,0xC3,0x5F,0x94,0xD5,0x15,0xC9,0x14,0x41,0x9C,0xC4,0x5D,0x75,0x64,0x15,0x0D, +0xFF,0x55,0x30,0xEC,0x86,0x8F,0xFF,0x0D,0xEF,0x2C,0xB9,0x63,0x46,0xF6,0xAA,0xFC, +0xDF,0xBC,0x69,0xFD,0x2E,0x12,0x48,0x64,0x9A,0xE0,0x95,0xF0,0xA6,0xEF,0x29,0x8F, +0x01,0xB1,0x15,0xB5,0x0C,0x1D,0xA5,0xFE,0x69,0x2C,0x69,0x24,0x78,0x1E,0xB3,0xA7, +0x1C,0x71,0x62,0xEE,0xCA,0xC8,0x97,0xAC,0x17,0x5D,0x8A,0xC2,0xF8,0x47,0x86,0x6E, +0x2A,0xC4,0x56,0x31,0x95,0xD0,0x67,0x89,0x85,0x2B,0xF9,0x6C,0xA6,0x5D,0x46,0x9D, +0x0C,0xAA,0x82,0xE4,0x99,0x51,0xDD,0x70,0xB7,0xDB,0x56,0x3D,0x61,0xE4,0x6A,0xE1, +0x5C,0xD6,0xF6,0xFE,0x3D,0xDE,0x41,0xCC,0x07,0xAE,0x63,0x52,0xBF,0x53,0x53,0xF4, +0x2B,0xE9,0xC7,0xFD,0xB6,0xF7,0x82,0x5F,0x85,0xD2,0x41,0x18,0xDB,0x81,0xB3,0x04, +0x1C,0xC5,0x1F,0xA4,0x80,0x6F,0x15,0x20,0xC9,0xDE,0x0C,0x88,0x0A,0x1D,0xD6,0x66, +0x55,0xE2,0xFC,0x48,0xC9,0x29,0x26,0x69,0xE0, +}; + + +/* subject:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign */ +/* issuer :/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign */ + + +const unsigned char GlobalSign_Root_CA___R2_certificate[958]={ +0x30,0x82,0x03,0xBA,0x30,0x82,0x02,0xA2,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x0F,0x86,0x26,0xE6,0x0D,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06, +0x03,0x55,0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x32,0x31,0x13,0x30, +0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69, +0x67,0x6E,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F, +0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x31, +0x35,0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x31,0x31,0x32,0x31,0x35, +0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,0x03,0x55, +0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52, +0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x32,0x31,0x13,0x30,0x11,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61, +0x6C,0x53,0x69,0x67,0x6E,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01, +0x0A,0x02,0x82,0x01,0x01,0x00,0xA6,0xCF,0x24,0x0E,0xBE,0x2E,0x6F,0x28,0x99,0x45, +0x42,0xC4,0xAB,0x3E,0x21,0x54,0x9B,0x0B,0xD3,0x7F,0x84,0x70,0xFA,0x12,0xB3,0xCB, +0xBF,0x87,0x5F,0xC6,0x7F,0x86,0xD3,0xB2,0x30,0x5C,0xD6,0xFD,0xAD,0xF1,0x7B,0xDC, +0xE5,0xF8,0x60,0x96,0x09,0x92,0x10,0xF5,0xD0,0x53,0xDE,0xFB,0x7B,0x7E,0x73,0x88, +0xAC,0x52,0x88,0x7B,0x4A,0xA6,0xCA,0x49,0xA6,0x5E,0xA8,0xA7,0x8C,0x5A,0x11,0xBC, +0x7A,0x82,0xEB,0xBE,0x8C,0xE9,0xB3,0xAC,0x96,0x25,0x07,0x97,0x4A,0x99,0x2A,0x07, +0x2F,0xB4,0x1E,0x77,0xBF,0x8A,0x0F,0xB5,0x02,0x7C,0x1B,0x96,0xB8,0xC5,0xB9,0x3A, +0x2C,0xBC,0xD6,0x12,0xB9,0xEB,0x59,0x7D,0xE2,0xD0,0x06,0x86,0x5F,0x5E,0x49,0x6A, +0xB5,0x39,0x5E,0x88,0x34,0xEC,0xBC,0x78,0x0C,0x08,0x98,0x84,0x6C,0xA8,0xCD,0x4B, +0xB4,0xA0,0x7D,0x0C,0x79,0x4D,0xF0,0xB8,0x2D,0xCB,0x21,0xCA,0xD5,0x6C,0x5B,0x7D, +0xE1,0xA0,0x29,0x84,0xA1,0xF9,0xD3,0x94,0x49,0xCB,0x24,0x62,0x91,0x20,0xBC,0xDD, +0x0B,0xD5,0xD9,0xCC,0xF9,0xEA,0x27,0x0A,0x2B,0x73,0x91,0xC6,0x9D,0x1B,0xAC,0xC8, +0xCB,0xE8,0xE0,0xA0,0xF4,0x2F,0x90,0x8B,0x4D,0xFB,0xB0,0x36,0x1B,0xF6,0x19,0x7A, +0x85,0xE0,0x6D,0xF2,0x61,0x13,0x88,0x5C,0x9F,0xE0,0x93,0x0A,0x51,0x97,0x8A,0x5A, +0xCE,0xAF,0xAB,0xD5,0xF7,0xAA,0x09,0xAA,0x60,0xBD,0xDC,0xD9,0x5F,0xDF,0x72,0xA9, +0x60,0x13,0x5E,0x00,0x01,0xC9,0x4A,0xFA,0x3F,0xA4,0xEA,0x07,0x03,0x21,0x02,0x8E, +0x82,0xCA,0x03,0xC2,0x9B,0x8F,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x9C,0x30,0x81, +0x99,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9B,0xE2,0x07, +0x57,0x67,0x1C,0x1E,0xC0,0x6A,0x06,0xDE,0x59,0xB4,0x9A,0x2D,0xDF,0xDC,0x19,0x86, +0x2E,0x30,0x36,0x06,0x03,0x55,0x1D,0x1F,0x04,0x2F,0x30,0x2D,0x30,0x2B,0xA0,0x29, +0xA0,0x27,0x86,0x25,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x67, +0x6C,0x6F,0x62,0x61,0x6C,0x73,0x69,0x67,0x6E,0x2E,0x6E,0x65,0x74,0x2F,0x72,0x6F, +0x6F,0x74,0x2D,0x72,0x32,0x2E,0x63,0x72,0x6C,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23, +0x04,0x18,0x30,0x16,0x80,0x14,0x9B,0xE2,0x07,0x57,0x67,0x1C,0x1E,0xC0,0x6A,0x06, +0xDE,0x59,0xB4,0x9A,0x2D,0xDF,0xDC,0x19,0x86,0x2E,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x99,0x81, +0x53,0x87,0x1C,0x68,0x97,0x86,0x91,0xEC,0xE0,0x4A,0xB8,0x44,0x0B,0xAB,0x81,0xAC, +0x27,0x4F,0xD6,0xC1,0xB8,0x1C,0x43,0x78,0xB3,0x0C,0x9A,0xFC,0xEA,0x2C,0x3C,0x6E, +0x61,0x1B,0x4D,0x4B,0x29,0xF5,0x9F,0x05,0x1D,0x26,0xC1,0xB8,0xE9,0x83,0x00,0x62, +0x45,0xB6,0xA9,0x08,0x93,0xB9,0xA9,0x33,0x4B,0x18,0x9A,0xC2,0xF8,0x87,0x88,0x4E, +0xDB,0xDD,0x71,0x34,0x1A,0xC1,0x54,0xDA,0x46,0x3F,0xE0,0xD3,0x2A,0xAB,0x6D,0x54, +0x22,0xF5,0x3A,0x62,0xCD,0x20,0x6F,0xBA,0x29,0x89,0xD7,0xDD,0x91,0xEE,0xD3,0x5C, +0xA2,0x3E,0xA1,0x5B,0x41,0xF5,0xDF,0xE5,0x64,0x43,0x2D,0xE9,0xD5,0x39,0xAB,0xD2, +0xA2,0xDF,0xB7,0x8B,0xD0,0xC0,0x80,0x19,0x1C,0x45,0xC0,0x2D,0x8C,0xE8,0xF8,0x2D, +0xA4,0x74,0x56,0x49,0xC5,0x05,0xB5,0x4F,0x15,0xDE,0x6E,0x44,0x78,0x39,0x87,0xA8, +0x7E,0xBB,0xF3,0x79,0x18,0x91,0xBB,0xF4,0x6F,0x9D,0xC1,0xF0,0x8C,0x35,0x8C,0x5D, +0x01,0xFB,0xC3,0x6D,0xB9,0xEF,0x44,0x6D,0x79,0x46,0x31,0x7E,0x0A,0xFE,0xA9,0x82, +0xC1,0xFF,0xEF,0xAB,0x6E,0x20,0xC4,0x50,0xC9,0x5F,0x9D,0x4D,0x9B,0x17,0x8C,0x0C, +0xE5,0x01,0xC9,0xA0,0x41,0x6A,0x73,0x53,0xFA,0xA5,0x50,0xB4,0x6E,0x25,0x0F,0xFB, +0x4C,0x18,0xF4,0xFD,0x52,0xD9,0x8E,0x69,0xB1,0xE8,0x11,0x0F,0xDE,0x88,0xD8,0xFB, +0x1D,0x49,0xF7,0xAA,0xDE,0x95,0xCF,0x20,0x78,0xC2,0x60,0x12,0xDB,0x25,0x40,0x8C, +0x6A,0xFC,0x7E,0x42,0x38,0x40,0x64,0x12,0xF7,0x9E,0x81,0xE1,0x93,0x2E, +}; + + +/* subject:/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign */ +/* issuer :/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign */ + + +const unsigned char GlobalSign_Root_CA___R3_certificate[867]={ +0x30,0x82,0x03,0x5F,0x30,0x82,0x02,0x47,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x21,0x58,0x53,0x08,0xA2,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06, +0x03,0x55,0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x33,0x31,0x13,0x30, +0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69, +0x67,0x6E,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F, +0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x33,0x31, +0x38,0x31,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x31,0x38, +0x31,0x30,0x30,0x30,0x30,0x30,0x5A,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,0x03,0x55, +0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52, +0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x33,0x31,0x13,0x30,0x11,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61, +0x6C,0x53,0x69,0x67,0x6E,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01, +0x0A,0x02,0x82,0x01,0x01,0x00,0xCC,0x25,0x76,0x90,0x79,0x06,0x78,0x22,0x16,0xF5, +0xC0,0x83,0xB6,0x84,0xCA,0x28,0x9E,0xFD,0x05,0x76,0x11,0xC5,0xAD,0x88,0x72,0xFC, +0x46,0x02,0x43,0xC7,0xB2,0x8A,0x9D,0x04,0x5F,0x24,0xCB,0x2E,0x4B,0xE1,0x60,0x82, +0x46,0xE1,0x52,0xAB,0x0C,0x81,0x47,0x70,0x6C,0xDD,0x64,0xD1,0xEB,0xF5,0x2C,0xA3, +0x0F,0x82,0x3D,0x0C,0x2B,0xAE,0x97,0xD7,0xB6,0x14,0x86,0x10,0x79,0xBB,0x3B,0x13, +0x80,0x77,0x8C,0x08,0xE1,0x49,0xD2,0x6A,0x62,0x2F,0x1F,0x5E,0xFA,0x96,0x68,0xDF, +0x89,0x27,0x95,0x38,0x9F,0x06,0xD7,0x3E,0xC9,0xCB,0x26,0x59,0x0D,0x73,0xDE,0xB0, +0xC8,0xE9,0x26,0x0E,0x83,0x15,0xC6,0xEF,0x5B,0x8B,0xD2,0x04,0x60,0xCA,0x49,0xA6, +0x28,0xF6,0x69,0x3B,0xF6,0xCB,0xC8,0x28,0x91,0xE5,0x9D,0x8A,0x61,0x57,0x37,0xAC, +0x74,0x14,0xDC,0x74,0xE0,0x3A,0xEE,0x72,0x2F,0x2E,0x9C,0xFB,0xD0,0xBB,0xBF,0xF5, +0x3D,0x00,0xE1,0x06,0x33,0xE8,0x82,0x2B,0xAE,0x53,0xA6,0x3A,0x16,0x73,0x8C,0xDD, +0x41,0x0E,0x20,0x3A,0xC0,0xB4,0xA7,0xA1,0xE9,0xB2,0x4F,0x90,0x2E,0x32,0x60,0xE9, +0x57,0xCB,0xB9,0x04,0x92,0x68,0x68,0xE5,0x38,0x26,0x60,0x75,0xB2,0x9F,0x77,0xFF, +0x91,0x14,0xEF,0xAE,0x20,0x49,0xFC,0xAD,0x40,0x15,0x48,0xD1,0x02,0x31,0x61,0x19, +0x5E,0xB8,0x97,0xEF,0xAD,0x77,0xB7,0x64,0x9A,0x7A,0xBF,0x5F,0xC1,0x13,0xEF,0x9B, +0x62,0xFB,0x0D,0x6C,0xE0,0x54,0x69,0x16,0xA9,0x03,0xDA,0x6E,0xE9,0x83,0x93,0x71, +0x76,0xC6,0x69,0x85,0x82,0x17,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30, +0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30, +0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x8F,0xF0,0x4B,0x7F,0xA8, +0x2E,0x45,0x24,0xAE,0x4D,0x50,0xFA,0x63,0x9A,0x8B,0xDE,0xE2,0xDD,0x1B,0xBC,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x4B,0x40,0xDB,0xC0,0x50,0xAA,0xFE,0xC8,0x0C,0xEF,0xF7,0x96,0x54, +0x45,0x49,0xBB,0x96,0x00,0x09,0x41,0xAC,0xB3,0x13,0x86,0x86,0x28,0x07,0x33,0xCA, +0x6B,0xE6,0x74,0xB9,0xBA,0x00,0x2D,0xAE,0xA4,0x0A,0xD3,0xF5,0xF1,0xF1,0x0F,0x8A, +0xBF,0x73,0x67,0x4A,0x83,0xC7,0x44,0x7B,0x78,0xE0,0xAF,0x6E,0x6C,0x6F,0x03,0x29, +0x8E,0x33,0x39,0x45,0xC3,0x8E,0xE4,0xB9,0x57,0x6C,0xAA,0xFC,0x12,0x96,0xEC,0x53, +0xC6,0x2D,0xE4,0x24,0x6C,0xB9,0x94,0x63,0xFB,0xDC,0x53,0x68,0x67,0x56,0x3E,0x83, +0xB8,0xCF,0x35,0x21,0xC3,0xC9,0x68,0xFE,0xCE,0xDA,0xC2,0x53,0xAA,0xCC,0x90,0x8A, +0xE9,0xF0,0x5D,0x46,0x8C,0x95,0xDD,0x7A,0x58,0x28,0x1A,0x2F,0x1D,0xDE,0xCD,0x00, +0x37,0x41,0x8F,0xED,0x44,0x6D,0xD7,0x53,0x28,0x97,0x7E,0xF3,0x67,0x04,0x1E,0x15, +0xD7,0x8A,0x96,0xB4,0xD3,0xDE,0x4C,0x27,0xA4,0x4C,0x1B,0x73,0x73,0x76,0xF4,0x17, +0x99,0xC2,0x1F,0x7A,0x0E,0xE3,0x2D,0x08,0xAD,0x0A,0x1C,0x2C,0xFF,0x3C,0xAB,0x55, +0x0E,0x0F,0x91,0x7E,0x36,0xEB,0xC3,0x57,0x49,0xBE,0xE1,0x2E,0x2D,0x7C,0x60,0x8B, +0xC3,0x41,0x51,0x13,0x23,0x9D,0xCE,0xF7,0x32,0x6B,0x94,0x01,0xA8,0x99,0xE7,0x2C, +0x33,0x1F,0x3A,0x3B,0x25,0xD2,0x86,0x40,0xCE,0x3B,0x2C,0x86,0x78,0xC9,0x61,0x2F, +0x14,0xBA,0xEE,0xDB,0x55,0x6F,0xDF,0x84,0xEE,0x05,0x09,0x4D,0xBD,0x28,0xD8,0x72, +0xCE,0xD3,0x62,0x50,0x65,0x1E,0xEB,0x92,0x97,0x83,0x31,0xD9,0xB3,0xB5,0xCA,0x47, +0x58,0x3F,0x5F, +}; + + +/* subject:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority */ +/* issuer :/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority */ + + +const unsigned char Go_Daddy_Class_2_CA_certificate[1028]={ +0x30,0x82,0x04,0x00,0x30,0x82,0x02,0xE8,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21, +0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x54,0x68,0x65,0x20,0x47,0x6F,0x20, +0x44,0x61,0x64,0x64,0x79,0x20,0x47,0x72,0x6F,0x75,0x70,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x0B,0x13,0x28,0x47,0x6F,0x20,0x44, +0x61,0x64,0x64,0x79,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F, +0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x36,0x32,0x39,0x31,0x37, +0x30,0x36,0x32,0x30,0x5A,0x17,0x0D,0x33,0x34,0x30,0x36,0x32,0x39,0x31,0x37,0x30, +0x36,0x32,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x54,0x68, +0x65,0x20,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x47,0x72,0x6F,0x75,0x70, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x0B,0x13, +0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x43,0x6C,0x61,0x73,0x73,0x20, +0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x20,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0D, +0x00,0x30,0x82,0x01,0x08,0x02,0x82,0x01,0x01,0x00,0xDE,0x9D,0xD7,0xEA,0x57,0x18, +0x49,0xA1,0x5B,0xEB,0xD7,0x5F,0x48,0x86,0xEA,0xBE,0xDD,0xFF,0xE4,0xEF,0x67,0x1C, +0xF4,0x65,0x68,0xB3,0x57,0x71,0xA0,0x5E,0x77,0xBB,0xED,0x9B,0x49,0xE9,0x70,0x80, +0x3D,0x56,0x18,0x63,0x08,0x6F,0xDA,0xF2,0xCC,0xD0,0x3F,0x7F,0x02,0x54,0x22,0x54, +0x10,0xD8,0xB2,0x81,0xD4,0xC0,0x75,0x3D,0x4B,0x7F,0xC7,0x77,0xC3,0x3E,0x78,0xAB, +0x1A,0x03,0xB5,0x20,0x6B,0x2F,0x6A,0x2B,0xB1,0xC5,0x88,0x7E,0xC4,0xBB,0x1E,0xB0, +0xC1,0xD8,0x45,0x27,0x6F,0xAA,0x37,0x58,0xF7,0x87,0x26,0xD7,0xD8,0x2D,0xF6,0xA9, +0x17,0xB7,0x1F,0x72,0x36,0x4E,0xA6,0x17,0x3F,0x65,0x98,0x92,0xDB,0x2A,0x6E,0x5D, +0xA2,0xFE,0x88,0xE0,0x0B,0xDE,0x7F,0xE5,0x8D,0x15,0xE1,0xEB,0xCB,0x3A,0xD5,0xE2, +0x12,0xA2,0x13,0x2D,0xD8,0x8E,0xAF,0x5F,0x12,0x3D,0xA0,0x08,0x05,0x08,0xB6,0x5C, +0xA5,0x65,0x38,0x04,0x45,0x99,0x1E,0xA3,0x60,0x60,0x74,0xC5,0x41,0xA5,0x72,0x62, +0x1B,0x62,0xC5,0x1F,0x6F,0x5F,0x1A,0x42,0xBE,0x02,0x51,0x65,0xA8,0xAE,0x23,0x18, +0x6A,0xFC,0x78,0x03,0xA9,0x4D,0x7F,0x80,0xC3,0xFA,0xAB,0x5A,0xFC,0xA1,0x40,0xA4, +0xCA,0x19,0x16,0xFE,0xB2,0xC8,0xEF,0x5E,0x73,0x0D,0xEE,0x77,0xBD,0x9A,0xF6,0x79, +0x98,0xBC,0xB1,0x07,0x67,0xA2,0x15,0x0D,0xDD,0xA0,0x58,0xC6,0x44,0x7B,0x0A,0x3E, +0x62,0x28,0x5F,0xBA,0x41,0x07,0x53,0x58,0xCF,0x11,0x7E,0x38,0x74,0xC5,0xF8,0xFF, +0xB5,0x69,0x90,0x8F,0x84,0x74,0xEA,0x97,0x1B,0xAF,0x02,0x01,0x03,0xA3,0x81,0xC0, +0x30,0x81,0xBD,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xD2,0xC4, +0xB0,0xD2,0x91,0xD4,0x4C,0x11,0x71,0xB3,0x61,0xCB,0x3D,0xA1,0xFE,0xDD,0xA8,0x6A, +0xD4,0xE3,0x30,0x81,0x8D,0x06,0x03,0x55,0x1D,0x23,0x04,0x81,0x85,0x30,0x81,0x82, +0x80,0x14,0xD2,0xC4,0xB0,0xD2,0x91,0xD4,0x4C,0x11,0x71,0xB3,0x61,0xCB,0x3D,0xA1, +0xFE,0xDD,0xA8,0x6A,0xD4,0xE3,0xA1,0x67,0xA4,0x65,0x30,0x63,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55, +0x04,0x0A,0x13,0x18,0x54,0x68,0x65,0x20,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79, +0x20,0x47,0x72,0x6F,0x75,0x70,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F, +0x06,0x03,0x55,0x04,0x0B,0x13,0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x82, +0x01,0x00,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03, +0x82,0x01,0x01,0x00,0x32,0x4B,0xF3,0xB2,0xCA,0x3E,0x91,0xFC,0x12,0xC6,0xA1,0x07, +0x8C,0x8E,0x77,0xA0,0x33,0x06,0x14,0x5C,0x90,0x1E,0x18,0xF7,0x08,0xA6,0x3D,0x0A, +0x19,0xF9,0x87,0x80,0x11,0x6E,0x69,0xE4,0x96,0x17,0x30,0xFF,0x34,0x91,0x63,0x72, +0x38,0xEE,0xCC,0x1C,0x01,0xA3,0x1D,0x94,0x28,0xA4,0x31,0xF6,0x7A,0xC4,0x54,0xD7, +0xF6,0xE5,0x31,0x58,0x03,0xA2,0xCC,0xCE,0x62,0xDB,0x94,0x45,0x73,0xB5,0xBF,0x45, +0xC9,0x24,0xB5,0xD5,0x82,0x02,0xAD,0x23,0x79,0x69,0x8D,0xB8,0xB6,0x4D,0xCE,0xCF, +0x4C,0xCA,0x33,0x23,0xE8,0x1C,0x88,0xAA,0x9D,0x8B,0x41,0x6E,0x16,0xC9,0x20,0xE5, +0x89,0x9E,0xCD,0x3B,0xDA,0x70,0xF7,0x7E,0x99,0x26,0x20,0x14,0x54,0x25,0xAB,0x6E, +0x73,0x85,0xE6,0x9B,0x21,0x9D,0x0A,0x6C,0x82,0x0E,0xA8,0xF8,0xC2,0x0C,0xFA,0x10, +0x1E,0x6C,0x96,0xEF,0x87,0x0D,0xC4,0x0F,0x61,0x8B,0xAD,0xEE,0x83,0x2B,0x95,0xF8, +0x8E,0x92,0x84,0x72,0x39,0xEB,0x20,0xEA,0x83,0xED,0x83,0xCD,0x97,0x6E,0x08,0xBC, +0xEB,0x4E,0x26,0xB6,0x73,0x2B,0xE4,0xD3,0xF6,0x4C,0xFE,0x26,0x71,0xE2,0x61,0x11, +0x74,0x4A,0xFF,0x57,0x1A,0x87,0x0F,0x75,0x48,0x2E,0xCF,0x51,0x69,0x17,0xA0,0x02, +0x12,0x61,0x95,0xD5,0xD1,0x40,0xB2,0x10,0x4C,0xEE,0xC4,0xAC,0x10,0x43,0xA6,0xA5, +0x9E,0x0A,0xD5,0x95,0x62,0x9A,0x0D,0xCF,0x88,0x82,0xC5,0x32,0x0C,0xE4,0x2B,0x9F, +0x45,0xE6,0x0D,0x9F,0x28,0x9C,0xB1,0xB9,0x2A,0x5A,0x57,0xAD,0x37,0x0F,0xAF,0x1D, +0x7F,0xDB,0xBD,0x9F, +}; + + +/* subject:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2 */ +/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2 */ + + +const unsigned char Go_Daddy_Root_Certificate_Authority___G2_certificate[969]={ +0x30,0x82,0x03,0xC5,0x30,0x82,0x02,0xAD,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x81,0x83,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E, +0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74, +0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13, +0x11,0x47,0x6F,0x44,0x61,0x64,0x64,0x79,0x2E,0x63,0x6F,0x6D,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28,0x47,0x6F,0x20, +0x44,0x61,0x64,0x64,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79, +0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x39,0x30,0x31,0x30, +0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32,0x33,0x31,0x32,0x33, +0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x83,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07, +0x41,0x72,0x69,0x7A,0x6F,0x6E,0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07, +0x13,0x0A,0x53,0x63,0x6F,0x74,0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x1A,0x30,0x18, +0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x47,0x6F,0x44,0x61,0x64,0x64,0x79,0x2E,0x63, +0x6F,0x6D,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04, +0x03,0x13,0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x52,0x6F,0x6F,0x74, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xBF,0x71,0x62,0x08, +0xF1,0xFA,0x59,0x34,0xF7,0x1B,0xC9,0x18,0xA3,0xF7,0x80,0x49,0x58,0xE9,0x22,0x83, +0x13,0xA6,0xC5,0x20,0x43,0x01,0x3B,0x84,0xF1,0xE6,0x85,0x49,0x9F,0x27,0xEA,0xF6, +0x84,0x1B,0x4E,0xA0,0xB4,0xDB,0x70,0x98,0xC7,0x32,0x01,0xB1,0x05,0x3E,0x07,0x4E, +0xEE,0xF4,0xFA,0x4F,0x2F,0x59,0x30,0x22,0xE7,0xAB,0x19,0x56,0x6B,0xE2,0x80,0x07, +0xFC,0xF3,0x16,0x75,0x80,0x39,0x51,0x7B,0xE5,0xF9,0x35,0xB6,0x74,0x4E,0xA9,0x8D, +0x82,0x13,0xE4,0xB6,0x3F,0xA9,0x03,0x83,0xFA,0xA2,0xBE,0x8A,0x15,0x6A,0x7F,0xDE, +0x0B,0xC3,0xB6,0x19,0x14,0x05,0xCA,0xEA,0xC3,0xA8,0x04,0x94,0x3B,0x46,0x7C,0x32, +0x0D,0xF3,0x00,0x66,0x22,0xC8,0x8D,0x69,0x6D,0x36,0x8C,0x11,0x18,0xB7,0xD3,0xB2, +0x1C,0x60,0xB4,0x38,0xFA,0x02,0x8C,0xCE,0xD3,0xDD,0x46,0x07,0xDE,0x0A,0x3E,0xEB, +0x5D,0x7C,0xC8,0x7C,0xFB,0xB0,0x2B,0x53,0xA4,0x92,0x62,0x69,0x51,0x25,0x05,0x61, +0x1A,0x44,0x81,0x8C,0x2C,0xA9,0x43,0x96,0x23,0xDF,0xAC,0x3A,0x81,0x9A,0x0E,0x29, +0xC5,0x1C,0xA9,0xE9,0x5D,0x1E,0xB6,0x9E,0x9E,0x30,0x0A,0x39,0xCE,0xF1,0x88,0x80, +0xFB,0x4B,0x5D,0xCC,0x32,0xEC,0x85,0x62,0x43,0x25,0x34,0x02,0x56,0x27,0x01,0x91, +0xB4,0x3B,0x70,0x2A,0x3F,0x6E,0xB1,0xE8,0x9C,0x88,0x01,0x7D,0x9F,0xD4,0xF9,0xDB, +0x53,0x6D,0x60,0x9D,0xBF,0x2C,0xE7,0x58,0xAB,0xB8,0x5F,0x46,0xFC,0xCE,0xC4,0x1B, +0x03,0x3C,0x09,0xEB,0x49,0x31,0x5C,0x69,0x46,0xB3,0xE0,0x47,0x02,0x03,0x01,0x00, +0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x3A,0x9A,0x85,0x07,0x10,0x67,0x28,0xB6,0xEF,0xF6,0xBD,0x05,0x41,0x6E,0x20, +0xC1,0x94,0xDA,0x0F,0xDE,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x99,0xDB,0x5D,0x79,0xD5,0xF9,0x97, +0x59,0x67,0x03,0x61,0xF1,0x7E,0x3B,0x06,0x31,0x75,0x2D,0xA1,0x20,0x8E,0x4F,0x65, +0x87,0xB4,0xF7,0xA6,0x9C,0xBC,0xD8,0xE9,0x2F,0xD0,0xDB,0x5A,0xEE,0xCF,0x74,0x8C, +0x73,0xB4,0x38,0x42,0xDA,0x05,0x7B,0xF8,0x02,0x75,0xB8,0xFD,0xA5,0xB1,0xD7,0xAE, +0xF6,0xD7,0xDE,0x13,0xCB,0x53,0x10,0x7E,0x8A,0x46,0xD1,0x97,0xFA,0xB7,0x2E,0x2B, +0x11,0xAB,0x90,0xB0,0x27,0x80,0xF9,0xE8,0x9F,0x5A,0xE9,0x37,0x9F,0xAB,0xE4,0xDF, +0x6C,0xB3,0x85,0x17,0x9D,0x3D,0xD9,0x24,0x4F,0x79,0x91,0x35,0xD6,0x5F,0x04,0xEB, +0x80,0x83,0xAB,0x9A,0x02,0x2D,0xB5,0x10,0xF4,0xD8,0x90,0xC7,0x04,0x73,0x40,0xED, +0x72,0x25,0xA0,0xA9,0x9F,0xEC,0x9E,0xAB,0x68,0x12,0x99,0x57,0xC6,0x8F,0x12,0x3A, +0x09,0xA4,0xBD,0x44,0xFD,0x06,0x15,0x37,0xC1,0x9B,0xE4,0x32,0xA3,0xED,0x38,0xE8, +0xD8,0x64,0xF3,0x2C,0x7E,0x14,0xFC,0x02,0xEA,0x9F,0xCD,0xFF,0x07,0x68,0x17,0xDB, +0x22,0x90,0x38,0x2D,0x7A,0x8D,0xD1,0x54,0xF1,0x69,0xE3,0x5F,0x33,0xCA,0x7A,0x3D, +0x7B,0x0A,0xE3,0xCA,0x7F,0x5F,0x39,0xE5,0xE2,0x75,0xBA,0xC5,0x76,0x18,0x33,0xCE, +0x2C,0xF0,0x2F,0x4C,0xAD,0xF7,0xB1,0xE7,0xCE,0x4F,0xA8,0xC4,0x9B,0x4A,0x54,0x06, +0xC5,0x7F,0x7D,0xD5,0x08,0x0F,0xE2,0x1C,0xFE,0x7E,0x17,0xB8,0xAC,0x5E,0xF6,0xD4, +0x16,0xB2,0x43,0x09,0x0C,0x4D,0xF6,0xA7,0x6B,0xB4,0x99,0x84,0x65,0xCA,0x7A,0x88, +0xE2,0xE2,0x44,0xBE,0x5C,0xF7,0xEA,0x1C,0xF5, +}; + + +/* subject:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root */ +/* issuer :/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root */ + + +const unsigned char GTE_CyberTrust_Global_Root_certificate[606]={ +0x30,0x82,0x02,0x5A,0x30,0x82,0x01,0xC3,0x02,0x02,0x01,0xA5,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,0x75,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x18,0x30,0x16,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0F,0x47,0x54,0x45,0x20,0x43,0x6F,0x72,0x70,0x6F,0x72,0x61, +0x74,0x69,0x6F,0x6E,0x31,0x27,0x30,0x25,0x06,0x03,0x55,0x04,0x0B,0x13,0x1E,0x47, +0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x53,0x6F, +0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x23,0x30, +0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x47,0x54,0x45,0x20,0x43,0x79,0x62,0x65, +0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F, +0x6F,0x74,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x38,0x31,0x33,0x30,0x30,0x32,0x39, +0x30,0x30,0x5A,0x17,0x0D,0x31,0x38,0x30,0x38,0x31,0x33,0x32,0x33,0x35,0x39,0x30, +0x30,0x5A,0x30,0x75,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x18,0x30,0x16,0x06,0x03,0x55,0x04,0x0A,0x13,0x0F,0x47,0x54,0x45,0x20, +0x43,0x6F,0x72,0x70,0x6F,0x72,0x61,0x74,0x69,0x6F,0x6E,0x31,0x27,0x30,0x25,0x06, +0x03,0x55,0x04,0x0B,0x13,0x1E,0x47,0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54, +0x72,0x75,0x73,0x74,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x47, +0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C, +0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F,0x74,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30, +0x81,0x89,0x02,0x81,0x81,0x00,0x95,0x0F,0xA0,0xB6,0xF0,0x50,0x9C,0xE8,0x7A,0xC7, +0x88,0xCD,0xDD,0x17,0x0E,0x2E,0xB0,0x94,0xD0,0x1B,0x3D,0x0E,0xF6,0x94,0xC0,0x8A, +0x94,0xC7,0x06,0xC8,0x90,0x97,0xC8,0xB8,0x64,0x1A,0x7A,0x7E,0x6C,0x3C,0x53,0xE1, +0x37,0x28,0x73,0x60,0x7F,0xB2,0x97,0x53,0x07,0x9F,0x53,0xF9,0x6D,0x58,0x94,0xD2, +0xAF,0x8D,0x6D,0x88,0x67,0x80,0xE6,0xED,0xB2,0x95,0xCF,0x72,0x31,0xCA,0xA5,0x1C, +0x72,0xBA,0x5C,0x02,0xE7,0x64,0x42,0xE7,0xF9,0xA9,0x2C,0xD6,0x3A,0x0D,0xAC,0x8D, +0x42,0xAA,0x24,0x01,0x39,0xE6,0x9C,0x3F,0x01,0x85,0x57,0x0D,0x58,0x87,0x45,0xF8, +0xD3,0x85,0xAA,0x93,0x69,0x26,0x85,0x70,0x48,0x80,0x3F,0x12,0x15,0xC7,0x79,0xB4, +0x1F,0x05,0x2F,0x3B,0x62,0x99,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x6D,0xEB, +0x1B,0x09,0xE9,0x5E,0xD9,0x51,0xDB,0x67,0x22,0x61,0xA4,0x2A,0x3C,0x48,0x77,0xE3, +0xA0,0x7C,0xA6,0xDE,0x73,0xA2,0x14,0x03,0x85,0x3D,0xFB,0xAB,0x0E,0x30,0xC5,0x83, +0x16,0x33,0x81,0x13,0x08,0x9E,0x7B,0x34,0x4E,0xDF,0x40,0xC8,0x74,0xD7,0xB9,0x7D, +0xDC,0xF4,0x76,0x55,0x7D,0x9B,0x63,0x54,0x18,0xE9,0xF0,0xEA,0xF3,0x5C,0xB1,0xD9, +0x8B,0x42,0x1E,0xB9,0xC0,0x95,0x4E,0xBA,0xFA,0xD5,0xE2,0x7C,0xF5,0x68,0x61,0xBF, +0x8E,0xEC,0x05,0x97,0x5F,0x5B,0xB0,0xD7,0xA3,0x85,0x34,0xC4,0x24,0xA7,0x0D,0x0F, +0x95,0x93,0xEF,0xCB,0x94,0xD8,0x9E,0x1F,0x9D,0x5C,0x85,0x6D,0xC7,0xAA,0xAE,0x4F, +0x1F,0x22,0xB5,0xCD,0x95,0xAD,0xBA,0xA7,0xCC,0xF9,0xAB,0x0B,0x7A,0x7F, +}; + + +/* subject:/C=US/O=Network Solutions L.L.C./CN=Network Solutions Certificate Authority */ +/* issuer :/C=US/O=Network Solutions L.L.C./CN=Network Solutions Certificate Authority */ + + +const unsigned char Network_Solutions_Certificate_Authority_certificate[1002]={ +0x30,0x82,0x03,0xE6,0x30,0x82,0x02,0xCE,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x57, +0xCB,0x33,0x6F,0xC2,0x5C,0x16,0xE6,0x47,0x16,0x17,0xE3,0x90,0x31,0x68,0xE0,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x62, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21,0x30, +0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x20, +0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x4C,0x2E,0x4C,0x2E,0x43,0x2E, +0x31,0x30,0x30,0x2E,0x06,0x03,0x55,0x04,0x03,0x13,0x27,0x4E,0x65,0x74,0x77,0x6F, +0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x30,0x31,0x30,0x30,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35, +0x39,0x5A,0x30,0x62,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x4C,0x2E, +0x4C,0x2E,0x43,0x2E,0x31,0x30,0x30,0x2E,0x06,0x03,0x55,0x04,0x03,0x13,0x27,0x4E, +0x65,0x74,0x77,0x6F,0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xE4,0xBC,0x7E,0x92,0x30,0x6D,0xC6,0xD8,0x8E, +0x2B,0x0B,0xBC,0x46,0xCE,0xE0,0x27,0x96,0xDE,0xDE,0xF9,0xFA,0x12,0xD3,0x3C,0x33, +0x73,0xB3,0x04,0x2F,0xBC,0x71,0x8C,0xE5,0x9F,0xB6,0x22,0x60,0x3E,0x5F,0x5D,0xCE, +0x09,0xFF,0x82,0x0C,0x1B,0x9A,0x51,0x50,0x1A,0x26,0x89,0xDD,0xD5,0x61,0x5D,0x19, +0xDC,0x12,0x0F,0x2D,0x0A,0xA2,0x43,0x5D,0x17,0xD0,0x34,0x92,0x20,0xEA,0x73,0xCF, +0x38,0x2C,0x06,0x26,0x09,0x7A,0x72,0xF7,0xFA,0x50,0x32,0xF8,0xC2,0x93,0xD3,0x69, +0xA2,0x23,0xCE,0x41,0xB1,0xCC,0xE4,0xD5,0x1F,0x36,0xD1,0x8A,0x3A,0xF8,0x8C,0x63, +0xE2,0x14,0x59,0x69,0xED,0x0D,0xD3,0x7F,0x6B,0xE8,0xB8,0x03,0xE5,0x4F,0x6A,0xE5, +0x98,0x63,0x69,0x48,0x05,0xBE,0x2E,0xFF,0x33,0xB6,0xE9,0x97,0x59,0x69,0xF8,0x67, +0x19,0xAE,0x93,0x61,0x96,0x44,0x15,0xD3,0x72,0xB0,0x3F,0xBC,0x6A,0x7D,0xEC,0x48, +0x7F,0x8D,0xC3,0xAB,0xAA,0x71,0x2B,0x53,0x69,0x41,0x53,0x34,0xB5,0xB0,0xB9,0xC5, +0x06,0x0A,0xC4,0xB0,0x45,0xF5,0x41,0x5D,0x6E,0x89,0x45,0x7B,0x3D,0x3B,0x26,0x8C, +0x74,0xC2,0xE5,0xD2,0xD1,0x7D,0xB2,0x11,0xD4,0xFB,0x58,0x32,0x22,0x9A,0x80,0xC9, +0xDC,0xFD,0x0C,0xE9,0x7F,0x5E,0x03,0x97,0xCE,0x3B,0x00,0x14,0x87,0x27,0x70,0x38, +0xA9,0x8E,0x6E,0xB3,0x27,0x76,0x98,0x51,0xE0,0x05,0xE3,0x21,0xAB,0x1A,0xD5,0x85, +0x22,0x3C,0x29,0xB5,0x9A,0x16,0xC5,0x80,0xA8,0xF4,0xBB,0x6B,0x30,0x8F,0x2F,0x46, +0x02,0xA2,0xB1,0x0C,0x22,0xE0,0xD3,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x97,0x30, +0x81,0x94,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x21,0x30,0xC9, +0xFB,0x00,0xD7,0x4E,0x98,0xDA,0x87,0xAA,0x2A,0xD0,0xA7,0x2E,0xB1,0x40,0x31,0xA7, +0x4C,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x52,0x06,0x03,0x55,0x1D,0x1F,0x04,0x4B,0x30,0x49,0x30,0x47,0xA0, +0x45,0xA0,0x43,0x86,0x41,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E, +0x6E,0x65,0x74,0x73,0x6F,0x6C,0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x4E,0x65, +0x74,0x77,0x6F,0x72,0x6B,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x43,0x65, +0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x2E,0x63,0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xBB,0xAE,0x4B,0xE7,0xB7,0x57, +0xEB,0x7F,0xAA,0x2D,0xB7,0x73,0x47,0x85,0x6A,0xC1,0xE4,0xA5,0x1D,0xE4,0xE7,0x3C, +0xE9,0xF4,0x59,0x65,0x77,0xB5,0x7A,0x5B,0x5A,0x8D,0x25,0x36,0xE0,0x7A,0x97,0x2E, +0x38,0xC0,0x57,0x60,0x83,0x98,0x06,0x83,0x9F,0xB9,0x76,0x7A,0x6E,0x50,0xE0,0xBA, +0x88,0x2C,0xFC,0x45,0xCC,0x18,0xB0,0x99,0x95,0x51,0x0E,0xEC,0x1D,0xB8,0x88,0xFF, +0x87,0x50,0x1C,0x82,0xC2,0xE3,0xE0,0x32,0x80,0xBF,0xA0,0x0B,0x47,0xC8,0xC3,0x31, +0xEF,0x99,0x67,0x32,0x80,0x4F,0x17,0x21,0x79,0x0C,0x69,0x5C,0xDE,0x5E,0x34,0xAE, +0x02,0xB5,0x26,0xEA,0x50,0xDF,0x7F,0x18,0x65,0x2C,0xC9,0xF2,0x63,0xE1,0xA9,0x07, +0xFE,0x7C,0x71,0x1F,0x6B,0x33,0x24,0x6A,0x1E,0x05,0xF7,0x05,0x68,0xC0,0x6A,0x12, +0xCB,0x2E,0x5E,0x61,0xCB,0xAE,0x28,0xD3,0x7E,0xC2,0xB4,0x66,0x91,0x26,0x5F,0x3C, +0x2E,0x24,0x5F,0xCB,0x58,0x0F,0xEB,0x28,0xEC,0xAF,0x11,0x96,0xF3,0xDC,0x7B,0x6F, +0xC0,0xA7,0x88,0xF2,0x53,0x77,0xB3,0x60,0x5E,0xAE,0xAE,0x28,0xDA,0x35,0x2C,0x6F, +0x34,0x45,0xD3,0x26,0xE1,0xDE,0xEC,0x5B,0x4F,0x27,0x6B,0x16,0x7C,0xBD,0x44,0x04, +0x18,0x82,0xB3,0x89,0x79,0x17,0x10,0x71,0x3D,0x7A,0xA2,0x16,0x4E,0xF5,0x01,0xCD, +0xA4,0x6C,0x65,0x68,0xA1,0x49,0x76,0x5C,0x43,0xC9,0xD8,0xBC,0x36,0x67,0x6C,0xA5, +0x94,0xB5,0xD4,0xCC,0xB9,0xBD,0x6A,0x35,0x56,0x21,0xDE,0xD8,0xC3,0xEB,0xFB,0xCB, +0xA4,0x60,0x4C,0xB0,0x55,0xA0,0xA0,0x7B,0x57,0xB2, +}; + + +/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 3 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ +/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 3 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ + + +const unsigned char RSA_Root_Certificate_1_certificate[747]={ +0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30, +0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74, +0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61, +0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33, +0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72, +0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69, +0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36, +0x32,0x36,0x30,0x30,0x32,0x32,0x33,0x33,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32, +0x36,0x30,0x30,0x32,0x32,0x33,0x33,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06, +0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69, +0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03, +0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C, +0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E, +0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02, +0x81,0x81,0x00,0xE3,0x98,0x51,0x96,0x1C,0xE8,0xD5,0xB1,0x06,0x81,0x6A,0x57,0xC3, +0x72,0x75,0x93,0xAB,0xCF,0x9E,0xA6,0xFC,0xF3,0x16,0x52,0xD6,0x2D,0x4D,0x9F,0x35, +0x44,0xA8,0x2E,0x04,0x4D,0x07,0x49,0x8A,0x38,0x29,0xF5,0x77,0x37,0xE7,0xB7,0xAB, +0x5D,0xDF,0x36,0x71,0x14,0x99,0x8F,0xDC,0xC2,0x92,0xF1,0xE7,0x60,0x92,0x97,0xEC, +0xD8,0x48,0xDC,0xBF,0xC1,0x02,0x20,0xC6,0x24,0xA4,0x28,0x4C,0x30,0x5A,0x76,0x6D, +0xB1,0x5C,0xF3,0xDD,0xDE,0x9E,0x10,0x71,0xA1,0x88,0xC7,0x5B,0x9B,0x41,0x6D,0xCA, +0xB0,0xB8,0x8E,0x15,0xEE,0xAD,0x33,0x2B,0xCF,0x47,0x04,0x5C,0x75,0x71,0x0A,0x98, +0x24,0x98,0x29,0xA7,0x49,0x59,0xA5,0xDD,0xF8,0xB7,0x43,0x62,0x61,0xF3,0xD3,0xE2, +0xD0,0x55,0x3F,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x56,0xBB,0x02,0x58,0x84, +0x67,0x08,0x2C,0xDF,0x1F,0xDB,0x7B,0x49,0x33,0xF5,0xD3,0x67,0x9D,0xF4,0xB4,0x0A, +0x10,0xB3,0xC9,0xC5,0x2C,0xE2,0x92,0x6A,0x71,0x78,0x27,0xF2,0x70,0x83,0x42,0xD3, +0x3E,0xCF,0xA9,0x54,0xF4,0xF1,0xD8,0x92,0x16,0x8C,0xD1,0x04,0xCB,0x4B,0xAB,0xC9, +0x9F,0x45,0xAE,0x3C,0x8A,0xA9,0xB0,0x71,0x33,0x5D,0xC8,0xC5,0x57,0xDF,0xAF,0xA8, +0x35,0xB3,0x7F,0x89,0x87,0xE9,0xE8,0x25,0x92,0xB8,0x7F,0x85,0x7A,0xAE,0xD6,0xBC, +0x1E,0x37,0x58,0x2A,0x67,0xC9,0x91,0xCF,0x2A,0x81,0x3E,0xED,0xC6,0x39,0xDF,0xC0, +0x3E,0x19,0x9C,0x19,0xCC,0x13,0x4D,0x82,0x41,0xB5,0x8C,0xDE,0xE0,0x3D,0x60,0x08, +0x20,0x0F,0x45,0x7E,0x6B,0xA2,0x7F,0xA3,0x8C,0x15,0xEE, +}; + + +/* subject:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority */ +/* issuer :/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority */ + + +const unsigned char Starfield_Class_2_CA_certificate[1043]={ +0x30,0x82,0x04,0x0F,0x30,0x82,0x02,0xF7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x68,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x25, +0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65, +0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x0B,0x13,0x29, +0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x43,0x6C,0x61,0x73,0x73,0x20, +0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30, +0x36,0x32,0x39,0x31,0x37,0x33,0x39,0x31,0x36,0x5A,0x17,0x0D,0x33,0x34,0x30,0x36, +0x32,0x39,0x31,0x37,0x33,0x39,0x31,0x36,0x5A,0x30,0x68,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04, +0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63, +0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x0B,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69, +0x65,0x6C,0x64,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x30,0x82,0x01,0x20,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0D,0x00,0x30,0x82,0x01,0x08,0x02, +0x82,0x01,0x01,0x00,0xB7,0x32,0xC8,0xFE,0xE9,0x71,0xA6,0x04,0x85,0xAD,0x0C,0x11, +0x64,0xDF,0xCE,0x4D,0xEF,0xC8,0x03,0x18,0x87,0x3F,0xA1,0xAB,0xFB,0x3C,0xA6,0x9F, +0xF0,0xC3,0xA1,0xDA,0xD4,0xD8,0x6E,0x2B,0x53,0x90,0xFB,0x24,0xA4,0x3E,0x84,0xF0, +0x9E,0xE8,0x5F,0xEC,0xE5,0x27,0x44,0xF5,0x28,0xA6,0x3F,0x7B,0xDE,0xE0,0x2A,0xF0, +0xC8,0xAF,0x53,0x2F,0x9E,0xCA,0x05,0x01,0x93,0x1E,0x8F,0x66,0x1C,0x39,0xA7,0x4D, +0xFA,0x5A,0xB6,0x73,0x04,0x25,0x66,0xEB,0x77,0x7F,0xE7,0x59,0xC6,0x4A,0x99,0x25, +0x14,0x54,0xEB,0x26,0xC7,0xF3,0x7F,0x19,0xD5,0x30,0x70,0x8F,0xAF,0xB0,0x46,0x2A, +0xFF,0xAD,0xEB,0x29,0xED,0xD7,0x9F,0xAA,0x04,0x87,0xA3,0xD4,0xF9,0x89,0xA5,0x34, +0x5F,0xDB,0x43,0x91,0x82,0x36,0xD9,0x66,0x3C,0xB1,0xB8,0xB9,0x82,0xFD,0x9C,0x3A, +0x3E,0x10,0xC8,0x3B,0xEF,0x06,0x65,0x66,0x7A,0x9B,0x19,0x18,0x3D,0xFF,0x71,0x51, +0x3C,0x30,0x2E,0x5F,0xBE,0x3D,0x77,0x73,0xB2,0x5D,0x06,0x6C,0xC3,0x23,0x56,0x9A, +0x2B,0x85,0x26,0x92,0x1C,0xA7,0x02,0xB3,0xE4,0x3F,0x0D,0xAF,0x08,0x79,0x82,0xB8, +0x36,0x3D,0xEA,0x9C,0xD3,0x35,0xB3,0xBC,0x69,0xCA,0xF5,0xCC,0x9D,0xE8,0xFD,0x64, +0x8D,0x17,0x80,0x33,0x6E,0x5E,0x4A,0x5D,0x99,0xC9,0x1E,0x87,0xB4,0x9D,0x1A,0xC0, +0xD5,0x6E,0x13,0x35,0x23,0x5E,0xDF,0x9B,0x5F,0x3D,0xEF,0xD6,0xF7,0x76,0xC2,0xEA, +0x3E,0xBB,0x78,0x0D,0x1C,0x42,0x67,0x6B,0x04,0xD8,0xF8,0xD6,0xDA,0x6F,0x8B,0xF2, +0x44,0xA0,0x01,0xAB,0x02,0x01,0x03,0xA3,0x81,0xC5,0x30,0x81,0xC2,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBF,0x5F,0xB7,0xD1,0xCE,0xDD,0x1F,0x86, +0xF4,0x5B,0x55,0xAC,0xDC,0xD7,0x10,0xC2,0x0E,0xA9,0x88,0xE7,0x30,0x81,0x92,0x06, +0x03,0x55,0x1D,0x23,0x04,0x81,0x8A,0x30,0x81,0x87,0x80,0x14,0xBF,0x5F,0xB7,0xD1, +0xCE,0xDD,0x1F,0x86,0xF4,0x5B,0x55,0xAC,0xDC,0xD7,0x10,0xC2,0x0E,0xA9,0x88,0xE7, +0xA1,0x6C,0xA4,0x6A,0x30,0x68,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74, +0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F, +0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30,0x30,0x06,0x03, +0x55,0x04,0x0B,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x43, +0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x82,0x01, +0x00,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x05,0x9D,0x3F,0x88,0x9D,0xD1,0xC9,0x1A,0x55,0xA1,0xAC,0x69,0xF3, +0xF3,0x59,0xDA,0x9B,0x01,0x87,0x1A,0x4F,0x57,0xA9,0xA1,0x79,0x09,0x2A,0xDB,0xF7, +0x2F,0xB2,0x1E,0xCC,0xC7,0x5E,0x6A,0xD8,0x83,0x87,0xA1,0x97,0xEF,0x49,0x35,0x3E, +0x77,0x06,0x41,0x58,0x62,0xBF,0x8E,0x58,0xB8,0x0A,0x67,0x3F,0xEC,0xB3,0xDD,0x21, +0x66,0x1F,0xC9,0x54,0xFA,0x72,0xCC,0x3D,0x4C,0x40,0xD8,0x81,0xAF,0x77,0x9E,0x83, +0x7A,0xBB,0xA2,0xC7,0xF5,0x34,0x17,0x8E,0xD9,0x11,0x40,0xF4,0xFC,0x2C,0x2A,0x4D, +0x15,0x7F,0xA7,0x62,0x5D,0x2E,0x25,0xD3,0x00,0x0B,0x20,0x1A,0x1D,0x68,0xF9,0x17, +0xB8,0xF4,0xBD,0x8B,0xED,0x28,0x59,0xDD,0x4D,0x16,0x8B,0x17,0x83,0xC8,0xB2,0x65, +0xC7,0x2D,0x7A,0xA5,0xAA,0xBC,0x53,0x86,0x6D,0xDD,0x57,0xA4,0xCA,0xF8,0x20,0x41, +0x0B,0x68,0xF0,0xF4,0xFB,0x74,0xBE,0x56,0x5D,0x7A,0x79,0xF5,0xF9,0x1D,0x85,0xE3, +0x2D,0x95,0xBE,0xF5,0x71,0x90,0x43,0xCC,0x8D,0x1F,0x9A,0x00,0x0A,0x87,0x29,0xE9, +0x55,0x22,0x58,0x00,0x23,0xEA,0xE3,0x12,0x43,0x29,0x5B,0x47,0x08,0xDD,0x8C,0x41, +0x6A,0x65,0x06,0xA8,0xE5,0x21,0xAA,0x41,0xB4,0x95,0x21,0x95,0xB9,0x7D,0xD1,0x34, +0xAB,0x13,0xD6,0xAD,0xBC,0xDC,0xE2,0x3D,0x39,0xCD,0xBD,0x3E,0x75,0x70,0xA1,0x18, +0x59,0x03,0xC9,0x22,0xB4,0x8F,0x9C,0xD5,0x5E,0x2A,0xD7,0xA5,0xB6,0xD4,0x0A,0x6D, +0xF8,0xB7,0x40,0x11,0x46,0x9A,0x1F,0x79,0x0E,0x62,0xBF,0x0F,0x97,0xEC,0xE0,0x2F, +0x1F,0x17,0x94, +}; + + +/* subject:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 */ +/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 */ + + +const unsigned char Starfield_Root_Certificate_Authority___G2_certificate[993]={ +0x30,0x82,0x03,0xDD,0x30,0x82,0x02,0xC5,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x81,0x8F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E, +0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74, +0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13, +0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E, +0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30, +0x30,0x06,0x03,0x55,0x04,0x03,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C, +0x64,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47, +0x32,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x39,0x30,0x31,0x30,0x30,0x30,0x30,0x30, +0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39, +0x5A,0x30,0x81,0x8F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A, +0x6F,0x6E,0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63, +0x6F,0x74,0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04, +0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63, +0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x03,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69, +0x65,0x6C,0x64,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69, +0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D, +0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02, +0x82,0x01,0x01,0x00,0xBD,0xED,0xC1,0x03,0xFC,0xF6,0x8F,0xFC,0x02,0xB1,0x6F,0x5B, +0x9F,0x48,0xD9,0x9D,0x79,0xE2,0xA2,0xB7,0x03,0x61,0x56,0x18,0xC3,0x47,0xB6,0xD7, +0xCA,0x3D,0x35,0x2E,0x89,0x43,0xF7,0xA1,0x69,0x9B,0xDE,0x8A,0x1A,0xFD,0x13,0x20, +0x9C,0xB4,0x49,0x77,0x32,0x29,0x56,0xFD,0xB9,0xEC,0x8C,0xDD,0x22,0xFA,0x72,0xDC, +0x27,0x61,0x97,0xEE,0xF6,0x5A,0x84,0xEC,0x6E,0x19,0xB9,0x89,0x2C,0xDC,0x84,0x5B, +0xD5,0x74,0xFB,0x6B,0x5F,0xC5,0x89,0xA5,0x10,0x52,0x89,0x46,0x55,0xF4,0xB8,0x75, +0x1C,0xE6,0x7F,0xE4,0x54,0xAE,0x4B,0xF8,0x55,0x72,0x57,0x02,0x19,0xF8,0x17,0x71, +0x59,0xEB,0x1E,0x28,0x07,0x74,0xC5,0x9D,0x48,0xBE,0x6C,0xB4,0xF4,0xA4,0xB0,0xF3, +0x64,0x37,0x79,0x92,0xC0,0xEC,0x46,0x5E,0x7F,0xE1,0x6D,0x53,0x4C,0x62,0xAF,0xCD, +0x1F,0x0B,0x63,0xBB,0x3A,0x9D,0xFB,0xFC,0x79,0x00,0x98,0x61,0x74,0xCF,0x26,0x82, +0x40,0x63,0xF3,0xB2,0x72,0x6A,0x19,0x0D,0x99,0xCA,0xD4,0x0E,0x75,0xCC,0x37,0xFB, +0x8B,0x89,0xC1,0x59,0xF1,0x62,0x7F,0x5F,0xB3,0x5F,0x65,0x30,0xF8,0xA7,0xB7,0x4D, +0x76,0x5A,0x1E,0x76,0x5E,0x34,0xC0,0xE8,0x96,0x56,0x99,0x8A,0xB3,0xF0,0x7F,0xA4, +0xCD,0xBD,0xDC,0x32,0x31,0x7C,0x91,0xCF,0xE0,0x5F,0x11,0xF8,0x6B,0xAA,0x49,0x5C, +0xD1,0x99,0x94,0xD1,0xA2,0xE3,0x63,0x5B,0x09,0x76,0xB5,0x56,0x62,0xE1,0x4B,0x74, +0x1D,0x96,0xD4,0x26,0xD4,0x08,0x04,0x59,0xD0,0x98,0x0E,0x0E,0xE6,0xDE,0xFC,0xC3, +0xEC,0x1F,0x90,0xF1,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06, +0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7C,0x0C,0x32,0x1F,0xA7,0xD9,0x30, +0x7F,0xC4,0x7D,0x68,0xA3,0x62,0xA8,0xA1,0xCE,0xAB,0x07,0x5B,0x27,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01, +0x00,0x11,0x59,0xFA,0x25,0x4F,0x03,0x6F,0x94,0x99,0x3B,0x9A,0x1F,0x82,0x85,0x39, +0xD4,0x76,0x05,0x94,0x5E,0xE1,0x28,0x93,0x6D,0x62,0x5D,0x09,0xC2,0xA0,0xA8,0xD4, +0xB0,0x75,0x38,0xF1,0x34,0x6A,0x9D,0xE4,0x9F,0x8A,0x86,0x26,0x51,0xE6,0x2C,0xD1, +0xC6,0x2D,0x6E,0x95,0x20,0x4A,0x92,0x01,0xEC,0xB8,0x8A,0x67,0x7B,0x31,0xE2,0x67, +0x2E,0x8C,0x95,0x03,0x26,0x2E,0x43,0x9D,0x4A,0x31,0xF6,0x0E,0xB5,0x0C,0xBB,0xB7, +0xE2,0x37,0x7F,0x22,0xBA,0x00,0xA3,0x0E,0x7B,0x52,0xFB,0x6B,0xBB,0x3B,0xC4,0xD3, +0x79,0x51,0x4E,0xCD,0x90,0xF4,0x67,0x07,0x19,0xC8,0x3C,0x46,0x7A,0x0D,0x01,0x7D, +0xC5,0x58,0xE7,0x6D,0xE6,0x85,0x30,0x17,0x9A,0x24,0xC4,0x10,0xE0,0x04,0xF7,0xE0, +0xF2,0x7F,0xD4,0xAA,0x0A,0xFF,0x42,0x1D,0x37,0xED,0x94,0xE5,0x64,0x59,0x12,0x20, +0x77,0x38,0xD3,0x32,0x3E,0x38,0x81,0x75,0x96,0x73,0xFA,0x68,0x8F,0xB1,0xCB,0xCE, +0x1F,0xC5,0xEC,0xFA,0x9C,0x7E,0xCF,0x7E,0xB1,0xF1,0x07,0x2D,0xB6,0xFC,0xBF,0xCA, +0xA4,0xBF,0xD0,0x97,0x05,0x4A,0xBC,0xEA,0x18,0x28,0x02,0x90,0xBD,0x54,0x78,0x09, +0x21,0x71,0xD3,0xD1,0x7D,0x1D,0xD9,0x16,0xB0,0xA9,0x61,0x3D,0xD0,0x0A,0x00,0x22, +0xFC,0xC7,0x7B,0xCB,0x09,0x64,0x45,0x0B,0x3B,0x40,0x81,0xF7,0x7D,0x7C,0x32,0xF5, +0x98,0xCA,0x58,0x8E,0x7D,0x2A,0xEE,0x90,0x59,0x73,0x64,0xF9,0x36,0x74,0x5E,0x25, +0xA1,0xF5,0x66,0x05,0x2E,0x7F,0x39,0x15,0xA9,0x2A,0xFB,0x50,0x8B,0x8E,0x85,0x69, +0xF4, +}; + + +/* subject:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 */ +/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 */ + + +const unsigned char Starfield_Services_Root_Certificate_Authority___G2_certificate[1011]={ +0x30,0x82,0x03,0xEF,0x30,0x82,0x02,0xD7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x81,0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E, +0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74, +0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13, +0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E, +0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3B,0x30, +0x39,0x06,0x03,0x55,0x04,0x03,0x13,0x32,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C, +0x64,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68, +0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x39, +0x30,0x39,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31, +0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x10,0x30,0x0E,0x06,0x03, +0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E,0x61,0x31,0x13,0x30,0x11, +0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74,0x74,0x73,0x64,0x61,0x6C, +0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74,0x61,0x72, +0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69, +0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3B,0x30,0x39,0x06,0x03,0x55,0x04, +0x03,0x13,0x32,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x53,0x65,0x72, +0x76,0x69,0x63,0x65,0x73,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79, +0x20,0x2D,0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01, +0x0A,0x02,0x82,0x01,0x01,0x00,0xD5,0x0C,0x3A,0xC4,0x2A,0xF9,0x4E,0xE2,0xF5,0xBE, +0x19,0x97,0x5F,0x8E,0x88,0x53,0xB1,0x1F,0x3F,0xCB,0xCF,0x9F,0x20,0x13,0x6D,0x29, +0x3A,0xC8,0x0F,0x7D,0x3C,0xF7,0x6B,0x76,0x38,0x63,0xD9,0x36,0x60,0xA8,0x9B,0x5E, +0x5C,0x00,0x80,0xB2,0x2F,0x59,0x7F,0xF6,0x87,0xF9,0x25,0x43,0x86,0xE7,0x69,0x1B, +0x52,0x9A,0x90,0xE1,0x71,0xE3,0xD8,0x2D,0x0D,0x4E,0x6F,0xF6,0xC8,0x49,0xD9,0xB6, +0xF3,0x1A,0x56,0xAE,0x2B,0xB6,0x74,0x14,0xEB,0xCF,0xFB,0x26,0xE3,0x1A,0xBA,0x1D, +0x96,0x2E,0x6A,0x3B,0x58,0x94,0x89,0x47,0x56,0xFF,0x25,0xA0,0x93,0x70,0x53,0x83, +0xDA,0x84,0x74,0x14,0xC3,0x67,0x9E,0x04,0x68,0x3A,0xDF,0x8E,0x40,0x5A,0x1D,0x4A, +0x4E,0xCF,0x43,0x91,0x3B,0xE7,0x56,0xD6,0x00,0x70,0xCB,0x52,0xEE,0x7B,0x7D,0xAE, +0x3A,0xE7,0xBC,0x31,0xF9,0x45,0xF6,0xC2,0x60,0xCF,0x13,0x59,0x02,0x2B,0x80,0xCC, +0x34,0x47,0xDF,0xB9,0xDE,0x90,0x65,0x6D,0x02,0xCF,0x2C,0x91,0xA6,0xA6,0xE7,0xDE, +0x85,0x18,0x49,0x7C,0x66,0x4E,0xA3,0x3A,0x6D,0xA9,0xB5,0xEE,0x34,0x2E,0xBA,0x0D, +0x03,0xB8,0x33,0xDF,0x47,0xEB,0xB1,0x6B,0x8D,0x25,0xD9,0x9B,0xCE,0x81,0xD1,0x45, +0x46,0x32,0x96,0x70,0x87,0xDE,0x02,0x0E,0x49,0x43,0x85,0xB6,0x6C,0x73,0xBB,0x64, +0xEA,0x61,0x41,0xAC,0xC9,0xD4,0x54,0xDF,0x87,0x2F,0xC7,0x22,0xB2,0x26,0xCC,0x9F, +0x59,0x54,0x68,0x9F,0xFC,0xBE,0x2A,0x2F,0xC4,0x55,0x1C,0x75,0x40,0x60,0x17,0x85, +0x02,0x55,0x39,0x8B,0x7F,0x05,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30, +0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06, +0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9C,0x5F,0x00,0xDF,0xAA, +0x01,0xD7,0x30,0x2B,0x38,0x88,0xA2,0xB8,0x6D,0x4A,0x9C,0xF2,0x11,0x91,0x83,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x4B,0x36,0xA6,0x84,0x77,0x69,0xDD,0x3B,0x19,0x9F,0x67,0x23,0x08, +0x6F,0x0E,0x61,0xC9,0xFD,0x84,0xDC,0x5F,0xD8,0x36,0x81,0xCD,0xD8,0x1B,0x41,0x2D, +0x9F,0x60,0xDD,0xC7,0x1A,0x68,0xD9,0xD1,0x6E,0x86,0xE1,0x88,0x23,0xCF,0x13,0xDE, +0x43,0xCF,0xE2,0x34,0xB3,0x04,0x9D,0x1F,0x29,0xD5,0xBF,0xF8,0x5E,0xC8,0xD5,0xC1, +0xBD,0xEE,0x92,0x6F,0x32,0x74,0xF2,0x91,0x82,0x2F,0xBD,0x82,0x42,0x7A,0xAD,0x2A, +0xB7,0x20,0x7D,0x4D,0xBC,0x7A,0x55,0x12,0xC2,0x15,0xEA,0xBD,0xF7,0x6A,0x95,0x2E, +0x6C,0x74,0x9F,0xCF,0x1C,0xB4,0xF2,0xC5,0x01,0xA3,0x85,0xD0,0x72,0x3E,0xAD,0x73, +0xAB,0x0B,0x9B,0x75,0x0C,0x6D,0x45,0xB7,0x8E,0x94,0xAC,0x96,0x37,0xB5,0xA0,0xD0, +0x8F,0x15,0x47,0x0E,0xE3,0xE8,0x83,0xDD,0x8F,0xFD,0xEF,0x41,0x01,0x77,0xCC,0x27, +0xA9,0x62,0x85,0x33,0xF2,0x37,0x08,0xEF,0x71,0xCF,0x77,0x06,0xDE,0xC8,0x19,0x1D, +0x88,0x40,0xCF,0x7D,0x46,0x1D,0xFF,0x1E,0xC7,0xE1,0xCE,0xFF,0x23,0xDB,0xC6,0xFA, +0x8D,0x55,0x4E,0xA9,0x02,0xE7,0x47,0x11,0x46,0x3E,0xF4,0xFD,0xBD,0x7B,0x29,0x26, +0xBB,0xA9,0x61,0x62,0x37,0x28,0xB6,0x2D,0x2A,0xF6,0x10,0x86,0x64,0xC9,0x70,0xA7, +0xD2,0xAD,0xB7,0x29,0x70,0x79,0xEA,0x3C,0xDA,0x63,0x25,0x9F,0xFD,0x68,0xB7,0x30, +0xEC,0x70,0xFB,0x75,0x8A,0xB7,0x6D,0x60,0x67,0xB2,0x1E,0xC8,0xB9,0xE9,0xD8,0xA8, +0x6F,0x02,0x8B,0x67,0x0D,0x4D,0x26,0x57,0x71,0xDA,0x20,0xFC,0xC1,0x4A,0x50,0x8D, +0xB1,0x28,0xBA, +}; + + +/* subject:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */ +/* issuer :/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */ + + +const unsigned char StartCom_Certification_Authority_certificate[1931]={ +0x30,0x82,0x07,0x87,0x30,0x82,0x05,0x6F,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x2D, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x7D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F, +0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x0B,0x13, +0x22,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x44,0x69,0x67,0x69,0x74,0x61,0x6C,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x69,0x67,0x6E, +0x69,0x6E,0x67,0x31,0x29,0x30,0x27,0x06,0x03,0x55,0x04,0x03,0x13,0x20,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E, +0x17,0x0D,0x30,0x36,0x30,0x39,0x31,0x37,0x31,0x39,0x34,0x36,0x33,0x37,0x5A,0x17, +0x0D,0x33,0x36,0x30,0x39,0x31,0x37,0x31,0x39,0x34,0x36,0x33,0x36,0x5A,0x30,0x7D, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16,0x30, +0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D, +0x20,0x4C,0x74,0x64,0x2E,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x0B,0x13,0x22, +0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x44,0x69,0x67,0x69,0x74,0x61,0x6C,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x69,0x67,0x6E,0x69, +0x6E,0x67,0x31,0x29,0x30,0x27,0x06,0x03,0x55,0x04,0x03,0x13,0x20,0x53,0x74,0x61, +0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74, +0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x02, +0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xC1,0x88, +0xDB,0x09,0xBC,0x6C,0x46,0x7C,0x78,0x9F,0x95,0x7B,0xB5,0x33,0x90,0xF2,0x72,0x62, +0xD6,0xC1,0x36,0x20,0x22,0x24,0x5E,0xCE,0xE9,0x77,0xF2,0x43,0x0A,0xA2,0x06,0x64, +0xA4,0xCC,0x8E,0x36,0xF8,0x38,0xE6,0x23,0xF0,0x6E,0x6D,0xB1,0x3C,0xDD,0x72,0xA3, +0x85,0x1C,0xA1,0xD3,0x3D,0xB4,0x33,0x2B,0xD3,0x2F,0xAF,0xFE,0xEA,0xB0,0x41,0x59, +0x67,0xB6,0xC4,0x06,0x7D,0x0A,0x9E,0x74,0x85,0xD6,0x79,0x4C,0x80,0x37,0x7A,0xDF, +0x39,0x05,0x52,0x59,0xF7,0xF4,0x1B,0x46,0x43,0xA4,0xD2,0x85,0x85,0xD2,0xC3,0x71, +0xF3,0x75,0x62,0x34,0xBA,0x2C,0x8A,0x7F,0x1E,0x8F,0xEE,0xED,0x34,0xD0,0x11,0xC7, +0x96,0xCD,0x52,0x3D,0xBA,0x33,0xD6,0xDD,0x4D,0xDE,0x0B,0x3B,0x4A,0x4B,0x9F,0xC2, +0x26,0x2F,0xFA,0xB5,0x16,0x1C,0x72,0x35,0x77,0xCA,0x3C,0x5D,0xE6,0xCA,0xE1,0x26, +0x8B,0x1A,0x36,0x76,0x5C,0x01,0xDB,0x74,0x14,0x25,0xFE,0xED,0xB5,0xA0,0x88,0x0F, +0xDD,0x78,0xCA,0x2D,0x1F,0x07,0x97,0x30,0x01,0x2D,0x72,0x79,0xFA,0x46,0xD6,0x13, +0x2A,0xA8,0xB9,0xA6,0xAB,0x83,0x49,0x1D,0xE5,0xF2,0xEF,0xDD,0xE4,0x01,0x8E,0x18, +0x0A,0x8F,0x63,0x53,0x16,0x85,0x62,0xA9,0x0E,0x19,0x3A,0xCC,0xB5,0x66,0xA6,0xC2, +0x6B,0x74,0x07,0xE4,0x2B,0xE1,0x76,0x3E,0xB4,0x6D,0xD8,0xF6,0x44,0xE1,0x73,0x62, +0x1F,0x3B,0xC4,0xBE,0xA0,0x53,0x56,0x25,0x6C,0x51,0x09,0xF7,0xAA,0xAB,0xCA,0xBF, +0x76,0xFD,0x6D,0x9B,0xF3,0x9D,0xDB,0xBF,0x3D,0x66,0xBC,0x0C,0x56,0xAA,0xAF,0x98, +0x48,0x95,0x3A,0x4B,0xDF,0xA7,0x58,0x50,0xD9,0x38,0x75,0xA9,0x5B,0xEA,0x43,0x0C, +0x02,0xFF,0x99,0xEB,0xE8,0x6C,0x4D,0x70,0x5B,0x29,0x65,0x9C,0xDD,0xAA,0x5D,0xCC, +0xAF,0x01,0x31,0xEC,0x0C,0xEB,0xD2,0x8D,0xE8,0xEA,0x9C,0x7B,0xE6,0x6E,0xF7,0x27, +0x66,0x0C,0x1A,0x48,0xD7,0x6E,0x42,0xE3,0x3F,0xDE,0x21,0x3E,0x7B,0xE1,0x0D,0x70, +0xFB,0x63,0xAA,0xA8,0x6C,0x1A,0x54,0xB4,0x5C,0x25,0x7A,0xC9,0xA2,0xC9,0x8B,0x16, +0xA6,0xBB,0x2C,0x7E,0x17,0x5E,0x05,0x4D,0x58,0x6E,0x12,0x1D,0x01,0xEE,0x12,0x10, +0x0D,0xC6,0x32,0x7F,0x18,0xFF,0xFC,0xF4,0xFA,0xCD,0x6E,0x91,0xE8,0x36,0x49,0xBE, +0x1A,0x48,0x69,0x8B,0xC2,0x96,0x4D,0x1A,0x12,0xB2,0x69,0x17,0xC1,0x0A,0x90,0xD6, +0xFA,0x79,0x22,0x48,0xBF,0xBA,0x7B,0x69,0xF8,0x70,0xC7,0xFA,0x7A,0x37,0xD8,0xD8, +0x0D,0xD2,0x76,0x4F,0x57,0xFF,0x90,0xB7,0xE3,0x91,0xD2,0xDD,0xEF,0xC2,0x60,0xB7, +0x67,0x3A,0xDD,0xFE,0xAA,0x9C,0xF0,0xD4,0x8B,0x7F,0x72,0x22,0xCE,0xC6,0x9F,0x97, +0xB6,0xF8,0xAF,0x8A,0xA0,0x10,0xA8,0xD9,0xFB,0x18,0xC6,0xB6,0xB5,0x5C,0x52,0x3C, +0x89,0xB6,0x19,0x2A,0x73,0x01,0x0A,0x0F,0x03,0xB3,0x12,0x60,0xF2,0x7A,0x2F,0x81, +0xDB,0xA3,0x6E,0xFF,0x26,0x30,0x97,0xF5,0x8B,0xDD,0x89,0x57,0xB6,0xAD,0x3D,0xB3, +0xAF,0x2B,0xC5,0xB7,0x76,0x02,0xF0,0xA5,0xD6,0x2B,0x9A,0x86,0x14,0x2A,0x72,0xF6, +0xE3,0x33,0x8C,0x5D,0x09,0x4B,0x13,0xDF,0xBB,0x8C,0x74,0x13,0x52,0x4B,0x02,0x03, +0x01,0x00,0x01,0xA3,0x82,0x02,0x10,0x30,0x82,0x02,0x0C,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4E,0x0B,0xEF,0x1A,0xA4,0x40,0x5B,0xA5,0x17, +0x69,0x87,0x30,0xCA,0x34,0x68,0x43,0xD0,0x41,0xAE,0xF2,0x30,0x1F,0x06,0x03,0x55, +0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x4E,0x0B,0xEF,0x1A,0xA4,0x40,0x5B,0xA5, +0x17,0x69,0x87,0x30,0xCA,0x34,0x68,0x43,0xD0,0x41,0xAE,0xF2,0x30,0x82,0x01,0x5A, +0x06,0x03,0x55,0x1D,0x20,0x04,0x82,0x01,0x51,0x30,0x82,0x01,0x4D,0x30,0x82,0x01, +0x49,0x06,0x0B,0x2B,0x06,0x01,0x04,0x01,0x81,0xB5,0x37,0x01,0x01,0x01,0x30,0x82, +0x01,0x38,0x30,0x2E,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x22, +0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74, +0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x70,0x6F,0x6C,0x69,0x63,0x79,0x2E,0x70, +0x64,0x66,0x30,0x34,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x28, +0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74, +0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x69,0x6E,0x74,0x65,0x72,0x6D,0x65,0x64, +0x69,0x61,0x74,0x65,0x2E,0x70,0x64,0x66,0x30,0x81,0xCF,0x06,0x08,0x2B,0x06,0x01, +0x05,0x05,0x07,0x02,0x02,0x30,0x81,0xC2,0x30,0x27,0x16,0x20,0x53,0x74,0x61,0x72, +0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69,0x61,0x6C,0x20,0x28,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x29,0x20,0x4C,0x74,0x64,0x2E,0x30,0x03,0x02,0x01, +0x01,0x1A,0x81,0x96,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x20,0x4C,0x69,0x61,0x62, +0x69,0x6C,0x69,0x74,0x79,0x2C,0x20,0x72,0x65,0x61,0x64,0x20,0x74,0x68,0x65,0x20, +0x73,0x65,0x63,0x74,0x69,0x6F,0x6E,0x20,0x2A,0x4C,0x65,0x67,0x61,0x6C,0x20,0x4C, +0x69,0x6D,0x69,0x74,0x61,0x74,0x69,0x6F,0x6E,0x73,0x2A,0x20,0x6F,0x66,0x20,0x74, +0x68,0x65,0x20,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x61,0x76,0x61,0x69,0x6C, +0x61,0x62,0x6C,0x65,0x20,0x61,0x74,0x20,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77, +0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74,0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F, +0x70,0x6F,0x6C,0x69,0x63,0x79,0x2E,0x70,0x64,0x66,0x30,0x11,0x06,0x09,0x60,0x86, +0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x38,0x06, +0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x0D,0x04,0x2B,0x16,0x29,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x46,0x72,0x65,0x65,0x20,0x53,0x53,0x4C,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75, +0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x8E,0x8F,0xE7,0xDC,0x94, +0x79,0x7C,0xF1,0x85,0x7F,0x9F,0x49,0x6F,0x6B,0xCA,0x5D,0xFB,0x8C,0xFE,0x04,0xC5, +0xC1,0x62,0xD1,0x7D,0x42,0x8A,0xBC,0x53,0xB7,0x94,0x03,0x66,0x30,0x3F,0xB1,0xE7, +0x0A,0xA7,0x50,0x20,0x55,0x25,0x7F,0x76,0x7A,0x14,0x0D,0xEB,0x04,0x0E,0x40,0xE6, +0x3E,0xD8,0x88,0xAB,0x07,0x27,0x83,0xA9,0x75,0xA6,0x37,0x73,0xC7,0xFD,0x4B,0xD2, +0x4D,0xAD,0x17,0x40,0xC8,0x46,0xBE,0x3B,0x7F,0x51,0xFC,0xC3,0xB6,0x05,0x31,0xDC, +0xCD,0x85,0x22,0x4E,0x71,0xB7,0xF2,0x71,0x5E,0xB0,0x1A,0xC6,0xBA,0x93,0x8B,0x78, +0x92,0x4A,0x85,0xF8,0x78,0x0F,0x83,0xFE,0x2F,0xAD,0x2C,0xF7,0xE4,0xA4,0xBB,0x2D, +0xD0,0xE7,0x0D,0x3A,0xB8,0x3E,0xCE,0xF6,0x78,0xF6,0xAE,0x47,0x24,0xCA,0xA3,0x35, +0x36,0xCE,0xC7,0xC6,0x87,0x98,0xDA,0xEC,0xFB,0xE9,0xB2,0xCE,0x27,0x9B,0x88,0xC3, +0x04,0xA1,0xF6,0x0B,0x59,0x68,0xAF,0xC9,0xDB,0x10,0x0F,0x4D,0xF6,0x64,0x63,0x5C, +0xA5,0x12,0x6F,0x92,0xB2,0x93,0x94,0xC7,0x88,0x17,0x0E,0x93,0xB6,0x7E,0x62,0x8B, +0x90,0x7F,0xAB,0x4E,0x9F,0xFC,0xE3,0x75,0x14,0x4F,0x2A,0x32,0xDF,0x5B,0x0D,0xE0, +0xF5,0x7B,0x93,0x0D,0xAB,0xA1,0xCF,0x87,0xE1,0xA5,0x04,0x45,0xE8,0x3C,0x12,0xA5, +0x09,0xC5,0xB0,0xD1,0xB7,0x53,0xF3,0x60,0x14,0xBA,0x85,0x69,0x6A,0x21,0x7C,0x1F, +0x75,0x61,0x17,0x20,0x17,0x7B,0x6C,0x3B,0x41,0x29,0x5C,0xE1,0xAC,0x5A,0xD1,0xCD, +0x8C,0x9B,0xEB,0x60,0x1D,0x19,0xEC,0xF7,0xE5,0xB0,0xDA,0xF9,0x79,0x18,0xA5,0x45, +0x3F,0x49,0x43,0x57,0xD2,0xDD,0x24,0xD5,0x2C,0xA3,0xFD,0x91,0x8D,0x27,0xB5,0xE5, +0xEB,0x14,0x06,0x9A,0x4C,0x7B,0x21,0xBB,0x3A,0xAD,0x30,0x06,0x18,0xC0,0xD8,0xC1, +0x6B,0x2C,0x7F,0x59,0x5C,0x5D,0x91,0xB1,0x70,0x22,0x57,0xEB,0x8A,0x6B,0x48,0x4A, +0xD5,0x0F,0x29,0xEC,0xC6,0x40,0xC0,0x2F,0x88,0x4C,0x68,0x01,0x17,0x77,0xF4,0x24, +0x19,0x4F,0xBD,0xFA,0xE1,0xB2,0x20,0x21,0x4B,0xDD,0x1A,0xD8,0x29,0x7D,0xAA,0xB8, +0xDE,0x54,0xEC,0x21,0x55,0x80,0x6C,0x1E,0xF5,0x30,0xC8,0xA3,0x10,0xE5,0xB2,0xE6, +0x2A,0x14,0x31,0xC3,0x85,0x2D,0x8C,0x98,0xB1,0x86,0x5A,0x4F,0x89,0x59,0x2D,0xB9, +0xC7,0xF7,0x1C,0xC8,0x8A,0x7F,0xC0,0x9D,0x05,0x4A,0xE6,0x42,0x4F,0x62,0xA3,0x6D, +0x29,0xA4,0x1F,0x85,0xAB,0xDB,0xE5,0x81,0xC8,0xAD,0x2A,0x3D,0x4C,0x5D,0x5B,0x84, +0x26,0x71,0xC4,0x85,0x5E,0x71,0x24,0xCA,0xA5,0x1B,0x6C,0xD8,0x61,0xD3,0x1A,0xE0, +0x54,0xDB,0xCE,0xBA,0xA9,0x32,0xB5,0x22,0xF6,0x73,0x41,0x09,0x5D,0xB8,0x17,0x5D, +0x0E,0x0F,0x99,0x90,0xD6,0x47,0xDA,0x6F,0x0A,0x3A,0x62,0x28,0x14,0x67,0x82,0xD9, +0xF1,0xD0,0x80,0x59,0x9B,0xCB,0x31,0xD8,0x9B,0x0F,0x8C,0x77,0x4E,0xB5,0x68,0x8A, +0xF2,0x6C,0xF6,0x24,0x0E,0x2D,0x6C,0x70,0xC5,0x73,0xD1,0xDE,0x14,0xD0,0x71,0x8F, +0xB6,0xD3,0x7B,0x02,0xF6,0xE3,0xB8,0xD4,0x09,0x6E,0x6B,0x9E,0x75,0x84,0x39,0xE6, +0x7F,0x25,0xA5,0xF2,0x48,0x00,0xC0,0xA4,0x01,0xDA,0x3F, +}; + + +/* subject:/C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */ +/* issuer :/C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */ + + +const unsigned char StartCom_Certification_Authority_G2_certificate[1383]={ +0x30,0x82,0x05,0x63,0x30,0x82,0x03,0x4B,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x3B, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F, +0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2C,0x30,0x2A,0x06,0x03,0x55,0x04,0x03,0x13, +0x23,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66, +0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x31,0x30,0x30,0x31,0x30,0x31,0x30,0x31, +0x30,0x30,0x30,0x31,0x5A,0x17,0x0D,0x33,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35, +0x39,0x30,0x31,0x5A,0x30,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x49,0x4C,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2C,0x30,0x2A,0x06, +0x03,0x55,0x04,0x03,0x13,0x23,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x47,0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F, +0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xB6,0x89,0x36,0x5B,0x07,0xB7, +0x20,0x36,0xBD,0x82,0xBB,0xE1,0x16,0x20,0x03,0x95,0x7A,0xAF,0x0E,0xA3,0x55,0xC9, +0x25,0x99,0x4A,0xC5,0xD0,0x56,0x41,0x87,0x90,0x4D,0x21,0x60,0xA4,0x14,0x87,0x3B, +0xCD,0xFD,0xB2,0x3E,0xB4,0x67,0x03,0x6A,0xED,0xE1,0x0F,0x4B,0xC0,0x91,0x85,0x70, +0x45,0xE0,0x42,0x9E,0xDE,0x29,0x23,0xD4,0x01,0x0D,0xA0,0x10,0x79,0xB8,0xDB,0x03, +0xBD,0xF3,0xA9,0x2F,0xD1,0xC6,0xE0,0x0F,0xCB,0x9E,0x8A,0x14,0x0A,0xB8,0xBD,0xF6, +0x56,0x62,0xF1,0xC5,0x72,0xB6,0x32,0x25,0xD9,0xB2,0xF3,0xBD,0x65,0xC5,0x0D,0x2C, +0x6E,0xD5,0x92,0x6F,0x18,0x8B,0x00,0x41,0x14,0x82,0x6F,0x40,0x20,0x26,0x7A,0x28, +0x0F,0xF5,0x1E,0x7F,0x27,0xF7,0x94,0xB1,0x37,0x3D,0xB7,0xC7,0x91,0xF7,0xE2,0x01, +0xEC,0xFD,0x94,0x89,0xE1,0xCC,0x6E,0xD3,0x36,0xD6,0x0A,0x19,0x79,0xAE,0xD7,0x34, +0x82,0x65,0xFF,0x7C,0x42,0xBB,0xB6,0xDD,0x0B,0xA6,0x34,0xAF,0x4B,0x60,0xFE,0x7F, +0x43,0x49,0x06,0x8B,0x8C,0x43,0xB8,0x56,0xF2,0xD9,0x7F,0x21,0x43,0x17,0xEA,0xA7, +0x48,0x95,0x01,0x75,0x75,0xEA,0x2B,0xA5,0x43,0x95,0xEA,0x15,0x84,0x9D,0x08,0x8D, +0x26,0x6E,0x55,0x9B,0xAB,0xDC,0xD2,0x39,0xD2,0x31,0x1D,0x60,0xE2,0xAC,0xCC,0x56, +0x45,0x24,0xF5,0x1C,0x54,0xAB,0xEE,0x86,0xDD,0x96,0x32,0x85,0xF8,0x4C,0x4F,0xE8, +0x95,0x76,0xB6,0x05,0xDD,0x36,0x23,0x67,0xBC,0xFF,0x15,0xE2,0xCA,0x3B,0xE6,0xA6, +0xEC,0x3B,0xEC,0x26,0x11,0x34,0x48,0x8D,0xF6,0x80,0x2B,0x1A,0x23,0x02,0xEB,0x8A, +0x1C,0x3A,0x76,0x2A,0x7B,0x56,0x16,0x1C,0x72,0x2A,0xB3,0xAA,0xE3,0x60,0xA5,0x00, +0x9F,0x04,0x9B,0xE2,0x6F,0x1E,0x14,0x58,0x5B,0xA5,0x6C,0x8B,0x58,0x3C,0xC3,0xBA, +0x4E,0x3A,0x5C,0xF7,0xE1,0x96,0x2B,0x3E,0xEF,0x07,0xBC,0xA4,0xE5,0x5D,0xCC,0x4D, +0x9F,0x0D,0xE1,0xDC,0xAA,0xBB,0xE1,0x6E,0x1A,0xEC,0x8F,0xE1,0xB6,0x4C,0x4D,0x79, +0x72,0x5D,0x17,0x35,0x0B,0x1D,0xD7,0xC1,0x47,0xDA,0x96,0x24,0xE0,0xD0,0x72,0xA8, +0x5A,0x5F,0x66,0x2D,0x10,0xDC,0x2F,0x2A,0x13,0xAE,0x26,0xFE,0x0A,0x1C,0x19,0xCC, +0xD0,0x3E,0x0B,0x9C,0xC8,0x09,0x2E,0xF9,0x5B,0x96,0x7A,0x47,0x9C,0xE9,0x7A,0xF3, +0x05,0x50,0x74,0x95,0x73,0x9E,0x30,0x09,0xF3,0x97,0x82,0x5E,0xE6,0x8F,0x39,0x08, +0x1E,0x59,0xE5,0x35,0x14,0x42,0x13,0xFF,0x00,0x9C,0xF7,0xBE,0xAA,0x50,0xCF,0xE2, +0x51,0x48,0xD7,0xB8,0x6F,0xAF,0xF8,0x4E,0x7E,0x33,0x98,0x92,0x14,0x62,0x3A,0x75, +0x63,0xCF,0x7B,0xFA,0xDE,0x82,0x3B,0xA9,0xBB,0x39,0xE2,0xC4,0xBD,0x2C,0x00,0x0E, +0xC8,0x17,0xAC,0x13,0xEF,0x4D,0x25,0x8E,0xD8,0xB3,0x90,0x2F,0xA9,0xDA,0x29,0x7D, +0x1D,0xAF,0x74,0x3A,0xB2,0x27,0xC0,0xC1,0x1E,0x3E,0x75,0xA3,0x16,0xA9,0xAF,0x7A, +0x22,0x5D,0x9F,0x13,0x1A,0xCF,0xA7,0xA0,0xEB,0xE3,0x86,0x0A,0xD3,0xFD,0xE6,0x96, +0x95,0xD7,0x23,0xC8,0x37,0xDD,0xC4,0x7C,0xAA,0x36,0xAC,0x98,0x1A,0x12,0xB1,0xE0, +0x4E,0xE8,0xB1,0x3B,0xF5,0xD6,0x6F,0xF1,0x30,0xD7,0x02,0x03,0x01,0x00,0x01,0xA3, +0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4B, +0xC5,0xB4,0x40,0x6B,0xAD,0x1C,0xB3,0xA5,0x1C,0x65,0x6E,0x46,0x36,0x89,0x87,0x05, +0x0C,0x0E,0xB6,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B, +0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x73,0x57,0x3F,0x2C,0xD5,0x95,0x32,0x7E,0x37, +0xDB,0x96,0x92,0xEB,0x19,0x5E,0x7E,0x53,0xE7,0x41,0xEC,0x11,0xB6,0x47,0xEF,0xB5, +0xDE,0xED,0x74,0x5C,0xC5,0xF1,0x8E,0x49,0xE0,0xFC,0x6E,0x99,0x13,0xCD,0x9F,0x8A, +0xDA,0xCD,0x3A,0x0A,0xD8,0x3A,0x5A,0x09,0x3F,0x5F,0x34,0xD0,0x2F,0x03,0xD2,0x66, +0x1D,0x1A,0xBD,0x9C,0x90,0x37,0xC8,0x0C,0x8E,0x07,0x5A,0x94,0x45,0x46,0x2A,0xE6, +0xBE,0x7A,0xDA,0xA1,0xA9,0xA4,0x69,0x12,0x92,0xB0,0x7D,0x36,0xD4,0x44,0x87,0xD7, +0x51,0xF1,0x29,0x63,0xD6,0x75,0xCD,0x16,0xE4,0x27,0x89,0x1D,0xF8,0xC2,0x32,0x48, +0xFD,0xDB,0x99,0xD0,0x8F,0x5F,0x54,0x74,0xCC,0xAC,0x67,0x34,0x11,0x62,0xD9,0x0C, +0x0A,0x37,0x87,0xD1,0xA3,0x17,0x48,0x8E,0xD2,0x17,0x1D,0xF6,0xD7,0xFD,0xDB,0x65, +0xEB,0xFD,0xA8,0xD4,0xF5,0xD6,0x4F,0xA4,0x5B,0x75,0xE8,0xC5,0xD2,0x60,0xB2,0xDB, +0x09,0x7E,0x25,0x8B,0x7B,0xBA,0x52,0x92,0x9E,0x3E,0xE8,0xC5,0x77,0xA1,0x3C,0xE0, +0x4A,0x73,0x6B,0x61,0xCF,0x86,0xDC,0x43,0xFF,0xFF,0x21,0xFE,0x23,0x5D,0x24,0x4A, +0xF5,0xD3,0x6D,0x0F,0x62,0x04,0x05,0x57,0x82,0xDA,0x6E,0xA4,0x33,0x25,0x79,0x4B, +0x2E,0x54,0x19,0x8B,0xCC,0x2C,0x3D,0x30,0xE9,0xD1,0x06,0xFF,0xE8,0x32,0x46,0xBE, +0xB5,0x33,0x76,0x77,0xA8,0x01,0x5D,0x96,0xC1,0xC1,0xD5,0xBE,0xAE,0x25,0xC0,0xC9, +0x1E,0x0A,0x09,0x20,0x88,0xA1,0x0E,0xC9,0xF3,0x6F,0x4D,0x82,0x54,0x00,0x20,0xA7, +0xD2,0x8F,0xE4,0x39,0x54,0x17,0x2E,0x8D,0x1E,0xB8,0x1B,0xBB,0x1B,0xBD,0x9A,0x4E, +0x3B,0x10,0x34,0xDC,0x9C,0x88,0x53,0xEF,0xA2,0x31,0x5B,0x58,0x4F,0x91,0x62,0xC8, +0xC2,0x9A,0x9A,0xCD,0x15,0x5D,0x38,0xA9,0xD6,0xBE,0xF8,0x13,0xB5,0x9F,0x12,0x69, +0xF2,0x50,0x62,0xAC,0xFB,0x17,0x37,0xF4,0xEE,0xB8,0x75,0x67,0x60,0x10,0xFB,0x83, +0x50,0xF9,0x44,0xB5,0x75,0x9C,0x40,0x17,0xB2,0xFE,0xFD,0x79,0x5D,0x6E,0x58,0x58, +0x5F,0x30,0xFC,0x00,0xAE,0xAF,0x33,0xC1,0x0E,0x4E,0x6C,0xBA,0xA7,0xA6,0xA1,0x7F, +0x32,0xDB,0x38,0xE0,0xB1,0x72,0x17,0x0A,0x2B,0x91,0xEC,0x6A,0x63,0x26,0xED,0x89, +0xD4,0x78,0xCC,0x74,0x1E,0x05,0xF8,0x6B,0xFE,0x8C,0x6A,0x76,0x39,0x29,0xAE,0x65, +0x23,0x12,0x95,0x08,0x22,0x1C,0x97,0xCE,0x5B,0x06,0xEE,0x0C,0xE2,0xBB,0xBC,0x1F, +0x44,0x93,0xF6,0xD8,0x38,0x45,0x05,0x21,0xED,0xE4,0xAD,0xAB,0x12,0xB6,0x03,0xA4, +0x42,0x2E,0x2D,0xC4,0x09,0x3A,0x03,0x67,0x69,0x84,0x9A,0xE1,0x59,0x90,0x8A,0x28, +0x85,0xD5,0x5D,0x74,0xB1,0xD1,0x0E,0x20,0x58,0x9B,0x13,0xA5,0xB0,0x63,0xA6,0xED, +0x7B,0x47,0xFD,0x45,0x55,0x30,0xA4,0xEE,0x9A,0xD4,0xE6,0xE2,0x87,0xEF,0x98,0xC9, +0x32,0x82,0x11,0x29,0x22,0xBC,0x00,0x0A,0x31,0x5E,0x2D,0x0F,0xC0,0x8E,0xE9,0x6B, +0xB2,0x8F,0x2E,0x06,0xD8,0xD1,0x91,0xC7,0xC6,0x12,0xF4,0x4C,0xFD,0x30,0x17,0xC3, +0xC1,0xDA,0x38,0x5B,0xE3,0xA9,0xEA,0xE6,0xA1,0xBA,0x79,0xEF,0x73,0xD8,0xB6,0x53, +0x57,0x2D,0xF6,0xD0,0xE1,0xD7,0x48, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 2 CA/CN=TC TrustCenter Class 2 CA II */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 2 CA/CN=TC TrustCenter Class 2 CA II */ + + +const unsigned char TC_TrustCenter_Class_2_CA_II_certificate[1198]={ +0x30,0x82,0x04,0xAA,0x30,0x82,0x03,0x92,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x2E, +0x6A,0x00,0x01,0x00,0x02,0x1F,0xD7,0x52,0x21,0x2C,0x11,0x5C,0x3B,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x76,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55, +0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x41,0x31,0x25,0x30, +0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74, +0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43, +0x41,0x20,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x31,0x31,0x32,0x31,0x34, +0x33,0x38,0x34,0x33,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31,0x32,0x32,0x35, +0x39,0x35,0x39,0x5A,0x30,0x76,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43, +0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62, +0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54, +0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73, +0x20,0x32,0x20,0x43,0x41,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C, +0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43, +0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x41,0x20,0x49,0x49,0x30,0x82,0x01,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAB,0x80,0x87, +0x9B,0x8E,0xF0,0xC3,0x7C,0x87,0xD7,0xE8,0x24,0x82,0x11,0xB3,0x3C,0xDD,0x43,0x62, +0xEE,0xF8,0xC3,0x45,0xDA,0xE8,0xE1,0xA0,0x5F,0xD1,0x2A,0xB2,0xEA,0x93,0x68,0xDF, +0xB4,0xC8,0xD6,0x43,0xE9,0xC4,0x75,0x59,0x7F,0xFC,0xE1,0x1D,0xF8,0x31,0x70,0x23, +0x1B,0x88,0x9E,0x27,0xB9,0x7B,0xFD,0x3A,0xD2,0xC9,0xA9,0xE9,0x14,0x2F,0x90,0xBE, +0x03,0x52,0xC1,0x49,0xCD,0xF6,0xFD,0xE4,0x08,0x66,0x0B,0x57,0x8A,0xA2,0x42,0xA0, +0xB8,0xD5,0x7F,0x69,0x5C,0x90,0x32,0xB2,0x97,0x0D,0xCA,0x4A,0xDC,0x46,0x3E,0x02, +0x55,0x89,0x53,0xE3,0x1A,0x5A,0xCB,0x36,0xC6,0x07,0x56,0xF7,0x8C,0xCF,0x11,0xF4, +0x4C,0xBB,0x30,0x70,0x04,0x95,0xA5,0xF6,0x39,0x8C,0xFD,0x73,0x81,0x08,0x7D,0x89, +0x5E,0x32,0x1E,0x22,0xA9,0x22,0x45,0x4B,0xB0,0x66,0x2E,0x30,0xCC,0x9F,0x65,0xFD, +0xFC,0xCB,0x81,0xA9,0xF1,0xE0,0x3B,0xAF,0xA3,0x86,0xD1,0x89,0xEA,0xC4,0x45,0x79, +0x50,0x5D,0xAE,0xE9,0x21,0x74,0x92,0x4D,0x8B,0x59,0x82,0x8F,0x94,0xE3,0xE9,0x4A, +0xF1,0xE7,0x49,0xB0,0x14,0xE3,0xF5,0x62,0xCB,0xD5,0x72,0xBD,0x1F,0xB9,0xD2,0x9F, +0xA0,0xCD,0xA8,0xFA,0x01,0xC8,0xD9,0x0D,0xDF,0xDA,0xFC,0x47,0x9D,0xB3,0xC8,0x54, +0xDF,0x49,0x4A,0xF1,0x21,0xA9,0xFE,0x18,0x4E,0xEE,0x48,0xD4,0x19,0xBB,0xEF,0x7D, +0xE4,0xE2,0x9D,0xCB,0x5B,0xB6,0x6E,0xFF,0xE3,0xCD,0x5A,0xE7,0x74,0x82,0x05,0xBA, +0x80,0x25,0x38,0xCB,0xE4,0x69,0x9E,0xAF,0x41,0xAA,0x1A,0x84,0xF5,0x02,0x03,0x01, +0x00,0x01,0xA3,0x82,0x01,0x34,0x30,0x82,0x01,0x30,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0xE3,0xAB,0x54,0x4C,0x80,0xA1,0xDB,0x56,0x43,0xB7, +0x91,0x4A,0xCB,0xF3,0x82,0x7A,0x13,0x5C,0x08,0xAB,0x30,0x81,0xED,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x81,0xE5,0x30,0x81,0xE2,0x30,0x81,0xDF,0xA0,0x81,0xDC,0xA0,0x81, +0xD9,0x86,0x35,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72, +0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,0x72,0x2E,0x64,0x65,0x2F,0x63,0x72,0x6C, +0x2F,0x76,0x32,0x2F,0x74,0x63,0x5F,0x63,0x6C,0x61,0x73,0x73,0x5F,0x32,0x5F,0x63, +0x61,0x5F,0x49,0x49,0x2E,0x63,0x72,0x6C,0x86,0x81,0x9F,0x6C,0x64,0x61,0x70,0x3A, +0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65, +0x72,0x2E,0x64,0x65,0x2F,0x43,0x4E,0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x25,0x32,0x30,0x43,0x6C,0x61,0x73,0x73, +0x25,0x32,0x30,0x32,0x25,0x32,0x30,0x43,0x41,0x25,0x32,0x30,0x49,0x49,0x2C,0x4F, +0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65, +0x72,0x25,0x32,0x30,0x47,0x6D,0x62,0x48,0x2C,0x4F,0x55,0x3D,0x72,0x6F,0x6F,0x74, +0x63,0x65,0x72,0x74,0x73,0x2C,0x44,0x43,0x3D,0x74,0x72,0x75,0x73,0x74,0x63,0x65, +0x6E,0x74,0x65,0x72,0x2C,0x44,0x43,0x3D,0x64,0x65,0x3F,0x63,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x52,0x65,0x76,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x4C,0x69,0x73,0x74,0x3F,0x62,0x61,0x73,0x65,0x3F,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x8C,0xD7, +0xDF,0x7E,0xEE,0x1B,0x80,0x10,0xB3,0x83,0xF5,0xDB,0x11,0xEA,0x6B,0x4B,0xA8,0x92, +0x18,0xD9,0xF7,0x07,0x39,0xF5,0x2C,0xBE,0x06,0x75,0x7A,0x68,0x53,0x15,0x1C,0xEA, +0x4A,0xED,0x5E,0xFC,0x23,0xB2,0x13,0xA0,0xD3,0x09,0xFF,0xF6,0xF6,0x2E,0x6B,0x41, +0x71,0x79,0xCD,0xE2,0x6D,0xFD,0xAE,0x59,0x6B,0x85,0x1D,0xB8,0x4E,0x22,0x9A,0xED, +0x66,0x39,0x6E,0x4B,0x94,0xE6,0x55,0xFC,0x0B,0x1B,0x8B,0x77,0xC1,0x53,0x13,0x66, +0x89,0xD9,0x28,0xD6,0x8B,0xF3,0x45,0x4A,0x63,0xB7,0xFD,0x7B,0x0B,0x61,0x5D,0xB8, +0x6D,0xBE,0xC3,0xDC,0x5B,0x79,0xD2,0xED,0x86,0xE5,0xA2,0x4D,0xBE,0x5E,0x74,0x7C, +0x6A,0xED,0x16,0x38,0x1F,0x7F,0x58,0x81,0x5A,0x1A,0xEB,0x32,0x88,0x2D,0xB2,0xF3, +0x39,0x77,0x80,0xAF,0x5E,0xB6,0x61,0x75,0x29,0xDB,0x23,0x4D,0x88,0xCA,0x50,0x28, +0xCB,0x85,0xD2,0xD3,0x10,0xA2,0x59,0x6E,0xD3,0x93,0x54,0x00,0x7A,0xA2,0x46,0x95, +0x86,0x05,0x9C,0xA9,0x19,0x98,0xE5,0x31,0x72,0x0C,0x00,0xE2,0x67,0xD9,0x40,0xE0, +0x24,0x33,0x7B,0x6F,0x2C,0xB9,0x5C,0xAB,0x65,0x9D,0x2C,0xAC,0x76,0xEA,0x35,0x99, +0xF5,0x97,0xB9,0x0F,0x24,0xEC,0xC7,0x76,0x21,0x28,0x65,0xAE,0x57,0xE8,0x07,0x88, +0x75,0x4A,0x56,0xA0,0xD2,0x05,0x3A,0xA4,0xE6,0x8D,0x92,0x88,0x2C,0xF3,0xF2,0xE1, +0xC1,0xC6,0x61,0xDB,0x41,0xC5,0xC7,0x9B,0xF7,0x0E,0x1A,0x51,0x45,0xC2,0x61,0x6B, +0xDC,0x64,0x27,0x17,0x8C,0x5A,0xB7,0xDA,0x74,0x28,0xCD,0x97,0xE4,0xBD, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 3 CA/CN=TC TrustCenter Class 3 CA II */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 3 CA/CN=TC TrustCenter Class 3 CA II */ + + +const unsigned char TC_TrustCenter_Class_3_CA_II_certificate[1198]={ +0x30,0x82,0x04,0xAA,0x30,0x82,0x03,0x92,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x4A, +0x47,0x00,0x01,0x00,0x02,0xE5,0xA0,0x5D,0xD6,0x3F,0x00,0x51,0xBF,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x76,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55, +0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43,0x41,0x31,0x25,0x30, +0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74, +0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43, +0x41,0x20,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x31,0x31,0x32,0x31,0x34, +0x34,0x31,0x35,0x37,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31,0x32,0x32,0x35, +0x39,0x35,0x39,0x5A,0x30,0x76,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43, +0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62, +0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54, +0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73, +0x20,0x33,0x20,0x43,0x41,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C, +0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43, +0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43,0x41,0x20,0x49,0x49,0x30,0x82,0x01,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xB4,0xE0,0xBB, +0x51,0xBB,0x39,0x5C,0x8B,0x04,0xC5,0x4C,0x79,0x1C,0x23,0x86,0x31,0x10,0x63,0x43, +0x55,0x27,0x3F,0xC6,0x45,0xC7,0xA4,0x3D,0xEC,0x09,0x0D,0x1A,0x1E,0x20,0xC2,0x56, +0x1E,0xDE,0x1B,0x37,0x07,0x30,0x22,0x2F,0x6F,0xF1,0x06,0xF1,0xAB,0xAD,0xD6,0xC8, +0xAB,0x61,0xA3,0x2F,0x43,0xC4,0xB0,0xB2,0x2D,0xFC,0xC3,0x96,0x69,0x7B,0x7E,0x8A, +0xE4,0xCC,0xC0,0x39,0x12,0x90,0x42,0x60,0xC9,0xCC,0x35,0x68,0xEE,0xDA,0x5F,0x90, +0x56,0x5F,0xCD,0x1C,0x4D,0x5B,0x58,0x49,0xEB,0x0E,0x01,0x4F,0x64,0xFA,0x2C,0x3C, +0x89,0x58,0xD8,0x2F,0x2E,0xE2,0xB0,0x68,0xE9,0x22,0x3B,0x75,0x89,0xD6,0x44,0x1A, +0x65,0xF2,0x1B,0x97,0x26,0x1D,0x28,0x6D,0xAC,0xE8,0xBD,0x59,0x1D,0x2B,0x24,0xF6, +0xD6,0x84,0x03,0x66,0x88,0x24,0x00,0x78,0x60,0xF1,0xF8,0xAB,0xFE,0x02,0xB2,0x6B, +0xFB,0x22,0xFB,0x35,0xE6,0x16,0xD1,0xAD,0xF6,0x2E,0x12,0xE4,0xFA,0x35,0x6A,0xE5, +0x19,0xB9,0x5D,0xDB,0x3B,0x1E,0x1A,0xFB,0xD3,0xFF,0x15,0x14,0x08,0xD8,0x09,0x6A, +0xBA,0x45,0x9D,0x14,0x79,0x60,0x7D,0xAF,0x40,0x8A,0x07,0x73,0xB3,0x93,0x96,0xD3, +0x74,0x34,0x8D,0x3A,0x37,0x29,0xDE,0x5C,0xEC,0xF5,0xEE,0x2E,0x31,0xC2,0x20,0xDC, +0xBE,0xF1,0x4F,0x7F,0x23,0x52,0xD9,0x5B,0xE2,0x64,0xD9,0x9C,0xAA,0x07,0x08,0xB5, +0x45,0xBD,0xD1,0xD0,0x31,0xC1,0xAB,0x54,0x9F,0xA9,0xD2,0xC3,0x62,0x60,0x03,0xF1, +0xBB,0x39,0x4A,0x92,0x4A,0x3D,0x0A,0xB9,0x9D,0xC5,0xA0,0xFE,0x37,0x02,0x03,0x01, +0x00,0x01,0xA3,0x82,0x01,0x34,0x30,0x82,0x01,0x30,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0xD4,0xA2,0xFC,0x9F,0xB3,0xC3,0xD8,0x03,0xD3,0x57, +0x5C,0x07,0xA4,0xD0,0x24,0xA7,0xC0,0xF2,0x00,0xD4,0x30,0x81,0xED,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x81,0xE5,0x30,0x81,0xE2,0x30,0x81,0xDF,0xA0,0x81,0xDC,0xA0,0x81, +0xD9,0x86,0x35,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72, +0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,0x72,0x2E,0x64,0x65,0x2F,0x63,0x72,0x6C, +0x2F,0x76,0x32,0x2F,0x74,0x63,0x5F,0x63,0x6C,0x61,0x73,0x73,0x5F,0x33,0x5F,0x63, +0x61,0x5F,0x49,0x49,0x2E,0x63,0x72,0x6C,0x86,0x81,0x9F,0x6C,0x64,0x61,0x70,0x3A, +0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65, +0x72,0x2E,0x64,0x65,0x2F,0x43,0x4E,0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x25,0x32,0x30,0x43,0x6C,0x61,0x73,0x73, +0x25,0x32,0x30,0x33,0x25,0x32,0x30,0x43,0x41,0x25,0x32,0x30,0x49,0x49,0x2C,0x4F, +0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65, +0x72,0x25,0x32,0x30,0x47,0x6D,0x62,0x48,0x2C,0x4F,0x55,0x3D,0x72,0x6F,0x6F,0x74, +0x63,0x65,0x72,0x74,0x73,0x2C,0x44,0x43,0x3D,0x74,0x72,0x75,0x73,0x74,0x63,0x65, +0x6E,0x74,0x65,0x72,0x2C,0x44,0x43,0x3D,0x64,0x65,0x3F,0x63,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x52,0x65,0x76,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x4C,0x69,0x73,0x74,0x3F,0x62,0x61,0x73,0x65,0x3F,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x36,0x60, +0xE4,0x70,0xF7,0x06,0x20,0x43,0xD9,0x23,0x1A,0x42,0xF2,0xF8,0xA3,0xB2,0xB9,0x4D, +0x8A,0xB4,0xF3,0xC2,0x9A,0x55,0x31,0x7C,0xC4,0x3B,0x67,0x9A,0xB4,0xDF,0x4D,0x0E, +0x8A,0x93,0x4A,0x17,0x8B,0x1B,0x8D,0xCA,0x89,0xE1,0xCF,0x3A,0x1E,0xAC,0x1D,0xF1, +0x9C,0x32,0xB4,0x8E,0x59,0x76,0xA2,0x41,0x85,0x25,0x37,0xA0,0x13,0xD0,0xF5,0x7C, +0x4E,0xD5,0xEA,0x96,0xE2,0x6E,0x72,0xC1,0xBB,0x2A,0xFE,0x6C,0x6E,0xF8,0x91,0x98, +0x46,0xFC,0xC9,0x1B,0x57,0x5B,0xEA,0xC8,0x1A,0x3B,0x3F,0xB0,0x51,0x98,0x3C,0x07, +0xDA,0x2C,0x59,0x01,0xDA,0x8B,0x44,0xE8,0xE1,0x74,0xFD,0xA7,0x68,0xDD,0x54,0xBA, +0x83,0x46,0xEC,0xC8,0x46,0xB5,0xF8,0xAF,0x97,0xC0,0x3B,0x09,0x1C,0x8F,0xCE,0x72, +0x96,0x3D,0x33,0x56,0x70,0xBC,0x96,0xCB,0xD8,0xD5,0x7D,0x20,0x9A,0x83,0x9F,0x1A, +0xDC,0x39,0xF1,0xC5,0x72,0xA3,0x11,0x03,0xFD,0x3B,0x42,0x52,0x29,0xDB,0xE8,0x01, +0xF7,0x9B,0x5E,0x8C,0xD6,0x8D,0x86,0x4E,0x19,0xFA,0xBC,0x1C,0xBE,0xC5,0x21,0xA5, +0x87,0x9E,0x78,0x2E,0x36,0xDB,0x09,0x71,0xA3,0x72,0x34,0xF8,0x6C,0xE3,0x06,0x09, +0xF2,0x5E,0x56,0xA5,0xD3,0xDD,0x98,0xFA,0xD4,0xE6,0x06,0xF4,0xF0,0xB6,0x20,0x63, +0x4B,0xEA,0x29,0xBD,0xAA,0x82,0x66,0x1E,0xFB,0x81,0xAA,0xA7,0x37,0xAD,0x13,0x18, +0xE6,0x92,0xC3,0x81,0xC1,0x33,0xBB,0x88,0x1E,0xA1,0xE7,0xE2,0xB4,0xBD,0x31,0x6C, +0x0E,0x51,0x3D,0x6F,0xFB,0x96,0x56,0x80,0xE2,0x36,0x17,0xD1,0xDC,0xE4, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA I */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA I */ + + +const unsigned char TC_TrustCenter_Universal_CA_I_certificate[993]={ +0x30,0x82,0x03,0xDD,0x30,0x82,0x02,0xC5,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x1D, +0xA2,0x00,0x01,0x00,0x02,0xEC,0xB7,0x60,0x80,0x78,0x8D,0xB6,0x06,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x79,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31, +0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x54,0x43,0x20,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73, +0x61,0x6C,0x20,0x43,0x41,0x20,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x33,0x32, +0x32,0x31,0x35,0x35,0x34,0x32,0x38,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31, +0x32,0x32,0x35,0x39,0x35,0x39,0x5A,0x30,0x79,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13, +0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20, +0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0B,0x13,0x1B,0x54, +0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E, +0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,0x26,0x30,0x24,0x06,0x03, +0x55,0x04,0x03,0x13,0x1D,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E, +0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41, +0x20,0x49,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xA4,0x77,0x23,0x96,0x44,0xAF,0x90,0xF4,0x31,0xA7,0x10,0xF4,0x26, +0x87,0x9C,0xF3,0x38,0xD9,0x0F,0x5E,0xDE,0xCF,0x41,0xE8,0x31,0xAD,0xC6,0x74,0x91, +0x24,0x96,0x78,0x1E,0x09,0xA0,0x9B,0x9A,0x95,0x4A,0x4A,0xF5,0x62,0x7C,0x02,0xA8, +0xCA,0xAC,0xFB,0x5A,0x04,0x76,0x39,0xDE,0x5F,0xF1,0xF9,0xB3,0xBF,0xF3,0x03,0x58, +0x55,0xD2,0xAA,0xB7,0xE3,0x04,0x22,0xD1,0xF8,0x94,0xDA,0x22,0x08,0x00,0x8D,0xD3, +0x7C,0x26,0x5D,0xCC,0x77,0x79,0xE7,0x2C,0x78,0x39,0xA8,0x26,0x73,0x0E,0xA2,0x5D, +0x25,0x69,0x85,0x4F,0x55,0x0E,0x9A,0xEF,0xC6,0xB9,0x44,0xE1,0x57,0x3D,0xDF,0x1F, +0x54,0x22,0xE5,0x6F,0x65,0xAA,0x33,0x84,0x3A,0xF3,0xCE,0x7A,0xBE,0x55,0x97,0xAE, +0x8D,0x12,0x0F,0x14,0x33,0xE2,0x50,0x70,0xC3,0x49,0x87,0x13,0xBC,0x51,0xDE,0xD7, +0x98,0x12,0x5A,0xEF,0x3A,0x83,0x33,0x92,0x06,0x75,0x8B,0x92,0x7C,0x12,0x68,0x7B, +0x70,0x6A,0x0F,0xB5,0x9B,0xB6,0x77,0x5B,0x48,0x59,0x9D,0xE4,0xEF,0x5A,0xAD,0xF3, +0xC1,0x9E,0xD4,0xD7,0x45,0x4E,0xCA,0x56,0x34,0x21,0xBC,0x3E,0x17,0x5B,0x6F,0x77, +0x0C,0x48,0x01,0x43,0x29,0xB0,0xDD,0x3F,0x96,0x6E,0xE6,0x95,0xAA,0x0C,0xC0,0x20, +0xB6,0xFD,0x3E,0x36,0x27,0x9C,0xE3,0x5C,0xCF,0x4E,0x81,0xDC,0x19,0xBB,0x91,0x90, +0x7D,0xEC,0xE6,0x97,0x04,0x1E,0x93,0xCC,0x22,0x49,0xD7,0x97,0x86,0xB6,0x13,0x0A, +0x3C,0x43,0x23,0x77,0x7E,0xF0,0xDC,0xE6,0xCD,0x24,0x1F,0x3B,0x83,0x9B,0x34,0x3A, +0x83,0x34,0xE3,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x1F,0x06,0x03, +0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x92,0xA4,0x75,0x2C,0xA4,0x9E,0xBE, +0x81,0x44,0xEB,0x79,0xFC,0x8A,0xC5,0x95,0xA5,0xEB,0x10,0x75,0x73,0x30,0x0F,0x06, +0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x92,0xA4,0x75,0x2C,0xA4,0x9E,0xBE, +0x81,0x44,0xEB,0x79,0xFC,0x8A,0xC5,0x95,0xA5,0xEB,0x10,0x75,0x73,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01, +0x00,0x28,0xD2,0xE0,0x86,0xD5,0xE6,0xF8,0x7B,0xF0,0x97,0xDC,0x22,0x6B,0x3B,0x95, +0x14,0x56,0x0F,0x11,0x30,0xA5,0x9A,0x4F,0x3A,0xB0,0x3A,0xE0,0x06,0xCB,0x65,0xF5, +0xED,0xC6,0x97,0x27,0xFE,0x25,0xF2,0x57,0xE6,0x5E,0x95,0x8C,0x3E,0x64,0x60,0x15, +0x5A,0x7F,0x2F,0x0D,0x01,0xC5,0xB1,0x60,0xFD,0x45,0x35,0xCF,0xF0,0xB2,0xBF,0x06, +0xD9,0xEF,0x5A,0xBE,0xB3,0x62,0x21,0xB4,0xD7,0xAB,0x35,0x7C,0x53,0x3E,0xA6,0x27, +0xF1,0xA1,0x2D,0xDA,0x1A,0x23,0x9D,0xCC,0xDD,0xEC,0x3C,0x2D,0x9E,0x27,0x34,0x5D, +0x0F,0xC2,0x36,0x79,0xBC,0xC9,0x4A,0x62,0x2D,0xED,0x6B,0xD9,0x7D,0x41,0x43,0x7C, +0xB6,0xAA,0xCA,0xED,0x61,0xB1,0x37,0x82,0x15,0x09,0x1A,0x8A,0x16,0x30,0xD8,0xEC, +0xC9,0xD6,0x47,0x72,0x78,0x4B,0x10,0x46,0x14,0x8E,0x5F,0x0E,0xAF,0xEC,0xC7,0x2F, +0xAB,0x10,0xD7,0xB6,0xF1,0x6E,0xEC,0x86,0xB2,0xC2,0xE8,0x0D,0x92,0x73,0xDC,0xA2, +0xF4,0x0F,0x3A,0xBF,0x61,0x23,0x10,0x89,0x9C,0x48,0x40,0x6E,0x70,0x00,0xB3,0xD3, +0xBA,0x37,0x44,0x58,0x11,0x7A,0x02,0x6A,0x88,0xF0,0x37,0x34,0xF0,0x19,0xE9,0xAC, +0xD4,0x65,0x73,0xF6,0x69,0x8C,0x64,0x94,0x3A,0x79,0x85,0x29,0xB0,0x16,0x2B,0x0C, +0x82,0x3F,0x06,0x9C,0xC7,0xFD,0x10,0x2B,0x9E,0x0F,0x2C,0xB6,0x9E,0xE3,0x15,0xBF, +0xD9,0x36,0x1C,0xBA,0x25,0x1A,0x52,0x3D,0x1A,0xEC,0x22,0x0C,0x1C,0xE0,0xA4,0xA2, +0x3D,0xF0,0xE8,0x39,0xCF,0x81,0xC0,0x7B,0xED,0x5D,0x1F,0x6F,0xC5,0xD0,0x0B,0xD7, +0x98, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA III */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA III */ + + +const unsigned char TC_TrustCenter_Universal_CA_III_certificate[997]={ +0x30,0x82,0x03,0xE1,0x30,0x82,0x02,0xC9,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x63, +0x25,0x00,0x01,0x00,0x02,0x14,0x8D,0x33,0x15,0x02,0xE4,0x6C,0xF4,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x7B,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31, +0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x03,0x13,0x1F,0x54,0x43,0x20,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73, +0x61,0x6C,0x20,0x43,0x41,0x20,0x49,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30, +0x39,0x30,0x39,0x30,0x38,0x31,0x35,0x32,0x37,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32, +0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x7B,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04, +0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65, +0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0B,0x13, +0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20, +0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,0x28,0x30,0x26, +0x06,0x03,0x55,0x04,0x03,0x13,0x1F,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43, +0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20, +0x43,0x41,0x20,0x49,0x49,0x49,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC2,0xDA,0x9C,0x62,0xB0,0xB9,0x71,0x12,0xB0, +0x0B,0xC8,0x1A,0x57,0xB2,0xAE,0x83,0x14,0x99,0xB3,0x34,0x4B,0x9B,0x90,0xA2,0xC5, +0xE7,0xE7,0x2F,0x02,0xA0,0x4D,0x2D,0xA4,0xFA,0x85,0xDA,0x9B,0x25,0x85,0x2D,0x40, +0x28,0x20,0x6D,0xEA,0xE0,0xBD,0xB1,0x48,0x83,0x22,0x29,0x44,0x9F,0x4E,0x83,0xEE, +0x35,0x51,0x13,0x73,0x74,0xD5,0xBC,0xF2,0x30,0x66,0x94,0x53,0xC0,0x40,0x36,0x2F, +0x0C,0x84,0x65,0xCE,0x0F,0x6E,0xC2,0x58,0x93,0xE8,0x2C,0x0B,0x3A,0xE9,0xC1,0x8E, +0xFB,0xF2,0x6B,0xCA,0x3C,0xE2,0x9C,0x4E,0x8E,0xE4,0xF9,0x7D,0xD3,0x27,0x9F,0x1B, +0xD5,0x67,0x78,0x87,0x2D,0x7F,0x0B,0x47,0xB3,0xC7,0xE8,0xC9,0x48,0x7C,0xAF,0x2F, +0xCC,0x0A,0xD9,0x41,0xEF,0x9F,0xFE,0x9A,0xE1,0xB2,0xAE,0xF9,0x53,0xB5,0xE5,0xE9, +0x46,0x9F,0x60,0xE3,0xDF,0x8D,0xD3,0x7F,0xFB,0x96,0x7E,0xB3,0xB5,0x72,0xF8,0x4B, +0xAD,0x08,0x79,0xCD,0x69,0x89,0x40,0x27,0xF5,0x2A,0xC1,0xAD,0x43,0xEC,0xA4,0x53, +0xC8,0x61,0xB6,0xF7,0xD2,0x79,0x2A,0x67,0x18,0x76,0x48,0x6D,0x5B,0x25,0x01,0xD1, +0x26,0xC5,0xB7,0x57,0x69,0x23,0x15,0x5B,0x61,0x8A,0xAD,0xF0,0x1B,0x2D,0xD9,0xAF, +0x5C,0xF1,0x26,0x90,0x69,0xA9,0xD5,0x0C,0x40,0xF5,0x33,0x80,0x43,0x8F,0x9C,0xA3, +0x76,0x2A,0x45,0xB4,0xAF,0xBF,0x7F,0x3E,0x87,0x3F,0x76,0xC5,0xCD,0x2A,0xDE,0x20, +0xC5,0x16,0x58,0xCB,0xF9,0x1B,0xF5,0x0F,0xCB,0x0D,0x11,0x52,0x64,0xB8,0xD2,0x76, +0x62,0x77,0x83,0xF1,0x58,0x9F,0xFF,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61, +0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x56,0xE7,0xE1, +0x5B,0x25,0x43,0x80,0xE0,0xF6,0x8C,0xE1,0x71,0xBC,0x8E,0xE5,0x80,0x2F,0xC4,0x48, +0xE2,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02, +0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x56,0xE7,0xE1, +0x5B,0x25,0x43,0x80,0xE0,0xF6,0x8C,0xE1,0x71,0xBC,0x8E,0xE5,0x80,0x2F,0xC4,0x48, +0xE2,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x82,0x01,0x01,0x00,0x83,0xC7,0xAF,0xEA,0x7F,0x4D,0x0A,0x3C,0x39,0xB1,0x68, +0xBE,0x7B,0x6D,0x89,0x2E,0xE9,0xB3,0x09,0xE7,0x18,0x57,0x8D,0x85,0x9A,0x17,0xF3, +0x76,0x42,0x50,0x13,0x0F,0xC7,0x90,0x6F,0x33,0xAD,0xC5,0x49,0x60,0x2B,0x6C,0x49, +0x58,0x19,0xD4,0xE2,0xBE,0xB7,0xBF,0xAB,0x49,0xBC,0x94,0xC8,0xAB,0xBE,0x28,0x6C, +0x16,0x68,0xE0,0xC8,0x97,0x46,0x20,0xA0,0x68,0x67,0x60,0x88,0x39,0x20,0x51,0xD8, +0x68,0x01,0x11,0xCE,0xA7,0xF6,0x11,0x07,0xF6,0xEC,0xEC,0xAC,0x1A,0x1F,0xB2,0x66, +0x6E,0x56,0x67,0x60,0x7A,0x74,0x5E,0xC0,0x6D,0x97,0x36,0xAE,0xB5,0x0D,0x5D,0x66, +0x73,0xC0,0x25,0x32,0x45,0xD8,0x4A,0x06,0x07,0x8F,0xC4,0xB7,0x07,0xB1,0x4D,0x06, +0x0D,0xE1,0xA5,0xEB,0xF4,0x75,0xCA,0xBA,0x9C,0xD0,0xBD,0xB3,0xD3,0x32,0x24,0x4C, +0xEE,0x7E,0xE2,0x76,0x04,0x4B,0x49,0x53,0xD8,0xF2,0xE9,0x54,0x33,0xFC,0xE5,0x71, +0x1F,0x3D,0x14,0x5C,0x96,0x4B,0xF1,0x3A,0xF2,0x00,0xBB,0x6C,0xB4,0xFA,0x96,0x55, +0x08,0x88,0x09,0xC1,0xCC,0x91,0x19,0x29,0xB0,0x20,0x2D,0xFF,0xCB,0x38,0xA4,0x40, +0xE1,0x17,0xBE,0x79,0x61,0x80,0xFF,0x07,0x03,0x86,0x4C,0x4E,0x7B,0x06,0x9F,0x11, +0x86,0x8D,0x89,0xEE,0x27,0xC4,0xDB,0xE2,0xBC,0x19,0x8E,0x0B,0xC3,0xC3,0x13,0xC7, +0x2D,0x03,0x63,0x3B,0xD3,0xE8,0xE4,0xA2,0x2A,0xC2,0x82,0x08,0x94,0x16,0x54,0xF0, +0xEF,0x1F,0x27,0x90,0x25,0xB8,0x0D,0x0E,0x28,0x1B,0x47,0x77,0x47,0xBD,0x1C,0xA8, +0x25,0xF1,0x94,0xB4,0x66, +}; + + +/* subject:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com */ +/* issuer :/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com */ + + +const unsigned char Thawte_Premium_Server_CA_certificate[811]={ +0x30,0x82,0x03,0x27,0x30,0x82,0x02,0x90,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x81,0xCE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41,0x31, +0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65,0x72, +0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13, +0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03, +0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E,0x73, +0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73, +0x69,0x6F,0x6E,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x54,0x68, +0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x53,0x65,0x72, +0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x28,0x30,0x26,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x19,0x70,0x72,0x65,0x6D,0x69,0x75,0x6D,0x2D,0x73, +0x65,0x72,0x76,0x65,0x72,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F,0x6D, +0x30,0x1E,0x17,0x0D,0x39,0x36,0x30,0x38,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30, +0x5A,0x17,0x0D,0x32,0x30,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A, +0x30,0x81,0xCE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41, +0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65, +0x72,0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07, +0x13,0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06, +0x03,0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E, +0x73,0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03, +0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69, +0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69, +0x73,0x69,0x6F,0x6E,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x54, +0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x53,0x65, +0x72,0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x28,0x30,0x26,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x09,0x01,0x16,0x19,0x70,0x72,0x65,0x6D,0x69,0x75,0x6D,0x2D, +0x73,0x65,0x72,0x76,0x65,0x72,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F, +0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xD2,0x36, +0x36,0x6A,0x8B,0xD7,0xC2,0x5B,0x9E,0xDA,0x81,0x41,0x62,0x8F,0x38,0xEE,0x49,0x04, +0x55,0xD6,0xD0,0xEF,0x1C,0x1B,0x95,0x16,0x47,0xEF,0x18,0x48,0x35,0x3A,0x52,0xF4, +0x2B,0x6A,0x06,0x8F,0x3B,0x2F,0xEA,0x56,0xE3,0xAF,0x86,0x8D,0x9E,0x17,0xF7,0x9E, +0xB4,0x65,0x75,0x02,0x4D,0xEF,0xCB,0x09,0xA2,0x21,0x51,0xD8,0x9B,0xD0,0x67,0xD0, +0xBA,0x0D,0x92,0x06,0x14,0x73,0xD4,0x93,0xCB,0x97,0x2A,0x00,0x9C,0x5C,0x4E,0x0C, +0xBC,0xFA,0x15,0x52,0xFC,0xF2,0x44,0x6E,0xDA,0x11,0x4A,0x6E,0x08,0x9F,0x2F,0x2D, +0xE3,0xF9,0xAA,0x3A,0x86,0x73,0xB6,0x46,0x53,0x58,0xC8,0x89,0x05,0xBD,0x83,0x11, +0xB8,0x73,0x3F,0xAA,0x07,0x8D,0xF4,0x42,0x4D,0xE7,0x40,0x9D,0x1C,0x37,0x02,0x03, +0x01,0x00,0x01,0xA3,0x13,0x30,0x11,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01, +0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x26,0x48,0x2C,0x16,0xC2, +0x58,0xFA,0xE8,0x16,0x74,0x0C,0xAA,0xAA,0x5F,0x54,0x3F,0xF2,0xD7,0xC9,0x78,0x60, +0x5E,0x5E,0x6E,0x37,0x63,0x22,0x77,0x36,0x7E,0xB2,0x17,0xC4,0x34,0xB9,0xF5,0x08, +0x85,0xFC,0xC9,0x01,0x38,0xFF,0x4D,0xBE,0xF2,0x16,0x42,0x43,0xE7,0xBB,0x5A,0x46, +0xFB,0xC1,0xC6,0x11,0x1F,0xF1,0x4A,0xB0,0x28,0x46,0xC9,0xC3,0xC4,0x42,0x7D,0xBC, +0xFA,0xAB,0x59,0x6E,0xD5,0xB7,0x51,0x88,0x11,0xE3,0xA4,0x85,0x19,0x6B,0x82,0x4C, +0xA4,0x0C,0x12,0xAD,0xE9,0xA4,0xAE,0x3F,0xF1,0xC3,0x49,0x65,0x9A,0x8C,0xC5,0xC8, +0x3E,0x25,0xB7,0x94,0x99,0xBB,0x92,0x32,0x71,0x07,0xF0,0x86,0x5E,0xED,0x50,0x27, +0xA6,0x0D,0xA6,0x23,0xF9,0xBB,0xCB,0xA6,0x07,0x14,0x42, +}; + + +/* subject:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA */ +/* issuer :/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA */ + + +const unsigned char thawte_Primary_Root_CA_certificate[1060]={ +0x30,0x82,0x04,0x20,0x30,0x82,0x03,0x08,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x34, +0x4E,0xD5,0x57,0x20,0xD5,0xED,0xEC,0x49,0xF4,0x2F,0xCE,0x37,0xDB,0x2B,0x6D,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0xA9,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15, +0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65, +0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31, +0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30, +0x30,0x36,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20, +0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64, +0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55, +0x04,0x03,0x13,0x16,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61, +0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x36, +0x31,0x31,0x31,0x37,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30, +0x37,0x31,0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xA9,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63, +0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,0x38,0x30,0x36,0x06, +0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x74, +0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F, +0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65, +0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16, +0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x52, +0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAC,0xA0,0xF0,0xFB,0x80,0x59,0xD4,0x9C,0xC7, +0xA4,0xCF,0x9D,0xA1,0x59,0x73,0x09,0x10,0x45,0x0C,0x0D,0x2C,0x6E,0x68,0xF1,0x6C, +0x5B,0x48,0x68,0x49,0x59,0x37,0xFC,0x0B,0x33,0x19,0xC2,0x77,0x7F,0xCC,0x10,0x2D, +0x95,0x34,0x1C,0xE6,0xEB,0x4D,0x09,0xA7,0x1C,0xD2,0xB8,0xC9,0x97,0x36,0x02,0xB7, +0x89,0xD4,0x24,0x5F,0x06,0xC0,0xCC,0x44,0x94,0x94,0x8D,0x02,0x62,0x6F,0xEB,0x5A, +0xDD,0x11,0x8D,0x28,0x9A,0x5C,0x84,0x90,0x10,0x7A,0x0D,0xBD,0x74,0x66,0x2F,0x6A, +0x38,0xA0,0xE2,0xD5,0x54,0x44,0xEB,0x1D,0x07,0x9F,0x07,0xBA,0x6F,0xEE,0xE9,0xFD, +0x4E,0x0B,0x29,0xF5,0x3E,0x84,0xA0,0x01,0xF1,0x9C,0xAB,0xF8,0x1C,0x7E,0x89,0xA4, +0xE8,0xA1,0xD8,0x71,0x65,0x0D,0xA3,0x51,0x7B,0xEE,0xBC,0xD2,0x22,0x60,0x0D,0xB9, +0x5B,0x9D,0xDF,0xBA,0xFC,0x51,0x5B,0x0B,0xAF,0x98,0xB2,0xE9,0x2E,0xE9,0x04,0xE8, +0x62,0x87,0xDE,0x2B,0xC8,0xD7,0x4E,0xC1,0x4C,0x64,0x1E,0xDD,0xCF,0x87,0x58,0xBA, +0x4A,0x4F,0xCA,0x68,0x07,0x1D,0x1C,0x9D,0x4A,0xC6,0xD5,0x2F,0x91,0xCC,0x7C,0x71, +0x72,0x1C,0xC5,0xC0,0x67,0xEB,0x32,0xFD,0xC9,0x92,0x5C,0x94,0xDA,0x85,0xC0,0x9B, +0xBF,0x53,0x7D,0x2B,0x09,0xF4,0x8C,0x9D,0x91,0x1F,0x97,0x6A,0x52,0xCB,0xDE,0x09, +0x36,0xA4,0x77,0xD8,0x7B,0x87,0x50,0x44,0xD5,0x3E,0x6E,0x29,0x69,0xFB,0x39,0x49, +0x26,0x1E,0x09,0xA5,0x80,0x7B,0x40,0x2D,0xEB,0xE8,0x27,0x85,0xC9,0xFE,0x61,0xFD, +0x7E,0xE6,0x7C,0x97,0x1D,0xD5,0x9D,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40, +0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01, +0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7B,0x5B,0x45,0xCF, +0xAF,0xCE,0xCB,0x7A,0xFD,0x31,0x92,0x1A,0x6A,0xB6,0xF3,0x46,0xEB,0x57,0x48,0x50, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03, +0x82,0x01,0x01,0x00,0x79,0x11,0xC0,0x4B,0xB3,0x91,0xB6,0xFC,0xF0,0xE9,0x67,0xD4, +0x0D,0x6E,0x45,0xBE,0x55,0xE8,0x93,0xD2,0xCE,0x03,0x3F,0xED,0xDA,0x25,0xB0,0x1D, +0x57,0xCB,0x1E,0x3A,0x76,0xA0,0x4C,0xEC,0x50,0x76,0xE8,0x64,0x72,0x0C,0xA4,0xA9, +0xF1,0xB8,0x8B,0xD6,0xD6,0x87,0x84,0xBB,0x32,0xE5,0x41,0x11,0xC0,0x77,0xD9,0xB3, +0x60,0x9D,0xEB,0x1B,0xD5,0xD1,0x6E,0x44,0x44,0xA9,0xA6,0x01,0xEC,0x55,0x62,0x1D, +0x77,0xB8,0x5C,0x8E,0x48,0x49,0x7C,0x9C,0x3B,0x57,0x11,0xAC,0xAD,0x73,0x37,0x8E, +0x2F,0x78,0x5C,0x90,0x68,0x47,0xD9,0x60,0x60,0xE6,0xFC,0x07,0x3D,0x22,0x20,0x17, +0xC4,0xF7,0x16,0xE9,0xC4,0xD8,0x72,0xF9,0xC8,0x73,0x7C,0xDF,0x16,0x2F,0x15,0xA9, +0x3E,0xFD,0x6A,0x27,0xB6,0xA1,0xEB,0x5A,0xBA,0x98,0x1F,0xD5,0xE3,0x4D,0x64,0x0A, +0x9D,0x13,0xC8,0x61,0xBA,0xF5,0x39,0x1C,0x87,0xBA,0xB8,0xBD,0x7B,0x22,0x7F,0xF6, +0xFE,0xAC,0x40,0x79,0xE5,0xAC,0x10,0x6F,0x3D,0x8F,0x1B,0x79,0x76,0x8B,0xC4,0x37, +0xB3,0x21,0x18,0x84,0xE5,0x36,0x00,0xEB,0x63,0x20,0x99,0xB9,0xE9,0xFE,0x33,0x04, +0xBB,0x41,0xC8,0xC1,0x02,0xF9,0x44,0x63,0x20,0x9E,0x81,0xCE,0x42,0xD3,0xD6,0x3F, +0x2C,0x76,0xD3,0x63,0x9C,0x59,0xDD,0x8F,0xA6,0xE1,0x0E,0xA0,0x2E,0x41,0xF7,0x2E, +0x95,0x47,0xCF,0xBC,0xFD,0x33,0xF3,0xF6,0x0B,0x61,0x7E,0x7E,0x91,0x2B,0x81,0x47, +0xC2,0x27,0x30,0xEE,0xA7,0x10,0x5D,0x37,0x8F,0x5C,0x39,0x2B,0xE4,0x04,0xF0,0x7B, +0x8D,0x56,0x8C,0x68, +}; + + +/* subject:/C=US/O=thawte, Inc./OU=(c) 2007 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G2 */ +/* issuer :/C=US/O=thawte, Inc./OU=(c) 2007 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G2 */ + + +const unsigned char thawte_Primary_Root_CA___G2_certificate[652]={ +0x30,0x82,0x02,0x88,0x30,0x82,0x02,0x0D,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x35, +0xFC,0x26,0x5C,0xD9,0x84,0x4F,0xC9,0x3D,0x26,0x3D,0x57,0x9B,0xAE,0xD7,0x56,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x84,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29, +0x20,0x32,0x30,0x30,0x37,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69, +0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22, +0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72, +0x69,0x6D,0x61,0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20, +0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31,0x30,0x35,0x30,0x30,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32,0x33,0x35,0x39,0x35, +0x39,0x5A,0x30,0x81,0x84,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61, +0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x38,0x30,0x36,0x06,0x03,0x55, +0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x74,0x68,0x61, +0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20, +0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F, +0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x74,0x68, +0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x52,0x6F,0x6F, +0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x32,0x30,0x76,0x30,0x10,0x06,0x07,0x2A, +0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00, +0x04,0xA2,0xD5,0x9C,0x82,0x7B,0x95,0x9D,0xF1,0x52,0x78,0x87,0xFE,0x8A,0x16,0xBF, +0x05,0xE6,0xDF,0xA3,0x02,0x4F,0x0D,0x07,0xC6,0x00,0x51,0xBA,0x0C,0x02,0x52,0x2D, +0x22,0xA4,0x42,0x39,0xC4,0xFE,0x8F,0xEA,0xC9,0xC1,0xBE,0xD4,0x4D,0xFF,0x9F,0x7A, +0x9E,0xE2,0xB1,0x7C,0x9A,0xAD,0xA7,0x86,0x09,0x73,0x87,0xD1,0xE7,0x9A,0xE3,0x7A, +0xA5,0xAA,0x6E,0xFB,0xBA,0xB3,0x70,0xC0,0x67,0x88,0xA2,0x35,0xD4,0xA3,0x9A,0xB1, +0xFD,0xAD,0xC2,0xEF,0x31,0xFA,0xA8,0xB9,0xF3,0xFB,0x08,0xC6,0x91,0xD1,0xFB,0x29, +0x95,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x9A,0xD8,0x00,0x30,0x00,0xE7,0x6B,0x7F,0x85,0x18,0xEE,0x8B,0xB6,0xCE,0x8A, +0x0C,0xF8,0x11,0xE1,0xBB,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03, +0x03,0x03,0x69,0x00,0x30,0x66,0x02,0x31,0x00,0xDD,0xF8,0xE0,0x57,0x47,0x5B,0xA7, +0xE6,0x0A,0xC3,0xBD,0xF5,0x80,0x8A,0x97,0x35,0x0D,0x1B,0x89,0x3C,0x54,0x86,0x77, +0x28,0xCA,0xA1,0xF4,0x79,0xDE,0xB5,0xE6,0x38,0xB0,0xF0,0x65,0x70,0x8C,0x7F,0x02, +0x54,0xC2,0xBF,0xFF,0xD8,0xA1,0x3E,0xD9,0xCF,0x02,0x31,0x00,0xC4,0x8D,0x94,0xFC, +0xDC,0x53,0xD2,0xDC,0x9D,0x78,0x16,0x1F,0x15,0x33,0x23,0x53,0x52,0xE3,0x5A,0x31, +0x5D,0x9D,0xCA,0xAE,0xBD,0x13,0x29,0x44,0x0D,0x27,0x5B,0xA8,0xE7,0x68,0x9C,0x12, +0xF7,0x58,0x3F,0x2E,0x72,0x02,0x57,0xA3,0x8F,0xA1,0x14,0x2E, +}; + + +/* subject:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2008 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G3 */ +/* issuer :/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2008 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G3 */ + + +const unsigned char thawte_Primary_Root_CA___G3_certificate[1070]={ +0x30,0x82,0x04,0x2A,0x30,0x82,0x03,0x12,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x60, +0x01,0x97,0xB7,0x46,0xA7,0xEA,0xB4,0xB4,0x9A,0xD6,0x4B,0x2F,0xF7,0x90,0xFB,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81, +0xAE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15, +0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65, +0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31, +0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30, +0x30,0x38,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20, +0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64, +0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61, +0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x33,0x30, +0x1E,0x17,0x0D,0x30,0x38,0x30,0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A, +0x17,0x0D,0x33,0x37,0x31,0x32,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30, +0x81,0xAE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13, +0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53, +0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E, +0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32, +0x30,0x30,0x38,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E, +0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65, +0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03, +0x55,0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D, +0x61,0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x33, +0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01, +0x00,0xB2,0xBF,0x27,0x2C,0xFB,0xDB,0xD8,0x5B,0xDD,0x78,0x7B,0x1B,0x9E,0x77,0x66, +0x81,0xCB,0x3E,0xBC,0x7C,0xAE,0xF3,0xA6,0x27,0x9A,0x34,0xA3,0x68,0x31,0x71,0x38, +0x33,0x62,0xE4,0xF3,0x71,0x66,0x79,0xB1,0xA9,0x65,0xA3,0xA5,0x8B,0xD5,0x8F,0x60, +0x2D,0x3F,0x42,0xCC,0xAA,0x6B,0x32,0xC0,0x23,0xCB,0x2C,0x41,0xDD,0xE4,0xDF,0xFC, +0x61,0x9C,0xE2,0x73,0xB2,0x22,0x95,0x11,0x43,0x18,0x5F,0xC4,0xB6,0x1F,0x57,0x6C, +0x0A,0x05,0x58,0x22,0xC8,0x36,0x4C,0x3A,0x7C,0xA5,0xD1,0xCF,0x86,0xAF,0x88,0xA7, +0x44,0x02,0x13,0x74,0x71,0x73,0x0A,0x42,0x59,0x02,0xF8,0x1B,0x14,0x6B,0x42,0xDF, +0x6F,0x5F,0xBA,0x6B,0x82,0xA2,0x9D,0x5B,0xE7,0x4A,0xBD,0x1E,0x01,0x72,0xDB,0x4B, +0x74,0xE8,0x3B,0x7F,0x7F,0x7D,0x1F,0x04,0xB4,0x26,0x9B,0xE0,0xB4,0x5A,0xAC,0x47, +0x3D,0x55,0xB8,0xD7,0xB0,0x26,0x52,0x28,0x01,0x31,0x40,0x66,0xD8,0xD9,0x24,0xBD, +0xF6,0x2A,0xD8,0xEC,0x21,0x49,0x5C,0x9B,0xF6,0x7A,0xE9,0x7F,0x55,0x35,0x7E,0x96, +0x6B,0x8D,0x93,0x93,0x27,0xCB,0x92,0xBB,0xEA,0xAC,0x40,0xC0,0x9F,0xC2,0xF8,0x80, +0xCF,0x5D,0xF4,0x5A,0xDC,0xCE,0x74,0x86,0xA6,0x3E,0x6C,0x0B,0x53,0xCA,0xBD,0x92, +0xCE,0x19,0x06,0x72,0xE6,0x0C,0x5C,0x38,0x69,0xC7,0x04,0xD6,0xBC,0x6C,0xCE,0x5B, +0xF6,0xF7,0x68,0x9C,0xDC,0x25,0x15,0x48,0x88,0xA1,0xE9,0xA9,0xF8,0x98,0x9C,0xE0, +0xF3,0xD5,0x31,0x28,0x61,0x11,0x6C,0x67,0x96,0x8D,0x39,0x99,0xCB,0xC2,0x45,0x24, +0x39,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0xAD,0x6C,0xAA,0x94,0x60,0x9C,0xED,0xE4,0xFF,0xFA, +0x3E,0x0A,0x74,0x2B,0x63,0x03,0xF7,0xB6,0x59,0xBF,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x1A,0x40, +0xD8,0x95,0x65,0xAC,0x09,0x92,0x89,0xC6,0x39,0xF4,0x10,0xE5,0xA9,0x0E,0x66,0x53, +0x5D,0x78,0xDE,0xFA,0x24,0x91,0xBB,0xE7,0x44,0x51,0xDF,0xC6,0x16,0x34,0x0A,0xEF, +0x6A,0x44,0x51,0xEA,0x2B,0x07,0x8A,0x03,0x7A,0xC3,0xEB,0x3F,0x0A,0x2C,0x52,0x16, +0xA0,0x2B,0x43,0xB9,0x25,0x90,0x3F,0x70,0xA9,0x33,0x25,0x6D,0x45,0x1A,0x28,0x3B, +0x27,0xCF,0xAA,0xC3,0x29,0x42,0x1B,0xDF,0x3B,0x4C,0xC0,0x33,0x34,0x5B,0x41,0x88, +0xBF,0x6B,0x2B,0x65,0xAF,0x28,0xEF,0xB2,0xF5,0xC3,0xAA,0x66,0xCE,0x7B,0x56,0xEE, +0xB7,0xC8,0xCB,0x67,0xC1,0xC9,0x9C,0x1A,0x18,0xB8,0xC4,0xC3,0x49,0x03,0xF1,0x60, +0x0E,0x50,0xCD,0x46,0xC5,0xF3,0x77,0x79,0xF7,0xB6,0x15,0xE0,0x38,0xDB,0xC7,0x2F, +0x28,0xA0,0x0C,0x3F,0x77,0x26,0x74,0xD9,0x25,0x12,0xDA,0x31,0xDA,0x1A,0x1E,0xDC, +0x29,0x41,0x91,0x22,0x3C,0x69,0xA7,0xBB,0x02,0xF2,0xB6,0x5C,0x27,0x03,0x89,0xF4, +0x06,0xEA,0x9B,0xE4,0x72,0x82,0xE3,0xA1,0x09,0xC1,0xE9,0x00,0x19,0xD3,0x3E,0xD4, +0x70,0x6B,0xBA,0x71,0xA6,0xAA,0x58,0xAE,0xF4,0xBB,0xE9,0x6C,0xB6,0xEF,0x87,0xCC, +0x9B,0xBB,0xFF,0x39,0xE6,0x56,0x61,0xD3,0x0A,0xA7,0xC4,0x5C,0x4C,0x60,0x7B,0x05, +0x77,0x26,0x7A,0xBF,0xD8,0x07,0x52,0x2C,0x62,0xF7,0x70,0x63,0xD9,0x39,0xBC,0x6F, +0x1C,0xC2,0x79,0xDC,0x76,0x29,0xAF,0xCE,0xC5,0x2C,0x64,0x04,0x5E,0x88,0x36,0x6E, +0x31,0xD4,0x40,0x1A,0x62,0x34,0x36,0x3F,0x35,0x01,0xAE,0xAC,0x63,0xA0, +}; + + +/* subject:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Server CA/emailAddress=server-certs@thawte.com */ +/* issuer :/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Server CA/emailAddress=server-certs@thawte.com */ + + +const unsigned char Thawte_Server_CA_certificate[791]={ +0x30,0x82,0x03,0x13,0x30,0x82,0x02,0x7C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x81,0xC4,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41,0x31, +0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65,0x72, +0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13, +0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03, +0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E,0x73, +0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73, +0x69,0x6F,0x6E,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x03,0x13,0x10,0x54,0x68, +0x61,0x77,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x26, +0x30,0x24,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x09,0x01,0x16,0x17,0x73, +0x65,0x72,0x76,0x65,0x72,0x2D,0x63,0x65,0x72,0x74,0x73,0x40,0x74,0x68,0x61,0x77, +0x74,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x36,0x30,0x38,0x30,0x31, +0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,0x31,0x32,0x33,0x31,0x32, +0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xC4,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x5A,0x41,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13, +0x0C,0x57,0x65,0x73,0x74,0x65,0x72,0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30, +0x10,0x06,0x03,0x55,0x04,0x07,0x13,0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77, +0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77, +0x74,0x65,0x20,0x43,0x6F,0x6E,0x73,0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63, +0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65, +0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,0x19,0x30,0x17,0x06,0x03, +0x55,0x04,0x03,0x13,0x10,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x53,0x65,0x72,0x76, +0x65,0x72,0x20,0x43,0x41,0x31,0x26,0x30,0x24,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x09,0x01,0x16,0x17,0x73,0x65,0x72,0x76,0x65,0x72,0x2D,0x63,0x65,0x72, +0x74,0x73,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xD3,0xA4,0x50,0x6E,0xC8,0xFF, +0x56,0x6B,0xE6,0xCF,0x5D,0xB6,0xEA,0x0C,0x68,0x75,0x47,0xA2,0xAA,0xC2,0xDA,0x84, +0x25,0xFC,0xA8,0xF4,0x47,0x51,0xDA,0x85,0xB5,0x20,0x74,0x94,0x86,0x1E,0x0F,0x75, +0xC9,0xE9,0x08,0x61,0xF5,0x06,0x6D,0x30,0x6E,0x15,0x19,0x02,0xE9,0x52,0xC0,0x62, +0xDB,0x4D,0x99,0x9E,0xE2,0x6A,0x0C,0x44,0x38,0xCD,0xFE,0xBE,0xE3,0x64,0x09,0x70, +0xC5,0xFE,0xB1,0x6B,0x29,0xB6,0x2F,0x49,0xC8,0x3B,0xD4,0x27,0x04,0x25,0x10,0x97, +0x2F,0xE7,0x90,0x6D,0xC0,0x28,0x42,0x99,0xD7,0x4C,0x43,0xDE,0xC3,0xF5,0x21,0x6D, +0x54,0x9F,0x5D,0xC3,0x58,0xE1,0xC0,0xE4,0xD9,0x5B,0xB0,0xB8,0xDC,0xB4,0x7B,0xDF, +0x36,0x3A,0xC2,0xB5,0x66,0x22,0x12,0xD6,0x87,0x0D,0x02,0x03,0x01,0x00,0x01,0xA3, +0x13,0x30,0x11,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x07,0xFA,0x4C,0x69,0x5C,0xFB,0x95,0xCC,0x46, +0xEE,0x85,0x83,0x4D,0x21,0x30,0x8E,0xCA,0xD9,0xA8,0x6F,0x49,0x1A,0xE6,0xDA,0x51, +0xE3,0x60,0x70,0x6C,0x84,0x61,0x11,0xA1,0x1A,0xC8,0x48,0x3E,0x59,0x43,0x7D,0x4F, +0x95,0x3D,0xA1,0x8B,0xB7,0x0B,0x62,0x98,0x7A,0x75,0x8A,0xDD,0x88,0x4E,0x4E,0x9E, +0x40,0xDB,0xA8,0xCC,0x32,0x74,0xB9,0x6F,0x0D,0xC6,0xE3,0xB3,0x44,0x0B,0xD9,0x8A, +0x6F,0x9A,0x29,0x9B,0x99,0x18,0x28,0x3B,0xD1,0xE3,0x40,0x28,0x9A,0x5A,0x3C,0xD5, +0xB5,0xE7,0x20,0x1B,0x8B,0xCA,0xA4,0xAB,0x8D,0xE9,0x51,0xD9,0xE2,0x4C,0x2C,0x59, +0xA9,0xDA,0xB9,0xB2,0x75,0x1B,0xF6,0x42,0xF2,0xEF,0xC7,0xF2,0x18,0xF9,0x89,0xBC, +0xA3,0xFF,0x8A,0x23,0x2E,0x70,0x47, +}; + + +/* subject:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC */ +/* issuer :/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC */ + + +const unsigned char UTN_DATACorp_SGC_Root_CA_certificate[1122]={ +0x30,0x82,0x04,0x5E,0x30,0x82,0x03,0x46,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x44, +0xBE,0x0C,0x8B,0x50,0x00,0x21,0xB4,0x11,0xD3,0x2A,0x68,0x06,0xA9,0xAD,0x69,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x93,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20, +0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54, +0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72, +0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x03, +0x13,0x12,0x55,0x54,0x4E,0x20,0x2D,0x20,0x44,0x41,0x54,0x41,0x43,0x6F,0x72,0x70, +0x20,0x53,0x47,0x43,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x34,0x31,0x38, +0x35,0x37,0x32,0x31,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,0x34,0x31,0x39,0x30, +0x36,0x33,0x30,0x5A,0x30,0x81,0x93,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55, +0x54,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74, +0x20,0x4C,0x61,0x6B,0x65,0x20,0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03, +0x55,0x04,0x0A,0x13,0x15,0x54,0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55, +0x53,0x54,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03, +0x55,0x04,0x0B,0x13,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E, +0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1B,0x30, +0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x55,0x54,0x4E,0x20,0x2D,0x20,0x44,0x41, +0x54,0x41,0x43,0x6F,0x72,0x70,0x20,0x53,0x47,0x43,0x30,0x82,0x01,0x22,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01, +0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDF,0xEE,0x58,0x10,0xA2, +0x2B,0x6E,0x55,0xC4,0x8E,0xBF,0x2E,0x46,0x09,0xE7,0xE0,0x08,0x0F,0x2E,0x2B,0x7A, +0x13,0x94,0x1B,0xBD,0xF6,0xB6,0x80,0x8E,0x65,0x05,0x93,0x00,0x1E,0xBC,0xAF,0xE2, +0x0F,0x8E,0x19,0x0D,0x12,0x47,0xEC,0xAC,0xAD,0xA3,0xFA,0x2E,0x70,0xF8,0xDE,0x6E, +0xFB,0x56,0x42,0x15,0x9E,0x2E,0x5C,0xEF,0x23,0xDE,0x21,0xB9,0x05,0x76,0x27,0x19, +0x0F,0x4F,0xD6,0xC3,0x9C,0xB4,0xBE,0x94,0x19,0x63,0xF2,0xA6,0x11,0x0A,0xEB,0x53, +0x48,0x9C,0xBE,0xF2,0x29,0x3B,0x16,0xE8,0x1A,0xA0,0x4C,0xA6,0xC9,0xF4,0x18,0x59, +0x68,0xC0,0x70,0xF2,0x53,0x00,0xC0,0x5E,0x50,0x82,0xA5,0x56,0x6F,0x36,0xF9,0x4A, +0xE0,0x44,0x86,0xA0,0x4D,0x4E,0xD6,0x47,0x6E,0x49,0x4A,0xCB,0x67,0xD7,0xA6,0xC4, +0x05,0xB9,0x8E,0x1E,0xF4,0xFC,0xFF,0xCD,0xE7,0x36,0xE0,0x9C,0x05,0x6C,0xB2,0x33, +0x22,0x15,0xD0,0xB4,0xE0,0xCC,0x17,0xC0,0xB2,0xC0,0xF4,0xFE,0x32,0x3F,0x29,0x2A, +0x95,0x7B,0xD8,0xF2,0xA7,0x4E,0x0F,0x54,0x7C,0xA1,0x0D,0x80,0xB3,0x09,0x03,0xC1, +0xFF,0x5C,0xDD,0x5E,0x9A,0x3E,0xBC,0xAE,0xBC,0x47,0x8A,0x6A,0xAE,0x71,0xCA,0x1F, +0xB1,0x2A,0xB8,0x5F,0x42,0x05,0x0B,0xEC,0x46,0x30,0xD1,0x72,0x0B,0xCA,0xE9,0x56, +0x6D,0xF5,0xEF,0xDF,0x78,0xBE,0x61,0xBA,0xB2,0xA5,0xAE,0x04,0x4C,0xBC,0xA8,0xAC, +0x69,0x15,0x97,0xBD,0xEF,0xEB,0xB4,0x8C,0xBF,0x35,0xF8,0xD4,0xC3,0xD1,0x28,0x0E, +0x5C,0x3A,0x9F,0x70,0x18,0x33,0x20,0x77,0xC4,0xA2,0xAF,0x02,0x03,0x01,0x00,0x01, +0xA3,0x81,0xAB,0x30,0x81,0xA8,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03, +0x02,0x01,0xC6,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x53, +0x32,0xD1,0xB3,0xCF,0x7F,0xFA,0xE0,0xF1,0xA0,0x5D,0x85,0x4E,0x92,0xD2,0x9E,0x45, +0x1D,0xB4,0x4F,0x30,0x3D,0x06,0x03,0x55,0x1D,0x1F,0x04,0x36,0x30,0x34,0x30,0x32, +0xA0,0x30,0xA0,0x2E,0x86,0x2C,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C, +0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x55, +0x54,0x4E,0x2D,0x44,0x41,0x54,0x41,0x43,0x6F,0x72,0x70,0x53,0x47,0x43,0x2E,0x63, +0x72,0x6C,0x30,0x2A,0x06,0x03,0x55,0x1D,0x25,0x04,0x23,0x30,0x21,0x06,0x08,0x2B, +0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x0A,0x2B,0x06,0x01,0x04,0x01,0x82,0x37, +0x0A,0x03,0x03,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x04,0x01,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01, +0x01,0x00,0x27,0x35,0x97,0x00,0x8A,0x8B,0x28,0xBD,0xC6,0x33,0x30,0x1E,0x29,0xFC, +0xE2,0xF7,0xD5,0x98,0xD4,0x40,0xBB,0x60,0xCA,0xBF,0xAB,0x17,0x2C,0x09,0x36,0x7F, +0x50,0xFA,0x41,0xDC,0xAE,0x96,0x3A,0x0A,0x23,0x3E,0x89,0x59,0xC9,0xA3,0x07,0xED, +0x1B,0x37,0xAD,0xFC,0x7C,0xBE,0x51,0x49,0x5A,0xDE,0x3A,0x0A,0x54,0x08,0x16,0x45, +0xC2,0x99,0xB1,0x87,0xCD,0x8C,0x68,0xE0,0x69,0x03,0xE9,0xC4,0x4E,0x98,0xB2,0x3B, +0x8C,0x16,0xB3,0x0E,0xA0,0x0C,0x98,0x50,0x9B,0x93,0xA9,0x70,0x09,0xC8,0x2C,0xA3, +0x8F,0xDF,0x02,0xE4,0xE0,0x71,0x3A,0xF1,0xB4,0x23,0x72,0xA0,0xAA,0x01,0xDF,0xDF, +0x98,0x3E,0x14,0x50,0xA0,0x31,0x26,0xBD,0x28,0xE9,0x5A,0x30,0x26,0x75,0xF9,0x7B, +0x60,0x1C,0x8D,0xF3,0xCD,0x50,0x26,0x6D,0x04,0x27,0x9A,0xDF,0xD5,0x0D,0x45,0x47, +0x29,0x6B,0x2C,0xE6,0x76,0xD9,0xA9,0x29,0x7D,0x32,0xDD,0xC9,0x36,0x3C,0xBD,0xAE, +0x35,0xF1,0x11,0x9E,0x1D,0xBB,0x90,0x3F,0x12,0x47,0x4E,0x8E,0xD7,0x7E,0x0F,0x62, +0x73,0x1D,0x52,0x26,0x38,0x1C,0x18,0x49,0xFD,0x30,0x74,0x9A,0xC4,0xE5,0x22,0x2F, +0xD8,0xC0,0x8D,0xED,0x91,0x7A,0x4C,0x00,0x8F,0x72,0x7F,0x5D,0xDA,0xDD,0x1B,0x8B, +0x45,0x6B,0xE7,0xDD,0x69,0x97,0xA8,0xC5,0x56,0x4C,0x0F,0x0C,0xF6,0x9F,0x7A,0x91, +0x37,0xF6,0x97,0x82,0xE0,0xDD,0x71,0x69,0xFF,0x76,0x3F,0x60,0x4D,0x3C,0xCF,0xF7, +0x99,0xF9,0xC6,0x57,0xF4,0xC9,0x55,0x39,0x78,0xBA,0x2C,0x79,0xC9,0xA6,0x88,0x2B, +0xF4,0x08, +}; + + +/* subject:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware */ +/* issuer :/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware */ + + +const unsigned char UTN_USERFirst_Hardware_Root_CA_certificate[1144]={ +0x30,0x82,0x04,0x74,0x30,0x82,0x03,0x5C,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x44, +0xBE,0x0C,0x8B,0x50,0x00,0x24,0xB4,0x11,0xD3,0x36,0x2A,0xFE,0x65,0x0A,0xFD,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x97,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20, +0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54, +0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72, +0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03, +0x13,0x16,0x55,0x54,0x4E,0x2D,0x55,0x53,0x45,0x52,0x46,0x69,0x72,0x73,0x74,0x2D, +0x48,0x61,0x72,0x64,0x77,0x61,0x72,0x65,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x37, +0x30,0x39,0x31,0x38,0x31,0x30,0x34,0x32,0x5A,0x17,0x0D,0x31,0x39,0x30,0x37,0x30, +0x39,0x31,0x38,0x31,0x39,0x32,0x32,0x5A,0x30,0x81,0x97,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x07,0x13,0x0E, +0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20,0x43,0x69,0x74,0x79,0x31,0x1E, +0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54,0x68,0x65,0x20,0x55,0x53,0x45, +0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21, +0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F, +0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F, +0x6D,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,0x55,0x54,0x4E,0x2D, +0x55,0x53,0x45,0x52,0x46,0x69,0x72,0x73,0x74,0x2D,0x48,0x61,0x72,0x64,0x77,0x61, +0x72,0x65,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xB1,0xF7,0xC3,0x38,0x3F,0xB4,0xA8,0x7F,0xCF,0x39,0x82,0x51,0x67, +0xD0,0x6D,0x9F,0xD2,0xFF,0x58,0xF3,0xE7,0x9F,0x2B,0xEC,0x0D,0x89,0x54,0x99,0xB9, +0x38,0x99,0x16,0xF7,0xE0,0x21,0x79,0x48,0xC2,0xBB,0x61,0x74,0x12,0x96,0x1D,0x3C, +0x6A,0x72,0xD5,0x3C,0x10,0x67,0x3A,0x39,0xED,0x2B,0x13,0xCD,0x66,0xEB,0x95,0x09, +0x33,0xA4,0x6C,0x97,0xB1,0xE8,0xC6,0xEC,0xC1,0x75,0x79,0x9C,0x46,0x5E,0x8D,0xAB, +0xD0,0x6A,0xFD,0xB9,0x2A,0x55,0x17,0x10,0x54,0xB3,0x19,0xF0,0x9A,0xF6,0xF1,0xB1, +0x5D,0xB6,0xA7,0x6D,0xFB,0xE0,0x71,0x17,0x6B,0xA2,0x88,0xFB,0x00,0xDF,0xFE,0x1A, +0x31,0x77,0x0C,0x9A,0x01,0x7A,0xB1,0x32,0xE3,0x2B,0x01,0x07,0x38,0x6E,0xC3,0xA5, +0x5E,0x23,0xBC,0x45,0x9B,0x7B,0x50,0xC1,0xC9,0x30,0x8F,0xDB,0xE5,0x2B,0x7A,0xD3, +0x5B,0xFB,0x33,0x40,0x1E,0xA0,0xD5,0x98,0x17,0xBC,0x8B,0x87,0xC3,0x89,0xD3,0x5D, +0xA0,0x8E,0xB2,0xAA,0xAA,0xF6,0x8E,0x69,0x88,0x06,0xC5,0xFA,0x89,0x21,0xF3,0x08, +0x9D,0x69,0x2E,0x09,0x33,0x9B,0x29,0x0D,0x46,0x0F,0x8C,0xCC,0x49,0x34,0xB0,0x69, +0x51,0xBD,0xF9,0x06,0xCD,0x68,0xAD,0x66,0x4C,0xBC,0x3E,0xAC,0x61,0xBD,0x0A,0x88, +0x0E,0xC8,0xDF,0x3D,0xEE,0x7C,0x04,0x4C,0x9D,0x0A,0x5E,0x6B,0x91,0xD6,0xEE,0xC7, +0xED,0x28,0x8D,0xAB,0x4D,0x87,0x89,0x73,0xD0,0x6E,0xA4,0xD0,0x1E,0x16,0x8B,0x14, +0xE1,0x76,0x44,0x03,0x7F,0x63,0xAC,0xE4,0xCD,0x49,0x9C,0xC5,0x92,0xF4,0xAB,0x32, +0xA1,0x48,0x5B,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xB9,0x30,0x81,0xB6,0x30,0x0B, +0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0xC6,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xA1,0x72,0x5F,0x26,0x1B,0x28,0x98,0x43,0x95, +0x5D,0x07,0x37,0xD5,0x85,0x96,0x9D,0x4B,0xD2,0xC3,0x45,0x30,0x44,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x3D,0x30,0x3B,0x30,0x39,0xA0,0x37,0xA0,0x35,0x86,0x33,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75, +0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x55,0x54,0x4E,0x2D,0x55,0x53,0x45,0x52,0x46, +0x69,0x72,0x73,0x74,0x2D,0x48,0x61,0x72,0x64,0x77,0x61,0x72,0x65,0x2E,0x63,0x72, +0x6C,0x30,0x31,0x06,0x03,0x55,0x1D,0x25,0x04,0x2A,0x30,0x28,0x06,0x08,0x2B,0x06, +0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x05, +0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x06,0x06,0x08,0x2B,0x06,0x01,0x05, +0x05,0x07,0x03,0x07,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x47,0x19,0x0F,0xDE,0x74,0xC6,0x99,0x97, +0xAF,0xFC,0xAD,0x28,0x5E,0x75,0x8E,0xEB,0x2D,0x67,0xEE,0x4E,0x7B,0x2B,0xD7,0x0C, +0xFF,0xF6,0xDE,0xCB,0x55,0xA2,0x0A,0xE1,0x4C,0x54,0x65,0x93,0x60,0x6B,0x9F,0x12, +0x9C,0xAD,0x5E,0x83,0x2C,0xEB,0x5A,0xAE,0xC0,0xE4,0x2D,0xF4,0x00,0x63,0x1D,0xB8, +0xC0,0x6C,0xF2,0xCF,0x49,0xBB,0x4D,0x93,0x6F,0x06,0xA6,0x0A,0x22,0xB2,0x49,0x62, +0x08,0x4E,0xFF,0xC8,0xC8,0x14,0xB2,0x88,0x16,0x5D,0xE7,0x01,0xE4,0x12,0x95,0xE5, +0x45,0x34,0xB3,0x8B,0x69,0xBD,0xCF,0xB4,0x85,0x8F,0x75,0x51,0x9E,0x7D,0x3A,0x38, +0x3A,0x14,0x48,0x12,0xC6,0xFB,0xA7,0x3B,0x1A,0x8D,0x0D,0x82,0x40,0x07,0xE8,0x04, +0x08,0x90,0xA1,0x89,0xCB,0x19,0x50,0xDF,0xCA,0x1C,0x01,0xBC,0x1D,0x04,0x19,0x7B, +0x10,0x76,0x97,0x3B,0xEE,0x90,0x90,0xCA,0xC4,0x0E,0x1F,0x16,0x6E,0x75,0xEF,0x33, +0xF8,0xD3,0x6F,0x5B,0x1E,0x96,0xE3,0xE0,0x74,0x77,0x74,0x7B,0x8A,0xA2,0x6E,0x2D, +0xDD,0x76,0xD6,0x39,0x30,0x82,0xF0,0xAB,0x9C,0x52,0xF2,0x2A,0xC7,0xAF,0x49,0x5E, +0x7E,0xC7,0x68,0xE5,0x82,0x81,0xC8,0x6A,0x27,0xF9,0x27,0x88,0x2A,0xD5,0x58,0x50, +0x95,0x1F,0xF0,0x3B,0x1C,0x57,0xBB,0x7D,0x14,0x39,0x62,0x2B,0x9A,0xC9,0x94,0x92, +0x2A,0xA3,0x22,0x0C,0xFF,0x89,0x26,0x7D,0x5F,0x23,0x2B,0x47,0xD7,0x15,0x1D,0xA9, +0x6A,0x9E,0x51,0x0D,0x2A,0x51,0x9E,0x81,0xF9,0xD4,0x3B,0x5E,0x70,0x12,0x7F,0x10, +0x32,0x9C,0x1E,0xBB,0x9D,0xF8,0x66,0xA8, +}; + + +/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 1 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ +/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 1 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ + + +const unsigned char ValiCert_Class_1_VA_certificate[747]={ +0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30, +0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74, +0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61, +0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33, +0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72, +0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69, +0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36, +0x32,0x35,0x32,0x32,0x32,0x33,0x34,0x38,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32, +0x35,0x32,0x32,0x32,0x33,0x34,0x38,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06, +0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69, +0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03, +0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x31,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C, +0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E, +0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02, +0x81,0x81,0x00,0xD8,0x59,0x82,0x7A,0x89,0xB8,0x96,0xBA,0xA6,0x2F,0x68,0x6F,0x58, +0x2E,0xA7,0x54,0x1C,0x06,0x6E,0xF4,0xEA,0x8D,0x48,0xBC,0x31,0x94,0x17,0xF0,0xF3, +0x4E,0xBC,0xB2,0xB8,0x35,0x92,0x76,0xB0,0xD0,0xA5,0xA5,0x01,0xD7,0x00,0x03,0x12, +0x22,0x19,0x08,0xF8,0xFF,0x11,0x23,0x9B,0xCE,0x07,0xF5,0xBF,0x69,0x1A,0x26,0xFE, +0x4E,0xE9,0xD1,0x7F,0x9D,0x2C,0x40,0x1D,0x59,0x68,0x6E,0xA6,0xF8,0x58,0xB0,0x9D, +0x1A,0x8F,0xD3,0x3F,0xF1,0xDC,0x19,0x06,0x81,0xA8,0x0E,0xE0,0x3A,0xDD,0xC8,0x53, +0x45,0x09,0x06,0xE6,0x0F,0x70,0xC3,0xFA,0x40,0xA6,0x0E,0xE2,0x56,0x05,0x0F,0x18, +0x4D,0xFC,0x20,0x82,0xD1,0x73,0x55,0x74,0x8D,0x76,0x72,0xA0,0x1D,0x9D,0x1D,0xC0, +0xDD,0x3F,0x71,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x50,0x68,0x3D,0x49,0xF4, +0x2C,0x1C,0x06,0x94,0xDF,0x95,0x60,0x7F,0x96,0x7B,0x17,0xFE,0x4F,0x71,0xAD,0x64, +0xC8,0xDD,0x77,0xD2,0xEF,0x59,0x55,0xE8,0x3F,0xE8,0x8E,0x05,0x2A,0x21,0xF2,0x07, +0xD2,0xB5,0xA7,0x52,0xFE,0x9C,0xB1,0xB6,0xE2,0x5B,0x77,0x17,0x40,0xEA,0x72,0xD6, +0x23,0xCB,0x28,0x81,0x32,0xC3,0x00,0x79,0x18,0xEC,0x59,0x17,0x89,0xC9,0xC6,0x6A, +0x1E,0x71,0xC9,0xFD,0xB7,0x74,0xA5,0x25,0x45,0x69,0xC5,0x48,0xAB,0x19,0xE1,0x45, +0x8A,0x25,0x6B,0x19,0xEE,0xE5,0xBB,0x12,0xF5,0x7F,0xF7,0xA6,0x8D,0x51,0xC3,0xF0, +0x9D,0x74,0xB7,0xA9,0x3E,0xA0,0xA5,0xFF,0xB6,0x49,0x03,0x13,0xDA,0x22,0xCC,0xED, +0x71,0x82,0x2B,0x99,0xCF,0x3A,0xB7,0xF5,0x2D,0x72,0xC8, +}; + + +/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ +/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ + + +const unsigned char ValiCert_Class_2_VA_certificate[747]={ +0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30, +0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74, +0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61, +0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33, +0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72, +0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69, +0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36, +0x32,0x36,0x30,0x30,0x31,0x39,0x35,0x34,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32, +0x36,0x30,0x30,0x31,0x39,0x35,0x34,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06, +0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69, +0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03, +0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x32,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C, +0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E, +0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02, +0x81,0x81,0x00,0xCE,0x3A,0x71,0xCA,0xE5,0xAB,0xC8,0x59,0x92,0x55,0xD7,0xAB,0xD8, +0x74,0x0E,0xF9,0xEE,0xD9,0xF6,0x55,0x47,0x59,0x65,0x47,0x0E,0x05,0x55,0xDC,0xEB, +0x98,0x36,0x3C,0x5C,0x53,0x5D,0xD3,0x30,0xCF,0x38,0xEC,0xBD,0x41,0x89,0xED,0x25, +0x42,0x09,0x24,0x6B,0x0A,0x5E,0xB3,0x7C,0xDD,0x52,0x2D,0x4C,0xE6,0xD4,0xD6,0x7D, +0x5A,0x59,0xA9,0x65,0xD4,0x49,0x13,0x2D,0x24,0x4D,0x1C,0x50,0x6F,0xB5,0xC1,0x85, +0x54,0x3B,0xFE,0x71,0xE4,0xD3,0x5C,0x42,0xF9,0x80,0xE0,0x91,0x1A,0x0A,0x5B,0x39, +0x36,0x67,0xF3,0x3F,0x55,0x7C,0x1B,0x3F,0xB4,0x5F,0x64,0x73,0x34,0xE3,0xB4,0x12, +0xBF,0x87,0x64,0xF8,0xDA,0x12,0xFF,0x37,0x27,0xC1,0xB3,0x43,0xBB,0xEF,0x7B,0x6E, +0x2E,0x69,0xF7,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x3B,0x7F,0x50,0x6F,0x6F, +0x50,0x94,0x99,0x49,0x62,0x38,0x38,0x1F,0x4B,0xF8,0xA5,0xC8,0x3E,0xA7,0x82,0x81, +0xF6,0x2B,0xC7,0xE8,0xC5,0xCE,0xE8,0x3A,0x10,0x82,0xCB,0x18,0x00,0x8E,0x4D,0xBD, +0xA8,0x58,0x7F,0xA1,0x79,0x00,0xB5,0xBB,0xE9,0x8D,0xAF,0x41,0xD9,0x0F,0x34,0xEE, +0x21,0x81,0x19,0xA0,0x32,0x49,0x28,0xF4,0xC4,0x8E,0x56,0xD5,0x52,0x33,0xFD,0x50, +0xD5,0x7E,0x99,0x6C,0x03,0xE4,0xC9,0x4C,0xFC,0xCB,0x6C,0xAB,0x66,0xB3,0x4A,0x21, +0x8C,0xE5,0xB5,0x0C,0x32,0x3E,0x10,0xB2,0xCC,0x6C,0xA1,0xDC,0x9A,0x98,0x4C,0x02, +0x5B,0xF3,0xCE,0xB9,0x9E,0xA5,0x72,0x0E,0x4A,0xB7,0x3F,0x3C,0xE6,0x16,0x68,0xF8, +0xBE,0xED,0x74,0x4C,0xBC,0x5B,0xD5,0x62,0x1F,0x43,0xDD, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */ +/* issuer :/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */ + + +const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority_certificate[576]={ +0x30,0x82,0x02,0x3C,0x30,0x82,0x01,0xA5,0x02,0x10,0x3C,0x91,0x31,0xCB,0x1F,0xF6, +0xD0,0x1B,0x0E,0x9A,0xB8,0xD0,0x44,0xBF,0x12,0xBE,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x5F,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x37,0x30,0x35,0x06,0x03,0x55,0x04,0x0B,0x13,0x2E,0x43,0x6C,0x61,0x73, +0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61, +0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x36, +0x30,0x31,0x32,0x39,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x38,0x30, +0x38,0x30,0x32,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x5F,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55, +0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x37,0x30,0x35,0x06,0x03,0x55,0x04,0x0B,0x13,0x2E,0x43,0x6C,0x61, +0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D, +0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x81,0x9F,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D, +0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xC9,0x5C,0x59,0x9E,0xF2,0x1B,0x8A,0x01, +0x14,0xB4,0x10,0xDF,0x04,0x40,0xDB,0xE3,0x57,0xAF,0x6A,0x45,0x40,0x8F,0x84,0x0C, +0x0B,0xD1,0x33,0xD9,0xD9,0x11,0xCF,0xEE,0x02,0x58,0x1F,0x25,0xF7,0x2A,0xA8,0x44, +0x05,0xAA,0xEC,0x03,0x1F,0x78,0x7F,0x9E,0x93,0xB9,0x9A,0x00,0xAA,0x23,0x7D,0xD6, +0xAC,0x85,0xA2,0x63,0x45,0xC7,0x72,0x27,0xCC,0xF4,0x4C,0xC6,0x75,0x71,0xD2,0x39, +0xEF,0x4F,0x42,0xF0,0x75,0xDF,0x0A,0x90,0xC6,0x8E,0x20,0x6F,0x98,0x0F,0xF8,0xAC, +0x23,0x5F,0x70,0x29,0x36,0xA4,0xC9,0x86,0xE7,0xB1,0x9A,0x20,0xCB,0x53,0xA5,0x85, +0xE7,0x3D,0xBE,0x7D,0x9A,0xFE,0x24,0x45,0x33,0xDC,0x76,0x15,0xED,0x0F,0xA2,0x71, +0x64,0x4C,0x65,0x2E,0x81,0x68,0x45,0xA7,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00, +0x10,0x72,0x52,0xA9,0x05,0x14,0x19,0x32,0x08,0x41,0xF0,0xC5,0x6B,0x0A,0xCC,0x7E, +0x0F,0x21,0x19,0xCD,0xE4,0x67,0xDC,0x5F,0xA9,0x1B,0xE6,0xCA,0xE8,0x73,0x9D,0x22, +0xD8,0x98,0x6E,0x73,0x03,0x61,0x91,0xC5,0x7C,0xB0,0x45,0x40,0x6E,0x44,0x9D,0x8D, +0xB0,0xB1,0x96,0x74,0x61,0x2D,0x0D,0xA9,0x45,0xD2,0xA4,0x92,0x2A,0xD6,0x9A,0x75, +0x97,0x6E,0x3F,0x53,0xFD,0x45,0x99,0x60,0x1D,0xA8,0x2B,0x4C,0xF9,0x5E,0xA7,0x09, +0xD8,0x75,0x30,0xD7,0xD2,0x65,0x60,0x3D,0x67,0xD6,0x48,0x55,0x75,0x69,0x3F,0x91, +0xF5,0x48,0x0B,0x47,0x69,0x22,0x69,0x82,0x96,0xBE,0xC9,0xC8,0x38,0x86,0x4A,0x7A, +0x2C,0x73,0x19,0x48,0x69,0x4E,0x6B,0x7C,0x65,0xBF,0x0F,0xFC,0x70,0xCE,0x88,0x90, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network */ +/* issuer :/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network */ + + +const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority___G2_certificate[774]={ +0x30,0x82,0x03,0x02,0x30,0x82,0x02,0x6B,0x02,0x10,0x7D,0xD9,0xFE,0x07,0xCF,0xA8, +0x1E,0xB7,0x10,0x79,0x67,0xFB,0xA7,0x89,0x34,0xC6,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xC1,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55, +0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x3C,0x30,0x3A,0x06,0x03,0x55,0x04,0x0B,0x13,0x33,0x43,0x6C,0x61, +0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D, +0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32, +0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x31, +0x39,0x39,0x38,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69, +0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D, +0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20, +0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x30,0x1E,0x17, +0x0D,0x39,0x38,0x30,0x35,0x31,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D, +0x32,0x38,0x30,0x38,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xC1, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30, +0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3C,0x30,0x3A,0x06,0x03,0x55,0x04,0x0B,0x13, +0x33,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x32,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x38,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74, +0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79, +0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xCC,0x5E, +0xD1,0x11,0x5D,0x5C,0x69,0xD0,0xAB,0xD3,0xB9,0x6A,0x4C,0x99,0x1F,0x59,0x98,0x30, +0x8E,0x16,0x85,0x20,0x46,0x6D,0x47,0x3F,0xD4,0x85,0x20,0x84,0xE1,0x6D,0xB3,0xF8, +0xA4,0xED,0x0C,0xF1,0x17,0x0F,0x3B,0xF9,0xA7,0xF9,0x25,0xD7,0xC1,0xCF,0x84,0x63, +0xF2,0x7C,0x63,0xCF,0xA2,0x47,0xF2,0xC6,0x5B,0x33,0x8E,0x64,0x40,0x04,0x68,0xC1, +0x80,0xB9,0x64,0x1C,0x45,0x77,0xC7,0xD8,0x6E,0xF5,0x95,0x29,0x3C,0x50,0xE8,0x34, +0xD7,0x78,0x1F,0xA8,0xBA,0x6D,0x43,0x91,0x95,0x8F,0x45,0x57,0x5E,0x7E,0xC5,0xFB, +0xCA,0xA4,0x04,0xEB,0xEA,0x97,0x37,0x54,0x30,0x6F,0xBB,0x01,0x47,0x32,0x33,0xCD, +0xDC,0x57,0x9B,0x64,0x69,0x61,0xF8,0x9B,0x1D,0x1C,0x89,0x4F,0x5C,0x67,0x02,0x03, +0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x03,0x81,0x81,0x00,0x51,0x4D,0xCD,0xBE,0x5C,0xCB,0x98,0x19,0x9C,0x15, +0xB2,0x01,0x39,0x78,0x2E,0x4D,0x0F,0x67,0x70,0x70,0x99,0xC6,0x10,0x5A,0x94,0xA4, +0x53,0x4D,0x54,0x6D,0x2B,0xAF,0x0D,0x5D,0x40,0x8B,0x64,0xD3,0xD7,0xEE,0xDE,0x56, +0x61,0x92,0x5F,0xA6,0xC4,0x1D,0x10,0x61,0x36,0xD3,0x2C,0x27,0x3C,0xE8,0x29,0x09, +0xB9,0x11,0x64,0x74,0xCC,0xB5,0x73,0x9F,0x1C,0x48,0xA9,0xBC,0x61,0x01,0xEE,0xE2, +0x17,0xA6,0x0C,0xE3,0x40,0x08,0x3B,0x0E,0xE7,0xEB,0x44,0x73,0x2A,0x9A,0xF1,0x69, +0x92,0xEF,0x71,0x14,0xC3,0x39,0xAC,0x71,0xA7,0x91,0x09,0x6F,0xE4,0x71,0x06,0xB3, +0xBA,0x59,0x57,0x26,0x79,0x00,0xF6,0xF8,0x0D,0xA2,0x33,0x30,0x28,0xD4,0xAA,0x58, +0xA0,0x9D,0x9D,0x69,0x91,0xFD, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3 */ + + +const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority___G3_certificate[1054]={ +0x30,0x82,0x04,0x1A,0x30,0x82,0x03,0x02,0x02,0x11,0x00,0x9B,0x7E,0x06,0x49,0xA3, +0x3E,0x62,0xB9,0xD5,0xEE,0x90,0x48,0x71,0x29,0xEF,0x57,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xCA,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65, +0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74, +0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79, +0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C, +0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31,0x30,0x30, +0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31,0x36, +0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E, +0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20, +0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72, +0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30, +0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xCB,0xBA,0x9C,0x52,0xFC,0x78,0x1F,0x1A,0x1E,0x6F,0x1B, +0x37,0x73,0xBD,0xF8,0xC9,0x6B,0x94,0x12,0x30,0x4F,0xF0,0x36,0x47,0xF5,0xD0,0x91, +0x0A,0xF5,0x17,0xC8,0xA5,0x61,0xC1,0x16,0x40,0x4D,0xFB,0x8A,0x61,0x90,0xE5,0x76, +0x20,0xC1,0x11,0x06,0x7D,0xAB,0x2C,0x6E,0xA6,0xF5,0x11,0x41,0x8E,0xFA,0x2D,0xAD, +0x2A,0x61,0x59,0xA4,0x67,0x26,0x4C,0xD0,0xE8,0xBC,0x52,0x5B,0x70,0x20,0x04,0x58, +0xD1,0x7A,0xC9,0xA4,0x69,0xBC,0x83,0x17,0x64,0xAD,0x05,0x8B,0xBC,0xD0,0x58,0xCE, +0x8D,0x8C,0xF5,0xEB,0xF0,0x42,0x49,0x0B,0x9D,0x97,0x27,0x67,0x32,0x6E,0xE1,0xAE, +0x93,0x15,0x1C,0x70,0xBC,0x20,0x4D,0x2F,0x18,0xDE,0x92,0x88,0xE8,0x6C,0x85,0x57, +0x11,0x1A,0xE9,0x7E,0xE3,0x26,0x11,0x54,0xA2,0x45,0x96,0x55,0x83,0xCA,0x30,0x89, +0xE8,0xDC,0xD8,0xA3,0xED,0x2A,0x80,0x3F,0x7F,0x79,0x65,0x57,0x3E,0x15,0x20,0x66, +0x08,0x2F,0x95,0x93,0xBF,0xAA,0x47,0x2F,0xA8,0x46,0x97,0xF0,0x12,0xE2,0xFE,0xC2, +0x0A,0x2B,0x51,0xE6,0x76,0xE6,0xB7,0x46,0xB7,0xE2,0x0D,0xA6,0xCC,0xA8,0xC3,0x4C, +0x59,0x55,0x89,0xE6,0xE8,0x53,0x5C,0x1C,0xEA,0x9D,0xF0,0x62,0x16,0x0B,0xA7,0xC9, +0x5F,0x0C,0xF0,0xDE,0xC2,0x76,0xCE,0xAF,0xF7,0x6A,0xF2,0xFA,0x41,0xA6,0xA2,0x33, +0x14,0xC9,0xE5,0x7A,0x63,0xD3,0x9E,0x62,0x37,0xD5,0x85,0x65,0x9E,0x0E,0xE6,0x53, +0x24,0x74,0x1B,0x5E,0x1D,0x12,0x53,0x5B,0xC7,0x2C,0xE7,0x83,0x49,0x3B,0x15,0xAE, +0x8A,0x68,0xB9,0x57,0x97,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x11,0x14, +0x96,0xC1,0xAB,0x92,0x08,0xF7,0x3F,0x2F,0xC9,0xB2,0xFE,0xE4,0x5A,0x9F,0x64,0xDE, +0xDB,0x21,0x4F,0x86,0x99,0x34,0x76,0x36,0x57,0xDD,0xD0,0x15,0x2F,0xC5,0xAD,0x7F, +0x15,0x1F,0x37,0x62,0x73,0x3E,0xD4,0xE7,0x5F,0xCE,0x17,0x03,0xDB,0x35,0xFA,0x2B, +0xDB,0xAE,0x60,0x09,0x5F,0x1E,0x5F,0x8F,0x6E,0xBB,0x0B,0x3D,0xEA,0x5A,0x13,0x1E, +0x0C,0x60,0x6F,0xB5,0xC0,0xB5,0x23,0x22,0x2E,0x07,0x0B,0xCB,0xA9,0x74,0xCB,0x47, +0xBB,0x1D,0xC1,0xD7,0xA5,0x6B,0xCC,0x2F,0xD2,0x42,0xFD,0x49,0xDD,0xA7,0x89,0xCF, +0x53,0xBA,0xDA,0x00,0x5A,0x28,0xBF,0x82,0xDF,0xF8,0xBA,0x13,0x1D,0x50,0x86,0x82, +0xFD,0x8E,0x30,0x8F,0x29,0x46,0xB0,0x1E,0x3D,0x35,0xDA,0x38,0x62,0x16,0x18,0x4A, +0xAD,0xE6,0xB6,0x51,0x6C,0xDE,0xAF,0x62,0xEB,0x01,0xD0,0x1E,0x24,0xFE,0x7A,0x8F, +0x12,0x1A,0x12,0x68,0xB8,0xFB,0x66,0x99,0x14,0x14,0x45,0x5C,0xAE,0xE7,0xAE,0x69, +0x17,0x81,0x2B,0x5A,0x37,0xC9,0x5E,0x2A,0xF4,0xC6,0xE2,0xA1,0x5C,0x54,0x9B,0xA6, +0x54,0x00,0xCF,0xF0,0xF1,0xC1,0xC7,0x98,0x30,0x1A,0x3B,0x36,0x16,0xDB,0xA3,0x6E, +0xEA,0xFD,0xAD,0xB2,0xC2,0xDA,0xEF,0x02,0x47,0x13,0x8A,0xC0,0xF1,0xB3,0x31,0xAD, +0x4F,0x1C,0xE1,0x4F,0x9C,0xAF,0x0F,0x0C,0x9D,0xF7,0x78,0x0D,0xD8,0xF4,0x35,0x56, +0x80,0xDA,0xB7,0x6D,0x17,0x8F,0x9D,0x1E,0x81,0x64,0xE1,0xFE,0xC5,0x45,0xBA,0xAD, +0x6B,0xB9,0x0A,0x7A,0x4E,0x4F,0x4B,0x84,0xEE,0x4B,0xF1,0x7D,0xDD,0x11, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4 */ + + +const unsigned char VeriSign_Class_3_Public_Primary_Certification_Authority___G4_certificate[904]={ +0x30,0x82,0x03,0x84,0x30,0x82,0x03,0x0A,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x2F, +0x80,0xFE,0x23,0x8C,0x0E,0x22,0x0F,0x48,0x67,0x12,0x28,0x91,0x87,0xAC,0xB3,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0xCA,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65, +0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31, +0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75, +0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C, +0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62, +0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x34,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31, +0x30,0x35,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31, +0x38,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F, +0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29, +0x20,0x32,0x30,0x30,0x37,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F, +0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45, +0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63, +0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69, +0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79, +0x20,0x2D,0x20,0x47,0x34,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D, +0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0xA7,0x56,0x7A, +0x7C,0x52,0xDA,0x64,0x9B,0x0E,0x2D,0x5C,0xD8,0x5E,0xAC,0x92,0x3D,0xFE,0x01,0xE6, +0x19,0x4A,0x3D,0x14,0x03,0x4B,0xFA,0x60,0x27,0x20,0xD9,0x83,0x89,0x69,0xFA,0x54, +0xC6,0x9A,0x18,0x5E,0x55,0x2A,0x64,0xDE,0x06,0xF6,0x8D,0x4A,0x3B,0xAD,0x10,0x3C, +0x65,0x3D,0x90,0x88,0x04,0x89,0xE0,0x30,0x61,0xB3,0xAE,0x5D,0x01,0xA7,0x7B,0xDE, +0x7C,0xB2,0xBE,0xCA,0x65,0x61,0x00,0x86,0xAE,0xDA,0x8F,0x7B,0xD0,0x89,0xAD,0x4D, +0x1D,0x59,0x9A,0x41,0xB1,0xBC,0x47,0x80,0xDC,0x9E,0x62,0xC3,0xF9,0xA3,0x81,0xB2, +0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01,0x0C, +0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30,0x59,0x30,0x57,0x30,0x55,0x16,0x09, +0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66,0x30,0x21,0x30,0x1F,0x30,0x07,0x06, +0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F,0xE5,0xD3,0x1A,0x86,0xAC,0x8D,0x8E, +0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C,0x7B,0x19,0x2E,0x30,0x25,0x16,0x23, +0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F,0x67,0x6F,0x2E,0x76,0x65,0x72,0x69, +0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F,0x76,0x73,0x6C,0x6F,0x67,0x6F,0x2E, +0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xB3,0x16, +0x91,0xFD,0xEE,0xA6,0x6E,0xE4,0xB5,0x2E,0x49,0x8F,0x87,0x78,0x81,0x80,0xEC,0xE5, +0xB1,0xB5,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x68, +0x00,0x30,0x65,0x02,0x30,0x66,0x21,0x0C,0x18,0x26,0x60,0x5A,0x38,0x7B,0x56,0x42, +0xE0,0xA7,0xFC,0x36,0x84,0x51,0x91,0x20,0x2C,0x76,0x4D,0x43,0x3D,0xC4,0x1D,0x84, +0x23,0xD0,0xAC,0xD6,0x7C,0x35,0x06,0xCE,0xCD,0x69,0xBD,0x90,0x0D,0xDB,0x6C,0x48, +0x42,0x1D,0x0E,0xAA,0x42,0x02,0x31,0x00,0x9C,0x3D,0x48,0x39,0x23,0x39,0x58,0x1A, +0x15,0x12,0x59,0x6A,0x9E,0xEF,0xD5,0x59,0xB2,0x1D,0x52,0x2C,0x99,0x71,0xCD,0xC7, +0x29,0xDF,0x1B,0x2A,0x61,0x7B,0x71,0xD1,0xDE,0xF3,0xC0,0xE5,0x0D,0x3A,0x4A,0xAA, +0x2D,0xA7,0xD8,0x86,0x2A,0xDD,0x2E,0x10, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 */ + + +const unsigned char VeriSign_Class_3_Public_Primary_Certification_Authority___G5_certificate[1239]={ +0x30,0x82,0x04,0xD3,0x30,0x82,0x03,0xBB,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x18, +0xDA,0xD1,0x9E,0x26,0x7D,0xE8,0xBB,0x4A,0x21,0x58,0xCD,0xCC,0x6B,0x3B,0x4A,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0xCA,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B, +0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74, +0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04, +0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72, +0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20, +0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20, +0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x35,0x30,0x1E,0x17,0x0D,0x30, +0x36,0x31,0x31,0x30,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36, +0x30,0x37,0x31,0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65, +0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31, +0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75, +0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C, +0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62, +0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x35,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00, +0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAF,0x24,0x08,0x08,0x29,0x7A,0x35, +0x9E,0x60,0x0C,0xAA,0xE7,0x4B,0x3B,0x4E,0xDC,0x7C,0xBC,0x3C,0x45,0x1C,0xBB,0x2B, +0xE0,0xFE,0x29,0x02,0xF9,0x57,0x08,0xA3,0x64,0x85,0x15,0x27,0xF5,0xF1,0xAD,0xC8, +0x31,0x89,0x5D,0x22,0xE8,0x2A,0xAA,0xA6,0x42,0xB3,0x8F,0xF8,0xB9,0x55,0xB7,0xB1, +0xB7,0x4B,0xB3,0xFE,0x8F,0x7E,0x07,0x57,0xEC,0xEF,0x43,0xDB,0x66,0x62,0x15,0x61, +0xCF,0x60,0x0D,0xA4,0xD8,0xDE,0xF8,0xE0,0xC3,0x62,0x08,0x3D,0x54,0x13,0xEB,0x49, +0xCA,0x59,0x54,0x85,0x26,0xE5,0x2B,0x8F,0x1B,0x9F,0xEB,0xF5,0xA1,0x91,0xC2,0x33, +0x49,0xD8,0x43,0x63,0x6A,0x52,0x4B,0xD2,0x8F,0xE8,0x70,0x51,0x4D,0xD1,0x89,0x69, +0x7B,0xC7,0x70,0xF6,0xB3,0xDC,0x12,0x74,0xDB,0x7B,0x5D,0x4B,0x56,0xD3,0x96,0xBF, +0x15,0x77,0xA1,0xB0,0xF4,0xA2,0x25,0xF2,0xAF,0x1C,0x92,0x67,0x18,0xE5,0xF4,0x06, +0x04,0xEF,0x90,0xB9,0xE4,0x00,0xE4,0xDD,0x3A,0xB5,0x19,0xFF,0x02,0xBA,0xF4,0x3C, +0xEE,0xE0,0x8B,0xEB,0x37,0x8B,0xEC,0xF4,0xD7,0xAC,0xF2,0xF6,0xF0,0x3D,0xAF,0xDD, +0x75,0x91,0x33,0x19,0x1D,0x1C,0x40,0xCB,0x74,0x24,0x19,0x21,0x93,0xD9,0x14,0xFE, +0xAC,0x2A,0x52,0xC7,0x8F,0xD5,0x04,0x49,0xE4,0x8D,0x63,0x47,0x88,0x3C,0x69,0x83, +0xCB,0xFE,0x47,0xBD,0x2B,0x7E,0x4F,0xC5,0x95,0xAE,0x0E,0x9D,0xD4,0xD1,0x43,0xC0, +0x67,0x73,0xE3,0x14,0x08,0x7E,0xE5,0x3F,0x9F,0x73,0xB8,0x33,0x0A,0xCF,0x5D,0x3F, +0x34,0x87,0x96,0x8A,0xEE,0x53,0xE8,0x25,0x15,0x02,0x03,0x01,0x00,0x01,0xA3,0x81, +0xB2,0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04, +0x04,0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01, +0x0C,0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30,0x59,0x30,0x57,0x30,0x55,0x16, +0x09,0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66,0x30,0x21,0x30,0x1F,0x30,0x07, +0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F,0xE5,0xD3,0x1A,0x86,0xAC,0x8D, +0x8E,0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C,0x7B,0x19,0x2E,0x30,0x25,0x16, +0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F,0x67,0x6F,0x2E,0x76,0x65,0x72, +0x69,0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F,0x76,0x73,0x6C,0x6F,0x67,0x6F, +0x2E,0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7F, +0xD3,0x65,0xA7,0xC2,0xDD,0xEC,0xBB,0xF0,0x30,0x09,0xF3,0x43,0x39,0xFA,0x02,0xAF, +0x33,0x31,0x33,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x93,0x24,0x4A,0x30,0x5F,0x62,0xCF,0xD8,0x1A, +0x98,0x2F,0x3D,0xEA,0xDC,0x99,0x2D,0xBD,0x77,0xF6,0xA5,0x79,0x22,0x38,0xEC,0xC4, +0xA7,0xA0,0x78,0x12,0xAD,0x62,0x0E,0x45,0x70,0x64,0xC5,0xE7,0x97,0x66,0x2D,0x98, +0x09,0x7E,0x5F,0xAF,0xD6,0xCC,0x28,0x65,0xF2,0x01,0xAA,0x08,0x1A,0x47,0xDE,0xF9, +0xF9,0x7C,0x92,0x5A,0x08,0x69,0x20,0x0D,0xD9,0x3E,0x6D,0x6E,0x3C,0x0D,0x6E,0xD8, +0xE6,0x06,0x91,0x40,0x18,0xB9,0xF8,0xC1,0xED,0xDF,0xDB,0x41,0xAA,0xE0,0x96,0x20, +0xC9,0xCD,0x64,0x15,0x38,0x81,0xC9,0x94,0xEE,0xA2,0x84,0x29,0x0B,0x13,0x6F,0x8E, +0xDB,0x0C,0xDD,0x25,0x02,0xDB,0xA4,0x8B,0x19,0x44,0xD2,0x41,0x7A,0x05,0x69,0x4A, +0x58,0x4F,0x60,0xCA,0x7E,0x82,0x6A,0x0B,0x02,0xAA,0x25,0x17,0x39,0xB5,0xDB,0x7F, +0xE7,0x84,0x65,0x2A,0x95,0x8A,0xBD,0x86,0xDE,0x5E,0x81,0x16,0x83,0x2D,0x10,0xCC, +0xDE,0xFD,0xA8,0x82,0x2A,0x6D,0x28,0x1F,0x0D,0x0B,0xC4,0xE5,0xE7,0x1A,0x26,0x19, +0xE1,0xF4,0x11,0x6F,0x10,0xB5,0x95,0xFC,0xE7,0x42,0x05,0x32,0xDB,0xCE,0x9D,0x51, +0x5E,0x28,0xB6,0x9E,0x85,0xD3,0x5B,0xEF,0xA5,0x7D,0x45,0x40,0x72,0x8E,0xB7,0x0E, +0x6B,0x0E,0x06,0xFB,0x33,0x35,0x48,0x71,0xB8,0x9D,0x27,0x8B,0xC4,0x65,0x5F,0x0D, +0x86,0x76,0x9C,0x44,0x7A,0xF6,0x95,0x5C,0xF6,0x5D,0x32,0x08,0x33,0xA4,0x54,0xB6, +0x18,0x3F,0x68,0x5C,0xF2,0x42,0x4A,0x85,0x38,0x54,0x83,0x5F,0xD1,0xE8,0x2C,0xF2, +0xAC,0x11,0xD6,0xA8,0xED,0x63,0x6A, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 4 Public Primary Certification Authority - G3 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 4 Public Primary Certification Authority - G3 */ + + +const unsigned char Verisign_Class_4_Public_Primary_Certification_Authority___G3_certificate[1054]={ +0x30,0x82,0x04,0x1A,0x30,0x82,0x03,0x02,0x02,0x11,0x00,0xEC,0xA0,0xA7,0x8B,0x6E, +0x75,0x6A,0x01,0xCF,0xC4,0x7C,0xCC,0x2F,0x94,0x5E,0xD7,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xCA,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65, +0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74, +0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79, +0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x34,0x20,0x50,0x75,0x62,0x6C, +0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31,0x30,0x30, +0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31,0x36, +0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E, +0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20, +0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72, +0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30, +0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x34,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xAD,0xCB,0xA5,0x11,0x69,0xC6,0x59,0xAB,0xF1,0x8F,0xB5, +0x19,0x0F,0x56,0xCE,0xCC,0xB5,0x1F,0x20,0xE4,0x9E,0x26,0x25,0x4B,0xE0,0x73,0x65, +0x89,0x59,0xDE,0xD0,0x83,0xE4,0xF5,0x0F,0xB5,0xBB,0xAD,0xF1,0x7C,0xE8,0x21,0xFC, +0xE4,0xE8,0x0C,0xEE,0x7C,0x45,0x22,0x19,0x76,0x92,0xB4,0x13,0xB7,0x20,0x5B,0x09, +0xFA,0x61,0xAE,0xA8,0xF2,0xA5,0x8D,0x85,0xC2,0x2A,0xD6,0xDE,0x66,0x36,0xD2,0x9B, +0x02,0xF4,0xA8,0x92,0x60,0x7C,0x9C,0x69,0xB4,0x8F,0x24,0x1E,0xD0,0x86,0x52,0xF6, +0x32,0x9C,0x41,0x58,0x1E,0x22,0xBD,0xCD,0x45,0x62,0x95,0x08,0x6E,0xD0,0x66,0xDD, +0x53,0xA2,0xCC,0xF0,0x10,0xDC,0x54,0x73,0x8B,0x04,0xA1,0x46,0x33,0x33,0x5C,0x17, +0x40,0xB9,0x9E,0x4D,0xD3,0xF3,0xBE,0x55,0x83,0xE8,0xB1,0x89,0x8E,0x5A,0x7C,0x9A, +0x96,0x22,0x90,0x3B,0x88,0x25,0xF2,0xD2,0x53,0x88,0x02,0x0C,0x0B,0x78,0xF2,0xE6, +0x37,0x17,0x4B,0x30,0x46,0x07,0xE4,0x80,0x6D,0xA6,0xD8,0x96,0x2E,0xE8,0x2C,0xF8, +0x11,0xB3,0x38,0x0D,0x66,0xA6,0x9B,0xEA,0xC9,0x23,0x5B,0xDB,0x8E,0xE2,0xF3,0x13, +0x8E,0x1A,0x59,0x2D,0xAA,0x02,0xF0,0xEC,0xA4,0x87,0x66,0xDC,0xC1,0x3F,0xF5,0xD8, +0xB9,0xF4,0xEC,0x82,0xC6,0xD2,0x3D,0x95,0x1D,0xE5,0xC0,0x4F,0x84,0xC9,0xD9,0xA3, +0x44,0x28,0x06,0x6A,0xD7,0x45,0xAC,0xF0,0x6B,0x6A,0xEF,0x4E,0x5F,0xF8,0x11,0x82, +0x1E,0x38,0x63,0x34,0x66,0x50,0xD4,0x3E,0x93,0x73,0xFA,0x30,0xC3,0x66,0xAD,0xFF, +0x93,0x2D,0x97,0xEF,0x03,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x8F,0xFA, +0x25,0x6B,0x4F,0x5B,0xE4,0xA4,0x4E,0x27,0x55,0xAB,0x22,0x15,0x59,0x3C,0xCA,0xB5, +0x0A,0xD4,0x4A,0xDB,0xAB,0xDD,0xA1,0x5F,0x53,0xC5,0xA0,0x57,0x39,0xC2,0xCE,0x47, +0x2B,0xBE,0x3A,0xC8,0x56,0xBF,0xC2,0xD9,0x27,0x10,0x3A,0xB1,0x05,0x3C,0xC0,0x77, +0x31,0xBB,0x3A,0xD3,0x05,0x7B,0x6D,0x9A,0x1C,0x30,0x8C,0x80,0xCB,0x93,0x93,0x2A, +0x83,0xAB,0x05,0x51,0x82,0x02,0x00,0x11,0x67,0x6B,0xF3,0x88,0x61,0x47,0x5F,0x03, +0x93,0xD5,0x5B,0x0D,0xE0,0xF1,0xD4,0xA1,0x32,0x35,0x85,0xB2,0x3A,0xDB,0xB0,0x82, +0xAB,0xD1,0xCB,0x0A,0xBC,0x4F,0x8C,0x5B,0xC5,0x4B,0x00,0x3B,0x1F,0x2A,0x82,0xA6, +0x7E,0x36,0x85,0xDC,0x7E,0x3C,0x67,0x00,0xB5,0xE4,0x3B,0x52,0xE0,0xA8,0xEB,0x5D, +0x15,0xF9,0xC6,0x6D,0xF0,0xAD,0x1D,0x0E,0x85,0xB7,0xA9,0x9A,0x73,0x14,0x5A,0x5B, +0x8F,0x41,0x28,0xC0,0xD5,0xE8,0x2D,0x4D,0xA4,0x5E,0xCD,0xAA,0xD9,0xED,0xCE,0xDC, +0xD8,0xD5,0x3C,0x42,0x1D,0x17,0xC1,0x12,0x5D,0x45,0x38,0xC3,0x38,0xF3,0xFC,0x85, +0x2E,0x83,0x46,0x48,0xB2,0xD7,0x20,0x5F,0x92,0x36,0x8F,0xE7,0x79,0x0F,0x98,0x5E, +0x99,0xE8,0xF0,0xD0,0xA4,0xBB,0xF5,0x53,0xBD,0x2A,0xCE,0x59,0xB0,0xAF,0x6E,0x7F, +0x6C,0xBB,0xD2,0x1E,0x00,0xB0,0x21,0xED,0xF8,0x41,0x62,0x82,0xB9,0xD8,0xB2,0xC4, +0xBB,0x46,0x50,0xF3,0x31,0xC5,0x8F,0x01,0xA8,0x74,0xEB,0xF5,0x78,0x27,0xDA,0xE7, +0xF7,0x66,0x43,0xF3,0x9E,0x83,0x3E,0x20,0xAA,0xC3,0x35,0x60,0x91,0xCE, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority */ + + +const unsigned char VeriSign_Universal_Root_Certification_Authority_certificate[1213]={ +0x30,0x82,0x04,0xB9,0x30,0x82,0x03,0xA1,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x40, +0x1A,0xC4,0x64,0x21,0xB3,0x13,0x21,0x03,0x0E,0xBB,0xE4,0x12,0x1A,0xC5,0x1D,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81, +0xBD,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B, +0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74, +0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04, +0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72, +0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20, +0x6F,0x6E,0x6C,0x79,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x03,0x13,0x2F,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61, +0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E, +0x17,0x0D,0x30,0x38,0x30,0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17, +0x0D,0x33,0x37,0x31,0x32,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81, +0xBD,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B, +0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74, +0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04, +0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72, +0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20, +0x6F,0x6E,0x6C,0x79,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x03,0x13,0x2F,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61, +0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82, +0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05, +0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC7, +0x61,0x37,0x5E,0xB1,0x01,0x34,0xDB,0x62,0xD7,0x15,0x9B,0xFF,0x58,0x5A,0x8C,0x23, +0x23,0xD6,0x60,0x8E,0x91,0xD7,0x90,0x98,0x83,0x7A,0xE6,0x58,0x19,0x38,0x8C,0xC5, +0xF6,0xE5,0x64,0x85,0xB4,0xA2,0x71,0xFB,0xED,0xBD,0xB9,0xDA,0xCD,0x4D,0x00,0xB4, +0xC8,0x2D,0x73,0xA5,0xC7,0x69,0x71,0x95,0x1F,0x39,0x3C,0xB2,0x44,0x07,0x9C,0xE8, +0x0E,0xFA,0x4D,0x4A,0xC4,0x21,0xDF,0x29,0x61,0x8F,0x32,0x22,0x61,0x82,0xC5,0x87, +0x1F,0x6E,0x8C,0x7C,0x5F,0x16,0x20,0x51,0x44,0xD1,0x70,0x4F,0x57,0xEA,0xE3,0x1C, +0xE3,0xCC,0x79,0xEE,0x58,0xD8,0x0E,0xC2,0xB3,0x45,0x93,0xC0,0x2C,0xE7,0x9A,0x17, +0x2B,0x7B,0x00,0x37,0x7A,0x41,0x33,0x78,0xE1,0x33,0xE2,0xF3,0x10,0x1A,0x7F,0x87, +0x2C,0xBE,0xF6,0xF5,0xF7,0x42,0xE2,0xE5,0xBF,0x87,0x62,0x89,0x5F,0x00,0x4B,0xDF, +0xC5,0xDD,0xE4,0x75,0x44,0x32,0x41,0x3A,0x1E,0x71,0x6E,0x69,0xCB,0x0B,0x75,0x46, +0x08,0xD1,0xCA,0xD2,0x2B,0x95,0xD0,0xCF,0xFB,0xB9,0x40,0x6B,0x64,0x8C,0x57,0x4D, +0xFC,0x13,0x11,0x79,0x84,0xED,0x5E,0x54,0xF6,0x34,0x9F,0x08,0x01,0xF3,0x10,0x25, +0x06,0x17,0x4A,0xDA,0xF1,0x1D,0x7A,0x66,0x6B,0x98,0x60,0x66,0xA4,0xD9,0xEF,0xD2, +0x2E,0x82,0xF1,0xF0,0xEF,0x09,0xEA,0x44,0xC9,0x15,0x6A,0xE2,0x03,0x6E,0x33,0xD3, +0xAC,0x9F,0x55,0x00,0xC7,0xF6,0x08,0x6A,0x94,0xB9,0x5F,0xDC,0xE0,0x33,0xF1,0x84, +0x60,0xF9,0x5B,0x27,0x11,0xB4,0xFC,0x16,0xF2,0xBB,0x56,0x6A,0x80,0x25,0x8D,0x02, +0x03,0x01,0x00,0x01,0xA3,0x81,0xB2,0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B, +0x06,0x01,0x05,0x05,0x07,0x01,0x0C,0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30, +0x59,0x30,0x57,0x30,0x55,0x16,0x09,0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66, +0x30,0x21,0x30,0x1F,0x30,0x07,0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F, +0xE5,0xD3,0x1A,0x86,0xAC,0x8D,0x8E,0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C, +0x7B,0x19,0x2E,0x30,0x25,0x16,0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F, +0x67,0x6F,0x2E,0x76,0x65,0x72,0x69,0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F, +0x76,0x73,0x6C,0x6F,0x67,0x6F,0x2E,0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D, +0x0E,0x04,0x16,0x04,0x14,0xB6,0x77,0xFA,0x69,0x48,0x47,0x9F,0x53,0x12,0xD5,0xC2, +0xEA,0x07,0x32,0x76,0x07,0xD1,0x97,0x07,0x19,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x4A,0xF8,0xF8, +0xB0,0x03,0xE6,0x2C,0x67,0x7B,0xE4,0x94,0x77,0x63,0xCC,0x6E,0x4C,0xF9,0x7D,0x0E, +0x0D,0xDC,0xC8,0xB9,0x35,0xB9,0x70,0x4F,0x63,0xFA,0x24,0xFA,0x6C,0x83,0x8C,0x47, +0x9D,0x3B,0x63,0xF3,0x9A,0xF9,0x76,0x32,0x95,0x91,0xB1,0x77,0xBC,0xAC,0x9A,0xBE, +0xB1,0xE4,0x31,0x21,0xC6,0x81,0x95,0x56,0x5A,0x0E,0xB1,0xC2,0xD4,0xB1,0xA6,0x59, +0xAC,0xF1,0x63,0xCB,0xB8,0x4C,0x1D,0x59,0x90,0x4A,0xEF,0x90,0x16,0x28,0x1F,0x5A, +0xAE,0x10,0xFB,0x81,0x50,0x38,0x0C,0x6C,0xCC,0xF1,0x3D,0xC3,0xF5,0x63,0xE3,0xB3, +0xE3,0x21,0xC9,0x24,0x39,0xE9,0xFD,0x15,0x66,0x46,0xF4,0x1B,0x11,0xD0,0x4D,0x73, +0xA3,0x7D,0x46,0xF9,0x3D,0xED,0xA8,0x5F,0x62,0xD4,0xF1,0x3F,0xF8,0xE0,0x74,0x57, +0x2B,0x18,0x9D,0x81,0xB4,0xC4,0x28,0xDA,0x94,0x97,0xA5,0x70,0xEB,0xAC,0x1D,0xBE, +0x07,0x11,0xF0,0xD5,0xDB,0xDD,0xE5,0x8C,0xF0,0xD5,0x32,0xB0,0x83,0xE6,0x57,0xE2, +0x8F,0xBF,0xBE,0xA1,0xAA,0xBF,0x3D,0x1D,0xB5,0xD4,0x38,0xEA,0xD7,0xB0,0x5C,0x3A, +0x4F,0x6A,0x3F,0x8F,0xC0,0x66,0x6C,0x63,0xAA,0xE9,0xD9,0xA4,0x16,0xF4,0x81,0xD1, +0x95,0x14,0x0E,0x7D,0xCD,0x95,0x34,0xD9,0xD2,0x8F,0x70,0x73,0x81,0x7B,0x9C,0x7E, +0xBD,0x98,0x61,0xD8,0x45,0x87,0x98,0x90,0xC5,0xEB,0x86,0x30,0xC6,0x35,0xBF,0xF0, +0xFF,0xC3,0x55,0x88,0x83,0x4B,0xEF,0x05,0x92,0x06,0x71,0xF2,0xB8,0x98,0x93,0xB7, +0xEC,0xCD,0x82,0x61,0xF1,0x38,0xE6,0x4F,0x97,0x98,0x2A,0x5A,0x8D, +}; + + +/* subject:/C=US/OU=www.xrampsecurity.com/O=XRamp Security Services Inc/CN=XRamp Global Certification Authority */ +/* issuer :/C=US/OU=www.xrampsecurity.com/O=XRamp Security Services Inc/CN=XRamp Global Certification Authority */ + + +const unsigned char XRamp_Global_CA_Root_certificate[1076]={ +0x30,0x82,0x04,0x30,0x30,0x82,0x03,0x18,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x50, +0x94,0x6C,0xEC,0x18,0xEA,0xD5,0x9C,0x4D,0xD5,0x97,0xEF,0x75,0x8F,0xA0,0xAD,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x82,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1E, +0x30,0x1C,0x06,0x03,0x55,0x04,0x0B,0x13,0x15,0x77,0x77,0x77,0x2E,0x78,0x72,0x61, +0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x2E,0x63,0x6F,0x6D,0x31,0x24, +0x30,0x22,0x06,0x03,0x55,0x04,0x0A,0x13,0x1B,0x58,0x52,0x61,0x6D,0x70,0x20,0x53, +0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73, +0x20,0x49,0x6E,0x63,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x58, +0x52,0x61,0x6D,0x70,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x31,0x31,0x30,0x31,0x31,0x37,0x31, +0x34,0x30,0x34,0x5A,0x17,0x0D,0x33,0x35,0x30,0x31,0x30,0x31,0x30,0x35,0x33,0x37, +0x31,0x39,0x5A,0x30,0x81,0x82,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0B,0x13,0x15,0x77,0x77, +0x77,0x2E,0x78,0x72,0x61,0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x2E, +0x63,0x6F,0x6D,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0A,0x13,0x1B,0x58,0x52, +0x61,0x6D,0x70,0x20,0x53,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x20,0x53,0x65,0x72, +0x76,0x69,0x63,0x65,0x73,0x20,0x49,0x6E,0x63,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55, +0x04,0x03,0x13,0x24,0x58,0x52,0x61,0x6D,0x70,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00, +0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0x98,0x24,0x1E,0xBD,0x15,0xB4,0xBA, +0xDF,0xC7,0x8C,0xA5,0x27,0xB6,0x38,0x0B,0x69,0xF3,0xB6,0x4E,0xA8,0x2C,0x2E,0x21, +0x1D,0x5C,0x44,0xDF,0x21,0x5D,0x7E,0x23,0x74,0xFE,0x5E,0x7E,0xB4,0x4A,0xB7,0xA6, +0xAD,0x1F,0xAE,0xE0,0x06,0x16,0xE2,0x9B,0x5B,0xD9,0x67,0x74,0x6B,0x5D,0x80,0x8F, +0x29,0x9D,0x86,0x1B,0xD9,0x9C,0x0D,0x98,0x6D,0x76,0x10,0x28,0x58,0xE4,0x65,0xB0, +0x7F,0x4A,0x98,0x79,0x9F,0xE0,0xC3,0x31,0x7E,0x80,0x2B,0xB5,0x8C,0xC0,0x40,0x3B, +0x11,0x86,0xD0,0xCB,0xA2,0x86,0x36,0x60,0xA4,0xD5,0x30,0x82,0x6D,0xD9,0x6E,0xD0, +0x0F,0x12,0x04,0x33,0x97,0x5F,0x4F,0x61,0x5A,0xF0,0xE4,0xF9,0x91,0xAB,0xE7,0x1D, +0x3B,0xBC,0xE8,0xCF,0xF4,0x6B,0x2D,0x34,0x7C,0xE2,0x48,0x61,0x1C,0x8E,0xF3,0x61, +0x44,0xCC,0x6F,0xA0,0x4A,0xA9,0x94,0xB0,0x4D,0xDA,0xE7,0xA9,0x34,0x7A,0x72,0x38, +0xA8,0x41,0xCC,0x3C,0x94,0x11,0x7D,0xEB,0xC8,0xA6,0x8C,0xB7,0x86,0xCB,0xCA,0x33, +0x3B,0xD9,0x3D,0x37,0x8B,0xFB,0x7A,0x3E,0x86,0x2C,0xE7,0x73,0xD7,0x0A,0x57,0xAC, +0x64,0x9B,0x19,0xEB,0xF4,0x0F,0x04,0x08,0x8A,0xAC,0x03,0x17,0x19,0x64,0xF4,0x5A, +0x25,0x22,0x8D,0x34,0x2C,0xB2,0xF6,0x68,0x1D,0x12,0x6D,0xD3,0x8A,0x1E,0x14,0xDA, +0xC4,0x8F,0xA6,0xE2,0x23,0x85,0xD5,0x7A,0x0D,0xBD,0x6A,0xE0,0xE9,0xEC,0xEC,0x17, +0xBB,0x42,0x1B,0x67,0xAA,0x25,0xED,0x45,0x83,0x21,0xFC,0xC1,0xC9,0x7C,0xD5,0x62, +0x3E,0xFA,0xF2,0xC5,0x2D,0xD3,0xFD,0xD4,0x65,0x02,0x03,0x01,0x00,0x01,0xA3,0x81, +0x9F,0x30,0x81,0x9C,0x30,0x13,0x06,0x09,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x14, +0x02,0x04,0x06,0x1E,0x04,0x00,0x43,0x00,0x41,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F, +0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF, +0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0xC6,0x4F,0xA2,0x3D,0x06,0x63,0x84,0x09,0x9C,0xCE,0x62,0xE4,0x04,0xAC, +0x8D,0x5C,0xB5,0xE9,0xB6,0x1B,0x30,0x36,0x06,0x03,0x55,0x1D,0x1F,0x04,0x2F,0x30, +0x2D,0x30,0x2B,0xA0,0x29,0xA0,0x27,0x86,0x25,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F, +0x63,0x72,0x6C,0x2E,0x78,0x72,0x61,0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74, +0x79,0x2E,0x63,0x6F,0x6D,0x2F,0x58,0x47,0x43,0x41,0x2E,0x63,0x72,0x6C,0x30,0x10, +0x06,0x09,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x15,0x01,0x04,0x03,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03, +0x82,0x01,0x01,0x00,0x91,0x15,0x39,0x03,0x01,0x1B,0x67,0xFB,0x4A,0x1C,0xF9,0x0A, +0x60,0x5B,0xA1,0xDA,0x4D,0x97,0x62,0xF9,0x24,0x53,0x27,0xD7,0x82,0x64,0x4E,0x90, +0x2E,0xC3,0x49,0x1B,0x2B,0x9A,0xDC,0xFC,0xA8,0x78,0x67,0x35,0xF1,0x1D,0xF0,0x11, +0xBD,0xB7,0x48,0xE3,0x10,0xF6,0x0D,0xDF,0x3F,0xD2,0xC9,0xB6,0xAA,0x55,0xA4,0x48, +0xBA,0x02,0xDB,0xDE,0x59,0x2E,0x15,0x5B,0x3B,0x9D,0x16,0x7D,0x47,0xD7,0x37,0xEA, +0x5F,0x4D,0x76,0x12,0x36,0xBB,0x1F,0xD7,0xA1,0x81,0x04,0x46,0x20,0xA3,0x2C,0x6D, +0xA9,0x9E,0x01,0x7E,0x3F,0x29,0xCE,0x00,0x93,0xDF,0xFD,0xC9,0x92,0x73,0x89,0x89, +0x64,0x9E,0xE7,0x2B,0xE4,0x1C,0x91,0x2C,0xD2,0xB9,0xCE,0x7D,0xCE,0x6F,0x31,0x99, +0xD3,0xE6,0xBE,0xD2,0x1E,0x90,0xF0,0x09,0x14,0x79,0x5C,0x23,0xAB,0x4D,0xD2,0xDA, +0x21,0x1F,0x4D,0x99,0x79,0x9D,0xE1,0xCF,0x27,0x9F,0x10,0x9B,0x1C,0x88,0x0D,0xB0, +0x8A,0x64,0x41,0x31,0xB8,0x0E,0x6C,0x90,0x24,0xA4,0x9B,0x5C,0x71,0x8F,0xBA,0xBB, +0x7E,0x1C,0x1B,0xDB,0x6A,0x80,0x0F,0x21,0xBC,0xE9,0xDB,0xA6,0xB7,0x40,0xF4,0xB2, +0x8B,0xA9,0xB1,0xE4,0xEF,0x9A,0x1A,0xD0,0x3D,0x69,0x99,0xEE,0xA8,0x28,0xA3,0xE1, +0x3C,0xB3,0xF0,0xB2,0x11,0x9C,0xCF,0x7C,0x40,0xE6,0xDD,0xE7,0x43,0x7D,0xA2,0xD8, +0x3A,0xB5,0xA9,0x8D,0xF2,0x34,0x99,0xC4,0xD4,0x10,0xE1,0x06,0xFD,0x09,0x84,0x10, +0x3B,0xEE,0xC4,0x4C,0xF4,0xEC,0x27,0x7C,0x42,0xC2,0x74,0x7C,0x82,0x8A,0x09,0xC9, +0xB4,0x03,0x25,0xBC, +}; + + +const unsigned char* kSSLCertCertificateList[] = { + AddTrust_External_Root_certificate, + AddTrust_Low_Value_Services_Root_certificate, + AddTrust_Public_Services_Root_certificate, + AddTrust_Qualified_Certificates_Root_certificate, + AffirmTrust_Commercial_certificate, + AffirmTrust_Networking_certificate, + AffirmTrust_Premium_certificate, + AffirmTrust_Premium_ECC_certificate, + America_Online_Root_Certification_Authority_1_certificate, + America_Online_Root_Certification_Authority_2_certificate, + Baltimore_CyberTrust_Root_certificate, + Comodo_AAA_Services_root_certificate, + COMODO_Certification_Authority_certificate, + COMODO_ECC_Certification_Authority_certificate, + Comodo_Secure_Services_root_certificate, + Comodo_Trusted_Services_root_certificate, + Cybertrust_Global_Root_certificate, + DigiCert_Assured_ID_Root_CA_certificate, + DigiCert_Global_Root_CA_certificate, + DigiCert_High_Assurance_EV_Root_CA_certificate, + Entrust_net_Premium_2048_Secure_Server_CA_certificate, + Entrust_net_Secure_Server_CA_certificate, + Entrust_Root_Certification_Authority_certificate, + Equifax_Secure_CA_certificate, + Equifax_Secure_eBusiness_CA_1_certificate, + Equifax_Secure_eBusiness_CA_2_certificate, + Equifax_Secure_Global_eBusiness_CA_certificate, + GeoTrust_Global_CA_certificate, + GeoTrust_Global_CA_2_certificate, + GeoTrust_Primary_Certification_Authority_certificate, + GeoTrust_Primary_Certification_Authority___G2_certificate, + GeoTrust_Primary_Certification_Authority___G3_certificate, + GeoTrust_Universal_CA_certificate, + GeoTrust_Universal_CA_2_certificate, + GlobalSign_Root_CA_certificate, + GlobalSign_Root_CA___R2_certificate, + GlobalSign_Root_CA___R3_certificate, + Go_Daddy_Class_2_CA_certificate, + Go_Daddy_Root_Certificate_Authority___G2_certificate, + GTE_CyberTrust_Global_Root_certificate, + Network_Solutions_Certificate_Authority_certificate, + RSA_Root_Certificate_1_certificate, + Starfield_Class_2_CA_certificate, + Starfield_Root_Certificate_Authority___G2_certificate, + Starfield_Services_Root_Certificate_Authority___G2_certificate, + StartCom_Certification_Authority_certificate, + StartCom_Certification_Authority_G2_certificate, + TC_TrustCenter_Class_2_CA_II_certificate, + TC_TrustCenter_Class_3_CA_II_certificate, + TC_TrustCenter_Universal_CA_I_certificate, + TC_TrustCenter_Universal_CA_III_certificate, + Thawte_Premium_Server_CA_certificate, + thawte_Primary_Root_CA_certificate, + thawte_Primary_Root_CA___G2_certificate, + thawte_Primary_Root_CA___G3_certificate, + Thawte_Server_CA_certificate, + UTN_DATACorp_SGC_Root_CA_certificate, + UTN_USERFirst_Hardware_Root_CA_certificate, + ValiCert_Class_1_VA_certificate, + ValiCert_Class_2_VA_certificate, + Verisign_Class_3_Public_Primary_Certification_Authority_certificate, + Verisign_Class_3_Public_Primary_Certification_Authority___G2_certificate, + Verisign_Class_3_Public_Primary_Certification_Authority___G3_certificate, + VeriSign_Class_3_Public_Primary_Certification_Authority___G4_certificate, + VeriSign_Class_3_Public_Primary_Certification_Authority___G5_certificate, + Verisign_Class_4_Public_Primary_Certification_Authority___G3_certificate, + VeriSign_Universal_Root_Certification_Authority_certificate, + XRamp_Global_CA_Root_certificate, +}; + +const size_t kSSLCertCertificateSizeList[] = { + 1082, + 1052, + 1049, + 1058, + 848, + 848, + 1354, + 514, + 936, + 1448, + 891, + 1078, + 1057, + 653, + 1091, + 1095, + 933, + 955, + 947, + 969, + 1120, + 1244, + 1173, + 804, + 646, + 804, + 660, + 856, + 874, + 896, + 690, + 1026, + 1388, + 1392, + 889, + 958, + 867, + 1028, + 969, + 606, + 1002, + 747, + 1043, + 993, + 1011, + 1931, + 1383, + 1198, + 1198, + 993, + 997, + 811, + 1060, + 652, + 1070, + 791, + 1122, + 1144, + 747, + 747, + 576, + 774, + 1054, + 904, + 1239, + 1054, + 1213, + 1076, +}; + diff --git a/webrtc/base/sslsocketfactory.cc b/webrtc/base/sslsocketfactory.cc new file mode 100644 index 000000000..0e37ab84e --- /dev/null +++ b/webrtc/base/sslsocketfactory.cc @@ -0,0 +1,175 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/autodetectproxy.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslsocketfactory.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// ProxySocketAdapter +// TODO: Consider combining AutoDetectProxy and ProxySocketAdapter. I think +// the socket adapter is the more appropriate idiom for automatic proxy +// detection. We may or may not want to combine proxydetect.* as well. +/////////////////////////////////////////////////////////////////////////////// + +class ProxySocketAdapter : public AsyncSocketAdapter { + public: + ProxySocketAdapter(SslSocketFactory* factory, int family, int type) + : AsyncSocketAdapter(NULL), factory_(factory), family_(family), + type_(type), detect_(NULL) { + } + virtual ~ProxySocketAdapter() { + Close(); + } + + virtual int Connect(const SocketAddress& addr) { + ASSERT(NULL == detect_); + ASSERT(NULL == socket_); + remote_ = addr; + if (remote_.IsAnyIP() && remote_.hostname().empty()) { + LOG_F(LS_ERROR) << "Empty address"; + return SOCKET_ERROR; + } + Url url("/", remote_.HostAsURIString(), remote_.port()); + detect_ = new AutoDetectProxy(factory_->agent_); + detect_->set_server_url(url.url()); + detect_->SignalWorkDone.connect(this, + &ProxySocketAdapter::OnProxyDetectionComplete); + detect_->Start(); + return SOCKET_ERROR; + } + virtual int GetError() const { + if (socket_) { + return socket_->GetError(); + } + return detect_ ? EWOULDBLOCK : EADDRNOTAVAIL; + } + virtual int Close() { + if (socket_) { + return socket_->Close(); + } + if (detect_) { + detect_->Destroy(false); + detect_ = NULL; + } + return 0; + } + virtual ConnState GetState() const { + if (socket_) { + return socket_->GetState(); + } + return detect_ ? CS_CONNECTING : CS_CLOSED; + } + +private: + // AutoDetectProxy Slots + void OnProxyDetectionComplete(SignalThread* thread) { + ASSERT(detect_ == thread); + Attach(factory_->CreateProxySocket(detect_->proxy(), family_, type_)); + detect_->Release(); + detect_ = NULL; + if (0 == AsyncSocketAdapter::Connect(remote_)) { + SignalConnectEvent(this); + } else if (!IsBlockingError(socket_->GetError())) { + SignalCloseEvent(this, socket_->GetError()); + } + } + + SslSocketFactory* factory_; + int family_; + int type_; + SocketAddress remote_; + AutoDetectProxy* detect_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SslSocketFactory +/////////////////////////////////////////////////////////////////////////////// + +Socket* SslSocketFactory::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* SslSocketFactory::CreateSocket(int family, int type) { + return factory_->CreateSocket(family, type); +} + +AsyncSocket* SslSocketFactory::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* SslSocketFactory::CreateAsyncSocket(int family, int type) { + if (autodetect_proxy_) { + return new ProxySocketAdapter(this, family, type); + } else { + return CreateProxySocket(proxy_, family, type); + } +} + + +AsyncSocket* SslSocketFactory::CreateProxySocket(const ProxyInfo& proxy, + int family, + int type) { + AsyncSocket* socket = factory_->CreateAsyncSocket(family, type); + if (!socket) + return NULL; + + // Binary logging happens at the lowest level + if (!logging_label_.empty() && binary_mode_) { + socket = new LoggingSocketAdapter(socket, logging_level_, + logging_label_.c_str(), binary_mode_); + } + + if (proxy.type) { + AsyncSocket* proxy_socket = 0; + if (proxy_.type == PROXY_SOCKS5) { + proxy_socket = new AsyncSocksProxySocket(socket, proxy.address, + proxy.username, proxy.password); + } else { + // Note: we are trying unknown proxies as HTTPS currently + AsyncHttpsProxySocket* http_proxy = + new AsyncHttpsProxySocket(socket, agent_, proxy.address, + proxy.username, proxy.password); + http_proxy->SetForceConnect(force_connect_ || !hostname_.empty()); + proxy_socket = http_proxy; + } + if (!proxy_socket) { + delete socket; + return NULL; + } + socket = proxy_socket; // for our purposes the proxy is now the socket + } + + if (!hostname_.empty()) { + if (SSLAdapter* ssl_adapter = SSLAdapter::Create(socket)) { + ssl_adapter->set_ignore_bad_cert(ignore_bad_cert_); + ssl_adapter->StartSSL(hostname_.c_str(), true); + socket = ssl_adapter; + } else { + LOG_F(LS_ERROR) << "SSL unavailable"; + } + } + + // Regular logging occurs at the highest level + if (!logging_label_.empty() && !binary_mode_) { + socket = new LoggingSocketAdapter(socket, logging_level_, + logging_label_.c_str(), binary_mode_); + } + return socket; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/sslsocketfactory.h b/webrtc/base/sslsocketfactory.h new file mode 100644 index 000000000..edb23dbb8 --- /dev/null +++ b/webrtc/base/sslsocketfactory.h @@ -0,0 +1,81 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SSLSOCKETFACTORY_H__ +#define WEBRTC_BASE_SSLSOCKETFACTORY_H__ + +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/socketserver.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// SslSocketFactory +/////////////////////////////////////////////////////////////////////////////// + +class SslSocketFactory : public SocketFactory { + public: + SslSocketFactory(SocketFactory* factory, const std::string& user_agent) + : factory_(factory), agent_(user_agent), autodetect_proxy_(true), + force_connect_(false), logging_level_(LS_VERBOSE), binary_mode_(false), + ignore_bad_cert_(false) { + } + + void SetAutoDetectProxy() { + autodetect_proxy_ = true; + } + void SetForceConnect(bool force) { + force_connect_ = force; + } + void SetProxy(const ProxyInfo& proxy) { + autodetect_proxy_ = false; + proxy_ = proxy; + } + bool autodetect_proxy() const { return autodetect_proxy_; } + const ProxyInfo& proxy() const { return proxy_; } + + void UseSSL(const char* hostname) { hostname_ = hostname; } + void DisableSSL() { hostname_.clear(); } + void SetIgnoreBadCert(bool ignore) { ignore_bad_cert_ = ignore; } + bool ignore_bad_cert() const { return ignore_bad_cert_; } + + void SetLogging(LoggingSeverity level, const std::string& label, + bool binary_mode = false) { + logging_level_ = level; + logging_label_ = label; + binary_mode_ = binary_mode; + } + + // SocketFactory Interface + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + private: + friend class ProxySocketAdapter; + AsyncSocket* CreateProxySocket(const ProxyInfo& proxy, int family, int type); + + SocketFactory* factory_; + std::string agent_; + bool autodetect_proxy_, force_connect_; + ProxyInfo proxy_; + std::string hostname_, logging_label_; + LoggingSeverity logging_level_; + bool binary_mode_; + bool ignore_bad_cert_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLSOCKETFACTORY_H__ diff --git a/webrtc/base/sslstreamadapter.cc b/webrtc/base/sslstreamadapter.cc new file mode 100644 index 000000000..44df2eedd --- /dev/null +++ b/webrtc/base/sslstreamadapter.cc @@ -0,0 +1,77 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/sslconfig.h" + +#if SSL_USE_SCHANNEL + +// SChannel support for DTLS and peer-to-peer mode are not +// done. +#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL + +#include "webrtc/base/opensslstreamadapter.h" + +#elif SSL_USE_NSS // && !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + +#include "webrtc/base/nssstreamadapter.h" + +#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +SSLStreamAdapter* SSLStreamAdapter::Create(StreamInterface* stream) { +#if SSL_USE_SCHANNEL + return NULL; +#elif SSL_USE_OPENSSL // !SSL_USE_SCHANNEL + return new OpenSSLStreamAdapter(stream); +#elif SSL_USE_NSS // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + return new NSSStreamAdapter(stream); +#else // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS + return NULL; +#endif +} + +// Note: this matches the logic above with SCHANNEL dominating +#if SSL_USE_SCHANNEL +bool SSLStreamAdapter::HaveDtls() { return false; } +bool SSLStreamAdapter::HaveDtlsSrtp() { return false; } +bool SSLStreamAdapter::HaveExporter() { return false; } +#elif SSL_USE_OPENSSL +bool SSLStreamAdapter::HaveDtls() { + return OpenSSLStreamAdapter::HaveDtls(); +} +bool SSLStreamAdapter::HaveDtlsSrtp() { + return OpenSSLStreamAdapter::HaveDtlsSrtp(); +} +bool SSLStreamAdapter::HaveExporter() { + return OpenSSLStreamAdapter::HaveExporter(); +} +#elif SSL_USE_NSS +bool SSLStreamAdapter::HaveDtls() { + return NSSStreamAdapter::HaveDtls(); +} +bool SSLStreamAdapter::HaveDtlsSrtp() { + return NSSStreamAdapter::HaveDtlsSrtp(); +} +bool SSLStreamAdapter::HaveExporter() { + return NSSStreamAdapter::HaveExporter(); +} +#endif // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/sslstreamadapter.h b/webrtc/base/sslstreamadapter.h new file mode 100644 index 000000000..ffe6b2f7b --- /dev/null +++ b/webrtc/base/sslstreamadapter.h @@ -0,0 +1,162 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SSLSTREAMADAPTER_H_ +#define WEBRTC_BASE_SSLSTREAMADAPTER_H_ + +#include +#include + +#include "webrtc/base/stream.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +// SSLStreamAdapter : A StreamInterfaceAdapter that does SSL/TLS. +// After SSL has been started, the stream will only open on successful +// SSL verification of certificates, and the communication is +// encrypted of course. +// +// This class was written with SSLAdapter as a starting point. It +// offers a similar interface, with two differences: there is no +// support for a restartable SSL connection, and this class has a +// peer-to-peer mode. +// +// The SSL library requires initialization and cleanup. Static method +// for doing this are in SSLAdapter. They should possibly be moved out +// to a neutral class. + + +enum SSLRole { SSL_CLIENT, SSL_SERVER }; +enum SSLMode { SSL_MODE_TLS, SSL_MODE_DTLS }; + +// Errors for Read -- in the high range so no conflict with OpenSSL. +enum { SSE_MSG_TRUNC = 0xff0001 }; + +class SSLStreamAdapter : public StreamAdapterInterface { + public: + // Instantiate an SSLStreamAdapter wrapping the given stream, + // (using the selected implementation for the platform). + // Caller is responsible for freeing the returned object. + static SSLStreamAdapter* Create(StreamInterface* stream); + + explicit SSLStreamAdapter(StreamInterface* stream) + : StreamAdapterInterface(stream), ignore_bad_cert_(false) { } + + void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; } + bool ignore_bad_cert() const { return ignore_bad_cert_; } + + // Specify our SSL identity: key and certificate. Mostly this is + // only used in the peer-to-peer mode (unless we actually want to + // provide a client certificate to a server). + // SSLStream takes ownership of the SSLIdentity object and will + // free it when appropriate. Should be called no more than once on a + // given SSLStream instance. + virtual void SetIdentity(SSLIdentity* identity) = 0; + + // Call this to indicate that we are to play the server's role in + // the peer-to-peer mode. + // The default argument is for backward compatibility + // TODO(ekr@rtfm.com): rename this SetRole to reflect its new function + virtual void SetServerRole(SSLRole role = SSL_SERVER) = 0; + + // Do DTLS or TLS + virtual void SetMode(SSLMode mode) = 0; + + // The mode of operation is selected by calling either + // StartSSLWithServer or StartSSLWithPeer. + // Use of the stream prior to calling either of these functions will + // pass data in clear text. + // Calling one of these functions causes SSL negotiation to begin as + // soon as possible: right away if the underlying wrapped stream is + // already opened, or else as soon as it opens. + // + // These functions return a negative error code on failure. + // Returning 0 means success so far, but negotiation is probably not + // complete and will continue asynchronously. In that case, the + // exposed stream will open after successful negotiation and + // verification, or an SE_CLOSE event will be raised if negotiation + // fails. + + // StartSSLWithServer starts SSL negotiation with a server in + // traditional mode. server_name specifies the expected server name + // which the server's certificate needs to specify. + virtual int StartSSLWithServer(const char* server_name) = 0; + + // StartSSLWithPeer starts negotiation in the special peer-to-peer + // mode. + // Generally, SetIdentity() and possibly SetServerRole() should have + // been called before this. + // SetPeerCertificate() or SetPeerCertificateDigest() must also be called. + // It may be called after StartSSLWithPeer() but must be called before the + // underlying stream opens. + virtual int StartSSLWithPeer() = 0; + + // Specify the digest of the certificate that our peer is expected to use in + // peer-to-peer mode. Only this certificate will be accepted during + // SSL verification. The certificate is assumed to have been + // obtained through some other secure channel (such as the XMPP + // channel). Unlike SetPeerCertificate(), this must specify the + // terminal certificate, not just a CA. + // SSLStream makes a copy of the digest value. + virtual bool SetPeerCertificateDigest(const std::string& digest_alg, + const unsigned char* digest_val, + size_t digest_len) = 0; + + // Retrieves the peer's X.509 certificate, if a connection has been + // established. It returns the transmitted over SSL, including the entire + // chain. The returned certificate is owned by the caller. + virtual bool GetPeerCertificate(SSLCertificate** cert) const = 0; + + // Key Exporter interface from RFC 5705 + // Arguments are: + // label -- the exporter label. + // part of the RFC defining each exporter + // usage (IN) + // context/context_len -- a context to bind to for this connection; + // optional, can be NULL, 0 (IN) + // use_context -- whether to use the context value + // (needed to distinguish no context from + // zero-length ones). + // result -- where to put the computed value + // result_len -- the length of the computed value + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + return false; // Default is unsupported + } + + + // DTLS-SRTP interface + virtual bool SetDtlsSrtpCiphers(const std::vector& ciphers) { + return false; + } + + virtual bool GetDtlsSrtpCipher(std::string* cipher) { + return false; + } + + // Capabilities testing + static bool HaveDtls(); + static bool HaveDtlsSrtp(); + static bool HaveExporter(); + + // If true, the server certificate need not match the configured + // server_name, and in fact missing certificate authority and other + // verification errors are ignored. + bool ignore_bad_cert_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLSTREAMADAPTER_H_ diff --git a/webrtc/base/sslstreamadapter_unittest.cc b/webrtc/base/sslstreamadapter_unittest.cc new file mode 100644 index 000000000..af78bfff5 --- /dev/null +++ b/webrtc/base/sslstreamadapter_unittest.cc @@ -0,0 +1,940 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#include +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslconfig.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/stream.h" + +static const int kBlockSize = 4096; +static const char kAES_CM_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80"; +static const char kAES_CM_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32"; +static const char kExporterLabel[] = "label"; +static const unsigned char kExporterContext[] = "context"; +static int kExporterContextLen = sizeof(kExporterContext); + +static const char kRSA_PRIVATE_KEY_PEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAECgYAvgOs4FJcgvp+TuREx7YtiYVsH\n" + "mwQPTum2z/8VzWGwR8BBHBvIpVe1MbD/Y4seyI2aco/7UaisatSgJhsU46/9Y4fq\n" + "2TwXH9QANf4at4d9n/R6rzwpAJOpgwZgKvdQjkfrKTtgLV+/dawvpxUYkRH4JZM1\n" + "CVGukMfKNrSVH4Ap4QJBAOJmGV1ASPnB4r4nc99at7JuIJmd7fmuVUwUgYi4XgaR\n" + "WhScBsgYwZ/JoywdyZJgnbcrTDuVcWG56B3vXbhdpMsCQQDf9zeJrjnPZ3Cqm79y\n" + "kdqANep0uwZciiNiWxsQrCHztywOvbFhdp8iYVFG9EK8DMY41Y5TxUwsHD+67zao\n" + "ZNqJAkEA1suLUP/GvL8IwuRneQd2tWDqqRQ/Td3qq03hP7e77XtF/buya3Ghclo5\n" + "54czUR89QyVfJEC6278nzA7n2h1uVQJAcG6mztNL6ja/dKZjYZye2CY44QjSlLo0\n" + "MTgTSjdfg/28fFn2Jjtqf9Pi/X+50LWI/RcYMC2no606wRk9kyOuIQJBAK6VSAim\n" + "1pOEjsYQn0X5KEIrz1G3bfCbB848Ime3U2/FWlCHMr6ch8kCZ5d1WUeJD3LbwMNG\n" + "UCXiYxSsu20QNVw=\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char kCERT_PEM[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIBmTCCAQKgAwIBAgIEbzBSAjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZX\n" + "ZWJSVEMwHhcNMTQwMTAyMTgyNDQ3WhcNMTQwMjAxMTgyNDQ3WjARMQ8wDQYDVQQD\n" + "EwZXZWJSVEMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAUflI\n" + "VUe5Krqf5RVa5C3u/UTAOAUJBiDS3VANTCLBxjuMsvqOG0WvaYWP3HYPgrz0jXK2\n" + "LJE/mGw3MyFHEqi81jh95J+ypl6xKW6Rm8jKLR87gUvCaVYn/Z4/P3AqcQTB7wOv\n" + "UD0A8qfhfDM+LK6rPAnCsVN0NRDY3jvd6rzix9M=\n" + "-----END CERTIFICATE-----\n"; + +#define MAYBE_SKIP_TEST(feature) \ + if (!(rtc::SSLStreamAdapter::feature())) { \ + LOG(LS_INFO) << "Feature disabled... skipping"; \ + return; \ + } + +class SSLStreamAdapterTestBase; + +class SSLDummyStream : public rtc::StreamInterface, + public sigslot::has_slots<> { + public: + explicit SSLDummyStream(SSLStreamAdapterTestBase *test, + const std::string &side, + rtc::FifoBuffer *in, + rtc::FifoBuffer *out) : + test_(test), + side_(side), + in_(in), + out_(out), + first_packet_(true) { + in_->SignalEvent.connect(this, &SSLDummyStream::OnEventIn); + out_->SignalEvent.connect(this, &SSLDummyStream::OnEventOut); + } + + virtual rtc::StreamState GetState() const { return rtc::SS_OPEN; } + + virtual rtc::StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + rtc::StreamResult r; + + r = in_->Read(buffer, buffer_len, read, error); + if (r == rtc::SR_BLOCK) + return rtc::SR_BLOCK; + if (r == rtc::SR_EOS) + return rtc::SR_EOS; + + if (r != rtc::SR_SUCCESS) { + ADD_FAILURE(); + return rtc::SR_ERROR; + } + + return rtc::SR_SUCCESS; + } + + // Catch readability events on in and pass them up. + virtual void OnEventIn(rtc::StreamInterface *stream, int sig, + int err) { + int mask = (rtc::SE_READ | rtc::SE_CLOSE); + + if (sig & mask) { + LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig=" + << sig << " forwarding upward"; + PostEvent(sig & mask, 0); + } + } + + // Catch writeability events on out and pass them up. + virtual void OnEventOut(rtc::StreamInterface *stream, int sig, + int err) { + if (sig & rtc::SE_WRITE) { + LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig=" + << sig << " forwarding upward"; + + PostEvent(sig & rtc::SE_WRITE, 0); + } + } + + // Write to the outgoing FifoBuffer + rtc::StreamResult WriteData(const void* data, size_t data_len, + size_t* written, int* error) { + return out_->Write(data, data_len, written, error); + } + + // Defined later + virtual rtc::StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + virtual void Close() { + LOG(LS_INFO) << "Closing outbound stream"; + out_->Close(); + } + + private: + SSLStreamAdapterTestBase *test_; + const std::string side_; + rtc::FifoBuffer *in_; + rtc::FifoBuffer *out_; + bool first_packet_; +}; + +static const int kFifoBufferSize = 4096; + +class SSLStreamAdapterTestBase : public testing::Test, + public sigslot::has_slots<> { + public: + SSLStreamAdapterTestBase(const std::string& client_cert_pem, + const std::string& client_private_key_pem, + bool dtls) : + client_buffer_(kFifoBufferSize), server_buffer_(kFifoBufferSize), + client_stream_( + new SSLDummyStream(this, "c2s", &client_buffer_, &server_buffer_)), + server_stream_( + new SSLDummyStream(this, "s2c", &server_buffer_, &client_buffer_)), + client_ssl_(rtc::SSLStreamAdapter::Create(client_stream_)), + server_ssl_(rtc::SSLStreamAdapter::Create(server_stream_)), + client_identity_(NULL), server_identity_(NULL), + delay_(0), mtu_(1460), loss_(0), lose_first_packet_(false), + damage_(false), dtls_(dtls), + handshake_wait_(5000), identities_set_(false) { + // Set use of the test RNG to get predictable loss patterns. + rtc::SetRandomTestMode(true); + + // Set up the slots + client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + + if (!client_cert_pem.empty() && !client_private_key_pem.empty()) { + client_identity_ = rtc::SSLIdentity::FromPEMStrings( + client_private_key_pem, client_cert_pem); + } else { + client_identity_ = rtc::SSLIdentity::Generate("client"); + } + server_identity_ = rtc::SSLIdentity::Generate("server"); + + client_ssl_->SetIdentity(client_identity_); + server_ssl_->SetIdentity(server_identity_); + } + + ~SSLStreamAdapterTestBase() { + // Put it back for the next test. + rtc::SetRandomTestMode(false); + } + + static void SetUpTestCase() { + rtc::InitializeSSL(); + } + + static void TearDownTestCase() { + rtc::CleanupSSL(); + } + + // Recreate the client/server identities with the specified validity period. + // |not_before| and |not_after| are offsets from the current time in number + // of seconds. + void ResetIdentitiesWithValidity(int not_before, int not_after) { + client_stream_ = + new SSLDummyStream(this, "c2s", &client_buffer_, &server_buffer_); + server_stream_ = + new SSLDummyStream(this, "s2c", &server_buffer_, &client_buffer_); + + client_ssl_.reset(rtc::SSLStreamAdapter::Create(client_stream_)); + server_ssl_.reset(rtc::SSLStreamAdapter::Create(server_stream_)); + + client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + + rtc::SSLIdentityParams client_params; + client_params.common_name = "client"; + client_params.not_before = not_before; + client_params.not_after = not_after; + client_identity_ = rtc::SSLIdentity::GenerateForTest(client_params); + + rtc::SSLIdentityParams server_params; + server_params.common_name = "server"; + server_params.not_before = not_before; + server_params.not_after = not_after; + server_identity_ = rtc::SSLIdentity::GenerateForTest(server_params); + + client_ssl_->SetIdentity(client_identity_); + server_ssl_->SetIdentity(server_identity_); + } + + virtual void OnEvent(rtc::StreamInterface *stream, int sig, int err) { + LOG(LS_INFO) << "SSLStreamAdapterTestBase::OnEvent sig=" << sig; + + if (sig & rtc::SE_READ) { + ReadData(stream); + } + + if ((stream == client_ssl_.get()) && (sig & rtc::SE_WRITE)) { + WriteData(); + } + } + + void SetPeerIdentitiesByDigest(bool correct) { + unsigned char digest[20]; + size_t digest_len; + bool rv; + + LOG(LS_INFO) << "Setting peer identities by digest"; + + rv = server_identity_->certificate().ComputeDigest(rtc::DIGEST_SHA_1, + digest, 20, + &digest_len); + ASSERT_TRUE(rv); + if (!correct) { + LOG(LS_INFO) << "Setting bogus digest for server cert"; + digest[0]++; + } + rv = client_ssl_->SetPeerCertificateDigest(rtc::DIGEST_SHA_1, digest, + digest_len); + ASSERT_TRUE(rv); + + + rv = client_identity_->certificate().ComputeDigest(rtc::DIGEST_SHA_1, + digest, 20, &digest_len); + ASSERT_TRUE(rv); + if (!correct) { + LOG(LS_INFO) << "Setting bogus digest for client cert"; + digest[0]++; + } + rv = server_ssl_->SetPeerCertificateDigest(rtc::DIGEST_SHA_1, digest, + digest_len); + ASSERT_TRUE(rv); + + identities_set_ = true; + } + + void TestHandshake(bool expect_success = true) { + server_ssl_->SetMode(dtls_ ? rtc::SSL_MODE_DTLS : + rtc::SSL_MODE_TLS); + client_ssl_->SetMode(dtls_ ? rtc::SSL_MODE_DTLS : + rtc::SSL_MODE_TLS); + + if (!dtls_) { + // Make sure we simulate a reliable network for TLS. + // This is just a check to make sure that people don't write wrong + // tests. + ASSERT((mtu_ == 1460) && (loss_ == 0) && (lose_first_packet_ == 0)); + } + + if (!identities_set_) + SetPeerIdentitiesByDigest(true); + + // Start the handshake + int rv; + + server_ssl_->SetServerRole(); + rv = server_ssl_->StartSSLWithPeer(); + ASSERT_EQ(0, rv); + + rv = client_ssl_->StartSSLWithPeer(); + ASSERT_EQ(0, rv); + + // Now run the handshake + if (expect_success) { + EXPECT_TRUE_WAIT((client_ssl_->GetState() == rtc::SS_OPEN) + && (server_ssl_->GetState() == rtc::SS_OPEN), + handshake_wait_); + } else { + EXPECT_TRUE_WAIT(client_ssl_->GetState() == rtc::SS_CLOSED, + handshake_wait_); + } + } + + rtc::StreamResult DataWritten(SSLDummyStream *from, const void *data, + size_t data_len, size_t *written, + int *error) { + // Randomly drop loss_ percent of packets + if (rtc::CreateRandomId() % 100 < static_cast(loss_)) { + LOG(LS_INFO) << "Randomly dropping packet, size=" << data_len; + *written = data_len; + return rtc::SR_SUCCESS; + } + if (dtls_ && (data_len > mtu_)) { + LOG(LS_INFO) << "Dropping packet > mtu, size=" << data_len; + *written = data_len; + return rtc::SR_SUCCESS; + } + + // Optionally damage application data (type 23). Note that we don't damage + // handshake packets and we damage the last byte to keep the header + // intact but break the MAC. + if (damage_ && (*static_cast(data) == 23)) { + std::vector buf(data_len); + + LOG(LS_INFO) << "Damaging packet"; + + memcpy(&buf[0], data, data_len); + buf[data_len - 1]++; + + return from->WriteData(&buf[0], data_len, written, error); + } + + return from->WriteData(data, data_len, written, error); + } + + void SetDelay(int delay) { + delay_ = delay; + } + int GetDelay() { return delay_; } + + void SetLoseFirstPacket(bool lose) { + lose_first_packet_ = lose; + } + bool GetLoseFirstPacket() { return lose_first_packet_; } + + void SetLoss(int percent) { + loss_ = percent; + } + + void SetDamage() { + damage_ = true; + } + + void SetMtu(size_t mtu) { + mtu_ = mtu; + } + + void SetHandshakeWait(int wait) { + handshake_wait_ = wait; + } + + void SetDtlsSrtpCiphers(const std::vector &ciphers, + bool client) { + if (client) + client_ssl_->SetDtlsSrtpCiphers(ciphers); + else + server_ssl_->SetDtlsSrtpCiphers(ciphers); + } + + bool GetDtlsSrtpCipher(bool client, std::string *retval) { + if (client) + return client_ssl_->GetDtlsSrtpCipher(retval); + else + return server_ssl_->GetDtlsSrtpCipher(retval); + } + + bool GetPeerCertificate(bool client, rtc::SSLCertificate** cert) { + if (client) + return client_ssl_->GetPeerCertificate(cert); + else + return server_ssl_->GetPeerCertificate(cert); + } + + bool ExportKeyingMaterial(const char *label, + const unsigned char *context, + size_t context_len, + bool use_context, + bool client, + unsigned char *result, + size_t result_len) { + if (client) + return client_ssl_->ExportKeyingMaterial(label, + context, context_len, + use_context, + result, result_len); + else + return server_ssl_->ExportKeyingMaterial(label, + context, context_len, + use_context, + result, result_len); + } + + // To be implemented by subclasses. + virtual void WriteData() = 0; + virtual void ReadData(rtc::StreamInterface *stream) = 0; + virtual void TestTransfer(int size) = 0; + + protected: + rtc::FifoBuffer client_buffer_; + rtc::FifoBuffer server_buffer_; + SSLDummyStream *client_stream_; // freed by client_ssl_ destructor + SSLDummyStream *server_stream_; // freed by server_ssl_ destructor + rtc::scoped_ptr client_ssl_; + rtc::scoped_ptr server_ssl_; + rtc::SSLIdentity *client_identity_; // freed by client_ssl_ destructor + rtc::SSLIdentity *server_identity_; // freed by server_ssl_ destructor + int delay_; + size_t mtu_; + int loss_; + bool lose_first_packet_; + bool damage_; + bool dtls_; + int handshake_wait_; + bool identities_set_; +}; + +class SSLStreamAdapterTestTLS : public SSLStreamAdapterTestBase { + public: + SSLStreamAdapterTestTLS() : + SSLStreamAdapterTestBase("", "", false) { + }; + + // Test data transfer for TLS + virtual void TestTransfer(int size) { + LOG(LS_INFO) << "Starting transfer test with " << size << " bytes"; + // Create some dummy data to send. + size_t received; + + send_stream_.ReserveSize(size); + for (int i = 0; i < size; ++i) { + char ch = static_cast(i); + send_stream_.Write(&ch, 1, NULL, NULL); + } + send_stream_.Rewind(); + + // Prepare the receive stream. + recv_stream_.ReserveSize(size); + + // Start sending + WriteData(); + + // Wait for the client to close + EXPECT_TRUE_WAIT(server_ssl_->GetState() == rtc::SS_CLOSED, 10000); + + // Now check the data + recv_stream_.GetSize(&received); + + EXPECT_EQ(static_cast(size), received); + EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(), + recv_stream_.GetBuffer(), size)); + } + + void WriteData() { + size_t position, tosend, size; + rtc::StreamResult rv; + size_t sent; + char block[kBlockSize]; + + send_stream_.GetSize(&size); + if (!size) + return; + + for (;;) { + send_stream_.GetPosition(&position); + if (send_stream_.Read(block, sizeof(block), &tosend, NULL) != + rtc::SR_EOS) { + rv = client_ssl_->Write(block, tosend, &sent, 0); + + if (rv == rtc::SR_SUCCESS) { + send_stream_.SetPosition(position + sent); + LOG(LS_VERBOSE) << "Sent: " << position + sent; + } else if (rv == rtc::SR_BLOCK) { + LOG(LS_VERBOSE) << "Blocked..."; + send_stream_.SetPosition(position); + break; + } else { + ADD_FAILURE(); + break; + } + } else { + // Now close + LOG(LS_INFO) << "Wrote " << position << " bytes. Closing"; + client_ssl_->Close(); + break; + } + } + }; + + virtual void ReadData(rtc::StreamInterface *stream) { + char buffer[1600]; + size_t bread; + int err2; + rtc::StreamResult r; + + for (;;) { + r = stream->Read(buffer, sizeof(buffer), &bread, &err2); + + if (r == rtc::SR_ERROR || r == rtc::SR_EOS) { + // Unfortunately, errors are the way that the stream adapter + // signals close in OpenSSL + stream->Close(); + return; + } + + if (r == rtc::SR_BLOCK) + break; + + ASSERT_EQ(rtc::SR_SUCCESS, r); + LOG(LS_INFO) << "Read " << bread; + + recv_stream_.Write(buffer, bread, NULL, NULL); + } + } + + private: + rtc::MemoryStream send_stream_; + rtc::MemoryStream recv_stream_; +}; + +class SSLStreamAdapterTestDTLS : public SSLStreamAdapterTestBase { + public: + SSLStreamAdapterTestDTLS() : + SSLStreamAdapterTestBase("", "", true), + packet_size_(1000), count_(0), sent_(0) { + } + + SSLStreamAdapterTestDTLS(const std::string& cert_pem, + const std::string& private_key_pem) : + SSLStreamAdapterTestBase(cert_pem, private_key_pem, true), + packet_size_(1000), count_(0), sent_(0) { + } + + virtual void WriteData() { + unsigned char *packet = new unsigned char[1600]; + + do { + memset(packet, sent_ & 0xff, packet_size_); + *(reinterpret_cast(packet)) = sent_; + + size_t sent; + int rv = client_ssl_->Write(packet, packet_size_, &sent, 0); + if (rv == rtc::SR_SUCCESS) { + LOG(LS_VERBOSE) << "Sent: " << sent_; + sent_++; + } else if (rv == rtc::SR_BLOCK) { + LOG(LS_VERBOSE) << "Blocked..."; + break; + } else { + ADD_FAILURE(); + break; + } + } while (sent_ < count_); + + delete [] packet; + } + + virtual void ReadData(rtc::StreamInterface *stream) { + unsigned char buffer[2000]; + size_t bread; + int err2; + rtc::StreamResult r; + + for (;;) { + r = stream->Read(buffer, 2000, &bread, &err2); + + if (r == rtc::SR_ERROR) { + // Unfortunately, errors are the way that the stream adapter + // signals close right now + stream->Close(); + return; + } + + if (r == rtc::SR_BLOCK) + break; + + ASSERT_EQ(rtc::SR_SUCCESS, r); + LOG(LS_INFO) << "Read " << bread; + + // Now parse the datagram + ASSERT_EQ(packet_size_, bread); + unsigned char* ptr_to_buffer = buffer; + uint32_t packet_num = *(reinterpret_cast(ptr_to_buffer)); + + for (size_t i = 4; i < packet_size_; i++) { + ASSERT_EQ((packet_num & 0xff), buffer[i]); + } + received_.insert(packet_num); + } + } + + virtual void TestTransfer(int count) { + count_ = count; + + WriteData(); + + EXPECT_TRUE_WAIT(sent_ == count_, 10000); + LOG(LS_INFO) << "sent_ == " << sent_; + + if (damage_) { + WAIT(false, 2000); + EXPECT_EQ(0U, received_.size()); + } else if (loss_ == 0) { + EXPECT_EQ_WAIT(static_cast(sent_), received_.size(), 1000); + } else { + LOG(LS_INFO) << "Sent " << sent_ << " packets; received " << + received_.size(); + } + }; + + private: + size_t packet_size_; + int count_; + int sent_; + std::set received_; +}; + + +rtc::StreamResult SSLDummyStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + *written = data_len; + + LOG(LS_INFO) << "Writing to loopback " << data_len; + + if (first_packet_) { + first_packet_ = false; + if (test_->GetLoseFirstPacket()) { + LOG(LS_INFO) << "Losing initial packet of length " << data_len; + return rtc::SR_SUCCESS; + } + } + + return test_->DataWritten(this, data, data_len, written, error); + + return rtc::SR_SUCCESS; +}; + +class SSLStreamAdapterTestDTLSFromPEMStrings : public SSLStreamAdapterTestDTLS { + public: + SSLStreamAdapterTestDTLSFromPEMStrings() : + SSLStreamAdapterTestDTLS(kCERT_PEM, kRSA_PRIVATE_KEY_PEM) { + } +}; + +// Basic tests: TLS + +// Test that we cannot read/write if we have not yet handshaked. +// This test only applies to NSS because OpenSSL has passthrough +// semantics for I/O before the handshake is started. +#if SSL_USE_NSS +TEST_F(SSLStreamAdapterTestTLS, TestNoReadWriteBeforeConnect) { + rtc::StreamResult rv; + char block[kBlockSize]; + size_t dummy; + + rv = client_ssl_->Write(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_BLOCK, rv); + + rv = client_ssl_->Read(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_BLOCK, rv); +} +#endif + + +// Test that we can make a handshake work +TEST_F(SSLStreamAdapterTestTLS, TestTLSConnect) { + TestHandshake(); +}; + +// Test transfer -- trivial +TEST_F(SSLStreamAdapterTestTLS, TestTLSTransfer) { + TestHandshake(); + TestTransfer(100000); +}; + +// Test read-write after close. +TEST_F(SSLStreamAdapterTestTLS, ReadWriteAfterClose) { + TestHandshake(); + TestTransfer(100000); + client_ssl_->Close(); + + rtc::StreamResult rv; + char block[kBlockSize]; + size_t dummy; + + // It's an error to write after closed. + rv = client_ssl_->Write(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_ERROR, rv); + + // But after closed read gives you EOS. + rv = client_ssl_->Read(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_EOS, rv); +}; + +// Test a handshake with a bogus peer digest +TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusDigest) { + SetPeerIdentitiesByDigest(false); + TestHandshake(false); +}; + +// Test moving a bunch of data + +// Basic tests: DTLS +// Test that we can make a handshake work +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnect) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); +}; + +// Test that we can make a handshake work if the first packet in +// each direction is lost. This gives us predictable loss +// rather than having to tune random +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithLostFirstPacket) { + MAYBE_SKIP_TEST(HaveDtls); + SetLoseFirstPacket(true); + TestHandshake(); +}; + +// Test a handshake with loss and delay +TEST_F(SSLStreamAdapterTestDTLS, + TestDTLSConnectWithLostFirstPacketDelay2s) { + MAYBE_SKIP_TEST(HaveDtls); + SetLoseFirstPacket(true); + SetDelay(2000); + SetHandshakeWait(20000); + TestHandshake(); +}; + +// Test a handshake with small MTU +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithSmallMtu) { + MAYBE_SKIP_TEST(HaveDtls); + SetMtu(700); + SetHandshakeWait(20000); + TestHandshake(); +}; + +// Test transfer -- trivial +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransfer) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); + TestTransfer(100); +}; + +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithLoss) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); + SetLoss(10); + TestTransfer(100); +}; + +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithDamage) { + MAYBE_SKIP_TEST(HaveDtls); + SetDamage(); // Must be called first because first packet + // write happens at end of handshake. + TestHandshake(); + TestTransfer(100); +}; + +// Test DTLS-SRTP with all high ciphers +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHigh) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector high; + high.push_back(kAES_CM_HMAC_SHA1_80); + SetDtlsSrtpCiphers(high, true); + SetDtlsSrtpCiphers(high, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80); +}; + +// Test DTLS-SRTP with all low ciphers +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpLow) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector low; + low.push_back(kAES_CM_HMAC_SHA1_32); + SetDtlsSrtpCiphers(low, true); + SetDtlsSrtpCiphers(low, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_32); +}; + + +// Test DTLS-SRTP with a mismatch -- should not converge +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHighLow) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector high; + high.push_back(kAES_CM_HMAC_SHA1_80); + std::vector low; + low.push_back(kAES_CM_HMAC_SHA1_32); + SetDtlsSrtpCiphers(high, true); + SetDtlsSrtpCiphers(low, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_FALSE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_FALSE(GetDtlsSrtpCipher(false, &server_cipher)); +}; + +// Test DTLS-SRTP with each side being mixed -- should select high +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpMixed) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector mixed; + mixed.push_back(kAES_CM_HMAC_SHA1_80); + mixed.push_back(kAES_CM_HMAC_SHA1_32); + SetDtlsSrtpCiphers(mixed, true); + SetDtlsSrtpCiphers(mixed, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80); +}; + +// Test an exporter +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSExporter) { + MAYBE_SKIP_TEST(HaveExporter); + TestHandshake(); + unsigned char client_out[20]; + unsigned char server_out[20]; + + bool result; + result = ExportKeyingMaterial(kExporterLabel, + kExporterContext, kExporterContextLen, + true, true, + client_out, sizeof(client_out)); + ASSERT_TRUE(result); + + result = ExportKeyingMaterial(kExporterLabel, + kExporterContext, kExporterContextLen, + true, false, + server_out, sizeof(server_out)); + ASSERT_TRUE(result); + + ASSERT_TRUE(!memcmp(client_out, server_out, sizeof(client_out))); +} + +// Test not yet valid certificates are not rejected. +TEST_F(SSLStreamAdapterTestDTLS, TestCertNotYetValid) { + MAYBE_SKIP_TEST(HaveDtls); + long one_day = 60 * 60 * 24; + // Make the certificates not valid until one day later. + ResetIdentitiesWithValidity(one_day, one_day); + TestHandshake(); +} + +// Test expired certificates are not rejected. +TEST_F(SSLStreamAdapterTestDTLS, TestCertExpired) { + MAYBE_SKIP_TEST(HaveDtls); + long one_day = 60 * 60 * 24; + // Make the certificates already expired. + ResetIdentitiesWithValidity(-one_day, -one_day); + TestHandshake(); +} + +// Test data transfer using certs created from strings. +TEST_F(SSLStreamAdapterTestDTLSFromPEMStrings, TestTransfer) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); + TestTransfer(100); +} + +// Test getting the remote certificate. +TEST_F(SSLStreamAdapterTestDTLSFromPEMStrings, TestDTLSGetPeerCertificate) { + MAYBE_SKIP_TEST(HaveDtls); + + // Peer certificates haven't been received yet. + rtc::scoped_ptr client_peer_cert; + ASSERT_FALSE(GetPeerCertificate(true, client_peer_cert.accept())); + ASSERT_FALSE(client_peer_cert != NULL); + + rtc::scoped_ptr server_peer_cert; + ASSERT_FALSE(GetPeerCertificate(false, server_peer_cert.accept())); + ASSERT_FALSE(server_peer_cert != NULL); + + TestHandshake(); + + // The client should have a peer certificate after the handshake. + ASSERT_TRUE(GetPeerCertificate(true, client_peer_cert.accept())); + ASSERT_TRUE(client_peer_cert != NULL); + + // It's not kCERT_PEM. + std::string client_peer_string = client_peer_cert->ToPEMString(); + ASSERT_NE(kCERT_PEM, client_peer_string); + + // It must not have a chain, because the test certs are self-signed. + rtc::SSLCertChain* client_peer_chain; + ASSERT_FALSE(client_peer_cert->GetChain(&client_peer_chain)); + + // The server should have a peer certificate after the handshake. + ASSERT_TRUE(GetPeerCertificate(false, server_peer_cert.accept())); + ASSERT_TRUE(server_peer_cert != NULL); + + // It's kCERT_PEM + ASSERT_EQ(kCERT_PEM, server_peer_cert->ToPEMString()); + + // It must not have a chain, because the test certs are self-signed. + rtc::SSLCertChain* server_peer_chain; + ASSERT_FALSE(server_peer_cert->GetChain(&server_peer_chain)); +} diff --git a/webrtc/base/sslstreamadapterhelper.cc b/webrtc/base/sslstreamadapterhelper.cc new file mode 100644 index 000000000..d9c6afd40 --- /dev/null +++ b/webrtc/base/sslstreamadapterhelper.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/sslstreamadapterhelper.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +void SSLStreamAdapterHelper::SetIdentity(SSLIdentity* identity) { + ASSERT(identity_.get() == NULL); + identity_.reset(identity); +} + +void SSLStreamAdapterHelper::SetServerRole(SSLRole role) { + role_ = role; +} + +int SSLStreamAdapterHelper::StartSSLWithServer(const char* server_name) { + ASSERT(server_name != NULL && server_name[0] != '\0'); + ssl_server_name_ = server_name; + return StartSSL(); +} + +int SSLStreamAdapterHelper::StartSSLWithPeer() { + ASSERT(ssl_server_name_.empty()); + // It is permitted to specify peer_certificate_ only later. + return StartSSL(); +} + +void SSLStreamAdapterHelper::SetMode(SSLMode mode) { + ASSERT(state_ == SSL_NONE); + ssl_mode_ = mode; +} + +StreamState SSLStreamAdapterHelper::GetState() const { + switch (state_) { + case SSL_WAIT: + case SSL_CONNECTING: + return SS_OPENING; + case SSL_CONNECTED: + return SS_OPEN; + default: + return SS_CLOSED; + }; + // not reached +} + +bool SSLStreamAdapterHelper::GetPeerCertificate(SSLCertificate** cert) const { + if (!peer_certificate_) + return false; + + *cert = peer_certificate_->GetReference(); + return true; +} + +bool SSLStreamAdapterHelper::SetPeerCertificateDigest( + const std::string &digest_alg, + const unsigned char* digest_val, + size_t digest_len) { + ASSERT(peer_certificate_.get() == NULL); + ASSERT(peer_certificate_digest_algorithm_.empty()); + ASSERT(ssl_server_name_.empty()); + size_t expected_len; + + if (!GetDigestLength(digest_alg, &expected_len)) { + LOG(LS_WARNING) << "Unknown digest algorithm: " << digest_alg; + return false; + } + if (expected_len != digest_len) + return false; + + peer_certificate_digest_value_.SetData(digest_val, digest_len); + peer_certificate_digest_algorithm_ = digest_alg; + + return true; +} + +void SSLStreamAdapterHelper::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "SSLStreamAdapterHelper::Error(" + << context << ", " << err << "," << signal << ")"; + state_ = SSL_ERROR; + ssl_error_code_ = err; + Cleanup(); + if (signal) + StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err); +} + +void SSLStreamAdapterHelper::Close() { + Cleanup(); + ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR); + StreamAdapterInterface::Close(); +} + +int SSLStreamAdapterHelper::StartSSL() { + ASSERT(state_ == SSL_NONE); + + if (StreamAdapterInterface::GetState() != SS_OPEN) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + int err = BeginSSL(); + if (err) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +} // namespace rtc + diff --git a/webrtc/base/sslstreamadapterhelper.h b/webrtc/base/sslstreamadapterhelper.h new file mode 100644 index 000000000..ef06597b8 --- /dev/null +++ b/webrtc/base/sslstreamadapterhelper.h @@ -0,0 +1,118 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SSLSTREAMADAPTERHELPER_H_ +#define WEBRTC_BASE_SSLSTREAMADAPTERHELPER_H_ + +#include +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/base/sslstreamadapter.h" + +namespace rtc { + +// SSLStreamAdapterHelper : A stream adapter which implements much +// of the logic that is common between the known implementations +// (NSS and OpenSSL) +class SSLStreamAdapterHelper : public SSLStreamAdapter { + public: + explicit SSLStreamAdapterHelper(StreamInterface* stream) + : SSLStreamAdapter(stream), + state_(SSL_NONE), + role_(SSL_CLIENT), + ssl_error_code_(0), // Not meaningful yet + ssl_mode_(SSL_MODE_TLS) {} + + + // Overrides of SSLStreamAdapter + virtual void SetIdentity(SSLIdentity* identity); + virtual void SetServerRole(SSLRole role = SSL_SERVER); + virtual void SetMode(SSLMode mode); + + virtual int StartSSLWithServer(const char* server_name); + virtual int StartSSLWithPeer(); + + virtual bool SetPeerCertificateDigest(const std::string& digest_alg, + const unsigned char* digest_val, + size_t digest_len); + virtual bool GetPeerCertificate(SSLCertificate** cert) const; + virtual StreamState GetState() const; + virtual void Close(); + + protected: + // Internal helper methods + // The following method returns 0 on success and a negative + // error code on failure. The error code may be either -1 or + // from the impl on some other error cases, so it can't really be + // interpreted unfortunately. + + // Perform SSL negotiation steps. + int ContinueSSL(); + + // Error handler helper. signal is given as true for errors in + // asynchronous contexts (when an error code was not returned + // through some other method), and in that case an SE_CLOSE event is + // raised on the stream with the specified error. + // A 0 error means a graceful close, otherwise there is not really enough + // context to interpret the error code. + virtual void Error(const char* context, int err, bool signal); + + // Must be implemented by descendents + virtual int BeginSSL() = 0; + virtual void Cleanup() = 0; + virtual bool GetDigestLength(const std::string& algorithm, + size_t* length) = 0; + + enum SSLState { + // Before calling one of the StartSSL methods, data flows + // in clear text. + SSL_NONE, + SSL_WAIT, // waiting for the stream to open to start SSL negotiation + SSL_CONNECTING, // SSL negotiation in progress + SSL_CONNECTED, // SSL stream successfully established + SSL_ERROR, // some SSL error occurred, stream is closed + SSL_CLOSED // Clean close + }; + + // MSG_MAX is the maximum generic stream message number. + enum { MSG_DTLS_TIMEOUT = MSG_MAX + 1 }; + + SSLState state_; + SSLRole role_; + int ssl_error_code_; // valid when state_ == SSL_ERROR + + // Our key and certificate, mostly useful in peer-to-peer mode. + scoped_ptr identity_; + // in traditional mode, the server name that the server's certificate + // must specify. Empty in peer-to-peer mode. + std::string ssl_server_name_; + // The peer's certificate. Only used for GetPeerCertificate. + scoped_ptr peer_certificate_; + + // The digest of the certificate that the peer must present. + Buffer peer_certificate_digest_value_; + std::string peer_certificate_digest_algorithm_; + + // Do DTLS or not + SSLMode ssl_mode_; + + private: + // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT, + // depending on whether the underlying stream is already open or + // not. Returns 0 on success and a negative value on error. + int StartSSL(); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLSTREAMADAPTERHELPER_H_ diff --git a/webrtc/base/stream.cc b/webrtc/base/stream.cc new file mode 100644 index 000000000..9aa10d773 --- /dev/null +++ b/webrtc/base/stream.cc @@ -0,0 +1,1335 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_POSIX) +#include +#endif // WEBRTC_POSIX +#include +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#define fileno _fileno +#endif + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// StreamInterface +/////////////////////////////////////////////////////////////////////////////// +StreamInterface::~StreamInterface() { +} + +StreamResult StreamInterface::WriteAll(const void* data, size_t data_len, + size_t* written, int* error) { + StreamResult result = SR_SUCCESS; + size_t total_written = 0, current_written; + while (total_written < data_len) { + result = Write(static_cast(data) + total_written, + data_len - total_written, ¤t_written, error); + if (result != SR_SUCCESS) + break; + total_written += current_written; + } + if (written) + *written = total_written; + return result; +} + +StreamResult StreamInterface::ReadAll(void* buffer, size_t buffer_len, + size_t* read, int* error) { + StreamResult result = SR_SUCCESS; + size_t total_read = 0, current_read; + while (total_read < buffer_len) { + result = Read(static_cast(buffer) + total_read, + buffer_len - total_read, ¤t_read, error); + if (result != SR_SUCCESS) + break; + total_read += current_read; + } + if (read) + *read = total_read; + return result; +} + +StreamResult StreamInterface::ReadLine(std::string* line) { + line->clear(); + StreamResult result = SR_SUCCESS; + while (true) { + char ch; + result = Read(&ch, sizeof(ch), NULL, NULL); + if (result != SR_SUCCESS) { + break; + } + if (ch == '\n') { + break; + } + line->push_back(ch); + } + if (!line->empty()) { // give back the line we've collected so far with + result = SR_SUCCESS; // a success code. Otherwise return the last code + } + return result; +} + +void StreamInterface::PostEvent(Thread* t, int events, int err) { + t->Post(this, MSG_POST_EVENT, new StreamEventData(events, err)); +} + +void StreamInterface::PostEvent(int events, int err) { + PostEvent(Thread::Current(), events, err); +} + +StreamInterface::StreamInterface() { +} + +void StreamInterface::OnMessage(Message* msg) { + if (MSG_POST_EVENT == msg->message_id) { + StreamEventData* pe = static_cast(msg->pdata); + SignalEvent(this, pe->events, pe->error); + delete msg->pdata; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamAdapterInterface +/////////////////////////////////////////////////////////////////////////////// + +StreamAdapterInterface::StreamAdapterInterface(StreamInterface* stream, + bool owned) + : stream_(stream), owned_(owned) { + if (NULL != stream_) + stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent); +} + +void StreamAdapterInterface::Attach(StreamInterface* stream, bool owned) { + if (NULL != stream_) + stream_->SignalEvent.disconnect(this); + if (owned_) + delete stream_; + stream_ = stream; + owned_ = owned; + if (NULL != stream_) + stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent); +} + +StreamInterface* StreamAdapterInterface::Detach() { + if (NULL != stream_) + stream_->SignalEvent.disconnect(this); + StreamInterface* stream = stream_; + stream_ = NULL; + return stream; +} + +StreamAdapterInterface::~StreamAdapterInterface() { + if (owned_) + delete stream_; +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamTap +/////////////////////////////////////////////////////////////////////////////// + +StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap) + : StreamAdapterInterface(stream), tap_(), tap_result_(SR_SUCCESS), + tap_error_(0) { + AttachTap(tap); +} + +void StreamTap::AttachTap(StreamInterface* tap) { + tap_.reset(tap); +} + +StreamInterface* StreamTap::DetachTap() { + return tap_.release(); +} + +StreamResult StreamTap::GetTapResult(int* error) { + if (error) { + *error = tap_error_; + } + return tap_result_; +} + +StreamResult StreamTap::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + size_t backup_read; + if (!read) { + read = &backup_read; + } + StreamResult res = StreamAdapterInterface::Read(buffer, buffer_len, + read, error); + if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) { + tap_result_ = tap_->WriteAll(buffer, *read, NULL, &tap_error_); + } + return res; +} + +StreamResult StreamTap::Write(const void* data, size_t data_len, + size_t* written, int* error) { + size_t backup_written; + if (!written) { + written = &backup_written; + } + StreamResult res = StreamAdapterInterface::Write(data, data_len, + written, error); + if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) { + tap_result_ = tap_->WriteAll(data, *written, NULL, &tap_error_); + } + return res; +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamSegment +/////////////////////////////////////////////////////////////////////////////// + +StreamSegment::StreamSegment(StreamInterface* stream) + : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0), + length_(SIZE_UNKNOWN) { + // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN. + stream->GetPosition(&start_); +} + +StreamSegment::StreamSegment(StreamInterface* stream, size_t length) + : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0), + length_(length) { + // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN. + stream->GetPosition(&start_); +} + +StreamResult StreamSegment::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (SIZE_UNKNOWN != length_) { + if (pos_ >= length_) + return SR_EOS; + buffer_len = _min(buffer_len, length_ - pos_); + } + size_t backup_read; + if (!read) { + read = &backup_read; + } + StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, + read, error); + if (SR_SUCCESS == result) { + pos_ += *read; + } + return result; +} + +bool StreamSegment::SetPosition(size_t position) { + if (SIZE_UNKNOWN == start_) + return false; // Not seekable + if ((SIZE_UNKNOWN != length_) && (position > length_)) + return false; // Seek past end of segment + if (!StreamAdapterInterface::SetPosition(start_ + position)) + return false; + pos_ = position; + return true; +} + +bool StreamSegment::GetPosition(size_t* position) const { + if (SIZE_UNKNOWN == start_) + return false; // Not seekable + if (!StreamAdapterInterface::GetPosition(position)) + return false; + if (position) { + ASSERT(*position >= start_); + *position -= start_; + } + return true; +} + +bool StreamSegment::GetSize(size_t* size) const { + if (!StreamAdapterInterface::GetSize(size)) + return false; + if (size) { + if (SIZE_UNKNOWN != start_) { + ASSERT(*size >= start_); + *size -= start_; + } + if (SIZE_UNKNOWN != length_) { + *size = _min(*size, length_); + } + } + return true; +} + +bool StreamSegment::GetAvailable(size_t* size) const { + if (!StreamAdapterInterface::GetAvailable(size)) + return false; + if (size && (SIZE_UNKNOWN != length_)) + *size = _min(*size, length_ - pos_); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// NullStream +/////////////////////////////////////////////////////////////////////////////// + +NullStream::NullStream() { +} + +NullStream::~NullStream() { +} + +StreamState NullStream::GetState() const { + return SS_OPEN; +} + +StreamResult NullStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (error) *error = -1; + return SR_ERROR; +} + +StreamResult NullStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (written) *written = data_len; + return SR_SUCCESS; +} + +void NullStream::Close() { +} + +/////////////////////////////////////////////////////////////////////////////// +// FileStream +/////////////////////////////////////////////////////////////////////////////// + +FileStream::FileStream() : file_(NULL) { +} + +FileStream::~FileStream() { + FileStream::Close(); +} + +bool FileStream::Open(const std::string& filename, const char* mode, + int* error) { + Close(); +#if defined(WEBRTC_WIN) + std::wstring wfilename; + if (Utf8ToWindowsFilename(filename, &wfilename)) { + file_ = _wfopen(wfilename.c_str(), ToUtf16(mode).c_str()); + } else { + if (error) { + *error = -1; + return false; + } + } +#else + file_ = fopen(filename.c_str(), mode); +#endif + if (!file_ && error) { + *error = errno; + } + return (file_ != NULL); +} + +bool FileStream::OpenShare(const std::string& filename, const char* mode, + int shflag, int* error) { + Close(); +#if defined(WEBRTC_WIN) + std::wstring wfilename; + if (Utf8ToWindowsFilename(filename, &wfilename)) { + file_ = _wfsopen(wfilename.c_str(), ToUtf16(mode).c_str(), shflag); + if (!file_ && error) { + *error = errno; + return false; + } + return file_ != NULL; + } else { + if (error) { + *error = -1; + } + return false; + } +#else + return Open(filename, mode, error); +#endif +} + +bool FileStream::DisableBuffering() { + if (!file_) + return false; + return (setvbuf(file_, NULL, _IONBF, 0) == 0); +} + +StreamState FileStream::GetState() const { + return (file_ == NULL) ? SS_CLOSED : SS_OPEN; +} + +StreamResult FileStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (!file_) + return SR_EOS; + size_t result = fread(buffer, 1, buffer_len, file_); + if ((result == 0) && (buffer_len > 0)) { + if (feof(file_)) + return SR_EOS; + if (error) + *error = errno; + return SR_ERROR; + } + if (read) + *read = result; + return SR_SUCCESS; +} + +StreamResult FileStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (!file_) + return SR_EOS; + size_t result = fwrite(data, 1, data_len, file_); + if ((result == 0) && (data_len > 0)) { + if (error) + *error = errno; + return SR_ERROR; + } + if (written) + *written = result; + return SR_SUCCESS; +} + +void FileStream::Close() { + if (file_) { + DoClose(); + file_ = NULL; + } +} + +bool FileStream::SetPosition(size_t position) { + if (!file_) + return false; + return (fseek(file_, static_cast(position), SEEK_SET) == 0); +} + +bool FileStream::GetPosition(size_t* position) const { + ASSERT(NULL != position); + if (!file_) + return false; + long result = ftell(file_); + if (result < 0) + return false; + if (position) + *position = result; + return true; +} + +bool FileStream::GetSize(size_t* size) const { + ASSERT(NULL != size); + if (!file_) + return false; + struct stat file_stats; + if (fstat(fileno(file_), &file_stats) != 0) + return false; + if (size) + *size = file_stats.st_size; + return true; +} + +bool FileStream::GetAvailable(size_t* size) const { + ASSERT(NULL != size); + if (!GetSize(size)) + return false; + long result = ftell(file_); + if (result < 0) + return false; + if (size) + *size -= result; + return true; +} + +bool FileStream::ReserveSize(size_t size) { + // TODO: extend the file to the proper length + return true; +} + +bool FileStream::GetSize(const std::string& filename, size_t* size) { + struct stat file_stats; + if (stat(filename.c_str(), &file_stats) != 0) + return false; + *size = file_stats.st_size; + return true; +} + +bool FileStream::Flush() { + if (file_) { + return (0 == fflush(file_)); + } + // try to flush empty file? + ASSERT(false); + return false; +} + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) + +bool FileStream::TryLock() { + if (file_ == NULL) { + // Stream not open. + ASSERT(false); + return false; + } + + return flock(fileno(file_), LOCK_EX|LOCK_NB) == 0; +} + +bool FileStream::Unlock() { + if (file_ == NULL) { + // Stream not open. + ASSERT(false); + return false; + } + + return flock(fileno(file_), LOCK_UN) == 0; +} + +#endif + +void FileStream::DoClose() { + fclose(file_); +} + +CircularFileStream::CircularFileStream(size_t max_size) + : max_write_size_(max_size), + position_(0), + marked_position_(max_size / 2), + last_write_position_(0), + read_segment_(READ_LATEST), + read_segment_available_(0) { +} + +bool CircularFileStream::Open( + const std::string& filename, const char* mode, int* error) { + if (!FileStream::Open(filename.c_str(), mode, error)) + return false; + + if (strchr(mode, "r") != NULL) { // Opened in read mode. + // Check if the buffer has been overwritten and determine how to read the + // log in time sequence. + size_t file_size; + GetSize(&file_size); + if (file_size == position_) { + // The buffer has not been overwritten yet. Read 0 .. file_size + read_segment_ = READ_LATEST; + read_segment_available_ = file_size; + } else { + // The buffer has been over written. There are three segments: The first + // one is 0 .. marked_position_, which is the marked earliest log. The + // second one is position_ .. file_size, which is the middle log. The + // last one is marked_position_ .. position_, which is the latest log. + read_segment_ = READ_MARKED; + read_segment_available_ = marked_position_; + last_write_position_ = position_; + } + + // Read from the beginning. + position_ = 0; + SetPosition(position_); + } + + return true; +} + +StreamResult CircularFileStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (read_segment_available_ == 0) { + size_t file_size; + switch (read_segment_) { + case READ_MARKED: // Finished READ_MARKED and start READ_MIDDLE. + read_segment_ = READ_MIDDLE; + position_ = last_write_position_; + SetPosition(position_); + GetSize(&file_size); + read_segment_available_ = file_size - position_; + break; + + case READ_MIDDLE: // Finished READ_MIDDLE and start READ_LATEST. + read_segment_ = READ_LATEST; + position_ = marked_position_; + SetPosition(position_); + read_segment_available_ = last_write_position_ - position_; + break; + + default: // Finished READ_LATEST and return EOS. + return rtc::SR_EOS; + } + } + + size_t local_read; + if (!read) read = &local_read; + + size_t to_read = rtc::_min(buffer_len, read_segment_available_); + rtc::StreamResult result + = rtc::FileStream::Read(buffer, to_read, read, error); + if (result == rtc::SR_SUCCESS) { + read_segment_available_ -= *read; + position_ += *read; + } + return result; +} + +StreamResult CircularFileStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (position_ >= max_write_size_) { + ASSERT(position_ == max_write_size_); + position_ = marked_position_; + SetPosition(position_); + } + + size_t local_written; + if (!written) written = &local_written; + + size_t to_eof = max_write_size_ - position_; + size_t to_write = rtc::_min(data_len, to_eof); + rtc::StreamResult result + = rtc::FileStream::Write(data, to_write, written, error); + if (result == rtc::SR_SUCCESS) { + position_ += *written; + } + return result; +} + +AsyncWriteStream::~AsyncWriteStream() { + write_thread_->Clear(this, 0, NULL); + ClearBufferAndWrite(); + + CritScope cs(&crit_stream_); + stream_.reset(); +} + +// This is needed by some stream writers, such as RtpDumpWriter. +bool AsyncWriteStream::GetPosition(size_t* position) const { + CritScope cs(&crit_stream_); + return stream_->GetPosition(position); +} + +// This is needed by some stream writers, such as the plugin log writers. +StreamResult AsyncWriteStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + CritScope cs(&crit_stream_); + return stream_->Read(buffer, buffer_len, read, error); +} + +void AsyncWriteStream::Close() { + if (state_ == SS_CLOSED) { + return; + } + + write_thread_->Clear(this, 0, NULL); + ClearBufferAndWrite(); + + CritScope cs(&crit_stream_); + stream_->Close(); + state_ = SS_CLOSED; +} + +StreamResult AsyncWriteStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (state_ == SS_CLOSED) { + return SR_ERROR; + } + + size_t previous_buffer_length = 0; + { + CritScope cs(&crit_buffer_); + previous_buffer_length = buffer_.length(); + buffer_.AppendData(data, data_len); + } + + if (previous_buffer_length == 0) { + // If there's stuff already in the buffer, then we already called + // Post and the write_thread_ hasn't pulled it out yet, so we + // don't need to re-Post. + write_thread_->Post(this, 0, NULL); + } + // Return immediately, assuming that it works. + if (written) { + *written = data_len; + } + return SR_SUCCESS; +} + +void AsyncWriteStream::OnMessage(rtc::Message* pmsg) { + ClearBufferAndWrite(); +} + +bool AsyncWriteStream::Flush() { + if (state_ == SS_CLOSED) { + return false; + } + + ClearBufferAndWrite(); + + CritScope cs(&crit_stream_); + return stream_->Flush(); +} + +void AsyncWriteStream::ClearBufferAndWrite() { + Buffer to_write; + { + CritScope cs_buffer(&crit_buffer_); + buffer_.TransferTo(&to_write); + } + + if (to_write.length() > 0) { + CritScope cs(&crit_stream_); + stream_->WriteAll(to_write.data(), to_write.length(), NULL, NULL); + } +} + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) + +// Have to identically rewrite the FileStream destructor or else it would call +// the base class's Close() instead of the sub-class's. +POpenStream::~POpenStream() { + POpenStream::Close(); +} + +bool POpenStream::Open(const std::string& subcommand, + const char* mode, + int* error) { + Close(); + file_ = popen(subcommand.c_str(), mode); + if (file_ == NULL) { + if (error) + *error = errno; + return false; + } + return true; +} + +bool POpenStream::OpenShare(const std::string& subcommand, const char* mode, + int shflag, int* error) { + return Open(subcommand, mode, error); +} + +void POpenStream::DoClose() { + wait_status_ = pclose(file_); +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// +// MemoryStream +/////////////////////////////////////////////////////////////////////////////// + +MemoryStreamBase::MemoryStreamBase() + : buffer_(NULL), buffer_length_(0), data_length_(0), + seek_position_(0) { +} + +StreamState MemoryStreamBase::GetState() const { + return SS_OPEN; +} + +StreamResult MemoryStreamBase::Read(void* buffer, size_t bytes, + size_t* bytes_read, int* error) { + if (seek_position_ >= data_length_) { + return SR_EOS; + } + size_t available = data_length_ - seek_position_; + if (bytes > available) { + // Read partial buffer + bytes = available; + } + memcpy(buffer, &buffer_[seek_position_], bytes); + seek_position_ += bytes; + if (bytes_read) { + *bytes_read = bytes; + } + return SR_SUCCESS; +} + +StreamResult MemoryStreamBase::Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error) { + size_t available = buffer_length_ - seek_position_; + if (0 == available) { + // Increase buffer size to the larger of: + // a) new position rounded up to next 256 bytes + // b) double the previous length + size_t new_buffer_length = _max(((seek_position_ + bytes) | 0xFF) + 1, + buffer_length_ * 2); + StreamResult result = DoReserve(new_buffer_length, error); + if (SR_SUCCESS != result) { + return result; + } + ASSERT(buffer_length_ >= new_buffer_length); + available = buffer_length_ - seek_position_; + } + + if (bytes > available) { + bytes = available; + } + memcpy(&buffer_[seek_position_], buffer, bytes); + seek_position_ += bytes; + if (data_length_ < seek_position_) { + data_length_ = seek_position_; + } + if (bytes_written) { + *bytes_written = bytes; + } + return SR_SUCCESS; +} + +void MemoryStreamBase::Close() { + // nothing to do +} + +bool MemoryStreamBase::SetPosition(size_t position) { + if (position > data_length_) + return false; + seek_position_ = position; + return true; +} + +bool MemoryStreamBase::GetPosition(size_t* position) const { + if (position) + *position = seek_position_; + return true; +} + +bool MemoryStreamBase::GetSize(size_t* size) const { + if (size) + *size = data_length_; + return true; +} + +bool MemoryStreamBase::GetAvailable(size_t* size) const { + if (size) + *size = data_length_ - seek_position_; + return true; +} + +bool MemoryStreamBase::ReserveSize(size_t size) { + return (SR_SUCCESS == DoReserve(size, NULL)); +} + +StreamResult MemoryStreamBase::DoReserve(size_t size, int* error) { + return (buffer_length_ >= size) ? SR_SUCCESS : SR_EOS; +} + +/////////////////////////////////////////////////////////////////////////////// + +MemoryStream::MemoryStream() + : buffer_alloc_(NULL) { +} + +MemoryStream::MemoryStream(const char* data) + : buffer_alloc_(NULL) { + SetData(data, strlen(data)); +} + +MemoryStream::MemoryStream(const void* data, size_t length) + : buffer_alloc_(NULL) { + SetData(data, length); +} + +MemoryStream::~MemoryStream() { + delete [] buffer_alloc_; +} + +void MemoryStream::SetData(const void* data, size_t length) { + data_length_ = buffer_length_ = length; + delete [] buffer_alloc_; + buffer_alloc_ = new char[buffer_length_ + kAlignment]; + buffer_ = reinterpret_cast(ALIGNP(buffer_alloc_, kAlignment)); + memcpy(buffer_, data, data_length_); + seek_position_ = 0; +} + +StreamResult MemoryStream::DoReserve(size_t size, int* error) { + if (buffer_length_ >= size) + return SR_SUCCESS; + + if (char* new_buffer_alloc = new char[size + kAlignment]) { + char* new_buffer = reinterpret_cast( + ALIGNP(new_buffer_alloc, kAlignment)); + memcpy(new_buffer, buffer_, data_length_); + delete [] buffer_alloc_; + buffer_alloc_ = new_buffer_alloc; + buffer_ = new_buffer; + buffer_length_ = size; + return SR_SUCCESS; + } + + if (error) { + *error = ENOMEM; + } + return SR_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// + +ExternalMemoryStream::ExternalMemoryStream() { +} + +ExternalMemoryStream::ExternalMemoryStream(void* data, size_t length) { + SetData(data, length); +} + +ExternalMemoryStream::~ExternalMemoryStream() { +} + +void ExternalMemoryStream::SetData(void* data, size_t length) { + data_length_ = buffer_length_ = length; + buffer_ = static_cast(data); + seek_position_ = 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// FifoBuffer +/////////////////////////////////////////////////////////////////////////////// + +FifoBuffer::FifoBuffer(size_t size) + : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size), + data_length_(0), read_position_(0), owner_(Thread::Current()) { + // all events are done on the owner_ thread +} + +FifoBuffer::FifoBuffer(size_t size, Thread* owner) + : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size), + data_length_(0), read_position_(0), owner_(owner) { + // all events are done on the owner_ thread +} + +FifoBuffer::~FifoBuffer() { +} + +bool FifoBuffer::GetBuffered(size_t* size) const { + CritScope cs(&crit_); + *size = data_length_; + return true; +} + +bool FifoBuffer::SetCapacity(size_t size) { + CritScope cs(&crit_); + if (data_length_ > size) { + return false; + } + + if (size != buffer_length_) { + char* buffer = new char[size]; + const size_t copy = data_length_; + const size_t tail_copy = _min(copy, buffer_length_ - read_position_); + memcpy(buffer, &buffer_[read_position_], tail_copy); + memcpy(buffer + tail_copy, &buffer_[0], copy - tail_copy); + buffer_.reset(buffer); + read_position_ = 0; + buffer_length_ = size; + } + return true; +} + +StreamResult FifoBuffer::ReadOffset(void* buffer, size_t bytes, + size_t offset, size_t* bytes_read) { + CritScope cs(&crit_); + return ReadOffsetLocked(buffer, bytes, offset, bytes_read); +} + +StreamResult FifoBuffer::WriteOffset(const void* buffer, size_t bytes, + size_t offset, size_t* bytes_written) { + CritScope cs(&crit_); + return WriteOffsetLocked(buffer, bytes, offset, bytes_written); +} + +StreamState FifoBuffer::GetState() const { + return state_; +} + +StreamResult FifoBuffer::Read(void* buffer, size_t bytes, + size_t* bytes_read, int* error) { + CritScope cs(&crit_); + const bool was_writable = data_length_ < buffer_length_; + size_t copy = 0; + StreamResult result = ReadOffsetLocked(buffer, bytes, 0, ©); + + if (result == SR_SUCCESS) { + // If read was successful then adjust the read position and number of + // bytes buffered. + read_position_ = (read_position_ + copy) % buffer_length_; + data_length_ -= copy; + if (bytes_read) { + *bytes_read = copy; + } + + // if we were full before, and now we're not, post an event + if (!was_writable && copy > 0) { + PostEvent(owner_, SE_WRITE, 0); + } + } + return result; +} + +StreamResult FifoBuffer::Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error) { + CritScope cs(&crit_); + + const bool was_readable = (data_length_ > 0); + size_t copy = 0; + StreamResult result = WriteOffsetLocked(buffer, bytes, 0, ©); + + if (result == SR_SUCCESS) { + // If write was successful then adjust the number of readable bytes. + data_length_ += copy; + if (bytes_written) { + *bytes_written = copy; + } + + // if we didn't have any data to read before, and now we do, post an event + if (!was_readable && copy > 0) { + PostEvent(owner_, SE_READ, 0); + } + } + return result; +} + +void FifoBuffer::Close() { + CritScope cs(&crit_); + state_ = SS_CLOSED; +} + +const void* FifoBuffer::GetReadData(size_t* size) { + CritScope cs(&crit_); + *size = (read_position_ + data_length_ <= buffer_length_) ? + data_length_ : buffer_length_ - read_position_; + return &buffer_[read_position_]; +} + +void FifoBuffer::ConsumeReadData(size_t size) { + CritScope cs(&crit_); + ASSERT(size <= data_length_); + const bool was_writable = data_length_ < buffer_length_; + read_position_ = (read_position_ + size) % buffer_length_; + data_length_ -= size; + if (!was_writable && size > 0) { + PostEvent(owner_, SE_WRITE, 0); + } +} + +void* FifoBuffer::GetWriteBuffer(size_t* size) { + CritScope cs(&crit_); + if (state_ == SS_CLOSED) { + return NULL; + } + + // if empty, reset the write position to the beginning, so we can get + // the biggest possible block + if (data_length_ == 0) { + read_position_ = 0; + } + + const size_t write_position = (read_position_ + data_length_) + % buffer_length_; + *size = (write_position > read_position_ || data_length_ == 0) ? + buffer_length_ - write_position : read_position_ - write_position; + return &buffer_[write_position]; +} + +void FifoBuffer::ConsumeWriteBuffer(size_t size) { + CritScope cs(&crit_); + ASSERT(size <= buffer_length_ - data_length_); + const bool was_readable = (data_length_ > 0); + data_length_ += size; + if (!was_readable && size > 0) { + PostEvent(owner_, SE_READ, 0); + } +} + +bool FifoBuffer::GetWriteRemaining(size_t* size) const { + CritScope cs(&crit_); + *size = buffer_length_ - data_length_; + return true; +} + +StreamResult FifoBuffer::ReadOffsetLocked(void* buffer, + size_t bytes, + size_t offset, + size_t* bytes_read) { + if (offset >= data_length_) { + return (state_ != SS_CLOSED) ? SR_BLOCK : SR_EOS; + } + + const size_t available = data_length_ - offset; + const size_t read_position = (read_position_ + offset) % buffer_length_; + const size_t copy = _min(bytes, available); + const size_t tail_copy = _min(copy, buffer_length_ - read_position); + char* const p = static_cast(buffer); + memcpy(p, &buffer_[read_position], tail_copy); + memcpy(p + tail_copy, &buffer_[0], copy - tail_copy); + + if (bytes_read) { + *bytes_read = copy; + } + return SR_SUCCESS; +} + +StreamResult FifoBuffer::WriteOffsetLocked(const void* buffer, + size_t bytes, + size_t offset, + size_t* bytes_written) { + if (state_ == SS_CLOSED) { + return SR_EOS; + } + + if (data_length_ + offset >= buffer_length_) { + return SR_BLOCK; + } + + const size_t available = buffer_length_ - data_length_ - offset; + const size_t write_position = (read_position_ + data_length_ + offset) + % buffer_length_; + const size_t copy = _min(bytes, available); + const size_t tail_copy = _min(copy, buffer_length_ - write_position); + const char* const p = static_cast(buffer); + memcpy(&buffer_[write_position], p, tail_copy); + memcpy(&buffer_[0], p + tail_copy, copy - tail_copy); + + if (bytes_written) { + *bytes_written = copy; + } + return SR_SUCCESS; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// LoggingAdapter +/////////////////////////////////////////////////////////////////////////////// + +LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level, + const std::string& label, bool hex_mode) + : StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) { + set_label(label); +} + +void LoggingAdapter::set_label(const std::string& label) { + label_.assign("["); + label_.append(label); + label_.append("]"); +} + +StreamResult LoggingAdapter::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + size_t local_read; if (!read) read = &local_read; + StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, read, + error); + if (result == SR_SUCCESS) { + LogMultiline(level_, label_.c_str(), true, buffer, *read, hex_mode_, &lms_); + } + return result; +} + +StreamResult LoggingAdapter::Write(const void* data, size_t data_len, + size_t* written, int* error) { + size_t local_written; + if (!written) written = &local_written; + StreamResult result = StreamAdapterInterface::Write(data, data_len, written, + error); + if (result == SR_SUCCESS) { + LogMultiline(level_, label_.c_str(), false, data, *written, hex_mode_, + &lms_); + } + return result; +} + +void LoggingAdapter::Close() { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed locally"; + StreamAdapterInterface::Close(); +} + +void LoggingAdapter::OnEvent(StreamInterface* stream, int events, int err) { + if (events & SE_OPEN) { + LOG_V(level_) << label_ << " Open"; + } else if (events & SE_CLOSE) { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed with error: " << err; + } + StreamAdapterInterface::OnEvent(stream, events, err); +} + +/////////////////////////////////////////////////////////////////////////////// +// StringStream - Reads/Writes to an external std::string +/////////////////////////////////////////////////////////////////////////////// + +StringStream::StringStream(std::string& str) + : str_(str), read_pos_(0), read_only_(false) { +} + +StringStream::StringStream(const std::string& str) + : str_(const_cast(str)), read_pos_(0), read_only_(true) { +} + +StreamState StringStream::GetState() const { + return SS_OPEN; +} + +StreamResult StringStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + size_t available = _min(buffer_len, str_.size() - read_pos_); + if (!available) + return SR_EOS; + memcpy(buffer, str_.data() + read_pos_, available); + read_pos_ += available; + if (read) + *read = available; + return SR_SUCCESS; +} + +StreamResult StringStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (read_only_) { + if (error) { + *error = -1; + } + return SR_ERROR; + } + str_.append(static_cast(data), + static_cast(data) + data_len); + if (written) + *written = data_len; + return SR_SUCCESS; +} + +void StringStream::Close() { +} + +bool StringStream::SetPosition(size_t position) { + if (position > str_.size()) + return false; + read_pos_ = position; + return true; +} + +bool StringStream::GetPosition(size_t* position) const { + if (position) + *position = read_pos_; + return true; +} + +bool StringStream::GetSize(size_t* size) const { + if (size) + *size = str_.size(); + return true; +} + +bool StringStream::GetAvailable(size_t* size) const { + if (size) + *size = str_.size() - read_pos_; + return true; +} + +bool StringStream::ReserveSize(size_t size) { + if (read_only_) + return false; + str_.reserve(size); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamReference +/////////////////////////////////////////////////////////////////////////////// + +StreamReference::StreamReference(StreamInterface* stream) + : StreamAdapterInterface(stream, false) { + // owner set to false so the destructor does not free the stream. + stream_ref_count_ = new StreamRefCount(stream); +} + +StreamInterface* StreamReference::NewReference() { + stream_ref_count_->AddReference(); + return new StreamReference(stream_ref_count_, stream()); +} + +StreamReference::~StreamReference() { + stream_ref_count_->Release(); +} + +StreamReference::StreamReference(StreamRefCount* stream_ref_count, + StreamInterface* stream) + : StreamAdapterInterface(stream, false), + stream_ref_count_(stream_ref_count) { +} + +/////////////////////////////////////////////////////////////////////////////// + +StreamResult Flow(StreamInterface* source, + char* buffer, size_t buffer_len, + StreamInterface* sink, + size_t* data_len /* = NULL */) { + ASSERT(buffer_len > 0); + + StreamResult result; + size_t count, read_pos, write_pos; + if (data_len) { + read_pos = *data_len; + } else { + read_pos = 0; + } + + bool end_of_stream = false; + do { + // Read until buffer is full, end of stream, or error + while (!end_of_stream && (read_pos < buffer_len)) { + result = source->Read(buffer + read_pos, buffer_len - read_pos, + &count, NULL); + if (result == SR_EOS) { + end_of_stream = true; + } else if (result != SR_SUCCESS) { + if (data_len) { + *data_len = read_pos; + } + return result; + } else { + read_pos += count; + } + } + + // Write until buffer is empty, or error (including end of stream) + write_pos = 0; + while (write_pos < read_pos) { + result = sink->Write(buffer + write_pos, read_pos - write_pos, + &count, NULL); + if (result != SR_SUCCESS) { + if (data_len) { + *data_len = read_pos - write_pos; + if (write_pos > 0) { + memmove(buffer, buffer + write_pos, *data_len); + } + } + return result; + } + write_pos += count; + } + + read_pos = 0; + } while (!end_of_stream); + + if (data_len) { + *data_len = 0; + } + return SR_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/stream.h b/webrtc/base/stream.h new file mode 100644 index 000000000..00ded372c --- /dev/null +++ b/webrtc/base/stream.h @@ -0,0 +1,820 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_STREAM_H_ +#define WEBRTC_BASE_STREAM_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// StreamInterface is a generic asynchronous stream interface, supporting read, +// write, and close operations, and asynchronous signalling of state changes. +// The interface is designed with file, memory, and socket implementations in +// mind. Some implementations offer extended operations, such as seeking. +/////////////////////////////////////////////////////////////////////////////// + +// The following enumerations are declared outside of the StreamInterface +// class for brevity in use. + +// The SS_OPENING state indicates that the stream will signal open or closed +// in the future. +enum StreamState { SS_CLOSED, SS_OPENING, SS_OPEN }; + +// Stream read/write methods return this value to indicate various success +// and failure conditions described below. +enum StreamResult { SR_ERROR, SR_SUCCESS, SR_BLOCK, SR_EOS }; + +// StreamEvents are used to asynchronously signal state transitionss. The flags +// may be combined. +// SE_OPEN: The stream has transitioned to the SS_OPEN state +// SE_CLOSE: The stream has transitioned to the SS_CLOSED state +// SE_READ: Data is available, so Read is likely to not return SR_BLOCK +// SE_WRITE: Data can be written, so Write is likely to not return SR_BLOCK +enum StreamEvent { SE_OPEN = 1, SE_READ = 2, SE_WRITE = 4, SE_CLOSE = 8 }; + +class Thread; + +struct StreamEventData : public MessageData { + int events, error; + StreamEventData(int ev, int er) : events(ev), error(er) { } +}; + +class StreamInterface : public MessageHandler { + public: + enum { + MSG_POST_EVENT = 0xF1F1, MSG_MAX = MSG_POST_EVENT + }; + + virtual ~StreamInterface(); + + virtual StreamState GetState() const = 0; + + // Read attempts to fill buffer of size buffer_len. Write attempts to send + // data_len bytes stored in data. The variables read and write are set only + // on SR_SUCCESS (see below). Likewise, error is only set on SR_ERROR. + // Read and Write return a value indicating: + // SR_ERROR: an error occurred, which is returned in a non-null error + // argument. Interpretation of the error requires knowledge of the + // stream's concrete type, which limits its usefulness. + // SR_SUCCESS: some number of bytes were successfully written, which is + // returned in a non-null read/write argument. + // SR_BLOCK: the stream is in non-blocking mode, and the operation would + // block, or the stream is in SS_OPENING state. + // SR_EOS: the end-of-stream has been reached, or the stream is in the + // SS_CLOSED state. + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) = 0; + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) = 0; + // Attempt to transition to the SS_CLOSED state. SE_CLOSE will not be + // signalled as a result of this call. + virtual void Close() = 0; + + // Streams may signal one or more StreamEvents to indicate state changes. + // The first argument identifies the stream on which the state change occured. + // The second argument is a bit-wise combination of StreamEvents. + // If SE_CLOSE is signalled, then the third argument is the associated error + // code. Otherwise, the value is undefined. + // Note: Not all streams will support asynchronous event signalling. However, + // SS_OPENING and SR_BLOCK returned from stream member functions imply that + // certain events will be raised in the future. + sigslot::signal3 SignalEvent; + + // Like calling SignalEvent, but posts a message to the specified thread, + // which will call SignalEvent. This helps unroll the stack and prevent + // re-entrancy. + void PostEvent(Thread* t, int events, int err); + // Like the aforementioned method, but posts to the current thread. + void PostEvent(int events, int err); + + // + // OPTIONAL OPERATIONS + // + // Not all implementations will support the following operations. In general, + // a stream will only support an operation if it reasonably efficient to do + // so. For example, while a socket could buffer incoming data to support + // seeking, it will not do so. Instead, a buffering stream adapter should + // be used. + // + // Even though several of these operations are related, you should + // always use whichever operation is most relevant. For example, you may + // be tempted to use GetSize() and GetPosition() to deduce the result of + // GetAvailable(). However, a stream which is read-once may support the + // latter operation but not the former. + // + + // The following four methods are used to avoid copying data multiple times. + + // GetReadData returns a pointer to a buffer which is owned by the stream. + // The buffer contains data_len bytes. NULL is returned if no data is + // available, or if the method fails. If the caller processes the data, it + // must call ConsumeReadData with the number of processed bytes. GetReadData + // does not require a matching call to ConsumeReadData if the data is not + // processed. Read and ConsumeReadData invalidate the buffer returned by + // GetReadData. + virtual const void* GetReadData(size_t* data_len) { return NULL; } + virtual void ConsumeReadData(size_t used) {} + + // GetWriteBuffer returns a pointer to a buffer which is owned by the stream. + // The buffer has a capacity of buf_len bytes. NULL is returned if there is + // no buffer available, or if the method fails. The call may write data to + // the buffer, and then call ConsumeWriteBuffer with the number of bytes + // written. GetWriteBuffer does not require a matching call to + // ConsumeWriteData if no data is written. Write, ForceWrite, and + // ConsumeWriteData invalidate the buffer returned by GetWriteBuffer. + // TODO: Allow the caller to specify a minimum buffer size. If the specified + // amount of buffer is not yet available, return NULL and Signal SE_WRITE + // when it is available. If the requested amount is too large, return an + // error. + virtual void* GetWriteBuffer(size_t* buf_len) { return NULL; } + virtual void ConsumeWriteBuffer(size_t used) {} + + // Write data_len bytes found in data, circumventing any throttling which + // would could cause SR_BLOCK to be returned. Returns true if all the data + // was written. Otherwise, the method is unsupported, or an unrecoverable + // error occurred, and the error value is set. This method should be used + // sparingly to write critical data which should not be throttled. A stream + // which cannot circumvent its blocking constraints should not implement this + // method. + // NOTE: This interface is being considered experimentally at the moment. It + // would be used by JUDP and BandwidthStream as a way to circumvent certain + // soft limits in writing. + //virtual bool ForceWrite(const void* data, size_t data_len, int* error) { + // if (error) *error = -1; + // return false; + //} + + // Seek to a byte offset from the beginning of the stream. Returns false if + // the stream does not support seeking, or cannot seek to the specified + // position. + virtual bool SetPosition(size_t position) { return false; } + + // Get the byte offset of the current position from the start of the stream. + // Returns false if the position is not known. + virtual bool GetPosition(size_t* position) const { return false; } + + // Get the byte length of the entire stream. Returns false if the length + // is not known. + virtual bool GetSize(size_t* size) const { return false; } + + // Return the number of Read()-able bytes remaining before end-of-stream. + // Returns false if not known. + virtual bool GetAvailable(size_t* size) const { return false; } + + // Return the number of Write()-able bytes remaining before end-of-stream. + // Returns false if not known. + virtual bool GetWriteRemaining(size_t* size) const { return false; } + + // Return true if flush is successful. + virtual bool Flush() { return false; } + + // Communicates the amount of data which will be written to the stream. The + // stream may choose to preallocate memory to accomodate this data. The + // stream may return false to indicate that there is not enough room (ie, + // Write will return SR_EOS/SR_ERROR at some point). Note that calling this + // function should not affect the existing state of data in the stream. + virtual bool ReserveSize(size_t size) { return true; } + + // + // CONVENIENCE METHODS + // + // These methods are implemented in terms of other methods, for convenience. + // + + // Seek to the start of the stream. + inline bool Rewind() { return SetPosition(0); } + + // WriteAll is a helper function which repeatedly calls Write until all the + // data is written, or something other than SR_SUCCESS is returned. Note that + // unlike Write, the argument 'written' is always set, and may be non-zero + // on results other than SR_SUCCESS. The remaining arguments have the + // same semantics as Write. + StreamResult WriteAll(const void* data, size_t data_len, + size_t* written, int* error); + + // Similar to ReadAll. Calls Read until buffer_len bytes have been read, or + // until a non-SR_SUCCESS result is returned. 'read' is always set. + StreamResult ReadAll(void* buffer, size_t buffer_len, + size_t* read, int* error); + + // ReadLine is a helper function which repeatedly calls Read until it hits + // the end-of-line character, or something other than SR_SUCCESS. + // TODO: this is too inefficient to keep here. Break this out into a buffered + // readline object or adapter + StreamResult ReadLine(std::string* line); + + protected: + StreamInterface(); + + // MessageHandler Interface + virtual void OnMessage(Message* msg); + + private: + DISALLOW_EVIL_CONSTRUCTORS(StreamInterface); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamAdapterInterface is a convenient base-class for adapting a stream. +// By default, all operations are pass-through. Override the methods that you +// require adaptation. Streams should really be upgraded to reference-counted. +// In the meantime, use the owned flag to indicate whether the adapter should +// own the adapted stream. +/////////////////////////////////////////////////////////////////////////////// + +class StreamAdapterInterface : public StreamInterface, + public sigslot::has_slots<> { + public: + explicit StreamAdapterInterface(StreamInterface* stream, bool owned = true); + + // Core Stream Interface + virtual StreamState GetState() const { + return stream_->GetState(); + } + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + return stream_->Read(buffer, buffer_len, read, error); + } + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + return stream_->Write(data, data_len, written, error); + } + virtual void Close() { + stream_->Close(); + } + + // Optional Stream Interface + /* Note: Many stream adapters were implemented prior to this Read/Write + interface. Therefore, a simple pass through of data in those cases may + be broken. At a later time, we should do a once-over pass of all + adapters, and make them compliant with these interfaces, after which this + code can be uncommented. + virtual const void* GetReadData(size_t* data_len) { + return stream_->GetReadData(data_len); + } + virtual void ConsumeReadData(size_t used) { + stream_->ConsumeReadData(used); + } + + virtual void* GetWriteBuffer(size_t* buf_len) { + return stream_->GetWriteBuffer(buf_len); + } + virtual void ConsumeWriteBuffer(size_t used) { + stream_->ConsumeWriteBuffer(used); + } + */ + + /* Note: This interface is currently undergoing evaluation. + virtual bool ForceWrite(const void* data, size_t data_len, int* error) { + return stream_->ForceWrite(data, data_len, error); + } + */ + + virtual bool SetPosition(size_t position) { + return stream_->SetPosition(position); + } + virtual bool GetPosition(size_t* position) const { + return stream_->GetPosition(position); + } + virtual bool GetSize(size_t* size) const { + return stream_->GetSize(size); + } + virtual bool GetAvailable(size_t* size) const { + return stream_->GetAvailable(size); + } + virtual bool GetWriteRemaining(size_t* size) const { + return stream_->GetWriteRemaining(size); + } + virtual bool ReserveSize(size_t size) { + return stream_->ReserveSize(size); + } + virtual bool Flush() { + return stream_->Flush(); + } + + void Attach(StreamInterface* stream, bool owned = true); + StreamInterface* Detach(); + + protected: + virtual ~StreamAdapterInterface(); + + // Note that the adapter presents itself as the origin of the stream events, + // since users of the adapter may not recognize the adapted object. + virtual void OnEvent(StreamInterface* stream, int events, int err) { + SignalEvent(this, events, err); + } + StreamInterface* stream() { return stream_; } + + private: + StreamInterface* stream_; + bool owned_; + DISALLOW_EVIL_CONSTRUCTORS(StreamAdapterInterface); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamTap is a non-modifying, pass-through adapter, which copies all data +// in either direction to the tap. Note that errors or blocking on writing to +// the tap will prevent further tap writes from occurring. +/////////////////////////////////////////////////////////////////////////////// + +class StreamTap : public StreamAdapterInterface { + public: + explicit StreamTap(StreamInterface* stream, StreamInterface* tap); + + void AttachTap(StreamInterface* tap); + StreamInterface* DetachTap(); + StreamResult GetTapResult(int* error); + + // StreamAdapterInterface Interface + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + private: + scoped_ptr tap_; + StreamResult tap_result_; + int tap_error_; + DISALLOW_EVIL_CONSTRUCTORS(StreamTap); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamSegment adapts a read stream, to expose a subset of the adapted +// stream's data. This is useful for cases where a stream contains multiple +// documents concatenated together. StreamSegment can expose a subset of +// the data as an independent stream, including support for rewinding and +// seeking. +/////////////////////////////////////////////////////////////////////////////// + +class StreamSegment : public StreamAdapterInterface { + public: + // The current position of the adapted stream becomes the beginning of the + // segment. If a length is specified, it bounds the length of the segment. + explicit StreamSegment(StreamInterface* stream); + explicit StreamSegment(StreamInterface* stream, size_t length); + + // StreamAdapterInterface Interface + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + + private: + size_t start_, pos_, length_; + DISALLOW_EVIL_CONSTRUCTORS(StreamSegment); +}; + +/////////////////////////////////////////////////////////////////////////////// +// NullStream gives errors on read, and silently discards all written data. +/////////////////////////////////////////////////////////////////////////////// + +class NullStream : public StreamInterface { + public: + NullStream(); + virtual ~NullStream(); + + // StreamInterface Interface + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// FileStream is a simple implementation of a StreamInterface, which does not +// support asynchronous notification. +/////////////////////////////////////////////////////////////////////////////// + +class FileStream : public StreamInterface { + public: + FileStream(); + virtual ~FileStream(); + + // The semantics of filename and mode are the same as stdio's fopen + virtual bool Open(const std::string& filename, const char* mode, int* error); + virtual bool OpenShare(const std::string& filename, const char* mode, + int shflag, int* error); + + // By default, reads and writes are buffered for efficiency. Disabling + // buffering causes writes to block until the bytes on disk are updated. + virtual bool DisableBuffering(); + + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + virtual bool ReserveSize(size_t size); + + virtual bool Flush(); + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) + // Tries to aquire an exclusive lock on the file. + // Use OpenShare(...) on win32 to get similar functionality. + bool TryLock(); + bool Unlock(); +#endif + + // Note: Deprecated in favor of Filesystem::GetFileSize(). + static bool GetSize(const std::string& filename, size_t* size); + + protected: + virtual void DoClose(); + + FILE* file_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(FileStream); +}; + +// A stream that caps the output at a certain size, dropping content from the +// middle of the logical stream and maintaining equal parts of the start/end of +// the logical stream. +class CircularFileStream : public FileStream { + public: + explicit CircularFileStream(size_t max_size); + + virtual bool Open(const std::string& filename, const char* mode, int* error); + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + private: + enum ReadSegment { + READ_MARKED, // Read 0 .. marked_position_ + READ_MIDDLE, // Read position_ .. file_size + READ_LATEST, // Read marked_position_ .. position_ if the buffer was + // overwritten or 0 .. position_ otherwise. + }; + + size_t max_write_size_; + size_t position_; + size_t marked_position_; + size_t last_write_position_; + ReadSegment read_segment_; + size_t read_segment_available_; +}; + +// A stream which pushes writes onto a separate thread and +// returns from the write call immediately. +class AsyncWriteStream : public StreamInterface { + public: + // Takes ownership of the stream, but not the thread. + AsyncWriteStream(StreamInterface* stream, rtc::Thread* write_thread) + : stream_(stream), + write_thread_(write_thread), + state_(stream ? stream->GetState() : SS_CLOSED) { + } + + virtual ~AsyncWriteStream(); + + // StreamInterface Interface + virtual StreamState GetState() const { return state_; } + // This is needed by some stream writers, such as RtpDumpWriter. + virtual bool GetPosition(size_t* position) const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool Flush(); + + protected: + // From MessageHandler + virtual void OnMessage(rtc::Message* pmsg); + virtual void ClearBufferAndWrite(); + + private: + rtc::scoped_ptr stream_; + Thread* write_thread_; + StreamState state_; + Buffer buffer_; + mutable CriticalSection crit_stream_; + CriticalSection crit_buffer_; + + DISALLOW_EVIL_CONSTRUCTORS(AsyncWriteStream); +}; + + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) +// A FileStream that is actually not a file, but the output or input of a +// sub-command. See "man 3 popen" for documentation of the underlying OS popen() +// function. +class POpenStream : public FileStream { + public: + POpenStream() : wait_status_(-1) {} + virtual ~POpenStream(); + + virtual bool Open(const std::string& subcommand, const char* mode, + int* error); + // Same as Open(). shflag is ignored. + virtual bool OpenShare(const std::string& subcommand, const char* mode, + int shflag, int* error); + + // Returns the wait status from the last Close() of an Open()'ed stream, or + // -1 if no Open()+Close() has been done on this object. Meaning of the number + // is documented in "man 2 wait". + int GetWaitStatus() const { return wait_status_; } + + protected: + virtual void DoClose(); + + private: + int wait_status_; +}; +#endif // WEBRTC_POSIX + +/////////////////////////////////////////////////////////////////////////////// +// MemoryStream is a simple implementation of a StreamInterface over in-memory +// data. Data is read and written at the current seek position. Reads return +// end-of-stream when they reach the end of data. Writes actually extend the +// end of data mark. +/////////////////////////////////////////////////////////////////////////////// + +class MemoryStreamBase : public StreamInterface { + public: + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t bytes, size_t* bytes_read, + int* error); + virtual StreamResult Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + virtual bool ReserveSize(size_t size); + + char* GetBuffer() { return buffer_; } + const char* GetBuffer() const { return buffer_; } + + protected: + MemoryStreamBase(); + + virtual StreamResult DoReserve(size_t size, int* error); + + // Invariant: 0 <= seek_position <= data_length_ <= buffer_length_ + char* buffer_; + size_t buffer_length_; + size_t data_length_; + size_t seek_position_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(MemoryStreamBase); +}; + +// MemoryStream dynamically resizes to accomodate written data. + +class MemoryStream : public MemoryStreamBase { + public: + MemoryStream(); + explicit MemoryStream(const char* data); // Calls SetData(data, strlen(data)) + MemoryStream(const void* data, size_t length); // Calls SetData(data, length) + virtual ~MemoryStream(); + + void SetData(const void* data, size_t length); + + protected: + virtual StreamResult DoReserve(size_t size, int* error); + // Memory Streams are aligned for efficiency. + static const int kAlignment = 16; + char* buffer_alloc_; +}; + +// ExternalMemoryStream adapts an external memory buffer, so writes which would +// extend past the end of the buffer will return end-of-stream. + +class ExternalMemoryStream : public MemoryStreamBase { + public: + ExternalMemoryStream(); + ExternalMemoryStream(void* data, size_t length); + virtual ~ExternalMemoryStream(); + + void SetData(void* data, size_t length); +}; + +// FifoBuffer allows for efficient, thread-safe buffering of data between +// writer and reader. As the data can wrap around the end of the buffer, +// MemoryStreamBase can't help us here. + +class FifoBuffer : public StreamInterface { + public: + // Creates a FIFO buffer with the specified capacity. + explicit FifoBuffer(size_t length); + // Creates a FIFO buffer with the specified capacity and owner + FifoBuffer(size_t length, Thread* owner); + virtual ~FifoBuffer(); + // Gets the amount of data currently readable from the buffer. + bool GetBuffered(size_t* data_len) const; + // Resizes the buffer to the specified capacity. Fails if data_length_ > size + bool SetCapacity(size_t length); + + // Read into |buffer| with an offset from the current read position, offset + // is specified in number of bytes. + // This method doesn't adjust read position nor the number of available + // bytes, user has to call ConsumeReadData() to do this. + StreamResult ReadOffset(void* buffer, size_t bytes, size_t offset, + size_t* bytes_read); + + // Write |buffer| with an offset from the current write position, offset is + // specified in number of bytes. + // This method doesn't adjust the number of buffered bytes, user has to call + // ConsumeWriteBuffer() to do this. + StreamResult WriteOffset(const void* buffer, size_t bytes, size_t offset, + size_t* bytes_written); + + // StreamInterface methods + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t bytes, + size_t* bytes_read, int* error); + virtual StreamResult Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error); + virtual void Close(); + virtual const void* GetReadData(size_t* data_len); + virtual void ConsumeReadData(size_t used); + virtual void* GetWriteBuffer(size_t* buf_len); + virtual void ConsumeWriteBuffer(size_t used); + virtual bool GetWriteRemaining(size_t* size) const; + + private: + // Helper method that implements ReadOffset. Caller must acquire a lock + // when calling this method. + StreamResult ReadOffsetLocked(void* buffer, size_t bytes, size_t offset, + size_t* bytes_read); + + // Helper method that implements WriteOffset. Caller must acquire a lock + // when calling this method. + StreamResult WriteOffsetLocked(const void* buffer, size_t bytes, + size_t offset, size_t* bytes_written); + + StreamState state_; // keeps the opened/closed state of the stream + scoped_ptr buffer_; // the allocated buffer + size_t buffer_length_; // size of the allocated buffer + size_t data_length_; // amount of readable data in the buffer + size_t read_position_; // offset to the readable data + Thread* owner_; // stream callbacks are dispatched on this thread + mutable CriticalSection crit_; // object lock + DISALLOW_EVIL_CONSTRUCTORS(FifoBuffer); +}; + +/////////////////////////////////////////////////////////////////////////////// + +class LoggingAdapter : public StreamAdapterInterface { + public: + LoggingAdapter(StreamInterface* stream, LoggingSeverity level, + const std::string& label, bool hex_mode = false); + + void set_label(const std::string& label); + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + + protected: + virtual void OnEvent(StreamInterface* stream, int events, int err); + + private: + LoggingSeverity level_; + std::string label_; + bool hex_mode_; + LogMultilineState lms_; + + DISALLOW_EVIL_CONSTRUCTORS(LoggingAdapter); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StringStream - Reads/Writes to an external std::string +/////////////////////////////////////////////////////////////////////////////// + +class StringStream : public StreamInterface { + public: + explicit StringStream(std::string& str); + explicit StringStream(const std::string& str); + + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + virtual bool ReserveSize(size_t size); + + private: + std::string& str_; + size_t read_pos_; + bool read_only_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamReference - A reference counting stream adapter +/////////////////////////////////////////////////////////////////////////////// + +// Keep in mind that the streams and adapters defined in this file are +// not thread-safe, so this has limited uses. + +// A StreamRefCount holds the reference count and a pointer to the +// wrapped stream. It deletes the wrapped stream when there are no +// more references. We can then have multiple StreamReference +// instances pointing to one StreamRefCount, all wrapping the same +// stream. + +class StreamReference : public StreamAdapterInterface { + class StreamRefCount; + public: + // Constructor for the first reference to a stream + // Note: get more references through NewReference(). Use this + // constructor only once on a given stream. + explicit StreamReference(StreamInterface* stream); + StreamInterface* GetStream() { return stream(); } + StreamInterface* NewReference(); + virtual ~StreamReference(); + + private: + class StreamRefCount { + public: + explicit StreamRefCount(StreamInterface* stream) + : stream_(stream), ref_count_(1) { + } + void AddReference() { + CritScope lock(&cs_); + ++ref_count_; + } + void Release() { + int ref_count; + { // Atomic ops would have been a better fit here. + CritScope lock(&cs_); + ref_count = --ref_count_; + } + if (ref_count == 0) { + delete stream_; + delete this; + } + } + private: + StreamInterface* stream_; + int ref_count_; + CriticalSection cs_; + DISALLOW_EVIL_CONSTRUCTORS(StreamRefCount); + }; + + // Constructor for adding references + explicit StreamReference(StreamRefCount* stream_ref_count, + StreamInterface* stream); + + StreamRefCount* stream_ref_count_; + DISALLOW_EVIL_CONSTRUCTORS(StreamReference); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Flow attempts to move bytes from source to sink via buffer of size +// buffer_len. The function returns SR_SUCCESS when source reaches +// end-of-stream (returns SR_EOS), and all the data has been written successful +// to sink. Alternately, if source returns SR_BLOCK or SR_ERROR, or if sink +// returns SR_BLOCK, SR_ERROR, or SR_EOS, then the function immediately returns +// with the unexpected StreamResult value. +// data_len is the length of the valid data in buffer. in case of error +// this is the data that read from source but can't move to destination. +// as a pass in parameter, it indicates data in buffer that should move to sink +StreamResult Flow(StreamInterface* source, + char* buffer, size_t buffer_len, + StreamInterface* sink, size_t* data_len = NULL); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_STREAM_H_ diff --git a/webrtc/base/stream_unittest.cc b/webrtc/base/stream_unittest.cc new file mode 100644 index 000000000..a6664d716 --- /dev/null +++ b/webrtc/base/stream_unittest.cc @@ -0,0 +1,492 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// TestStream +/////////////////////////////////////////////////////////////////////////////// + +class TestStream : public StreamInterface { + public: + TestStream() : pos_(0) { } + + virtual StreamState GetState() const { return SS_OPEN; } + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + unsigned char* uc_buffer = static_cast(buffer); + for (size_t i = 0; i < buffer_len; ++i) { + uc_buffer[i] = static_cast(pos_++); + } + if (read) + *read = buffer_len; + return SR_SUCCESS; + } + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (error) + *error = -1; + return SR_ERROR; + } + virtual void Close() { } + virtual bool SetPosition(size_t position) { + pos_ = position; + return true; + } + virtual bool GetPosition(size_t* position) const { + if (position) *position = pos_; + return true; + } + virtual bool GetSize(size_t* size) const { + return false; + } + virtual bool GetAvailable(size_t* size) const { + return false; + } + + private: + size_t pos_; +}; + +bool VerifyTestBuffer(unsigned char* buffer, size_t len, + unsigned char value) { + bool passed = true; + for (size_t i = 0; i < len; ++i) { + if (buffer[i] != value++) { + passed = false; + break; + } + } + // Ensure that we don't pass again without re-writing + memset(buffer, 0, len); + return passed; +} + +void SeekTest(StreamInterface* stream, const unsigned char value) { + size_t bytes; + unsigned char buffer[13] = { 0 }; + const size_t kBufSize = sizeof(buffer); + + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value)); + EXPECT_TRUE(stream->GetPosition(&bytes)); + EXPECT_EQ(13U, bytes); + + EXPECT_TRUE(stream->SetPosition(7)); + + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value + 7)); + EXPECT_TRUE(stream->GetPosition(&bytes)); + EXPECT_EQ(20U, bytes); +} + +TEST(StreamSegment, TranslatesPosition) { + TestStream* test = new TestStream; + // Verify behavior of original stream + SeekTest(test, 0); + StreamSegment* segment = new StreamSegment(test); + // Verify behavior of adapted stream (all values offset by 20) + SeekTest(segment, 20); + delete segment; +} + +TEST(StreamSegment, SupportsArtificialTermination) { + TestStream* test = new TestStream; + + size_t bytes; + unsigned char buffer[5000] = { 0 }; + const size_t kBufSize = sizeof(buffer); + + { + StreamInterface* stream = test; + + // Read a lot of bytes + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 0)); + + // Test seeking far ahead + EXPECT_TRUE(stream->SetPosition(12345)); + + // Read a bunch more bytes + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 12345 % 256)); + } + + // Create a segment of test stream in range [100,600) + EXPECT_TRUE(test->SetPosition(100)); + StreamSegment* segment = new StreamSegment(test, 500); + + { + StreamInterface* stream = segment; + + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(500U, bytes); + EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS); + + // Test seeking past "end" of stream + EXPECT_FALSE(stream->SetPosition(12345)); + EXPECT_FALSE(stream->SetPosition(501)); + + // Test seeking to end (edge case) + EXPECT_TRUE(stream->SetPosition(500)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS); + + // Test seeking to start + EXPECT_TRUE(stream->SetPosition(0)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(500U, bytes); + EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS); + } + + delete segment; +} + +TEST(FifoBufferTest, TestAll) { + const size_t kSize = 16; + const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + char out[kSize * 2]; + void* p; + const void* q; + size_t bytes; + FifoBuffer buf(kSize); + StreamInterface* stream = &buf; + + // Test assumptions about base state + EXPECT_EQ(SS_OPEN, stream->GetState()); + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_TRUE(NULL != stream->GetReadData(&bytes)); + EXPECT_EQ((size_t)0, bytes); + stream->ConsumeReadData(0); + EXPECT_TRUE(NULL != stream->GetWriteBuffer(&bytes)); + EXPECT_EQ(kSize, bytes); + stream->ConsumeWriteBuffer(0); + + // Try a full write + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + + // Try a write that should block + EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL)); + + // Try a full read + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try a read that should block + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try a too-big write + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 2, &bytes, NULL)); + EXPECT_EQ(bytes, kSize); + + // Try a too-big read + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try some small writes and reads + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + + // Try wraparound reads and writes in the following pattern + // WWWWWWWWWWWW.... 0123456789AB.... + // RRRRRRRRXXXX.... ........89AB.... + // WWWW....XXXXWWWW 4567....89AB0123 + // XXXX....RRRRXXXX 4567........0123 + // XXXXWWWWWWWWXXXX 4567012345670123 + // RRRRXXXXXXXXRRRR ....01234567.... + // ....RRRRRRRR.... ................ + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(kSize * 3 / 4, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 4, &bytes, NULL)); + EXPECT_EQ(kSize / 4 , bytes); + EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2 , bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2 , bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + + // Use GetWriteBuffer to reset the read_position for the next tests + stream->GetWriteBuffer(&bytes); + stream->ConsumeWriteBuffer(0); + + // Try using GetReadData to do a full read + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(q, in, kSize)); + stream->ConsumeReadData(kSize); + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try using GetReadData to do some small reads + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(q, in, kSize / 2)); + stream->ConsumeReadData(kSize / 2); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(q, in + kSize / 2, kSize / 2)); + stream->ConsumeReadData(kSize / 2); + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try using GetReadData in a wraparound case + // WWWWWWWWWWWWWWWW 0123456789ABCDEF + // RRRRRRRRRRRRXXXX ............CDEF + // WWWWWWWW....XXXX 01234567....CDEF + // ............RRRR 01234567........ + // RRRRRRRR........ ................ + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize / 4, bytes); + EXPECT_EQ(0, memcmp(q, in + kSize * 3 / 4, kSize / 4)); + stream->ConsumeReadData(kSize / 4); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(q, in, kSize / 2)); + stream->ConsumeReadData(kSize / 2); + + // Use GetWriteBuffer to reset the read_position for the next tests + stream->GetWriteBuffer(&bytes); + stream->ConsumeWriteBuffer(0); + + // Try using GetWriteBuffer to do a full write + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize, bytes); + memcpy(p, in, kSize); + stream->ConsumeWriteBuffer(kSize); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try using GetWriteBuffer to do some small writes + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize, bytes); + memcpy(p, in, kSize / 2); + stream->ConsumeWriteBuffer(kSize / 2); + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize / 2, bytes); + memcpy(p, in + kSize / 2, kSize / 2); + stream->ConsumeWriteBuffer(kSize / 2); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try using GetWriteBuffer in a wraparound case + // WWWWWWWWWWWW.... 0123456789AB.... + // RRRRRRRRXXXX.... ........89AB.... + // ........XXXXWWWW ........89AB0123 + // WWWW....XXXXXXXX 4567....89AB0123 + // RRRR....RRRRRRRR ................ + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize / 4, bytes); + memcpy(p, in, kSize / 4); + stream->ConsumeWriteBuffer(kSize / 4); + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize / 2, bytes); + memcpy(p, in + kSize / 4, kSize / 4); + stream->ConsumeWriteBuffer(kSize / 4); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(kSize * 3 / 4, bytes); + EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4)); + EXPECT_EQ(0, memcmp(in, out + kSize / 4, kSize / 4)); + + // Check that the stream is now empty + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try growing the buffer + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_TRUE(buf.SetCapacity(kSize * 2)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in + kSize, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL)); + EXPECT_EQ(kSize * 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize * 2)); + + // Try shrinking the buffer + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_TRUE(buf.SetCapacity(kSize)); + EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Write to the stream, close it, read the remaining bytes + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + stream->Close(); + EXPECT_EQ(SS_CLOSED, stream->GetState()); + EXPECT_EQ(SR_EOS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_EOS, stream->Read(out, kSize / 2, &bytes, NULL)); +} + +TEST(FifoBufferTest, FullBufferCheck) { + FifoBuffer buff(10); + buff.ConsumeWriteBuffer(10); + + size_t free; + EXPECT_TRUE(buff.GetWriteBuffer(&free) != NULL); + EXPECT_EQ(0U, free); +} + +TEST(FifoBufferTest, WriteOffsetAndReadOffset) { + const size_t kSize = 16; + const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + char out[kSize * 2]; + FifoBuffer buf(kSize); + + // Write 14 bytes. + EXPECT_EQ(SR_SUCCESS, buf.Write(in, 14, NULL, NULL)); + + // Make sure data is in |buf|. + size_t buffered; + EXPECT_TRUE(buf.GetBuffered(&buffered)); + EXPECT_EQ(14u, buffered); + + // Read 10 bytes. + buf.ConsumeReadData(10); + + // There should be now 12 bytes of available space. + size_t remaining; + EXPECT_TRUE(buf.GetWriteRemaining(&remaining)); + EXPECT_EQ(12u, remaining); + + // Write at offset 12, this should fail. + EXPECT_EQ(SR_BLOCK, buf.WriteOffset(in, 10, 12, NULL)); + + // Write 8 bytes at offset 4, this wraps around the buffer. + EXPECT_EQ(SR_SUCCESS, buf.WriteOffset(in, 8, 4, NULL)); + + // Number of available space remains the same until we call + // ConsumeWriteBuffer(). + EXPECT_TRUE(buf.GetWriteRemaining(&remaining)); + EXPECT_EQ(12u, remaining); + buf.ConsumeWriteBuffer(12); + + // There's 4 bytes bypassed and 4 bytes no read so skip them and verify the + // 8 bytes written. + size_t read; + EXPECT_EQ(SR_SUCCESS, buf.ReadOffset(out, 8, 8, &read)); + EXPECT_EQ(8u, read); + EXPECT_EQ(0, memcmp(out, in, 8)); + + // There should still be 16 bytes available for reading. + EXPECT_TRUE(buf.GetBuffered(&buffered)); + EXPECT_EQ(16u, buffered); + + // Read at offset 16, this should fail since we don't have that much data. + EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL)); +} + +TEST(AsyncWriteTest, TestWrite) { + FifoBuffer* buf = new FifoBuffer(100); + AsyncWriteStream stream(buf, Thread::Current()); + EXPECT_EQ(SS_OPEN, stream.GetState()); + + // Write "abc". Will go to the logging thread, which is the current + // thread. + stream.Write("abc", 3, NULL, NULL); + char bytes[100]; + size_t count; + // Messages on the thread's queue haven't been processed, so "abc" + // hasn't been written yet. + EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 0, &count)); + // Now we process the messages on the thread's queue, so "abc" has + // been written. + EXPECT_TRUE_WAIT(SR_SUCCESS == buf->ReadOffset(&bytes, 3, 0, &count), 10); + EXPECT_EQ(3u, count); + EXPECT_EQ(0, memcmp(bytes, "abc", 3)); + + // Write "def". Will go to the logging thread, which is the current + // thread. + stream.Write("d", 1, &count, NULL); + stream.Write("e", 1, &count, NULL); + stream.Write("f", 1, &count, NULL); + EXPECT_EQ(1u, count); + // Messages on the thread's queue haven't been processed, so "def" + // hasn't been written yet. + EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 3, &count)); + // Flush() causes the message to be processed, so "def" has now been + // written. + stream.Flush(); + EXPECT_EQ(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 3, &count)); + EXPECT_EQ(3u, count); + EXPECT_EQ(0, memcmp(bytes, "def", 3)); + + // Write "xyz". Will go to the logging thread, which is the current + // thread. + stream.Write("xyz", 3, &count, NULL); + EXPECT_EQ(3u, count); + // Messages on the thread's queue haven't been processed, so "xyz" + // hasn't been written yet. + EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 6, &count)); + // Close() causes the message to be processed, so "xyz" has now been + // written. + stream.Close(); + EXPECT_EQ(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 6, &count)); + EXPECT_EQ(3u, count); + EXPECT_EQ(0, memcmp(bytes, "xyz", 3)); + EXPECT_EQ(SS_CLOSED, stream.GetState()); + + // Is't closed, so the writes should fail. + EXPECT_EQ(SR_ERROR, stream.Write("000", 3, NULL, NULL)); + +} + +} // namespace rtc diff --git a/webrtc/base/stringdigest.h b/webrtc/base/stringdigest.h new file mode 100644 index 000000000..7cf6f329a --- /dev/null +++ b/webrtc/base/stringdigest.h @@ -0,0 +1,17 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_STRINGDIGEST_H_ +#define WEBRTC_BASE_STRINGDIGEST_H_ + +// TODO: Update remaining callers to use messagedigest.h instead +#include "webrtc/base/messagedigest.h" + +#endif // WEBRTC_BASE_STRINGDIGEST_H_ diff --git a/webrtc/base/stringencode.cc b/webrtc/base/stringencode.cc new file mode 100644 index 000000000..1e0a1493c --- /dev/null +++ b/webrtc/base/stringencode.cc @@ -0,0 +1,657 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/stringencode.h" + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +///////////////////////////////////////////////////////////////////////////// +// String Encoding Utilities +///////////////////////////////////////////////////////////////////////////// + +size_t escape(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch == escape) || ::strchr(illegal, ch)) { + if (bufpos + 2 >= buflen) + break; + buffer[bufpos++] = escape; + } + buffer[bufpos++] = ch; + } + + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t unescape(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch == escape) && (srcpos < srclen)) { + ch = source[srcpos++]; + } + buffer[bufpos++] = ch; + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t encode(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch != escape) && !::strchr(illegal, ch)) { + buffer[bufpos++] = ch; + } else if (bufpos + 3 >= buflen) { + break; + } else { + buffer[bufpos+0] = escape; + buffer[bufpos+1] = hex_encode((static_cast(ch) >> 4) & 0xF); + buffer[bufpos+2] = hex_encode((static_cast(ch) ) & 0xF); + bufpos += 3; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t decode(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape) { + if (buflen <= 0) + return 0; + + unsigned char h1, h2; + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch == escape) + && (srcpos + 1 < srclen) + && hex_decode(source[srcpos], &h1) + && hex_decode(source[srcpos+1], &h2)) { + buffer[bufpos++] = (h1 << 4) | h2; + srcpos += 2; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +const char* unsafe_filename_characters() { + // It might be better to have a single specification which is the union of + // all operating systems, unless one system is overly restrictive. +#if defined(WEBRTC_WIN) + return "\\/:*?\"<>|"; +#else // !WEBRTC_WIN + // TODO + ASSERT(false); + return ""; +#endif // !WEBRTC_WIN +} + +const unsigned char URL_UNSAFE = 0x1; // 0-33 "#$%&+,/:;<=>?@[\]^`{|} 127 +const unsigned char XML_UNSAFE = 0x2; // "&'<> +const unsigned char HTML_UNSAFE = 0x2; // "&'<> + +// ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 6 5 7 8 9 : ; < = > ? +//@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ +//` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ + +const unsigned char ASCII_CLASS[128] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,0,3,1,1,1,3,2,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,3,1,3,1, + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0, + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1, +}; + +size_t url_encode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + if (NULL == buffer) + return srclen * 3 + 1; + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if ((ch < 128) && (ASCII_CLASS[ch] & URL_UNSAFE)) { + if (bufpos + 3 >= buflen) { + break; + } + buffer[bufpos+0] = '%'; + buffer[bufpos+1] = hex_encode((ch >> 4) & 0xF); + buffer[bufpos+2] = hex_encode((ch ) & 0xF); + bufpos += 3; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t url_decode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + if (NULL == buffer) + return srclen + 1; + if (buflen <= 0) + return 0; + + unsigned char h1, h2; + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if (ch == '+') { + buffer[bufpos++] = ' '; + } else if ((ch == '%') + && (srcpos + 1 < srclen) + && hex_decode(source[srcpos], &h1) + && hex_decode(source[srcpos+1], &h2)) + { + buffer[bufpos++] = (h1 << 4) | h2; + srcpos += 2; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t utf8_decode(const char* source, size_t srclen, unsigned long* value) { + const unsigned char* s = reinterpret_cast(source); + if ((s[0] & 0x80) == 0x00) { // Check s[0] == 0xxxxxxx + *value = s[0]; + return 1; + } + if ((srclen < 2) || ((s[1] & 0xC0) != 0x80)) { // Check s[1] != 10xxxxxx + return 0; + } + // Accumulate the trailer byte values in value16, and combine it with the + // relevant bits from s[0], once we've determined the sequence length. + unsigned long value16 = (s[1] & 0x3F); + if ((s[0] & 0xE0) == 0xC0) { // Check s[0] == 110xxxxx + *value = ((s[0] & 0x1F) << 6) | value16; + return 2; + } + if ((srclen < 3) || ((s[2] & 0xC0) != 0x80)) { // Check s[2] != 10xxxxxx + return 0; + } + value16 = (value16 << 6) | (s[2] & 0x3F); + if ((s[0] & 0xF0) == 0xE0) { // Check s[0] == 1110xxxx + *value = ((s[0] & 0x0F) << 12) | value16; + return 3; + } + if ((srclen < 4) || ((s[3] & 0xC0) != 0x80)) { // Check s[3] != 10xxxxxx + return 0; + } + value16 = (value16 << 6) | (s[3] & 0x3F); + if ((s[0] & 0xF8) == 0xF0) { // Check s[0] == 11110xxx + *value = ((s[0] & 0x07) << 18) | value16; + return 4; + } + return 0; +} + +size_t utf8_encode(char* buffer, size_t buflen, unsigned long value) { + if ((value <= 0x7F) && (buflen >= 1)) { + buffer[0] = static_cast(value); + return 1; + } + if ((value <= 0x7FF) && (buflen >= 2)) { + buffer[0] = 0xC0 | static_cast(value >> 6); + buffer[1] = 0x80 | static_cast(value & 0x3F); + return 2; + } + if ((value <= 0xFFFF) && (buflen >= 3)) { + buffer[0] = 0xE0 | static_cast(value >> 12); + buffer[1] = 0x80 | static_cast((value >> 6) & 0x3F); + buffer[2] = 0x80 | static_cast(value & 0x3F); + return 3; + } + if ((value <= 0x1FFFFF) && (buflen >= 4)) { + buffer[0] = 0xF0 | static_cast(value >> 18); + buffer[1] = 0x80 | static_cast((value >> 12) & 0x3F); + buffer[2] = 0x80 | static_cast((value >> 6) & 0x3F); + buffer[3] = 0x80 | static_cast(value & 0x3F); + return 4; + } + return 0; +} + +size_t html_encode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos]; + if (ch < 128) { + srcpos += 1; + if (ASCII_CLASS[ch] & HTML_UNSAFE) { + const char * escseq = 0; + size_t esclen = 0; + switch (ch) { + case '<': escseq = "<"; esclen = 4; break; + case '>': escseq = ">"; esclen = 4; break; + case '\'': escseq = "'"; esclen = 5; break; + case '\"': escseq = """; esclen = 6; break; + case '&': escseq = "&"; esclen = 5; break; + default: ASSERT(false); + } + if (bufpos + esclen >= buflen) { + break; + } + memcpy(buffer + bufpos, escseq, esclen); + bufpos += esclen; + } else { + buffer[bufpos++] = ch; + } + } else { + // Largest value is 0x1FFFFF => � (10 characters) + char escseq[11]; + unsigned long val; + if (size_t vallen = utf8_decode(&source[srcpos], srclen - srcpos, &val)) { + srcpos += vallen; + } else { + // Not a valid utf8 sequence, just use the raw character. + val = static_cast(source[srcpos++]); + } + size_t esclen = sprintfn(escseq, ARRAY_SIZE(escseq), "&#%lu;", val); + if (bufpos + esclen >= buflen) { + break; + } + memcpy(buffer + bufpos, escseq, esclen); + bufpos += esclen; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t html_decode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + return xml_decode(buffer, buflen, source, srclen); +} + +size_t xml_encode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if ((ch < 128) && (ASCII_CLASS[ch] & XML_UNSAFE)) { + const char * escseq = 0; + size_t esclen = 0; + switch (ch) { + case '<': escseq = "<"; esclen = 4; break; + case '>': escseq = ">"; esclen = 4; break; + case '\'': escseq = "'"; esclen = 6; break; + case '\"': escseq = """; esclen = 6; break; + case '&': escseq = "&"; esclen = 5; break; + default: ASSERT(false); + } + if (bufpos + esclen >= buflen) { + break; + } + memcpy(buffer + bufpos, escseq, esclen); + bufpos += esclen; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t xml_decode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if (ch != '&') { + buffer[bufpos++] = ch; + } else if ((srcpos + 2 < srclen) + && (memcmp(source + srcpos, "lt;", 3) == 0)) { + buffer[bufpos++] = '<'; + srcpos += 3; + } else if ((srcpos + 2 < srclen) + && (memcmp(source + srcpos, "gt;", 3) == 0)) { + buffer[bufpos++] = '>'; + srcpos += 3; + } else if ((srcpos + 4 < srclen) + && (memcmp(source + srcpos, "apos;", 5) == 0)) { + buffer[bufpos++] = '\''; + srcpos += 5; + } else if ((srcpos + 4 < srclen) + && (memcmp(source + srcpos, "quot;", 5) == 0)) { + buffer[bufpos++] = '\"'; + srcpos += 5; + } else if ((srcpos + 3 < srclen) + && (memcmp(source + srcpos, "amp;", 4) == 0)) { + buffer[bufpos++] = '&'; + srcpos += 4; + } else if ((srcpos < srclen) && (source[srcpos] == '#')) { + int int_base = 10; + if ((srcpos + 1 < srclen) && (source[srcpos+1] == 'x')) { + int_base = 16; + srcpos += 1; + } + char * ptr; + // TODO: Fix hack (ptr may go past end of data) + unsigned long val = strtoul(source + srcpos + 1, &ptr, int_base); + if ((static_cast(ptr - source) < srclen) && (*ptr == ';')) { + srcpos = ptr - source + 1; + } else { + // Not a valid escape sequence. + break; + } + if (size_t esclen = utf8_encode(buffer + bufpos, buflen - bufpos, val)) { + bufpos += esclen; + } else { + // Not enough room to encode the character, or illegal character + break; + } + } else { + // Unrecognized escape sequence. + break; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +static const char HEX[] = "0123456789abcdef"; + +char hex_encode(unsigned char val) { + ASSERT(val < 16); + return (val < 16) ? HEX[val] : '!'; +} + +bool hex_decode(char ch, unsigned char* val) { + if ((ch >= '0') && (ch <= '9')) { + *val = ch - '0'; + } else if ((ch >= 'A') && (ch <= 'Z')) { + *val = (ch - 'A') + 10; + } else if ((ch >= 'a') && (ch <= 'z')) { + *val = (ch - 'a') + 10; + } else { + return false; + } + return true; +} + +size_t hex_encode(char* buffer, size_t buflen, + const char* csource, size_t srclen) { + return hex_encode_with_delimiter(buffer, buflen, csource, srclen, 0); +} + +size_t hex_encode_with_delimiter(char* buffer, size_t buflen, + const char* csource, size_t srclen, + char delimiter) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen == 0) + return 0; + + // Init and check bounds. + const unsigned char* bsource = + reinterpret_cast(csource); + size_t srcpos = 0, bufpos = 0; + size_t needed = delimiter ? (srclen * 3) : (srclen * 2 + 1); + if (buflen < needed) + return 0; + + while (srcpos < srclen) { + unsigned char ch = bsource[srcpos++]; + buffer[bufpos ] = hex_encode((ch >> 4) & 0xF); + buffer[bufpos+1] = hex_encode((ch ) & 0xF); + bufpos += 2; + + // Don't write a delimiter after the last byte. + if (delimiter && (srcpos < srclen)) { + buffer[bufpos] = delimiter; + ++bufpos; + } + } + + // Null terminate. + buffer[bufpos] = '\0'; + return bufpos; +} + +std::string hex_encode(const char* source, size_t srclen) { + return hex_encode_with_delimiter(source, srclen, 0); +} + +std::string hex_encode_with_delimiter(const char* source, size_t srclen, + char delimiter) { + const size_t kBufferSize = srclen * 3; + char* buffer = STACK_ARRAY(char, kBufferSize); + size_t length = hex_encode_with_delimiter(buffer, kBufferSize, + source, srclen, delimiter); + ASSERT(srclen == 0 || length > 0); + return std::string(buffer, length); +} + +size_t hex_decode(char * cbuffer, size_t buflen, + const char * source, size_t srclen) { + return hex_decode_with_delimiter(cbuffer, buflen, source, srclen, 0); +} + +size_t hex_decode_with_delimiter(char* cbuffer, size_t buflen, + const char* source, size_t srclen, + char delimiter) { + ASSERT(NULL != cbuffer); // TODO: estimate output size + if (buflen == 0) + return 0; + + // Init and bounds check. + unsigned char* bbuffer = reinterpret_cast(cbuffer); + size_t srcpos = 0, bufpos = 0; + size_t needed = (delimiter) ? (srclen + 1) / 3 : srclen / 2; + if (buflen < needed) + return 0; + + while (srcpos < srclen) { + if ((srclen - srcpos) < 2) { + // This means we have an odd number of bytes. + return 0; + } + + unsigned char h1, h2; + if (!hex_decode(source[srcpos], &h1) || + !hex_decode(source[srcpos + 1], &h2)) + return 0; + + bbuffer[bufpos++] = (h1 << 4) | h2; + srcpos += 2; + + // Remove the delimiter if needed. + if (delimiter && (srclen - srcpos) > 1) { + if (source[srcpos] != delimiter) + return 0; + ++srcpos; + } + } + + return bufpos; +} + +size_t hex_decode(char* buffer, size_t buflen, const std::string& source) { + return hex_decode_with_delimiter(buffer, buflen, source, 0); +} +size_t hex_decode_with_delimiter(char* buffer, size_t buflen, + const std::string& source, char delimiter) { + return hex_decode_with_delimiter(buffer, buflen, + source.c_str(), source.length(), delimiter); +} + +size_t transform(std::string& value, size_t maxlen, const std::string& source, + Transform t) { + char* buffer = STACK_ARRAY(char, maxlen + 1); + size_t length = t(buffer, maxlen + 1, source.data(), source.length()); + value.assign(buffer, length); + return length; +} + +std::string s_transform(const std::string& source, Transform t) { + // Ask transformation function to approximate the destination size (returns upper bound) + size_t maxlen = t(NULL, 0, source.data(), source.length()); + char * buffer = STACK_ARRAY(char, maxlen); + size_t len = t(buffer, maxlen, source.data(), source.length()); + std::string result(buffer, len); + return result; +} + +size_t tokenize(const std::string& source, char delimiter, + std::vector* fields) { + ASSERT(NULL != fields); + fields->clear(); + size_t last = 0; + for (size_t i = 0; i < source.length(); ++i) { + if (source[i] == delimiter) { + if (i != last) { + fields->push_back(source.substr(last, i - last)); + } + last = i + 1; + } + } + if (last != source.length()) { + fields->push_back(source.substr(last, source.length() - last)); + } + return fields->size(); +} + +size_t tokenize_append(const std::string& source, char delimiter, + std::vector* fields) { + if (!fields) return 0; + + std::vector new_fields; + tokenize(source, delimiter, &new_fields); + fields->insert(fields->end(), new_fields.begin(), new_fields.end()); + return fields->size(); +} + +size_t tokenize(const std::string& source, char delimiter, char start_mark, + char end_mark, std::vector* fields) { + if (!fields) return 0; + fields->clear(); + + std::string remain_source = source; + while (!remain_source.empty()) { + size_t start_pos = remain_source.find(start_mark); + if (std::string::npos == start_pos) break; + std::string pre_mark; + if (start_pos > 0) { + pre_mark = remain_source.substr(0, start_pos - 1); + } + + ++start_pos; + size_t end_pos = remain_source.find(end_mark, start_pos); + if (std::string::npos == end_pos) break; + + // We have found the matching marks. First tokenize the pre-mask. Then add + // the marked part as a single field. Finally, loop back for the post-mark. + tokenize_append(pre_mark, delimiter, fields); + fields->push_back(remain_source.substr(start_pos, end_pos - start_pos)); + remain_source = remain_source.substr(end_pos + 1); + } + + return tokenize_append(remain_source, delimiter, fields); +} + +size_t split(const std::string& source, char delimiter, + std::vector* fields) { + ASSERT(NULL != fields); + fields->clear(); + size_t last = 0; + for (size_t i = 0; i < source.length(); ++i) { + if (source[i] == delimiter) { + fields->push_back(source.substr(last, i - last)); + last = i + 1; + } + } + fields->push_back(source.substr(last, source.length() - last)); + return fields->size(); +} + +char make_char_safe_for_filename(char c) { + if (c < 32) + return '_'; + + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '*': + case '?': + return '_'; + + default: + return c; + } +} + +/* +void sprintf(std::string& value, size_t maxlen, const char * format, ...) { + char * buffer = STACK_ARRAY(char, maxlen + 1); + va_list args; + va_start(args, format); + value.assign(buffer, vsprintfn(buffer, maxlen + 1, format, args)); + va_end(args); +} +*/ + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/stringencode.h b/webrtc/base/stringencode.h new file mode 100644 index 000000000..b6c666f98 --- /dev/null +++ b/webrtc/base/stringencode.h @@ -0,0 +1,210 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_STRINGENCODE_H_ +#define WEBRTC_BASE_STRINGENCODE_H_ + +#include +#include +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// String Encoding Utilities +////////////////////////////////////////////////////////////////////// + +// Convert an unsigned value to it's utf8 representation. Returns the length +// of the encoded string, or 0 if the encoding is longer than buflen - 1. +size_t utf8_encode(char* buffer, size_t buflen, unsigned long value); +// Decode the utf8 encoded value pointed to by source. Returns the number of +// bytes used by the encoding, or 0 if the encoding is invalid. +size_t utf8_decode(const char* source, size_t srclen, unsigned long* value); + +// Escaping prefixes illegal characters with the escape character. Compact, but +// illegal characters still appear in the string. +size_t escape(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape); +// Note: in-place unescaping (buffer == source) is allowed. +size_t unescape(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape); + +// Encoding replaces illegal characters with the escape character and 2 hex +// chars, so it's a little less compact than escape, but completely removes +// illegal characters. note that hex digits should not be used as illegal +// characters. +size_t encode(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape); +// Note: in-place decoding (buffer == source) is allowed. +size_t decode(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape); + +// Returns a list of characters that may be unsafe for use in the name of a +// file, suitable for passing to the 'illegal' member of escape or encode. +const char* unsafe_filename_characters(); + +// url_encode is an encode operation with a predefined set of illegal characters +// and escape character (for use in URLs, obviously). +size_t url_encode(char * buffer, size_t buflen, + const char * source, size_t srclen); +// Note: in-place decoding (buffer == source) is allowed. +size_t url_decode(char * buffer, size_t buflen, + const char * source, size_t srclen); + +// html_encode prevents data embedded in html from containing markup. +size_t html_encode(char * buffer, size_t buflen, + const char * source, size_t srclen); +// Note: in-place decoding (buffer == source) is allowed. +size_t html_decode(char * buffer, size_t buflen, + const char * source, size_t srclen); + +// xml_encode makes data suitable for inside xml attributes and values. +size_t xml_encode(char * buffer, size_t buflen, + const char * source, size_t srclen); +// Note: in-place decoding (buffer == source) is allowed. +size_t xml_decode(char * buffer, size_t buflen, + const char * source, size_t srclen); + +// Convert an unsigned value from 0 to 15 to the hex character equivalent... +char hex_encode(unsigned char val); +// ...and vice-versa. +bool hex_decode(char ch, unsigned char* val); + +// hex_encode shows the hex representation of binary data in ascii. +size_t hex_encode(char* buffer, size_t buflen, + const char* source, size_t srclen); + +// hex_encode, but separate each byte representation with a delimiter. +// |delimiter| == 0 means no delimiter +// If the buffer is too short, we return 0 +size_t hex_encode_with_delimiter(char* buffer, size_t buflen, + const char* source, size_t srclen, + char delimiter); + +// Helper functions for hex_encode. +std::string hex_encode(const char* source, size_t srclen); +std::string hex_encode_with_delimiter(const char* source, size_t srclen, + char delimiter); + +// hex_decode converts ascii hex to binary. +size_t hex_decode(char* buffer, size_t buflen, + const char* source, size_t srclen); + +// hex_decode, assuming that there is a delimiter between every byte +// pair. +// |delimiter| == 0 means no delimiter +// If the buffer is too short or the data is invalid, we return 0. +size_t hex_decode_with_delimiter(char* buffer, size_t buflen, + const char* source, size_t srclen, + char delimiter); + +// Helper functions for hex_decode. +size_t hex_decode(char* buffer, size_t buflen, const std::string& source); +size_t hex_decode_with_delimiter(char* buffer, size_t buflen, + const std::string& source, char delimiter); + +// Apply any suitable string transform (including the ones above) to an STL +// string. Stack-allocated temporary space is used for the transformation, +// so value and source may refer to the same string. +typedef size_t (*Transform)(char * buffer, size_t buflen, + const char * source, size_t srclen); +size_t transform(std::string& value, size_t maxlen, const std::string& source, + Transform t); + +// Return the result of applying transform t to source. +std::string s_transform(const std::string& source, Transform t); + +// Convenience wrappers. +inline std::string s_url_encode(const std::string& source) { + return s_transform(source, url_encode); +} +inline std::string s_url_decode(const std::string& source) { + return s_transform(source, url_decode); +} + +// Splits the source string into multiple fields separated by delimiter, +// with duplicates of delimiter creating empty fields. +size_t split(const std::string& source, char delimiter, + std::vector* fields); + +// Splits the source string into multiple fields separated by delimiter, +// with duplicates of delimiter ignored. Trailing delimiter ignored. +size_t tokenize(const std::string& source, char delimiter, + std::vector* fields); + +// Tokenize and append the tokens to fields. Return the new size of fields. +size_t tokenize_append(const std::string& source, char delimiter, + std::vector* fields); + +// Splits the source string into multiple fields separated by delimiter, with +// duplicates of delimiter ignored. Trailing delimiter ignored. A substring in +// between the start_mark and the end_mark is treated as a single field. Return +// the size of fields. For example, if source is "filename +// \"/Library/Application Support/media content.txt\"", delimiter is ' ', and +// the start_mark and end_mark are '"', this method returns two fields: +// "filename" and "/Library/Application Support/media content.txt". +size_t tokenize(const std::string& source, char delimiter, char start_mark, + char end_mark, std::vector* fields); + +// Safe sprintf to std::string +//void sprintf(std::string& value, size_t maxlen, const char * format, ...) +// PRINTF_FORMAT(3); + +// Convert arbitrary values to/from a string. + +template +static bool ToString(const T &t, std::string* s) { + ASSERT(NULL != s); + std::ostringstream oss; + oss << std::boolalpha << t; + *s = oss.str(); + return !oss.fail(); +} + +template +static bool FromString(const std::string& s, T* t) { + ASSERT(NULL != t); + std::istringstream iss(s); + iss >> std::boolalpha >> *t; + return !iss.fail(); +} + +// Inline versions of the string conversion routines. + +template +static inline std::string ToString(const T& val) { + std::string str; ToString(val, &str); return str; +} + +template +static inline T FromString(const std::string& str) { + T val; FromString(str, &val); return val; +} + +template +static inline T FromString(const T& defaultValue, const std::string& str) { + T val(defaultValue); FromString(str, &val); return val; +} + +// simple function to strip out characters which shouldn't be +// used in filenames +char make_char_safe_for_filename(char c); + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_STRINGENCODE_H__ diff --git a/webrtc/base/stringencode_unittest.cc b/webrtc/base/stringencode_unittest.cc new file mode 100644 index 000000000..c9e726ecb --- /dev/null +++ b/webrtc/base/stringencode_unittest.cc @@ -0,0 +1,385 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +TEST(Utf8EncodeTest, EncodeDecode) { + const struct Utf8Test { + const char* encoded; + size_t encsize, enclen; + unsigned long decoded; + } kTests[] = { + { "a ", 5, 1, 'a' }, + { "\x7F ", 5, 1, 0x7F }, + { "\xC2\x80 ", 5, 2, 0x80 }, + { "\xDF\xBF ", 5, 2, 0x7FF }, + { "\xE0\xA0\x80 ", 5, 3, 0x800 }, + { "\xEF\xBF\xBF ", 5, 3, 0xFFFF }, + { "\xF0\x90\x80\x80 ", 5, 4, 0x10000 }, + { "\xF0\x90\x80\x80 ", 3, 0, 0x10000 }, + { "\xF0\xF0\x80\x80 ", 5, 0, 0 }, + { "\xF0\x90\x80 ", 5, 0, 0 }, + { "\x90\x80\x80 ", 5, 0, 0 }, + { NULL, 0, 0 }, + }; + for (size_t i = 0; kTests[i].encoded; ++i) { + unsigned long val = 0; + ASSERT_EQ(kTests[i].enclen, utf8_decode(kTests[i].encoded, + kTests[i].encsize, + &val)); + unsigned long result = (kTests[i].enclen == 0) ? 0 : kTests[i].decoded; + ASSERT_EQ(result, val); + + if (kTests[i].decoded == 0) { + // Not an interesting encoding test case + continue; + } + + char buffer[5]; + memset(buffer, 0x01, ARRAY_SIZE(buffer)); + ASSERT_EQ(kTests[i].enclen, utf8_encode(buffer, + kTests[i].encsize, + kTests[i].decoded)); + ASSERT_TRUE(memcmp(buffer, kTests[i].encoded, kTests[i].enclen) == 0); + // Make sure remainder of buffer is unchanged + ASSERT_TRUE(memory_check(buffer + kTests[i].enclen, + 0x1, + ARRAY_SIZE(buffer) - kTests[i].enclen)); + } +} + +class HexEncodeTest : public testing::Test { + public: + HexEncodeTest() : enc_res_(0), dec_res_(0) { + for (size_t i = 0; i < sizeof(data_); ++i) { + data_[i] = (i + 128) & 0xff; + } + memset(decoded_, 0x7f, sizeof(decoded_)); + } + + char data_[10]; + char encoded_[31]; + char decoded_[11]; + size_t enc_res_; + size_t dec_res_; +}; + +// Test that we can convert to/from hex with no delimiter. +TEST_F(HexEncodeTest, TestWithNoDelimiter) { + enc_res_ = hex_encode(encoded_, sizeof(encoded_), data_, sizeof(data_)); + ASSERT_EQ(sizeof(data_) * 2, enc_res_); + ASSERT_STREQ("80818283848586878889", encoded_); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test that we can convert to/from hex with a colon delimiter. +TEST_F(HexEncodeTest, TestWithDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), + data_, sizeof(data_), ':'); + ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_); + ASSERT_STREQ("80:81:82:83:84:85:86:87:88:89", encoded_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, ':'); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test that encoding with one delimiter and decoding with another fails. +TEST_F(HexEncodeTest, TestWithWrongDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), + data_, sizeof(data_), ':'); + ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, '/'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that encoding without a delimiter and decoding with one fails. +TEST_F(HexEncodeTest, TestExpectedDelimiter) { + enc_res_ = hex_encode(encoded_, sizeof(encoded_), data_, sizeof(data_)); + ASSERT_EQ(sizeof(data_) * 2, enc_res_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that encoding with a delimiter and decoding without one fails. +TEST_F(HexEncodeTest, TestExpectedNoDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), + data_, sizeof(data_), ':'); + ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_); + ASSERT_EQ(0U, dec_res_); +} + +// Test that we handle a zero-length buffer with no delimiter. +TEST_F(HexEncodeTest, TestZeroLengthNoDelimiter) { + enc_res_ = hex_encode(encoded_, sizeof(encoded_), "", 0); + ASSERT_EQ(0U, enc_res_); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_); + ASSERT_EQ(0U, dec_res_); +} + +// Test that we handle a zero-length buffer with a delimiter. +TEST_F(HexEncodeTest, TestZeroLengthWithDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), "", 0, ':'); + ASSERT_EQ(0U, enc_res_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test the std::string variants that take no delimiter. +TEST_F(HexEncodeTest, TestHelpersNoDelimiter) { + std::string result = hex_encode(data_, sizeof(data_)); + ASSERT_EQ("80818283848586878889", result); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), result); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test the std::string variants that use a delimiter. +TEST_F(HexEncodeTest, TestHelpersWithDelimiter) { + std::string result = hex_encode_with_delimiter(data_, sizeof(data_), ':'); + ASSERT_EQ("80:81:82:83:84:85:86:87:88:89", result); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), result, ':'); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test that encoding into a too-small output buffer (without delimiter) fails. +TEST_F(HexEncodeTest, TestEncodeTooShort) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(data_) * 2, + data_, sizeof(data_), 0); + ASSERT_EQ(0U, enc_res_); +} + +// Test that encoding into a too-small output buffer (with delimiter) fails. +TEST_F(HexEncodeTest, TestEncodeWithDelimiterTooShort) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(data_) * 3 - 1, + data_, sizeof(data_), ':'); + ASSERT_EQ(0U, enc_res_); +} + +// Test that decoding into a too-small output buffer fails. +TEST_F(HexEncodeTest, TestDecodeTooShort) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, "0123456789", 10, 0); + ASSERT_EQ(0U, dec_res_); + ASSERT_EQ(0x7f, decoded_[4]); +} + +// Test that decoding non-hex data fails. +TEST_F(HexEncodeTest, TestDecodeBogusData) { + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), "xyz", 3, 0); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding an odd number of hex characters fails. +TEST_F(HexEncodeTest, TestDecodeOddHexDigits) { + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), "012", 3, 0); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding a string with too many delimiters fails. +TEST_F(HexEncodeTest, TestDecodeWithDelimiterTooManyDelimiters) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, "01::23::45::67", 14, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding a string with a leading delimiter fails. +TEST_F(HexEncodeTest, TestDecodeWithDelimiterLeadingDelimiter) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, ":01:23:45:67", 12, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding a string with a trailing delimiter fails. +TEST_F(HexEncodeTest, TestDecodeWithDelimiterTrailingDelimiter) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, "01:23:45:67:", 12, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Tests counting substrings. +TEST(TokenizeTest, CountSubstrings) { + std::vector fields; + + EXPECT_EQ(5ul, tokenize("one two three four five", ' ', &fields)); + fields.clear(); + EXPECT_EQ(1ul, tokenize("one", ' ', &fields)); + + // Extra spaces should be ignored. + fields.clear(); + EXPECT_EQ(5ul, tokenize(" one two three four five ", ' ', &fields)); + fields.clear(); + EXPECT_EQ(1ul, tokenize(" one ", ' ', &fields)); + fields.clear(); + EXPECT_EQ(0ul, tokenize(" ", ' ', &fields)); +} + +// Tests comparing substrings. +TEST(TokenizeTest, CompareSubstrings) { + std::vector fields; + + tokenize("find middle one", ' ', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("middle", fields.at(1).c_str()); + fields.clear(); + + // Extra spaces should be ignored. + tokenize(" find middle one ", ' ', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("middle", fields.at(1).c_str()); + fields.clear(); + tokenize(" ", ' ', &fields); + ASSERT_EQ(0ul, fields.size()); +} + +TEST(TokenizeTest, TokenizeAppend) { + ASSERT_EQ(0ul, tokenize_append("A B C", ' ', NULL)); + + std::vector fields; + + tokenize_append("A B C", ' ', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("B", fields.at(1).c_str()); + + tokenize_append("D E", ' ', &fields); + ASSERT_EQ(5ul, fields.size()); + ASSERT_STREQ("B", fields.at(1).c_str()); + ASSERT_STREQ("E", fields.at(4).c_str()); +} + +TEST(TokenizeTest, TokenizeWithMarks) { + ASSERT_EQ(0ul, tokenize("D \"A B", ' ', '(', ')', NULL)); + + std::vector fields; + tokenize("A B C", ' ', '"', '"', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("C", fields.at(2).c_str()); + + tokenize("\"A B\" C", ' ', '"', '"', &fields); + ASSERT_EQ(2ul, fields.size()); + ASSERT_STREQ("A B", fields.at(0).c_str()); + + tokenize("D \"A B\" C", ' ', '"', '"', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("A B", fields.at(1).c_str()); + + tokenize("D \"A B\" C \"E F\"", ' ', '"', '"', &fields); + ASSERT_EQ(4ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("A B", fields.at(1).c_str()); + ASSERT_STREQ("E F", fields.at(3).c_str()); + + // No matching marks. + tokenize("D \"A B", ' ', '"', '"', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("\"A", fields.at(1).c_str()); + + tokenize("D (A B) C (E F) G", ' ', '(', ')', &fields); + ASSERT_EQ(5ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("A B", fields.at(1).c_str()); + ASSERT_STREQ("E F", fields.at(3).c_str()); +} + +// Tests counting substrings. +TEST(SplitTest, CountSubstrings) { + std::vector fields; + + EXPECT_EQ(5ul, split("one,two,three,four,five", ',', &fields)); + fields.clear(); + EXPECT_EQ(1ul, split("one", ',', &fields)); + + // Empty fields between commas count. + fields.clear(); + EXPECT_EQ(5ul, split("one,,three,four,five", ',', &fields)); + fields.clear(); + EXPECT_EQ(3ul, split(",three,", ',', &fields)); + fields.clear(); + EXPECT_EQ(1ul, split("", ',', &fields)); +} + +// Tests comparing substrings. +TEST(SplitTest, CompareSubstrings) { + std::vector fields; + + split("find,middle,one", ',', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("middle", fields.at(1).c_str()); + fields.clear(); + + // Empty fields between commas count. + split("find,,middle,one", ',', &fields); + ASSERT_EQ(4ul, fields.size()); + ASSERT_STREQ("middle", fields.at(2).c_str()); + fields.clear(); + split("", ',', &fields); + ASSERT_EQ(1ul, fields.size()); + ASSERT_STREQ("", fields.at(0).c_str()); +} + +TEST(BoolTest, DecodeValid) { + bool value; + EXPECT_TRUE(FromString("true", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString("true,", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString("true , true", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString("true ,\n false", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString(" true \n", &value)); + EXPECT_TRUE(value); + + EXPECT_TRUE(FromString("false", &value)); + EXPECT_FALSE(value); + EXPECT_TRUE(FromString(" false ", &value)); + EXPECT_FALSE(value); + EXPECT_TRUE(FromString(" false, ", &value)); + EXPECT_FALSE(value); + + EXPECT_TRUE(FromString("true\n")); + EXPECT_FALSE(FromString("false\n")); +} + +TEST(BoolTest, DecodeInvalid) { + bool value; + EXPECT_FALSE(FromString("True", &value)); + EXPECT_FALSE(FromString("TRUE", &value)); + EXPECT_FALSE(FromString("False", &value)); + EXPECT_FALSE(FromString("FALSE", &value)); + EXPECT_FALSE(FromString("0", &value)); + EXPECT_FALSE(FromString("1", &value)); + EXPECT_FALSE(FromString("0,", &value)); + EXPECT_FALSE(FromString("1,", &value)); + EXPECT_FALSE(FromString("1,0", &value)); + EXPECT_FALSE(FromString("1.", &value)); + EXPECT_FALSE(FromString("1.0", &value)); + EXPECT_FALSE(FromString("", &value)); + EXPECT_FALSE(FromString("false\nfalse")); +} + +TEST(BoolTest, RoundTrip) { + bool value; + EXPECT_TRUE(FromString(ToString(true), &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString(ToString(false), &value)); + EXPECT_FALSE(value); +} +} // namespace rtc diff --git a/webrtc/base/stringutils.cc b/webrtc/base/stringutils.cc new file mode 100644 index 000000000..041708d3d --- /dev/null +++ b/webrtc/base/stringutils.cc @@ -0,0 +1,133 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/stringutils.h" +#include "webrtc/base/common.h" + +namespace rtc { + +bool memory_check(const void* memory, int c, size_t count) { + const char* char_memory = static_cast(memory); + char char_c = static_cast(c); + for (size_t i = 0; i < count; ++i) { + if (char_memory[i] != char_c) { + return false; + } + } + return true; +} + +bool string_match(const char* target, const char* pattern) { + while (*pattern) { + if (*pattern == '*') { + if (!*++pattern) { + return true; + } + while (*target) { + if ((toupper(*pattern) == toupper(*target)) + && string_match(target + 1, pattern + 1)) { + return true; + } + ++target; + } + return false; + } else { + if (toupper(*pattern) != toupper(*target)) { + return false; + } + ++target; + ++pattern; + } + } + return !*target; +} + +#if defined(WEBRTC_WIN) +int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n, + CharacterTransformation transformation) { + wchar_t c1, c2; + while (true) { + if (n-- == 0) return 0; + c1 = transformation(*s1); + // Double check that characters are not UTF-8 + ASSERT(static_cast(*s2) < 128); + // Note: *s2 gets implicitly promoted to wchar_t + c2 = transformation(*s2); + if (c1 != c2) return (c1 < c2) ? -1 : 1; + if (!c1) return 0; + ++s1; + ++s2; + } +} + +size_t asccpyn(wchar_t* buffer, size_t buflen, + const char* source, size_t srclen) { + if (buflen <= 0) + return 0; + + if (srclen == SIZE_UNKNOWN) { + srclen = strlenn(source, buflen - 1); + } else if (srclen >= buflen) { + srclen = buflen - 1; + } +#if _DEBUG + // Double check that characters are not UTF-8 + for (size_t pos = 0; pos < srclen; ++pos) + ASSERT(static_cast(source[pos]) < 128); +#endif // _DEBUG + std::copy(source, source + srclen, buffer); + buffer[srclen] = 0; + return srclen; +} + +#endif // WEBRTC_WIN + +void replace_substrs(const char *search, + size_t search_len, + const char *replace, + size_t replace_len, + std::string *s) { + size_t pos = 0; + while ((pos = s->find(search, pos, search_len)) != std::string::npos) { + s->replace(pos, search_len, replace, replace_len); + pos += replace_len; + } +} + +bool starts_with(const char *s1, const char *s2) { + return strncmp(s1, s2, strlen(s2)) == 0; +} + +bool ends_with(const char *s1, const char *s2) { + size_t s1_length = strlen(s1); + size_t s2_length = strlen(s2); + + if (s2_length > s1_length) { + return false; + } + + const char* start = s1 + (s1_length - s2_length); + return strncmp(start, s2, s2_length) == 0; +} + +static const char kWhitespace[] = " \n\r\t"; + +std::string string_trim(const std::string& s) { + std::string::size_type first = s.find_first_not_of(kWhitespace); + std::string::size_type last = s.find_last_not_of(kWhitespace); + + if (first == std::string::npos || last == std::string::npos) { + return std::string(""); + } + + return s.substr(first, last - first + 1); +} + +} // namespace rtc diff --git a/webrtc/base/stringutils.h b/webrtc/base/stringutils.h new file mode 100644 index 000000000..25990e0af --- /dev/null +++ b/webrtc/base/stringutils.h @@ -0,0 +1,318 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_STRINGUTILS_H__ +#define WEBRTC_BASE_STRINGUTILS_H__ + +#include +#include +#include +#include + +#if defined(WEBRTC_WIN) +#include +#include +#define alloca _alloca +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) +#ifdef BSD +#include +#else // BSD +#include +#endif // !BSD +#endif // WEBRTC_POSIX + +#include + +#include "webrtc/base/basictypes.h" + +/////////////////////////////////////////////////////////////////////////////// +// Generic string/memory utilities +/////////////////////////////////////////////////////////////////////////////// + +#define STACK_ARRAY(TYPE, LEN) static_cast(::alloca((LEN)*sizeof(TYPE))) + +namespace rtc { + +// Complement to memset. Verifies memory consists of count bytes of value c. +bool memory_check(const void* memory, int c, size_t count); + +// Determines whether the simple wildcard pattern matches target. +// Alpha characters in pattern match case-insensitively. +// Asterisks in pattern match 0 or more characters. +// Ex: string_match("www.TEST.GOOGLE.COM", "www.*.com") -> true +bool string_match(const char* target, const char* pattern); + +} // namespace rtc + +/////////////////////////////////////////////////////////////////////////////// +// Rename a bunch of common string functions so they are consistent across +// platforms and between char and wchar_t variants. +// Here is the full list of functions that are unified: +// strlen, strcmp, stricmp, strncmp, strnicmp +// strchr, vsnprintf, strtoul, tolowercase +// tolowercase is like tolower, but not compatible with end-of-file value +// +// It's not clear if we will ever use wchar_t strings on unix. In theory, +// all strings should be Utf8 all the time, except when interfacing with Win32 +// APIs that require Utf16. +/////////////////////////////////////////////////////////////////////////////// + +inline char tolowercase(char c) { + return static_cast(tolower(c)); +} + +#if defined(WEBRTC_WIN) + +inline size_t strlen(const wchar_t* s) { + return wcslen(s); +} +inline int strcmp(const wchar_t* s1, const wchar_t* s2) { + return wcscmp(s1, s2); +} +inline int stricmp(const wchar_t* s1, const wchar_t* s2) { + return _wcsicmp(s1, s2); +} +inline int strncmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return wcsncmp(s1, s2, n); +} +inline int strnicmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return _wcsnicmp(s1, s2, n); +} +inline const wchar_t* strchr(const wchar_t* s, wchar_t c) { + return wcschr(s, c); +} +inline const wchar_t* strstr(const wchar_t* haystack, const wchar_t* needle) { + return wcsstr(haystack, needle); +} +#ifndef vsnprintf +inline int vsnprintf(wchar_t* buf, size_t n, const wchar_t* fmt, va_list args) { + return _vsnwprintf(buf, n, fmt, args); +} +#endif // !vsnprintf +inline unsigned long strtoul(const wchar_t* snum, wchar_t** end, int base) { + return wcstoul(snum, end, base); +} +inline wchar_t tolowercase(wchar_t c) { + return static_cast(towlower(c)); +} + +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) + +inline int _stricmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline int _strnicmp(const char* s1, const char* s2, size_t n) { + return strncasecmp(s1, s2, n); +} + +#endif // WEBRTC_POSIX + +/////////////////////////////////////////////////////////////////////////////// +// Traits simplifies porting string functions to be CTYPE-agnostic +/////////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +const size_t SIZE_UNKNOWN = static_cast(-1); + +template +struct Traits { + // STL string type + //typedef XXX string; + // Null-terminated string + //inline static const CTYPE* empty_str(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// String utilities which work with char or wchar_t +/////////////////////////////////////////////////////////////////////////////// + +template +inline const CTYPE* nonnull(const CTYPE* str, const CTYPE* def_str = NULL) { + return str ? str : (def_str ? def_str : Traits::empty_str()); +} + +template +const CTYPE* strchr(const CTYPE* str, const CTYPE* chs) { + for (size_t i=0; str[i]; ++i) { + for (size_t j=0; chs[j]; ++j) { + if (str[i] == chs[j]) { + return str + i; + } + } + } + return 0; +} + +template +const CTYPE* strchrn(const CTYPE* str, size_t slen, CTYPE ch) { + for (size_t i=0; i +size_t strlenn(const CTYPE* buffer, size_t buflen) { + size_t bufpos = 0; + while (buffer[bufpos] && (bufpos < buflen)) { + ++bufpos; + } + return bufpos; +} + +// Safe versions of strncpy, strncat, snprintf and vsnprintf that always +// null-terminate. + +template +size_t strcpyn(CTYPE* buffer, size_t buflen, + const CTYPE* source, size_t srclen = SIZE_UNKNOWN) { + if (buflen <= 0) + return 0; + + if (srclen == SIZE_UNKNOWN) { + srclen = strlenn(source, buflen - 1); + } else if (srclen >= buflen) { + srclen = buflen - 1; + } + memcpy(buffer, source, srclen * sizeof(CTYPE)); + buffer[srclen] = 0; + return srclen; +} + +template +size_t strcatn(CTYPE* buffer, size_t buflen, + const CTYPE* source, size_t srclen = SIZE_UNKNOWN) { + if (buflen <= 0) + return 0; + + size_t bufpos = strlenn(buffer, buflen - 1); + return bufpos + strcpyn(buffer + bufpos, buflen - bufpos, source, srclen); +} + +// Some compilers (clang specifically) require vsprintfn be defined before +// sprintfn. +template +size_t vsprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, + va_list args) { + int len = vsnprintf(buffer, buflen, format, args); + if ((len < 0) || (static_cast(len) >= buflen)) { + len = static_cast(buflen - 1); + buffer[len] = 0; + } + return len; +} + +template +size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...); +template +size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...) { + va_list args; + va_start(args, format); + size_t len = vsprintfn(buffer, buflen, format, args); + va_end(args); + return len; +} + +/////////////////////////////////////////////////////////////////////////////// +// Allow safe comparing and copying ascii (not UTF-8) with both wide and +// non-wide character strings. +/////////////////////////////////////////////////////////////////////////////// + +inline int asccmp(const char* s1, const char* s2) { + return strcmp(s1, s2); +} +inline int ascicmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +inline int ascncmp(const char* s1, const char* s2, size_t n) { + return strncmp(s1, s2, n); +} +inline int ascnicmp(const char* s1, const char* s2, size_t n) { + return _strnicmp(s1, s2, n); +} +inline size_t asccpyn(char* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN) { + return strcpyn(buffer, buflen, source, srclen); +} + +#if defined(WEBRTC_WIN) + +typedef wchar_t(*CharacterTransformation)(wchar_t); +inline wchar_t identity(wchar_t c) { return c; } +int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n, + CharacterTransformation transformation); + +inline int asccmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), identity); +} +inline int ascicmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), tolowercase); +} +inline int ascncmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, identity); +} +inline int ascnicmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, tolowercase); +} +size_t asccpyn(wchar_t* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN); + +#endif // WEBRTC_WIN + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations +/////////////////////////////////////////////////////////////////////////////// + +template<> +struct Traits { + typedef std::string string; + inline static const char* empty_str() { return ""; } +}; + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations (Windows only, currently) +/////////////////////////////////////////////////////////////////////////////// + +#if defined(WEBRTC_WIN) + +template<> +struct Traits { + typedef std::wstring string; + inline static const wchar_t* Traits::empty_str() { return L""; } +}; + +#endif // WEBRTC_WIN + +// Replaces all occurrences of "search" with "replace". +void replace_substrs(const char *search, + size_t search_len, + const char *replace, + size_t replace_len, + std::string *s); + +// True iff s1 starts with s2. +bool starts_with(const char *s1, const char *s2); + +// True iff s1 ends with s2. +bool ends_with(const char *s1, const char *s2); + +// Remove leading and trailing whitespaces. +std::string string_trim(const std::string& s); + +} // namespace rtc + +#endif // WEBRTC_BASE_STRINGUTILS_H__ diff --git a/webrtc/base/stringutils_unittest.cc b/webrtc/base/stringutils_unittest.cc new file mode 100644 index 000000000..b82290d0a --- /dev/null +++ b/webrtc/base/stringutils_unittest.cc @@ -0,0 +1,109 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/common.h" + +namespace rtc { + +// Tests for string_match(). + +TEST(string_matchTest, Matches) { + EXPECT_TRUE( string_match("A.B.C.D", "a.b.c.d")); + EXPECT_TRUE( string_match("www.TEST.GOOGLE.COM", "www.*.com")); + EXPECT_TRUE( string_match("127.0.0.1", "12*.0.*1")); + EXPECT_TRUE( string_match("127.1.0.21", "12*.0.*1")); + EXPECT_FALSE(string_match("127.0.0.0", "12*.0.*1")); + EXPECT_FALSE(string_match("127.0.0.0", "12*.0.*1")); + EXPECT_FALSE(string_match("127.1.1.21", "12*.0.*1")); +} + +// It's not clear if we will ever use wchar_t strings on unix. In theory, +// all strings should be Utf8 all the time, except when interfacing with Win32 +// APIs that require Utf16. + +#if defined(WEBRTC_WIN) + +// Tests for ascii_string_compare(). + +// Tests NULL input. +TEST(ascii_string_compareTest, NullInput) { + // The following results in an access violation in + // ascii_string_compare. Is this a bug or by design? stringutils.h + // should document the expected behavior in this case. + + // EXPECT_EQ(0, ascii_string_compare(NULL, NULL, 1, identity)); +} + +// Tests comparing two strings of different lengths. +TEST(ascii_string_compareTest, DifferentLengths) { + EXPECT_EQ(-1, ascii_string_compare(L"Test", "Test1", 5, identity)); +} + +// Tests the case where the buffer size is smaller than the string +// lengths. +TEST(ascii_string_compareTest, SmallBuffer) { + EXPECT_EQ(0, ascii_string_compare(L"Test", "Test1", 3, identity)); +} + +// Tests the case where the buffer is not full. +TEST(ascii_string_compareTest, LargeBuffer) { + EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 10, identity)); +} + +// Tests comparing two eqaul strings. +TEST(ascii_string_compareTest, Equal) { + EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 5, identity)); + EXPECT_EQ(0, ascii_string_compare(L"TeSt", "tEsT", 5, tolowercase)); +} + +// Tests comparing a smller string to a larger one. +TEST(ascii_string_compareTest, LessThan) { + EXPECT_EQ(-1, ascii_string_compare(L"abc", "abd", 4, identity)); + EXPECT_EQ(-1, ascii_string_compare(L"ABC", "abD", 5, tolowercase)); +} + +// Tests comparing a larger string to a smaller one. +TEST(ascii_string_compareTest, GreaterThan) { + EXPECT_EQ(1, ascii_string_compare(L"xyz", "xy", 5, identity)); + EXPECT_EQ(1, ascii_string_compare(L"abc", "ABB", 5, tolowercase)); +} +#endif // WEBRTC_WIN + +TEST(string_trim_Test, Trimming) { + EXPECT_EQ("temp", string_trim("\n\r\t temp \n\r\t")); + EXPECT_EQ("temp\n\r\t temp", string_trim(" temp\n\r\t temp ")); + EXPECT_EQ("temp temp", string_trim("temp temp")); + EXPECT_EQ("", string_trim(" \r\n\t")); + EXPECT_EQ("", string_trim("")); +} + +TEST(string_startsTest, StartsWith) { + EXPECT_TRUE(starts_with("foobar", "foo")); + EXPECT_TRUE(starts_with("foobar", "foobar")); + EXPECT_TRUE(starts_with("foobar", "")); + EXPECT_TRUE(starts_with("", "")); + EXPECT_FALSE(starts_with("foobar", "bar")); + EXPECT_FALSE(starts_with("foobar", "foobarbaz")); + EXPECT_FALSE(starts_with("", "f")); +} + +TEST(string_endsTest, EndsWith) { + EXPECT_TRUE(ends_with("foobar", "bar")); + EXPECT_TRUE(ends_with("foobar", "foobar")); + EXPECT_TRUE(ends_with("foobar", "")); + EXPECT_TRUE(ends_with("", "")); + EXPECT_FALSE(ends_with("foobar", "foo")); + EXPECT_FALSE(ends_with("foobar", "foobarbaz")); + EXPECT_FALSE(ends_with("", "f")); +} + +} // namespace rtc diff --git a/webrtc/base/systeminfo.cc b/webrtc/base/systeminfo.cc new file mode 100644 index 000000000..213c272b3 --- /dev/null +++ b/webrtc/base/systeminfo.cc @@ -0,0 +1,518 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/systeminfo.h" + +#if defined(WEBRTC_WIN) +#include +#ifndef EXCLUDE_D3D9 +#include +#endif +#include // for __cpuid() +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#elif defined(WEBRTC_LINUX) +#include +#endif +#if defined(WEBRTC_MAC) +#include +#endif + +#if defined(WEBRTC_WIN) +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/win32.h" +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "webrtc/base/macconversion.h" +#elif defined(WEBRTC_LINUX) +#include "webrtc/base/linux.h" +#endif +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +// See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx +#if defined(WEBRTC_WIN) +typedef BOOL (WINAPI *LPFN_GLPI)( + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, + PDWORD); + +static void GetProcessorInformation(int* physical_cpus, int* cache_size) { + // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond. + LPFN_GLPI glpi = reinterpret_cast(GetProcAddress( + GetModuleHandle(L"kernel32"), + "GetLogicalProcessorInformation")); + if (NULL == glpi) { + return; + } + // Determine buffer size, allocate and get processor information. + // Size can change between calls (unlikely), so a loop is done. + DWORD return_length = 0; + scoped_ptr infos; + while (!glpi(infos.get(), &return_length)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + infos.reset(new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[ + return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]); + } else { + return; + } + } + *physical_cpus = 0; + *cache_size = 0; + for (size_t i = 0; + i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) { + if (infos[i].Relationship == RelationProcessorCore) { + ++*physical_cpus; + } else if (infos[i].Relationship == RelationCache) { + int next_cache_size = static_cast(infos[i].Cache.Size); + if (next_cache_size >= *cache_size) { + *cache_size = next_cache_size; + } + } + } + return; +} +#else +// TODO(fbarchard): Use gcc 4.4 provided cpuid intrinsic +// 32 bit fpic requires ebx be preserved +#if (defined(__pic__) || defined(__APPLE__)) && defined(__i386__) +static inline void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile ( // NOLINT + "mov %%ebx, %%edi\n" + "cpuid\n" + "xchg %%edi, %%ebx\n" + : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type) + ); // NOLINT +} +#elif defined(__i386__) || defined(__x86_64__) +static inline void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile ( // NOLINT + "cpuid\n" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type) + ); // NOLINT +} +#endif +#endif // WEBRTC_WIN + +// Note(fbarchard): +// Family and model are extended family and extended model. 8 bits each. +SystemInfo::SystemInfo() + : physical_cpus_(1), logical_cpus_(1), cache_size_(0), + cpu_family_(0), cpu_model_(0), cpu_stepping_(0), + cpu_speed_(0), memory_(0) { + // Initialize the basic information. +#if defined(__arm__) || defined(_M_ARM) + cpu_arch_ = SI_ARCH_ARM; +#elif defined(__x86_64__) || defined(_M_X64) + cpu_arch_ = SI_ARCH_X64; +#elif defined(__i386__) || defined(_M_IX86) + cpu_arch_ = SI_ARCH_X86; +#else + cpu_arch_ = SI_ARCH_UNKNOWN; +#endif + +#if defined(WEBRTC_WIN) + SYSTEM_INFO si; + GetSystemInfo(&si); + logical_cpus_ = si.dwNumberOfProcessors; + GetProcessorInformation(&physical_cpus_, &cache_size_); + if (physical_cpus_ <= 0) { + physical_cpus_ = logical_cpus_; + } + cpu_family_ = si.wProcessorLevel; + cpu_model_ = si.wProcessorRevision >> 8; + cpu_stepping_ = si.wProcessorRevision & 0xFF; +#elif defined(WEBRTC_MAC) + uint32_t sysctl_value; + size_t length = sizeof(sysctl_value); + if (!sysctlbyname("hw.physicalcpu_max", &sysctl_value, &length, NULL, 0)) { + physical_cpus_ = static_cast(sysctl_value); + } + length = sizeof(sysctl_value); + if (!sysctlbyname("hw.logicalcpu_max", &sysctl_value, &length, NULL, 0)) { + logical_cpus_ = static_cast(sysctl_value); + } + uint64_t sysctl_value64; + length = sizeof(sysctl_value64); + if (!sysctlbyname("hw.l3cachesize", &sysctl_value64, &length, NULL, 0)) { + cache_size_ = static_cast(sysctl_value64); + } + if (!cache_size_) { + length = sizeof(sysctl_value64); + if (!sysctlbyname("hw.l2cachesize", &sysctl_value64, &length, NULL, 0)) { + cache_size_ = static_cast(sysctl_value64); + } + } + length = sizeof(sysctl_value); + if (!sysctlbyname("machdep.cpu.family", &sysctl_value, &length, NULL, 0)) { + cpu_family_ = static_cast(sysctl_value); + } + length = sizeof(sysctl_value); + if (!sysctlbyname("machdep.cpu.model", &sysctl_value, &length, NULL, 0)) { + cpu_model_ = static_cast(sysctl_value); + } + length = sizeof(sysctl_value); + if (!sysctlbyname("machdep.cpu.stepping", &sysctl_value, &length, NULL, 0)) { + cpu_stepping_ = static_cast(sysctl_value); + } +#elif defined(__native_client__) + // TODO(ryanpetrie): Implement this via PPAPI when it's available. +#else // WEBRTC_LINUX + ProcCpuInfo proc_info; + if (proc_info.LoadFromSystem()) { + proc_info.GetNumCpus(&logical_cpus_); + proc_info.GetNumPhysicalCpus(&physical_cpus_); + proc_info.GetCpuFamily(&cpu_family_); +#if defined(CPU_X86) + // These values only apply to x86 systems. + proc_info.GetSectionIntValue(0, "model", &cpu_model_); + proc_info.GetSectionIntValue(0, "stepping", &cpu_stepping_); + proc_info.GetSectionIntValue(0, "cpu MHz", &cpu_speed_); + proc_info.GetSectionIntValue(0, "cache size", &cache_size_); + cache_size_ *= 1024; +#endif + } + // ProcCpuInfo reads cpu speed from "cpu MHz" under /proc/cpuinfo. + // But that number is a moving target which can change on-the-fly according to + // many factors including system workload. + // See /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors. + // The one in /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq is more + // accurate. We use it as our cpu speed when it is available. + // cpuinfo_max_freq is measured in KHz and requires conversion to MHz. + int max_freq = rtc::ReadCpuMaxFreq(); + if (max_freq > 0) { + cpu_speed_ = max_freq / 1000; + } +#endif +// For L2 CacheSize see also +// http://www.flounder.com/cpuid_explorer2.htm#CPUID(0x800000006) +#ifdef CPU_X86 + if (cache_size_ == 0) { + int cpu_info[4]; + __cpuid(cpu_info, 0x80000000); // query maximum extended cpuid function. + if (static_cast(cpu_info[0]) >= 0x80000006) { + __cpuid(cpu_info, 0x80000006); + cache_size_ = (cpu_info[2] >> 16) * 1024; + } + } +#endif +} + +// Return the number of cpu threads available to the system. +int SystemInfo::GetMaxCpus() { + return logical_cpus_; +} + +// Return the number of cpu cores available to the system. +int SystemInfo::GetMaxPhysicalCpus() { + return physical_cpus_; +} + +// Return the number of cpus available to the process. Since affinity can be +// changed on the fly, do not cache this value. +// Can be affected by heat. +int SystemInfo::GetCurCpus() { + int cur_cpus; +#if defined(WEBRTC_WIN) + DWORD_PTR process_mask, system_mask; + ::GetProcessAffinityMask(::GetCurrentProcess(), &process_mask, &system_mask); + for (cur_cpus = 0; process_mask; ++cur_cpus) { + // Sparse-ones algorithm. There are slightly faster methods out there but + // they are unintuitive and won't make a difference on a single dword. + process_mask &= (process_mask - 1); + } +#elif defined(WEBRTC_MAC) + uint32_t sysctl_value; + size_t length = sizeof(sysctl_value); + int error = sysctlbyname("hw.ncpu", &sysctl_value, &length, NULL, 0); + cur_cpus = !error ? static_cast(sysctl_value) : 1; +#else + // Linux, Solaris, WEBRTC_ANDROID + cur_cpus = static_cast(sysconf(_SC_NPROCESSORS_ONLN)); +#endif + return cur_cpus; +} + +// Return the type of this CPU. +SystemInfo::Architecture SystemInfo::GetCpuArchitecture() { + return cpu_arch_; +} + +// Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD". +// See "Intel Processor Identification and the CPUID Instruction" +// (Intel document number: 241618) +std::string SystemInfo::GetCpuVendor() { + if (cpu_vendor_.empty()) { +#if defined(CPU_X86) + int cpu_info[4]; + __cpuid(cpu_info, 0); + cpu_info[0] = cpu_info[1]; // Reorder output + cpu_info[1] = cpu_info[3]; + cpu_info[2] = cpu_info[2]; + cpu_info[3] = 0; + cpu_vendor_ = std::string(reinterpret_cast(&cpu_info[0])); +#elif defined(CPU_ARM) + cpu_vendor_ = std::string("ARM"); +#else + cpu_vendor_ = std::string("Undefined"); +#endif + } + return cpu_vendor_; +} + +int SystemInfo::GetCpuCacheSize() { + return cache_size_; +} + +// Return the "family" of this CPU. +int SystemInfo::GetCpuFamily() { + return cpu_family_; +} + +// Return the "model" of this CPU. +int SystemInfo::GetCpuModel() { + return cpu_model_; +} + +// Return the "stepping" of this CPU. +int SystemInfo::GetCpuStepping() { + return cpu_stepping_; +} + +// Return the clockrate of the primary processor in Mhz. This value can be +// cached. Returns -1 on error. +int SystemInfo::GetMaxCpuSpeed() { + if (cpu_speed_) { + return cpu_speed_; + } +#if defined(WEBRTC_WIN) + HKEY key; + static const WCHAR keyName[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key) + == ERROR_SUCCESS) { + DWORD data, len; + len = sizeof(data); + + if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast(&data), + &len) == ERROR_SUCCESS) { + cpu_speed_ = data; + } else { + LOG(LS_WARNING) << "Failed to query registry value HKLM\\" << keyName + << "\\~Mhz"; + cpu_speed_ = -1; + } + + RegCloseKey(key); + } else { + LOG(LS_WARNING) << "Failed to open registry key HKLM\\" << keyName; + cpu_speed_ = -1; + } +#elif defined(WEBRTC_MAC) + uint64_t sysctl_value; + size_t length = sizeof(sysctl_value); + int error = sysctlbyname("hw.cpufrequency_max", &sysctl_value, &length, + NULL, 0); + cpu_speed_ = !error ? static_cast(sysctl_value/1000000) : -1; +#else + // TODO(fbarchard): Implement using proc/cpuinfo + cpu_speed_ = 0; +#endif + return cpu_speed_; +} + +// Dynamically check the current clockrate, which could be reduced because of +// powersaving profiles. Eventually for windows we want to query WMI for +// root\WMI::ProcessorPerformance.InstanceName="Processor_Number_0".frequency +int SystemInfo::GetCurCpuSpeed() { +#if defined(WEBRTC_WIN) + // TODO(fbarchard): Add WMI check, requires COM initialization + // NOTE(fbarchard): Testable on Sandy Bridge. + return GetMaxCpuSpeed(); +#elif defined(WEBRTC_MAC) + uint64_t sysctl_value; + size_t length = sizeof(sysctl_value); + int error = sysctlbyname("hw.cpufrequency", &sysctl_value, &length, NULL, 0); + return !error ? static_cast(sysctl_value/1000000) : GetMaxCpuSpeed(); +#else // WEBRTC_LINUX + // TODO(fbarchard): Use proc/cpuinfo for Cur speed on Linux. + return GetMaxCpuSpeed(); +#endif +} + +// Returns the amount of installed physical memory in Bytes. Cacheable. +// Returns -1 on error. +int64 SystemInfo::GetMemorySize() { + if (memory_) { + return memory_; + } + +#if defined(WEBRTC_WIN) + MEMORYSTATUSEX status = {0}; + status.dwLength = sizeof(status); + + if (GlobalMemoryStatusEx(&status)) { + memory_ = status.ullTotalPhys; + } else { + LOG_GLE(LS_WARNING) << "GlobalMemoryStatusEx failed."; + memory_ = -1; + } + +#elif defined(WEBRTC_MAC) + size_t len = sizeof(memory_); + int error = sysctlbyname("hw.memsize", &memory_, &len, NULL, 0); + if (error || memory_ == 0) { + memory_ = -1; + } +#else // WEBRTC_LINUX + memory_ = static_cast(sysconf(_SC_PHYS_PAGES)) * + static_cast(sysconf(_SC_PAGESIZE)); + if (memory_ < 0) { + LOG(LS_WARNING) << "sysconf(_SC_PHYS_PAGES) failed." + << "sysconf(_SC_PHYS_PAGES) " << sysconf(_SC_PHYS_PAGES) + << "sysconf(_SC_PAGESIZE) " << sysconf(_SC_PAGESIZE); + memory_ = -1; + } +#endif + + return memory_; +} + + +// Return the name of the machine model we are currently running on. +// This is a human readable string that consists of the name and version +// number of the hardware, i.e 'MacBookAir1,1'. Returns an empty string if +// model can not be determined. The string is cached for subsequent calls. +std::string SystemInfo::GetMachineModel() { + if (!machine_model_.empty()) { + return machine_model_; + } + +#if defined(WEBRTC_MAC) + char buffer[128]; + size_t length = sizeof(buffer); + int error = sysctlbyname("hw.model", buffer, &length, NULL, 0); + if (!error) { + machine_model_.assign(buffer, length - 1); + } else { + machine_model_.clear(); + } +#else + machine_model_ = "Not available"; +#endif + + return machine_model_; +} + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +// Helper functions to query IOKit for video hardware properties. +static CFTypeRef SearchForProperty(io_service_t port, CFStringRef name) { + return IORegistryEntrySearchCFProperty(port, kIOServicePlane, + name, kCFAllocatorDefault, + kIORegistryIterateRecursively | kIORegistryIterateParents); +} + +static void GetProperty(io_service_t port, CFStringRef name, int* value) { + if (!value) return; + CFTypeRef ref = SearchForProperty(port, name); + if (ref) { + CFTypeID refType = CFGetTypeID(ref); + if (CFNumberGetTypeID() == refType) { + CFNumberRef number = reinterpret_cast(ref); + p_convertCFNumberToInt(number, value); + } else if (CFDataGetTypeID() == refType) { + CFDataRef data = reinterpret_cast(ref); + if (CFDataGetLength(data) == sizeof(UInt32)) { + *value = *reinterpret_cast(CFDataGetBytePtr(data)); + } + } + CFRelease(ref); + } +} + +static void GetProperty(io_service_t port, CFStringRef name, + std::string* value) { + if (!value) return; + CFTypeRef ref = SearchForProperty(port, name); + if (ref) { + CFTypeID refType = CFGetTypeID(ref); + if (CFStringGetTypeID() == refType) { + CFStringRef stringRef = reinterpret_cast(ref); + p_convertHostCFStringRefToCPPString(stringRef, *value); + } else if (CFDataGetTypeID() == refType) { + CFDataRef dataRef = reinterpret_cast(ref); + *value = std::string(reinterpret_cast( + CFDataGetBytePtr(dataRef)), CFDataGetLength(dataRef)); + } + CFRelease(ref); + } +} +#endif + +// Fills a struct with information on the graphics adapater and returns true +// iff successful. +bool SystemInfo::GetGpuInfo(GpuInfo *info) { + if (!info) return false; +#if defined(WEBRTC_WIN) && !defined(EXCLUDE_D3D9) + D3DADAPTER_IDENTIFIER9 identifier; + HRESULT hr = E_FAIL; + HINSTANCE d3d_lib = LoadLibrary(L"d3d9.dll"); + + if (d3d_lib) { + typedef IDirect3D9* (WINAPI *D3DCreate9Proc)(UINT); + D3DCreate9Proc d3d_create_proc = reinterpret_cast( + GetProcAddress(d3d_lib, "Direct3DCreate9")); + if (d3d_create_proc) { + IDirect3D9* d3d = d3d_create_proc(D3D_SDK_VERSION); + if (d3d) { + hr = d3d->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &identifier); + d3d->Release(); + } + } + FreeLibrary(d3d_lib); + } + + if (hr != D3D_OK) { + LOG(LS_ERROR) << "Failed to access Direct3D9 information."; + return false; + } + + info->device_name = identifier.DeviceName; + info->description = identifier.Description; + info->vendor_id = identifier.VendorId; + info->device_id = identifier.DeviceId; + info->driver = identifier.Driver; + // driver_version format: product.version.subversion.build + std::stringstream ss; + ss << HIWORD(identifier.DriverVersion.HighPart) << "." + << LOWORD(identifier.DriverVersion.HighPart) << "." + << HIWORD(identifier.DriverVersion.LowPart) << "." + << LOWORD(identifier.DriverVersion.LowPart); + info->driver_version = ss.str(); + return true; +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + // We'll query the IOKit for the gpu of the main display. + io_service_t display_service_port = CGDisplayIOServicePort( + kCGDirectMainDisplay); + GetProperty(display_service_port, CFSTR("vendor-id"), &info->vendor_id); + GetProperty(display_service_port, CFSTR("device-id"), &info->device_id); + GetProperty(display_service_port, CFSTR("model"), &info->description); + return true; +#else // WEBRTC_LINUX + // TODO(fbarchard): Implement this on Linux + return false; +#endif +} +} // namespace rtc diff --git a/webrtc/base/systeminfo.h b/webrtc/base/systeminfo.h new file mode 100644 index 000000000..44088629b --- /dev/null +++ b/webrtc/base/systeminfo.h @@ -0,0 +1,81 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_SYSTEMINFO_H__ +#define WEBRTC_BASE_SYSTEMINFO_H__ + +#include + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +class SystemInfo { + public: + enum Architecture { + SI_ARCH_UNKNOWN = -1, + SI_ARCH_X86 = 0, + SI_ARCH_X64 = 1, + SI_ARCH_ARM = 2 + }; + + SystemInfo(); + + // The number of CPU Cores in the system. + int GetMaxPhysicalCpus(); + // The number of CPU Threads in the system. + int GetMaxCpus(); + // The number of CPU Threads currently available to this process. + int GetCurCpus(); + // Identity of the CPUs. + Architecture GetCpuArchitecture(); + std::string GetCpuVendor(); + int GetCpuFamily(); + int GetCpuModel(); + int GetCpuStepping(); + // Return size of CPU cache in bytes. Uses largest available cache (L3). + int GetCpuCacheSize(); + // Estimated speed of the CPUs, in MHz. e.g. 2400 for 2.4 GHz + int GetMaxCpuSpeed(); + int GetCurCpuSpeed(); + // Total amount of physical memory, in bytes. + int64 GetMemorySize(); + // The model name of the machine, e.g. "MacBookAir1,1" + std::string GetMachineModel(); + + // The gpu identifier + struct GpuInfo { + GpuInfo() : vendor_id(0), device_id(0) {} + std::string device_name; + std::string description; + int vendor_id; + int device_id; + std::string driver; + std::string driver_version; + }; + bool GetGpuInfo(GpuInfo *info); + + private: + int physical_cpus_; + int logical_cpus_; + int cache_size_; + Architecture cpu_arch_; + std::string cpu_vendor_; + int cpu_family_; + int cpu_model_; + int cpu_stepping_; + int cpu_speed_; + int64 memory_; + std::string machine_model_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SYSTEMINFO_H__ diff --git a/webrtc/base/systeminfo_unittest.cc b/webrtc/base/systeminfo_unittest.cc new file mode 100644 index 000000000..fec553582 --- /dev/null +++ b/webrtc/base/systeminfo_unittest.cc @@ -0,0 +1,194 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/systeminfo.h" + +#if defined(CPU_X86) || defined(CPU_ARM) +TEST(SystemInfoTest, CpuVendorNonEmpty) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuVendor: " << info.GetCpuVendor(); + EXPECT_FALSE(info.GetCpuVendor().empty()); +} + +// Tests Vendor identification is Intel or AMD. +// See Also http://en.wikipedia.org/wiki/CPUID +TEST(SystemInfoTest, CpuVendorIntelAMDARM) { + rtc::SystemInfo info; +#if defined(CPU_X86) + EXPECT_TRUE(rtc::string_match(info.GetCpuVendor().c_str(), + "GenuineIntel") || + rtc::string_match(info.GetCpuVendor().c_str(), + "AuthenticAMD")); +#elif defined(CPU_ARM) + EXPECT_TRUE(rtc::string_match(info.GetCpuVendor().c_str(), "ARM")); +#endif +} +#endif // defined(CPU_X86) || defined(CPU_ARM) + +// Tests CpuArchitecture matches expectations. +TEST(SystemInfoTest, GetCpuArchitecture) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuArchitecture: " << info.GetCpuArchitecture(); + rtc::SystemInfo::Architecture architecture = info.GetCpuArchitecture(); +#if defined(CPU_X86) || defined(CPU_ARM) + if (sizeof(intptr_t) == 8) { + EXPECT_EQ(rtc::SystemInfo::SI_ARCH_X64, architecture); + } else if (sizeof(intptr_t) == 4) { +#if defined(CPU_ARM) + EXPECT_EQ(rtc::SystemInfo::SI_ARCH_ARM, architecture); +#else + EXPECT_EQ(rtc::SystemInfo::SI_ARCH_X86, architecture); +#endif + } +#endif +} + +// Tests Cpu Cache Size +TEST(SystemInfoTest, CpuCacheSize) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuCacheSize: " << info.GetCpuCacheSize(); + EXPECT_GE(info.GetCpuCacheSize(), 8192); // 8 KB min cache + EXPECT_LE(info.GetCpuCacheSize(), 1024 * 1024 * 1024); // 1 GB max cache +} + +// Tests MachineModel is set. On Mac test machine model is known. +TEST(SystemInfoTest, MachineModelKnown) { + rtc::SystemInfo info; + EXPECT_FALSE(info.GetMachineModel().empty()); + const char *machine_model = info.GetMachineModel().c_str(); + LOG(LS_INFO) << "MachineModel: " << machine_model; + bool known = true; +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + // Full list as of May 2012. Update when new OSX based models are added. + known = rtc::string_match(machine_model, "MacBookPro*") || + rtc::string_match(machine_model, "MacBookAir*") || + rtc::string_match(machine_model, "MacBook*") || + rtc::string_match(machine_model, "MacPro*") || + rtc::string_match(machine_model, "Macmini*") || + rtc::string_match(machine_model, "iMac*") || + rtc::string_match(machine_model, "Xserve*"); +#elif !defined(WEBRTC_IOS) + // All other machines return Not available. + known = rtc::string_match(info.GetMachineModel().c_str(), + "Not available"); +#endif + if (!known) { + LOG(LS_WARNING) << "Machine Model Unknown: " << machine_model; + } +} + +// Tests maximum cpu clockrate. +TEST(SystemInfoTest, CpuMaxCpuSpeed) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxCpuSpeed: " << info.GetMaxCpuSpeed(); + EXPECT_GT(info.GetMaxCpuSpeed(), 0); + EXPECT_LT(info.GetMaxCpuSpeed(), 100000); // 100 Ghz +} + +// Tests current cpu clockrate. +TEST(SystemInfoTest, CpuCurCpuSpeed) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxCurSpeed: " << info.GetCurCpuSpeed(); + EXPECT_GT(info.GetCurCpuSpeed(), 0); + EXPECT_LT(info.GetMaxCpuSpeed(), 100000); +} + +// Tests physical memory size. +TEST(SystemInfoTest, MemorySize) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MemorySize: " << info.GetMemorySize(); + EXPECT_GT(info.GetMemorySize(), -1); +} + +// Tests number of logical cpus available to the system. +TEST(SystemInfoTest, MaxCpus) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxCpus: " << info.GetMaxCpus(); + EXPECT_GT(info.GetMaxCpus(), 0); +} + +// Tests number of physical cpus available to the system. +TEST(SystemInfoTest, MaxPhysicalCpus) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxPhysicalCpus: " << info.GetMaxPhysicalCpus(); + EXPECT_GT(info.GetMaxPhysicalCpus(), 0); + EXPECT_LE(info.GetMaxPhysicalCpus(), info.GetMaxCpus()); +} + +// Tests number of logical cpus available to the process. +TEST(SystemInfoTest, CurCpus) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CurCpus: " << info.GetCurCpus(); + EXPECT_GT(info.GetCurCpus(), 0); + EXPECT_LE(info.GetCurCpus(), info.GetMaxCpus()); +} + +#ifdef CPU_X86 +// CPU family/model/stepping is only available on X86. The following tests +// that they are set when running on x86 CPUs. Valid Family/Model/Stepping +// values are non-zero on known CPUs. + +// Tests Intel CPU Family identification. +TEST(SystemInfoTest, CpuFamily) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily(); + EXPECT_GT(info.GetCpuFamily(), 0); +} + +// Tests Intel CPU Model identification. +TEST(SystemInfoTest, CpuModel) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel(); + EXPECT_GT(info.GetCpuModel(), 0); +} + +// Tests Intel CPU Stepping identification. +TEST(SystemInfoTest, CpuStepping) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping(); + EXPECT_GT(info.GetCpuStepping(), 0); +} +#else // CPU_X86 +// If not running on x86 CPU the following tests expect the functions to +// return 0. +TEST(SystemInfoTest, CpuFamily) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily(); + EXPECT_EQ(0, info.GetCpuFamily()); +} + +// Tests Intel CPU Model identification. +TEST(SystemInfoTest, CpuModel) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel(); + EXPECT_EQ(0, info.GetCpuModel()); +} + +// Tests Intel CPU Stepping identification. +TEST(SystemInfoTest, CpuStepping) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping(); + EXPECT_EQ(0, info.GetCpuStepping()); +} +#endif // CPU_X86 + +#if WEBRTC_WIN && !defined(EXCLUDE_D3D9) +TEST(SystemInfoTest, GpuInfo) { + rtc::SystemInfo info; + rtc::SystemInfo::GpuInfo gi; + EXPECT_TRUE(info.GetGpuInfo(&gi)); + LOG(LS_INFO) << "GpuDriver: " << gi.driver; + EXPECT_FALSE(gi.driver.empty()); + LOG(LS_INFO) << "GpuDriverVersion: " << gi.driver_version; + EXPECT_FALSE(gi.driver_version.empty()); +} +#endif diff --git a/webrtc/base/task.cc b/webrtc/base/task.cc new file mode 100644 index 000000000..ed9f42626 --- /dev/null +++ b/webrtc/base/task.cc @@ -0,0 +1,272 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/task.h" +#include "webrtc/base/common.h" +#include "webrtc/base/taskrunner.h" + +namespace rtc { + +int32 Task::unique_id_seed_ = 0; + +Task::Task(TaskParent *parent) + : TaskParent(this, parent), + state_(STATE_INIT), + blocked_(false), + done_(false), + aborted_(false), + busy_(false), + error_(false), + start_time_(0), + timeout_time_(0), + timeout_seconds_(0), + timeout_suspended_(false) { + unique_id_ = unique_id_seed_++; + + // sanity check that we didn't roll-over our id seed + ASSERT(unique_id_ < unique_id_seed_); +} + +Task::~Task() { + // Is this task being deleted in the correct manner? + ASSERT(!done_ || GetRunner()->is_ok_to_delete(this)); + ASSERT(state_ == STATE_INIT || done_); + ASSERT(state_ == STATE_INIT || blocked_); + + // If the task is being deleted without being done, it + // means that it hasn't been removed from its parent. + // This happens if a task is deleted outside of TaskRunner. + if (!done_) { + Stop(); + } +} + +int64 Task::CurrentTime() { + return GetRunner()->CurrentTime(); +} + +int64 Task::ElapsedTime() { + return CurrentTime() - start_time_; +} + +void Task::Start() { + if (state_ != STATE_INIT) + return; + // Set the start time before starting the task. Otherwise if the task + // finishes quickly and deletes the Task object, setting start_time_ + // will crash. + start_time_ = CurrentTime(); + GetRunner()->StartTask(this); +} + +void Task::Step() { + if (done_) { +#ifdef _DEBUG + // we do not know how !blocked_ happens when done_ - should be impossible. + // But it causes problems, so in retail build, we force blocked_, and + // under debug we assert. + ASSERT(blocked_); +#else + blocked_ = true; +#endif + return; + } + + // Async Error() was called + if (error_) { + done_ = true; + state_ = STATE_ERROR; + blocked_ = true; +// obsolete - an errored task is not considered done now +// SignalDone(); + + Stop(); +#ifdef _DEBUG + // verify that stop removed this from its parent + ASSERT(!parent()->IsChildTask(this)); +#endif + return; + } + + busy_ = true; + int new_state = Process(state_); + busy_ = false; + + if (aborted_) { + Abort(true); // no need to wake because we're awake + return; + } + + if (new_state == STATE_BLOCKED) { + blocked_ = true; + // Let the timeout continue + } else { + state_ = new_state; + blocked_ = false; + ResetTimeout(); + } + + if (new_state == STATE_DONE) { + done_ = true; + } else if (new_state == STATE_ERROR) { + done_ = true; + error_ = true; + } + + if (done_) { +// obsolete - call this yourself +// SignalDone(); + + Stop(); +#if _DEBUG + // verify that stop removed this from its parent + ASSERT(!parent()->IsChildTask(this)); +#endif + blocked_ = true; + } +} + +void Task::Abort(bool nowake) { + // Why only check for done_ (instead of "aborted_ || done_")? + // + // If aborted_ && !done_, it means the logic for aborting still + // needs to be executed (because busy_ must have been true when + // Abort() was previously called). + if (done_) + return; + aborted_ = true; + if (!busy_) { + done_ = true; + blocked_ = true; + error_ = true; + + // "done_" is set before calling "Stop()" to ensure that this code + // doesn't execute more than once (recursively) for the same task. + Stop(); +#ifdef _DEBUG + // verify that stop removed this from its parent + ASSERT(!parent()->IsChildTask(this)); +#endif + if (!nowake) { + // WakeTasks to self-delete. + // Don't call Wake() because it is a no-op after "done_" is set. + // Even if Wake() did run, it clears "blocked_" which isn't desireable. + GetRunner()->WakeTasks(); + } + } +} + +void Task::Wake() { + if (done_) + return; + if (blocked_) { + blocked_ = false; + GetRunner()->WakeTasks(); + } +} + +void Task::Error() { + if (error_ || done_) + return; + error_ = true; + Wake(); +} + +std::string Task::GetStateName(int state) const { + switch (state) { + case STATE_BLOCKED: return "BLOCKED"; + case STATE_INIT: return "INIT"; + case STATE_START: return "START"; + case STATE_DONE: return "DONE"; + case STATE_ERROR: return "ERROR"; + case STATE_RESPONSE: return "RESPONSE"; + } + return "??"; +} + +int Task::Process(int state) { + int newstate = STATE_ERROR; + + if (TimedOut()) { + ClearTimeout(); + newstate = OnTimeout(); + SignalTimeout(); + } else { + switch (state) { + case STATE_INIT: + newstate = STATE_START; + break; + case STATE_START: + newstate = ProcessStart(); + break; + case STATE_RESPONSE: + newstate = ProcessResponse(); + break; + case STATE_DONE: + case STATE_ERROR: + newstate = STATE_BLOCKED; + break; + } + } + + return newstate; +} + +void Task::Stop() { + // No need to wake because we're either awake or in abort + TaskParent::OnStopped(this); +} + +void Task::set_timeout_seconds(const int timeout_seconds) { + timeout_seconds_ = timeout_seconds; + ResetTimeout(); +} + +bool Task::TimedOut() { + return timeout_seconds_ && + timeout_time_ && + CurrentTime() >= timeout_time_; +} + +void Task::ResetTimeout() { + int64 previous_timeout_time = timeout_time_; + bool timeout_allowed = (state_ != STATE_INIT) + && (state_ != STATE_DONE) + && (state_ != STATE_ERROR); + if (timeout_seconds_ && timeout_allowed && !timeout_suspended_) + timeout_time_ = CurrentTime() + + (timeout_seconds_ * kSecToMsec * kMsecTo100ns); + else + timeout_time_ = 0; + + GetRunner()->UpdateTaskTimeout(this, previous_timeout_time); +} + +void Task::ClearTimeout() { + int64 previous_timeout_time = timeout_time_; + timeout_time_ = 0; + GetRunner()->UpdateTaskTimeout(this, previous_timeout_time); +} + +void Task::SuspendTimeout() { + if (!timeout_suspended_) { + timeout_suspended_ = true; + ResetTimeout(); + } +} + +void Task::ResumeTimeout() { + if (timeout_suspended_) { + timeout_suspended_ = false; + ResetTimeout(); + } +} + +} // namespace rtc diff --git a/webrtc/base/task.h b/webrtc/base/task.h new file mode 100644 index 000000000..77d767a78 --- /dev/null +++ b/webrtc/base/task.h @@ -0,0 +1,177 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TASK_H__ +#define WEBRTC_BASE_TASK_H__ + +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/taskparent.h" + +///////////////////////////////////////////////////////////////////// +// +// TASK +// +///////////////////////////////////////////////////////////////////// +// +// Task is a state machine infrastructure. States are pushed forward by +// pushing forwards a TaskRunner that holds on to all Tasks. The purpose +// of Task is threefold: +// +// (1) It manages ongoing work on the UI thread. Multitasking without +// threads, keeping it easy, keeping it real. :-) It does this by +// organizing a set of states for each task. When you return from your +// Process*() function, you return an integer for the next state. You do +// not go onto the next state yourself. Every time you enter a state, +// you check to see if you can do anything yet. If not, you return +// STATE_BLOCKED. If you _could_ do anything, do not return +// STATE_BLOCKED - even if you end up in the same state, return +// STATE_mysamestate. When you are done, return STATE_DONE and then the +// task will self-delete sometime afterwards. +// +// (2) It helps you avoid all those reentrancy problems when you chain +// too many triggers on one thread. Basically if you want to tell a task +// to process something for you, you feed your task some information and +// then you Wake() it. Don't tell it to process it right away. If it +// might be working on something as you send it information, you may want +// to have a queue in the task. +// +// (3) Finally it helps manage parent tasks and children. If a parent +// task gets aborted, all the children tasks are too. The nice thing +// about this, for example, is if you have one parent task that +// represents, say, and Xmpp connection, then you can spawn a whole bunch +// of infinite lifetime child tasks and now worry about cleaning them up. +// When the parent task goes to STATE_DONE, the task engine will make +// sure all those children are aborted and get deleted. +// +// Notice that Task has a few built-in states, e.g., +// +// STATE_INIT - the task isn't running yet +// STATE_START - the task is in its first state +// STATE_RESPONSE - the task is in its second state +// STATE_DONE - the task is done +// +// STATE_ERROR - indicates an error - we should audit the error code in +// light of any usage of it to see if it should be improved. When I +// first put down the task stuff I didn't have a good sense of what was +// needed for Abort and Error, and now the subclasses of Task will ground +// the design in a stronger way. +// +// STATE_NEXT - the first undefined state number. (like WM_USER) - you +// can start defining more task states there. +// +// When you define more task states, just override Process(int state) and +// add your own switch statement. If you want to delegate to +// Task::Process, you can effectively delegate to its switch statement. +// No fancy method pointers or such - this is all just pretty low tech, +// easy to debug, and fast. +// +// Also notice that Task has some primitive built-in timeout functionality. +// +// A timeout is defined as "the task stays in STATE_BLOCKED longer than +// timeout_seconds_." +// +// Descendant classes can override this behavior by calling the +// various protected methods to change the timeout behavior. For +// instance, a descendand might call SuspendTimeout() when it knows +// that it isn't waiting for anything that might timeout, but isn't +// yet in the STATE_DONE state. +// + +namespace rtc { + +// Executes a sequence of steps +class Task : public TaskParent { + public: + Task(TaskParent *parent); + virtual ~Task(); + + int32 unique_id() { return unique_id_; } + + void Start(); + void Step(); + int GetState() const { return state_; } + bool HasError() const { return (GetState() == STATE_ERROR); } + bool Blocked() const { return blocked_; } + bool IsDone() const { return done_; } + int64 ElapsedTime(); + + // Called from outside to stop task without any more callbacks + void Abort(bool nowake = false); + + bool TimedOut(); + + int64 timeout_time() const { return timeout_time_; } + int timeout_seconds() const { return timeout_seconds_; } + void set_timeout_seconds(int timeout_seconds); + + sigslot::signal0<> SignalTimeout; + + // Called inside the task to signal that the task may be unblocked + void Wake(); + + protected: + + enum { + STATE_BLOCKED = -1, + STATE_INIT = 0, + STATE_START = 1, + STATE_DONE = 2, + STATE_ERROR = 3, + STATE_RESPONSE = 4, + STATE_NEXT = 5, // Subclasses which need more states start here and higher + }; + + // Called inside to advise that the task should wake and signal an error + void Error(); + + int64 CurrentTime(); + + virtual std::string GetStateName(int state) const; + virtual int Process(int state); + virtual void Stop(); + virtual int ProcessStart() = 0; + virtual int ProcessResponse() { return STATE_DONE; } + + void ResetTimeout(); + void ClearTimeout(); + + void SuspendTimeout(); + void ResumeTimeout(); + + protected: + virtual int OnTimeout() { + // by default, we are finished after timing out + return STATE_DONE; + } + + private: + void Done(); + + int state_; + bool blocked_; + bool done_; + bool aborted_; + bool busy_; + bool error_; + int64 start_time_; + int64 timeout_time_; + int timeout_seconds_; + bool timeout_suspended_; + int32 unique_id_; + + static int32 unique_id_seed_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TASK_H__ diff --git a/webrtc/base/task_unittest.cc b/webrtc/base/task_unittest.cc new file mode 100644 index 000000000..8831259c6 --- /dev/null +++ b/webrtc/base/task_unittest.cc @@ -0,0 +1,545 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#if defined(WEBRTC_POSIX) +#include +#endif // WEBRTC_POSIX + +// TODO: Remove this once the cause of sporadic failures in these +// tests is tracked down. +#include + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif // WEBRTC_WIN + +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/task.h" +#include "webrtc/base/taskrunner.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +static int64 GetCurrentTime() { + return static_cast(Time()) * 10000; +} + +// feel free to change these numbers. Note that '0' won't work, though +#define STUCK_TASK_COUNT 5 +#define HAPPY_TASK_COUNT 20 + +// this is a generic timeout task which, when it signals timeout, will +// include the unique ID of the task in the signal (we don't use this +// in production code because we haven't yet had occasion to generate +// an array of the same types of task) + +class IdTimeoutTask : public Task, public sigslot::has_slots<> { + public: + explicit IdTimeoutTask(TaskParent *parent) : Task(parent) { + SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout); + } + + sigslot::signal1 SignalTimeoutId; + sigslot::signal1 SignalDoneId; + + virtual int ProcessStart() { + return STATE_RESPONSE; + } + + void OnLocalTimeout() { + SignalTimeoutId(unique_id()); + } + + protected: + virtual void Stop() { + SignalDoneId(unique_id()); + Task::Stop(); + } +}; + +class StuckTask : public IdTimeoutTask { + public: + explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {} + virtual int ProcessStart() { + return STATE_BLOCKED; + } +}; + +class HappyTask : public IdTimeoutTask { + public: + explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) { + time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2); + } + virtual int ProcessStart() { + if (ElapsedTime() > (time_to_perform_ * 1000 * 10000)) + return STATE_RESPONSE; + else + return STATE_BLOCKED; + } + + private: + int time_to_perform_; +}; + +// simple implementation of a task runner which uses Windows' +// GetSystemTimeAsFileTime() to get the current clock ticks + +class MyTaskRunner : public TaskRunner { + public: + virtual void WakeTasks() { RunTasks(); } + virtual int64 CurrentTime() { + return GetCurrentTime(); + } + + bool timeout_change() const { + return timeout_change_; + } + + void clear_timeout_change() { + timeout_change_ = false; + } + protected: + virtual void OnTimeoutChange() { + timeout_change_ = true; + } + bool timeout_change_; +}; + +// +// this unit test is primarily concerned (for now) with the timeout +// functionality in tasks. It works as follows: +// +// * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout) +// and some "happy" (will immediately finish). +// * Set the timeout on the "stuck" tasks to some number of seconds between +// 1 and the number of stuck tasks +// * Start all the stuck & happy tasks in random order +// * Wait "number of stuck tasks" seconds and make sure everything timed out + +class TaskTest : public sigslot::has_slots<> { + public: + TaskTest() {} + + // no need to delete any tasks; the task runner owns them + ~TaskTest() {} + + void Start() { + // create and configure tasks + for (int i = 0; i < STUCK_TASK_COUNT; ++i) { + stuck_[i].task_ = new StuckTask(&task_runner_); + stuck_[i].task_->SignalTimeoutId.connect(this, + &TaskTest::OnTimeoutStuck); + stuck_[i].timed_out_ = false; + stuck_[i].xlat_ = stuck_[i].task_->unique_id(); + stuck_[i].task_->set_timeout_seconds(i + 1); + LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout " + << stuck_[i].task_->timeout_seconds(); + } + + for (int i = 0; i < HAPPY_TASK_COUNT; ++i) { + happy_[i].task_ = new HappyTask(&task_runner_); + happy_[i].task_->SignalTimeoutId.connect(this, + &TaskTest::OnTimeoutHappy); + happy_[i].task_->SignalDoneId.connect(this, + &TaskTest::OnDoneHappy); + happy_[i].timed_out_ = false; + happy_[i].xlat_ = happy_[i].task_->unique_id(); + } + + // start all the tasks in random order + int stuck_index = 0; + int happy_index = 0; + for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) { + if ((stuck_index < STUCK_TASK_COUNT) && + (happy_index < HAPPY_TASK_COUNT)) { + if (rand() % 2 == 1) { + stuck_[stuck_index++].task_->Start(); + } else { + happy_[happy_index++].task_->Start(); + } + } else if (stuck_index < STUCK_TASK_COUNT) { + stuck_[stuck_index++].task_->Start(); + } else { + happy_[happy_index++].task_->Start(); + } + } + + for (int i = 0; i < STUCK_TASK_COUNT; ++i) { + std::cout << "Stuck task #" << i << " timeout is " << + stuck_[i].task_->timeout_seconds() << " at " << + stuck_[i].task_->timeout_time() << std::endl; + } + + // just a little self-check to make sure we started all the tasks + ASSERT_EQ(STUCK_TASK_COUNT, stuck_index); + ASSERT_EQ(HAPPY_TASK_COUNT, happy_index); + + // run the unblocked tasks + LOG(LS_INFO) << "Running tasks"; + task_runner_.RunTasks(); + + std::cout << "Start time is " << GetCurrentTime() << std::endl; + + // give all the stuck tasks time to timeout + for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT; + ++i) { + Thread::Current()->ProcessMessages(1000); + for (int j = 0; j < HAPPY_TASK_COUNT; ++j) { + if (happy_[j].task_) { + happy_[j].task_->Wake(); + } + } + LOG(LS_INFO) << "Polling tasks"; + task_runner_.PollTasks(); + } + + // We see occasional test failures here due to the stuck tasks not having + // timed-out yet, which seems like it should be impossible. To help track + // this down we have added logging of the timing information, which we send + // directly to stdout so that we get it in opt builds too. + std::cout << "End time is " << GetCurrentTime() << std::endl; + } + + void OnTimeoutStuck(const int id) { + LOG(LS_INFO) << "Timed out task " << id; + + int i; + for (i = 0; i < STUCK_TASK_COUNT; ++i) { + if (stuck_[i].xlat_ == id) { + stuck_[i].timed_out_ = true; + stuck_[i].task_ = NULL; + break; + } + } + + // getting a bad ID here is a failure, but let's continue + // running to see what else might go wrong + EXPECT_LT(i, STUCK_TASK_COUNT); + } + + void OnTimeoutHappy(const int id) { + int i; + for (i = 0; i < HAPPY_TASK_COUNT; ++i) { + if (happy_[i].xlat_ == id) { + happy_[i].timed_out_ = true; + happy_[i].task_ = NULL; + break; + } + } + + // getting a bad ID here is a failure, but let's continue + // running to see what else might go wrong + EXPECT_LT(i, HAPPY_TASK_COUNT); + } + + void OnDoneHappy(const int id) { + int i; + for (i = 0; i < HAPPY_TASK_COUNT; ++i) { + if (happy_[i].xlat_ == id) { + happy_[i].task_ = NULL; + break; + } + } + + // getting a bad ID here is a failure, but let's continue + // running to see what else might go wrong + EXPECT_LT(i, HAPPY_TASK_COUNT); + } + + void check_passed() { + EXPECT_TRUE(task_runner_.AllChildrenDone()); + + // make sure none of our happy tasks timed out + for (int i = 0; i < HAPPY_TASK_COUNT; ++i) { + EXPECT_FALSE(happy_[i].timed_out_); + } + + // make sure all of our stuck tasks timed out + for (int i = 0; i < STUCK_TASK_COUNT; ++i) { + EXPECT_TRUE(stuck_[i].timed_out_); + if (!stuck_[i].timed_out_) { + std::cout << "Stuck task #" << i << " timeout is at " + << stuck_[i].task_->timeout_time() << std::endl; + } + } + + std::cout.flush(); + } + + private: + struct TaskInfo { + IdTimeoutTask *task_; + bool timed_out_; + int xlat_; + }; + + MyTaskRunner task_runner_; + TaskInfo stuck_[STUCK_TASK_COUNT]; + TaskInfo happy_[HAPPY_TASK_COUNT]; +}; + +TEST(start_task_test, Timeout) { + TaskTest task_test; + task_test.Start(); + task_test.check_passed(); +} + +// Test for aborting the task while it is running + +class AbortTask : public Task { + public: + explicit AbortTask(TaskParent *parent) : Task(parent) { + set_timeout_seconds(1); + } + + virtual int ProcessStart() { + Abort(); + return STATE_NEXT; + } + private: + DISALLOW_EVIL_CONSTRUCTORS(AbortTask); +}; + +class TaskAbortTest : public sigslot::has_slots<> { + public: + TaskAbortTest() {} + + // no need to delete any tasks; the task runner owns them + ~TaskAbortTest() {} + + void Start() { + Task *abort_task = new AbortTask(&task_runner_); + abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout); + abort_task->Start(); + + // run the task + task_runner_.RunTasks(); + } + + private: + void OnTimeout() { + FAIL() << "Task timed out instead of aborting."; + } + + MyTaskRunner task_runner_; + DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest); +}; + +TEST(start_task_test, Abort) { + TaskAbortTest abort_test; + abort_test.Start(); +} + +// Test for aborting a task to verify that it does the Wake operation +// which gets it deleted. + +class SetBoolOnDeleteTask : public Task { + public: + SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted) + : Task(parent), + set_when_deleted_(set_when_deleted) { + EXPECT_TRUE(NULL != set_when_deleted); + EXPECT_FALSE(*set_when_deleted); + } + + virtual ~SetBoolOnDeleteTask() { + *set_when_deleted_ = true; + } + + virtual int ProcessStart() { + return STATE_BLOCKED; + } + + private: + bool* set_when_deleted_; + DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask); +}; + +class AbortShouldWakeTest : public sigslot::has_slots<> { + public: + AbortShouldWakeTest() {} + + // no need to delete any tasks; the task runner owns them + ~AbortShouldWakeTest() {} + + void Start() { + bool task_deleted = false; + Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted); + task_to_abort->Start(); + + // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls + // TaskRunner::RunTasks() immediately which should delete the task. + task_to_abort->Abort(); + EXPECT_TRUE(task_deleted); + + if (!task_deleted) { + // avoid a crash (due to referencing a local variable) + // if the test fails. + task_runner_.RunTasks(); + } + } + + private: + void OnTimeout() { + FAIL() << "Task timed out instead of aborting."; + } + + MyTaskRunner task_runner_; + DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest); +}; + +TEST(start_task_test, AbortShouldWake) { + AbortShouldWakeTest abort_should_wake_test; + abort_should_wake_test.Start(); +} + +// Validate that TaskRunner's OnTimeoutChange gets called appropriately +// * When a task calls UpdateTaskTimeout +// * When the next timeout task time, times out +class TimeoutChangeTest : public sigslot::has_slots<> { + public: + TimeoutChangeTest() + : task_count_(ARRAY_SIZE(stuck_tasks_)) {} + + // no need to delete any tasks; the task runner owns them + ~TimeoutChangeTest() {} + + void Start() { + for (int i = 0; i < task_count_; ++i) { + stuck_tasks_[i] = new StuckTask(&task_runner_); + stuck_tasks_[i]->set_timeout_seconds(i + 2); + stuck_tasks_[i]->SignalTimeoutId.connect(this, + &TimeoutChangeTest::OnTimeoutId); + } + + for (int i = task_count_ - 1; i >= 0; --i) { + stuck_tasks_[i]->Start(); + } + task_runner_.clear_timeout_change(); + + // At this point, our timeouts are set as follows + // task[0] is 2 seconds, task[1] at 3 seconds, etc. + + stuck_tasks_[0]->set_timeout_seconds(2); + // Now, task[0] is 2 seconds, task[1] at 3 seconds... + // so timeout change shouldn't be called. + EXPECT_FALSE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + + stuck_tasks_[0]->set_timeout_seconds(1); + // task[0] is 1 seconds, task[1] at 3 seconds... + // The smallest timeout got smaller so timeout change be called. + EXPECT_TRUE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + + stuck_tasks_[1]->set_timeout_seconds(2); + // task[0] is 1 seconds, task[1] at 2 seconds... + // The smallest timeout is still 1 second so no timeout change. + EXPECT_FALSE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + + while (task_count_ > 0) { + int previous_count = task_count_; + task_runner_.PollTasks(); + if (previous_count != task_count_) { + // We only get here when a task times out. When that + // happens, the timeout change should get called because + // the smallest timeout is now in the past. + EXPECT_TRUE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + } + Thread::Current()->socketserver()->Wait(500, false); + } + } + + private: + void OnTimeoutId(const int id) { + for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) { + if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) { + task_count_--; + stuck_tasks_[i] = NULL; + break; + } + } + } + + MyTaskRunner task_runner_; + StuckTask* (stuck_tasks_[3]); + int task_count_; + DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest); +}; + +TEST(start_task_test, TimeoutChange) { + TimeoutChangeTest timeout_change_test; + timeout_change_test.Start(); +} + +class DeleteTestTaskRunner : public TaskRunner { + public: + DeleteTestTaskRunner() { + } + virtual void WakeTasks() { } + virtual int64 CurrentTime() { + return GetCurrentTime(); + } + private: + DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner); +}; + +TEST(unstarted_task_test, DeleteTask) { + // This test ensures that we don't + // crash if a task is deleted without running it. + DeleteTestTaskRunner task_runner; + HappyTask* happy_task = new HappyTask(&task_runner); + happy_task->Start(); + + // try deleting the task directly + HappyTask* child_happy_task = new HappyTask(happy_task); + delete child_happy_task; + + // run the unblocked tasks + task_runner.RunTasks(); +} + +TEST(unstarted_task_test, DoNotDeleteTask1) { + // This test ensures that we don't + // crash if a task runner is deleted without + // running a certain task. + DeleteTestTaskRunner task_runner; + HappyTask* happy_task = new HappyTask(&task_runner); + happy_task->Start(); + + HappyTask* child_happy_task = new HappyTask(happy_task); + child_happy_task->Start(); + + // Never run the tasks +} + +TEST(unstarted_task_test, DoNotDeleteTask2) { + // This test ensures that we don't + // crash if a taskrunner is delete with a + // task that has never been started. + DeleteTestTaskRunner task_runner; + HappyTask* happy_task = new HappyTask(&task_runner); + happy_task->Start(); + + // Do not start the task. + // Note: this leaks memory, so don't do this. + // Instead, always run your tasks or delete them. + new HappyTask(happy_task); + + // run the unblocked tasks + task_runner.RunTasks(); +} + +} // namespace rtc diff --git a/webrtc/base/taskparent.cc b/webrtc/base/taskparent.cc new file mode 100644 index 000000000..edc146fd2 --- /dev/null +++ b/webrtc/base/taskparent.cc @@ -0,0 +1,95 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/taskparent.h" + +#include "webrtc/base/task.h" +#include "webrtc/base/taskrunner.h" + +namespace rtc { + +TaskParent::TaskParent(Task* derived_instance, TaskParent *parent) + : parent_(parent) { + ASSERT(derived_instance != NULL); + ASSERT(parent != NULL); + runner_ = parent->GetRunner(); + parent_->AddChild(derived_instance); + Initialize(); +} + +TaskParent::TaskParent(TaskRunner *derived_instance) + : parent_(NULL), + runner_(derived_instance) { + ASSERT(derived_instance != NULL); + Initialize(); +} + +// Does common initialization of member variables +void TaskParent::Initialize() { + children_.reset(new ChildSet()); + child_error_ = false; +} + +void TaskParent::AddChild(Task *child) { + children_->insert(child); +} + +#ifdef _DEBUG +bool TaskParent::IsChildTask(Task *task) { + ASSERT(task != NULL); + return task->parent_ == this && children_->find(task) != children_->end(); +} +#endif + +bool TaskParent::AllChildrenDone() { + for (ChildSet::iterator it = children_->begin(); + it != children_->end(); + ++it) { + if (!(*it)->IsDone()) + return false; + } + return true; +} + +bool TaskParent::AnyChildError() { + return child_error_; +} + +void TaskParent::AbortAllChildren() { + if (children_->size() > 0) { +#ifdef _DEBUG + runner_->IncrementAbortCount(); +#endif + + ChildSet copy = *children_; + for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) { + (*it)->Abort(true); // Note we do not wake + } + +#ifdef _DEBUG + runner_->DecrementAbortCount(); +#endif + } +} + +void TaskParent::OnStopped(Task *task) { + AbortAllChildren(); + parent_->OnChildStopped(task); +} + +void TaskParent::OnChildStopped(Task *child) { + if (child->HasError()) + child_error_ = true; + children_->erase(child); +} + +} // namespace rtc diff --git a/webrtc/base/taskparent.h b/webrtc/base/taskparent.h new file mode 100644 index 000000000..a3832024e --- /dev/null +++ b/webrtc/base/taskparent.h @@ -0,0 +1,62 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TASKPARENT_H__ +#define WEBRTC_BASE_TASKPARENT_H__ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class Task; +class TaskRunner; + +class TaskParent { + public: + TaskParent(Task *derived_instance, TaskParent *parent); + explicit TaskParent(TaskRunner *derived_instance); + virtual ~TaskParent() { } + + TaskParent *GetParent() { return parent_; } + TaskRunner *GetRunner() { return runner_; } + + bool AllChildrenDone(); + bool AnyChildError(); +#ifdef _DEBUG + bool IsChildTask(Task *task); +#endif + + protected: + void OnStopped(Task *task); + void AbortAllChildren(); + TaskParent *parent() { + return parent_; + } + + private: + void Initialize(); + void OnChildStopped(Task *child); + void AddChild(Task *child); + + TaskParent *parent_; + TaskRunner *runner_; + bool child_error_; + typedef std::set ChildSet; + scoped_ptr children_; + DISALLOW_EVIL_CONSTRUCTORS(TaskParent); +}; + + +} // namespace rtc + +#endif // WEBRTC_BASE_TASKPARENT_H__ diff --git a/webrtc/base/taskrunner.cc b/webrtc/base/taskrunner.cc new file mode 100644 index 000000000..bc4ab5e44 --- /dev/null +++ b/webrtc/base/taskrunner.cc @@ -0,0 +1,224 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/taskrunner.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/task.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +TaskRunner::TaskRunner() + : TaskParent(this), + next_timeout_task_(NULL), + tasks_running_(false) +#ifdef _DEBUG + , abort_count_(0), + deleting_task_(NULL) +#endif +{ +} + +TaskRunner::~TaskRunner() { + // this kills and deletes children silently! + AbortAllChildren(); + InternalRunTasks(true); +} + +void TaskRunner::StartTask(Task * task) { + tasks_.push_back(task); + + // the task we just started could be about to timeout -- + // make sure our "next timeout task" is correct + UpdateTaskTimeout(task, 0); + + WakeTasks(); +} + +void TaskRunner::RunTasks() { + InternalRunTasks(false); +} + +void TaskRunner::InternalRunTasks(bool in_destructor) { + // This shouldn't run while an abort is happening. + // If that occurs, then tasks may be deleted in this method, + // but pointers to them will still be in the + // "ChildSet copy" in TaskParent::AbortAllChildren. + // Subsequent use of those task may cause data corruption or crashes. + ASSERT(!abort_count_); + // Running continues until all tasks are Blocked (ok for a small # of tasks) + if (tasks_running_) { + return; // don't reenter + } + + tasks_running_ = true; + + int64 previous_timeout_time = next_task_timeout(); + + int did_run = true; + while (did_run) { + did_run = false; + // use indexing instead of iterators because tasks_ may grow + for (size_t i = 0; i < tasks_.size(); ++i) { + while (!tasks_[i]->Blocked()) { + tasks_[i]->Step(); + did_run = true; + } + } + } + // Tasks are deleted when running has paused + bool need_timeout_recalc = false; + for (size_t i = 0; i < tasks_.size(); ++i) { + if (tasks_[i]->IsDone()) { + Task* task = tasks_[i]; + if (next_timeout_task_ && + task->unique_id() == next_timeout_task_->unique_id()) { + next_timeout_task_ = NULL; + need_timeout_recalc = true; + } + +#ifdef _DEBUG + deleting_task_ = task; +#endif + delete task; +#ifdef _DEBUG + deleting_task_ = NULL; +#endif + tasks_[i] = NULL; + } + } + // Finally, remove nulls + std::vector::iterator it; + it = std::remove(tasks_.begin(), + tasks_.end(), + reinterpret_cast(NULL)); + + tasks_.erase(it, tasks_.end()); + + if (need_timeout_recalc) + RecalcNextTimeout(NULL); + + // Make sure that adjustments are done to account + // for any timeout changes (but don't call this + // while being destroyed since it calls a pure virtual function). + if (!in_destructor) + CheckForTimeoutChange(previous_timeout_time); + + tasks_running_ = false; +} + +void TaskRunner::PollTasks() { + // see if our "next potentially timed-out task" has indeed timed out. + // If it has, wake it up, then queue up the next task in line + // Repeat while we have new timed-out tasks. + // TODO: We need to guard against WakeTasks not updating + // next_timeout_task_. Maybe also add documentation in the header file once + // we understand this code better. + Task* old_timeout_task = NULL; + while (next_timeout_task_ && + old_timeout_task != next_timeout_task_ && + next_timeout_task_->TimedOut()) { + old_timeout_task = next_timeout_task_; + next_timeout_task_->Wake(); + WakeTasks(); + } +} + +int64 TaskRunner::next_task_timeout() const { + if (next_timeout_task_) { + return next_timeout_task_->timeout_time(); + } + return 0; +} + +// this function gets called frequently -- when each task changes +// state to something other than DONE, ERROR or BLOCKED, it calls +// ResetTimeout(), which will call this function to make sure that +// the next timeout-able task hasn't changed. The logic in this function +// prevents RecalcNextTimeout() from getting called in most cases, +// effectively making the task scheduler O-1 instead of O-N + +void TaskRunner::UpdateTaskTimeout(Task* task, + int64 previous_task_timeout_time) { + ASSERT(task != NULL); + int64 previous_timeout_time = next_task_timeout(); + bool task_is_timeout_task = next_timeout_task_ != NULL && + task->unique_id() == next_timeout_task_->unique_id(); + if (task_is_timeout_task) { + previous_timeout_time = previous_task_timeout_time; + } + + // if the relevant task has a timeout, then + // check to see if it's closer than the current + // "about to timeout" task + if (task->timeout_time()) { + if (next_timeout_task_ == NULL || + (task->timeout_time() <= next_timeout_task_->timeout_time())) { + next_timeout_task_ = task; + } + } else if (task_is_timeout_task) { + // otherwise, if the task doesn't have a timeout, + // and it used to be our "about to timeout" task, + // walk through all the tasks looking for the real + // "about to timeout" task + RecalcNextTimeout(task); + } + + // Note when task_running_, then the running routine + // (TaskRunner::InternalRunTasks) is responsible for calling + // CheckForTimeoutChange. + if (!tasks_running_) { + CheckForTimeoutChange(previous_timeout_time); + } +} + +void TaskRunner::RecalcNextTimeout(Task *exclude_task) { + // walk through all the tasks looking for the one + // which satisfies the following: + // it's not finished already + // we're not excluding it + // it has the closest timeout time + + int64 next_timeout_time = 0; + next_timeout_task_ = NULL; + + for (size_t i = 0; i < tasks_.size(); ++i) { + Task *task = tasks_[i]; + // if the task isn't complete, and it actually has a timeout time + if (!task->IsDone() && (task->timeout_time() > 0)) + // if it doesn't match our "exclude" task + if (exclude_task == NULL || + exclude_task->unique_id() != task->unique_id()) + // if its timeout time is sooner than our current timeout time + if (next_timeout_time == 0 || + task->timeout_time() <= next_timeout_time) { + // set this task as our next-to-timeout + next_timeout_time = task->timeout_time(); + next_timeout_task_ = task; + } + } +} + +void TaskRunner::CheckForTimeoutChange(int64 previous_timeout_time) { + int64 next_timeout = next_task_timeout(); + bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) || + next_timeout < previous_timeout_time || + (previous_timeout_time <= CurrentTime() && + previous_timeout_time != next_timeout); + if (timeout_change) { + OnTimeoutChange(); + } +} + +} // namespace rtc diff --git a/webrtc/base/taskrunner.h b/webrtc/base/taskrunner.h new file mode 100644 index 000000000..629c2d3ac --- /dev/null +++ b/webrtc/base/taskrunner.h @@ -0,0 +1,100 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TASKRUNNER_H__ +#define WEBRTC_BASE_TASKRUNNER_H__ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/taskparent.h" + +namespace rtc { +class Task; + +const int64 kSecToMsec = 1000; +const int64 kMsecTo100ns = 10000; +const int64 kSecTo100ns = kSecToMsec * kMsecTo100ns; + +class TaskRunner : public TaskParent, public sigslot::has_slots<> { + public: + TaskRunner(); + virtual ~TaskRunner(); + + virtual void WakeTasks() = 0; + + // Returns the current time in 100ns units. It is used for + // determining timeouts. The origin is not important, only + // the units and that rollover while the computer is running. + // + // On Windows, GetSystemTimeAsFileTime is the typical implementation. + virtual int64 CurrentTime() = 0 ; + + void StartTask(Task *task); + void RunTasks(); + void PollTasks(); + + void UpdateTaskTimeout(Task *task, int64 previous_task_timeout_time); + +#ifdef _DEBUG + bool is_ok_to_delete(Task* task) { + return task == deleting_task_; + } + + void IncrementAbortCount() { + ++abort_count_; + } + + void DecrementAbortCount() { + --abort_count_; + } +#endif + + // Returns the next absolute time when a task times out + // OR "0" if there is no next timeout. + int64 next_task_timeout() const; + + protected: + // The primary usage of this method is to know if + // a callback timer needs to be set-up or adjusted. + // This method will be called + // * when the next_task_timeout() becomes a smaller value OR + // * when next_task_timeout() has changed values and the previous + // value is in the past. + // + // If the next_task_timeout moves to the future, this method will *not* + // get called (because it subclass should check next_task_timeout() + // when its timer goes off up to see if it needs to set-up a new timer). + // + // Note that this maybe called conservatively. In that it may be + // called when no time change has happened. + virtual void OnTimeoutChange() { + // by default, do nothing. + } + + private: + void InternalRunTasks(bool in_destructor); + void CheckForTimeoutChange(int64 previous_timeout_time); + + std::vector tasks_; + Task *next_timeout_task_; + bool tasks_running_; +#ifdef _DEBUG + int abort_count_; + Task* deleting_task_; +#endif + + void RecalcNextTimeout(Task *exclude_task); +}; + +} // namespace rtc + +#endif // TASK_BASE_TASKRUNNER_H__ diff --git a/webrtc/base/template_util.h b/webrtc/base/template_util.h new file mode 100644 index 000000000..f0bf39c5f --- /dev/null +++ b/webrtc/base/template_util.h @@ -0,0 +1,112 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TEMPLATE_UTIL_H_ +#define WEBRTC_BASE_TEMPLATE_UTIL_H_ + +#include // For size_t. + +namespace rtc { + +// template definitions from tr1 + +template +struct integral_constant { + static const T value = v; + typedef T value_type; + typedef integral_constant type; +}; + +template const T integral_constant::value; + +typedef integral_constant true_type; +typedef integral_constant false_type; + +template struct is_pointer : false_type {}; +template struct is_pointer : true_type {}; + +template struct is_same : public false_type {}; +template struct is_same : true_type {}; + +template struct is_array : public false_type {}; +template struct is_array : public true_type {}; +template struct is_array : public true_type {}; + +template struct is_non_const_reference : false_type {}; +template struct is_non_const_reference : true_type {}; +template struct is_non_const_reference : false_type {}; + +template struct is_void : false_type {}; +template <> struct is_void : true_type {}; + +namespace internal { + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// This class is an implementation detail for is_convertible, and you +// don't need to know how it works to use is_convertible. For those +// who care: we declare two different functions, one whose argument is +// of type To and one with a variadic argument list. We give them +// return types of different size, so we can use sizeof to trick the +// compiler into telling us which function it would have chosen if we +// had called it with an argument of type From. See Alexandrescu's +// _Modern C++ Design_ for more details on this sort of trick. + +struct ConvertHelper { + template + static YesType Test(To); + + template + static NoType Test(...); + + template + static From& Create(); +}; + +// Used to determine if a type is a struct/union/class. Inspired by Boost's +// is_class type_trait implementation. +struct IsClassHelper { + template + static YesType Test(void(C::*)(void)); + + template + static NoType Test(...); +}; + +} // namespace internal + +// Inherits from true_type if From is convertible to To, false_type otherwise. +// +// Note that if the type is convertible, this will be a true_type REGARDLESS +// of whether or not the conversion would emit a warning. +template +struct is_convertible + : integral_constant( + internal::ConvertHelper::Create())) == + sizeof(internal::YesType)> { +}; + +template +struct is_class + : integral_constant(0)) == + sizeof(internal::YesType)> { +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TEMPLATE_UTIL_H_ diff --git a/webrtc/base/testbase64.h b/webrtc/base/testbase64.h new file mode 100644 index 000000000..39dd00ce3 --- /dev/null +++ b/webrtc/base/testbase64.h @@ -0,0 +1,5 @@ +/* This file was generated by googleclient/talk/binary2header.sh */ + +static unsigned char testbase64[] = { +0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xe1, 0x0d, 0x07, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x9e, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xbe, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0xc3, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd4, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xdc, 0x01, 0x32, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x3c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x04, 0x02, 0x13, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x02, 0xc4, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x53, 0x4f, 0x4e, 0x59, 0x00, 0x44, 0x53, 0x43, 0x2d, 0x50, 0x32, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x37, 0x2e, 0x30, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x33, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x31, 0x30, 0x3a, 0x30, 0x34, 0x00, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58, 0x20, 0x31, 0x30, 0x2e, 0x34, 0x2e, 0x38, 0x00, 0x00, 0x1c, 0x82, 0x9a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x6a, 0x82, 0x9d, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x72, 0x88, 0x22, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x88, 0x27, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x64, 0x00, 0x00, 0x90, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x7a, 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xa2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xaa, 0x92, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xb2, 0x92, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x92, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x92, 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xba, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa0, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0a, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x12, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x1a, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x22, 0x02, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0xff, 0xed, 0x2e, 0x1c, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x33, 0x2e, 0x30, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x1c, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x1c, 0x02, 0x78, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfb, 0x09, 0xa6, 0xbd, 0x07, 0x4c, 0x2a, 0x36, 0x9d, 0x8f, 0xe2, 0xcc, 0x57, 0xa9, 0xac, 0x85, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xea, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xb0, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x22, 0x2d, 0x2f, 0x2f, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x2f, 0x44, 0x54, 0x44, 0x20, 0x50, 0x4c, 0x49, 0x53, 0x54, 0x20, 0x31, 0x2e, 0x30, 0x2f, 0x2f, 0x45, 0x4e, 0x22, 0x20, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x54, 0x44, 0x73, 0x2f, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x2d, 0x31, 0x2e, 0x30, 0x2e, 0x64, 0x74, 0x64, 0x22, 0x3e, 0x0a, 0x3c, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x3e, 0x0a, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x73, 0x75, 0x62, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x70, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x6e, 0x61, 0x2d, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x55, 0x53, 0x20, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x3c, 0x2f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x3e, 0x0a, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xde, 0x02, 0x40, 0xff, 0xee, 0xff, 0xee, 0x03, 0x06, 0x02, 0x52, 0x03, 0x67, 0x05, 0x28, 0x03, 0xfc, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd8, 0x02, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x08, 0x00, 0x19, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x27, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0x6c, 0x66, 0x66, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0xa1, 0x99, 0x9a, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x03, 0x45, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x08, 0x00, 0x44, 0x00, 0x53, 0x00, 0x43, 0x00, 0x30, 0x00, 0x32, 0x00, 0x33, 0x00, 0x32, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x06, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x56, 0x6c, 0x4c, 0x73, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x07, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0c, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x6f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0a, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x49, 0x6d, 0x67, 0x20, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x73, 0x67, 0x65, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x61, 0x6c, 0x74, 0x54, 0x61, 0x67, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x49, 0x73, 0x48, 0x54, 0x4d, 0x4c, 0x62, 0x6f, 0x6f, 0x6c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x68, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x48, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x09, 0x76, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x0b, 0x62, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x11, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x42, 0x47, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x09, 0x74, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x6c, 0x65, 0x66, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x72, 0x69, 0x67, 0x68, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf9, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x18, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x00, 0x00, 0x13, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x20, 0x00, 0x37, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xff, 0xe1, 0x15, 0x67, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x00, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x27, 0xef, 0xbb, 0xbf, 0x27, 0x20, 0x69, 0x64, 0x3d, 0x27, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x27, 0x3f, 0x3e, 0x0a, 0x3c, 0x3f, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2d, 0x78, 0x61, 0x70, 0x2d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x20, 0x65, 0x73, 0x63, 0x3d, 0x22, 0x43, 0x52, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x27, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x27, 0x20, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x74, 0x6b, 0x3d, 0x27, 0x58, 0x4d, 0x50, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69, 0x74, 0x20, 0x32, 0x2e, 0x38, 0x2e, 0x32, 0x2d, 0x33, 0x33, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x31, 0x2e, 0x35, 0x27, 0x3e, 0x0a, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x27, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x69, 0x58, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x58, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x64, 0x66, 0x2f, 0x31, 0x2e, 0x33, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x64, 0x66, 0x3a, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x43, 0x61, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x78, 0x61, 0x70, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x64, 0x6f, 0x63, 0x69, 0x64, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x36, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x3c, 0x2f, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x64, 0x63, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x72, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x63, 0x2f, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x31, 0x2e, 0x31, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x20, 0x78, 0x6d, 0x6c, 0x3a, 0x6c, 0x61, 0x6e, 0x67, 0x3d, 0x27, 0x78, 0x2d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x27, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x27, 0x77, 0x27, 0x3f, 0x3e, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x40, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x06, 0x04, 0x03, 0x04, 0x06, 0x07, 0x05, 0x04, 0x04, 0x05, 0x07, 0x08, 0x06, 0x06, 0x07, 0x06, 0x06, 0x08, 0x0a, 0x08, 0x09, 0x09, 0x09, 0x09, 0x08, 0x0a, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x04, 0x05, 0x05, 0x08, 0x07, 0x08, 0x0f, 0x0a, 0x0a, 0x0f, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x0d, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x00, 0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x03, 0x02, 0x06, 0x01, 0x00, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x02, 0x02, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x02, 0x06, 0x07, 0x03, 0x04, 0x02, 0x06, 0x02, 0x73, 0x01, 0x02, 0x03, 0x11, 0x04, 0x00, 0x05, 0x21, 0x12, 0x31, 0x41, 0x51, 0x06, 0x13, 0x61, 0x22, 0x71, 0x81, 0x14, 0x32, 0x91, 0xa1, 0x07, 0x15, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xe1, 0x33, 0x16, 0x62, 0xf0, 0x24, 0x72, 0x82, 0xf1, 0x25, 0x43, 0x34, 0x53, 0x92, 0xa2, 0xb2, 0x63, 0x73, 0xc2, 0x35, 0x44, 0x27, 0x93, 0xa3, 0xb3, 0x36, 0x17, 0x54, 0x64, 0x74, 0xc3, 0xd2, 0xe2, 0x08, 0x26, 0x83, 0x09, 0x0a, 0x18, 0x19, 0x84, 0x94, 0x45, 0x46, 0xa4, 0xb4, 0x56, 0xd3, 0x55, 0x28, 0x1a, 0xf2, 0xe3, 0xf3, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x03, 0x05, 0x05, 0x04, 0x05, 0x06, 0x04, 0x08, 0x03, 0x03, 0x6d, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x41, 0x05, 0x51, 0x13, 0x61, 0x22, 0x06, 0x71, 0x81, 0x91, 0x32, 0xa1, 0xb1, 0xf0, 0x14, 0xc1, 0xd1, 0xe1, 0x23, 0x42, 0x15, 0x52, 0x62, 0x72, 0xf1, 0x33, 0x24, 0x34, 0x43, 0x82, 0x16, 0x92, 0x53, 0x25, 0xa2, 0x63, 0xb2, 0xc2, 0x07, 0x73, 0xd2, 0x35, 0xe2, 0x44, 0x83, 0x17, 0x54, 0x93, 0x08, 0x09, 0x0a, 0x18, 0x19, 0x26, 0x36, 0x45, 0x1a, 0x27, 0x64, 0x74, 0x55, 0x37, 0xf2, 0xa3, 0xb3, 0xc3, 0x28, 0x29, 0xd3, 0xe3, 0xf3, 0x84, 0x94, 0xa4, 0xb4, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x46, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf0, 0x67, 0xa6, 0x5c, 0x0f, 0x01, 0xd4, 0x7e, 0x18, 0x12, 0x98, 0xe9, 0xd6, 0x2d, 0x34, 0x6d, 0x70, 0xdf, 0xdc, 0xa1, 0xe3, 0xec, 0x5b, 0xfb, 0x32, 0x24, 0xb2, 0x01, 0x1f, 0x15, 0xa4, 0x52, 0x4a, 0x82, 0x31, 0xf1, 0xfe, 0xd1, 0x3d, 0x14, 0x64, 0x49, 0x64, 0x22, 0x98, 0xcf, 0xa5, 0x46, 0x6c, 0x16, 0x55, 0x71, 0x56, 0x62, 0x28, 0x07, 0xc5, 0x45, 0x15, 0xa0, 0xc8, 0x89, 0x33, 0xe1, 0x63, 0xd2, 0xd8, 0x34, 0x44, 0x17, 0xa0, 0x2c, 0x4d, 0x16, 0xbb, 0xed, 0xdc, 0xf8, 0x64, 0xc1, 0x6b, 0x31, 0x42, 0x18, 0x8e, 0xc7, 0xb5, 0x2a, 0x7d, 0xb2, 0x56, 0xc5, 0x61, 0x8c, 0xf2, 0xa0, 0x1b, 0x1e, 0x83, 0x0d, 0xa1, 0x63, 0x50, 0x1f, 0x97, 0x7c, 0x2a, 0xa9, 0x1a, 0x9a, 0x86, 0x4f, 0xb4, 0xb4, 0x38, 0x0a, 0xa6, 0x0b, 0xb8, 0x0c, 0x05, 0x14, 0xf8, 0x76, 0x3e, 0x19, 0x14, 0xb6, 0x78, 0xf8, 0x8c, 0x2a, 0xd5, 0x01, 0xdc, 0x6f, 0x8a, 0x1a, 0xe3, 0x8d, 0xab, 0xff, 0xd0, 0xf0, 0xec, 0xe9, 0x15, 0xb5, 0xb9, 0x5a, 0x7c, 0x4c, 0xa2, 0x9e, 0x24, 0xf5, 0xca, 0xc6, 0xe5, 0x99, 0xd9, 0x34, 0x99, 0x04, 0x3a, 0x7d, 0xb5, 0xba, 0xd5, 0x51, 0x63, 0x0e, 0xc7, 0xc5, 0x9b, 0x73, 0xf8, 0xe4, 0x6f, 0x76, 0xca, 0xd9, 0xda, 0x54, 0x6d, 0x72, 0x2e, 0x1a, 0x57, 0x11, 0x44, 0x40, 0x0d, 0x27, 0x7a, 0x0f, 0xd9, 0x5f, 0x12, 0x69, 0x4c, 0x84, 0xcd, 0x36, 0xe3, 0x85, 0xb2, 0xcd, 0x2f, 0x4a, 0x8b, 0x58, 0x36, 0xf6, 0x76, 0xa8, 0x64, 0x64, 0x3c, 0xa4, 0x93, 0xaa, 0x25, 0x3c, 0x49, 0xda, 0xa4, 0xe5, 0x26, 0x54, 0xe4, 0x8c, 0x7c, 0x5c, 0x93, 0x4d, 0x67, 0xc9, 0x3a, 0x6e, 0x9f, 0x13, 0xb4, 0xce, 0xf7, 0x3a, 0x9b, 0xad, 0x52, 0xd6, 0x2a, 0xd1, 0x49, 0xee, 0xc7, 0xf8, 0x64, 0x46, 0x42, 0x4e, 0xcd, 0x92, 0xc2, 0x00, 0xdd, 0x8a, 0x47, 0xe5, 0x69, 0x6e, 0xd4, 0xa4, 0x08, 0x16, 0x83, 0x9c, 0x8c, 0xdd, 0x95, 0x6b, 0xb9, 0xf6, 0xef, 0x97, 0x78, 0x94, 0xe3, 0x78, 0x04, 0xa4, 0xf3, 0xe8, 0xee, 0x64, 0xe1, 0x12, 0x10, 0x05, 0x6a, 0xc7, 0xc0, 0x6f, 0x53, 0xf3, 0xc9, 0x89, 0xb4, 0x9c, 0x4e, 0xb4, 0xf2, 0xd3, 0xde, 0x7a, 0xd2, 0x19, 0x16, 0x38, 0x61, 0x5d, 0xd9, 0x88, 0x05, 0x9c, 0xf4, 0x0a, 0x0f, 0x5f, 0x73, 0x84, 0xe4, 0xa4, 0xc7, 0x0d, 0xa5, 0xf1, 0x59, 0xba, 0x5c, 0x08, 0x98, 0x6f, 0xc8, 0x20, 0xfa, 0x4e, 0x4e, 0xf6, 0x69, 0xe1, 0xa2, 0x89, 0xfd, 0x1f, 0x77, 0x2c, 0xe6, 0xce, 0xd6, 0x17, 0x9a, 0x69, 0xdb, 0xd3, 0x86, 0x18, 0xc1, 0x67, 0x77, 0x26, 0x80, 0x28, 0x1b, 0x93, 0x88, 0x41, 0x0f, 0x40, 0xb0, 0xfc, 0x87, 0xf3, 0x43, 0x98, 0xd7, 0x58, 0x96, 0xdb, 0x4d, 0x91, 0x88, 0xe5, 0x6c, 0x58, 0xdc, 0x5c, 0x2a, 0xf7, 0x2c, 0xb1, 0xfc, 0x20, 0x8f, 0x02, 0xd9, 0x65, 0x06, 0xbe, 0x26, 0x6f, 0xa2, 0x7f, 0xce, 0x3d, 0x69, 0x26, 0xdd, 0x13, 0x52, 0xbf, 0xbd, 0x92, 0x62, 0x59, 0x4c, 0x90, 0xac, 0x50, 0x45, 0x5e, 0xbb, 0x09, 0x03, 0x12, 0x29, 0x84, 0x00, 0xc4, 0xc9, 0x11, 0xff, 0x00, 0x42, 0xe7, 0xa7, 0x7a, 0xd4, 0xfd, 0x21, 0x79, 0xe9, 0x78, 0x71, 0x8b, 0x95, 0x39, 0x75, 0xaf, 0x4e, 0x98, 0x78, 0x42, 0x38, 0xdf, 0xff, 0xd1, 0xf0, 0xe6, 0xa0, 0x58, 0xc8, 0x84, 0x9a, 0xaa, 0x30, 0x55, 0xf9, 0x0a, 0x6f, 0x90, 0x0c, 0xca, 0x72, 0x48, 0xb8, 0x1e, 0x89, 0xa7, 0x23, 0x17, 0x24, 0xff, 0x00, 0x61, 0xb6, 0x54, 0x76, 0x6e, 0x1b, 0xa7, 0xbe, 0x50, 0xf2, 0xc1, 0xd7, 0x4c, 0x52, 0x5e, 0x33, 0x5b, 0xe9, 0x10, 0xf4, 0x54, 0x3c, 0x5e, 0x77, 0xee, 0x49, 0xec, 0x2b, 0xb6, 0x63, 0xe4, 0xc9, 0xc3, 0xef, 0x73, 0xf0, 0xe1, 0x32, 0x1b, 0xf2, 0x7a, 0x05, 0xce, 0xad, 0x65, 0xa1, 0x98, 0xb4, 0x0f, 0x2a, 0x5b, 0x23, 0xeb, 0x12, 0x00, 0x88, 0xb0, 0xa8, 0x66, 0x46, 0x3d, 0xea, 0x7b, 0xfb, 0x9e, 0x99, 0x89, 0xbc, 0x8d, 0x97, 0x3a, 0x34, 0x05, 0x32, 0x5d, 0x1f, 0xc9, 0x1a, 0x8c, 0x36, 0x8c, 0x6f, 0x66, 0xfa, 0xc6, 0xb7, 0x7d, 0xf0, 0x94, 0x04, 0xf0, 0x88, 0xc9, 0xd5, 0x9d, 0x8d, 0x4b, 0x11, 0xd4, 0x9f, 0xbb, 0x25, 0xc5, 0xdc, 0xa2, 0x03, 0x99, 0x4b, 0xbc, 0xf3, 0x0d, 0x97, 0x96, 0x74, 0xe5, 0xf2, 0xb6, 0x80, 0x95, 0xbd, 0x99, 0x15, 0xf5, 0x4b, 0xd2, 0x37, 0x58, 0x46, 0xd4, 0x27, 0xc5, 0xce, 0xc1, 0x7c, 0x30, 0x8e, 0x68, 0x94, 0x7b, 0x9e, 0x6d, 0xe6, 0x7b, 0x9b, 0x5d, 0x3a, 0xd8, 0xdb, 0x32, 0xfa, 0x77, 0x65, 0x15, 0xe4, 0x57, 0xa7, 0x21, 0x55, 0x04, 0x57, 0xef, 0xd8, 0x66, 0x56, 0x38, 0x19, 0x1b, 0xe8, 0xe0, 0x67, 0x98, 0xc7, 0x1a, 0x1c, 0xde, 0x71, 0x71, 0x79, 0x2c, 0xf2, 0xfa, 0x8c, 0x48, 0xec, 0xb5, 0x24, 0x9a, 0x0c, 0xce, 0x75, 0x29, 0xae, 0x8c, 0x67, 0xd4, 0xb5, 0x0b, 0x4b, 0x04, 0x05, 0xef, 0x2e, 0x66, 0x8e, 0x18, 0x08, 0x15, 0xdd, 0x8f, 0x11, 0xb0, 0xeb, 0x4c, 0x04, 0x5b, 0x21, 0x2a, 0x7d, 0x41, 0xe4, 0x4f, 0xcb, 0xcb, 0x5d, 0x12, 0x45, 0xb8, 0xb7, 0x53, 0x71, 0xaa, 0x9f, 0x86, 0x5b, 0xd6, 0x50, 0x4a, 0xed, 0xba, 0x46, 0x77, 0x00, 0x13, 0xd4, 0x8c, 0x85, 0xd3, 0x12, 0x6d, 0xeb, 0x1a, 0x67, 0x95, 0xd9, 0x39, 0x39, 0x50, 0xac, 0xff, 0x00, 0x6f, 0xc4, 0xff, 0x00, 0x1c, 0x81, 0x92, 0xb2, 0x6b, 0x6d, 0x02, 0xdd, 0xbd, 0x36, 0x92, 0x36, 0x2d, 0x1f, 0xc0, 0x2a, 0x0b, 0x28, 0x1b, 0x91, 0x41, 0xf4, 0x9c, 0xb6, 0x25, 0x81, 0x46, 0xfe, 0x81, 0xb5, 0xad, 0x3d, 0xba, 0x57, 0xb7, 0xf9, 0xf6, 0xc9, 0xb0, 0x7f, 0xff, 0xd2, 0xf0, 0xe2, 0x86, 0x95, 0xc4, 0x67, 0x7e, 0x3f, 0x11, 0xf7, 0xa8, 0x19, 0x06, 0x69, 0x8d, 0xca, 0xca, 0x24, 0x8f, 0xd3, 0x52, 0x24, 0x89, 0x47, 0x25, 0x1f, 0xcb, 0x20, 0xf8, 0xb2, 0xb2, 0x76, 0x6e, 0x88, 0x36, 0xf6, 0x6f, 0x2a, 0xc1, 0x6e, 0xfa, 0x45, 0xad, 0xbc, 0x3f, 0x0b, 0x46, 0x81, 0x4d, 0x46, 0xea, 0x7a, 0x9a, 0x83, 0x9a, 0xa9, 0xdd, 0xbb, 0xec, 0x7b, 0x06, 0x5b, 0xe5, 0xcf, 0x2e, 0x69, 0xfa, 0x5c, 0xcd, 0x7b, 0x14, 0x5e, 0xa5, 0xee, 0xf5, 0xb8, 0x7d, 0xdd, 0x99, 0xba, 0xef, 0x91, 0x16, 0x5b, 0x36, 0xb6, 0x65, 0x0d, 0xac, 0xb2, 0x5b, 0xed, 0x34, 0x81, 0x7a, 0xbb, 0x46, 0x40, 0x6a, 0x9e, 0xb4, 0x39, 0x31, 0x13, 0x49, 0xda, 0xd2, 0x9b, 0xed, 0x1e, 0xc4, 0x24, 0xb3, 0x35, 0xb2, 0x88, 0x60, 0x06, 0xe6, 0x56, 0x98, 0x96, 0x79, 0x1e, 0x31, 0x51, 0xc9, 0x8f, 0xcb, 0x00, 0xe6, 0xb3, 0xe4, 0xf9, 0x2b, 0xcc, 0x7a, 0x94, 0xda, 0x96, 0xa9, 0x71, 0x77, 0x70, 0x79, 0xcd, 0x33, 0x97, 0x76, 0x3f, 0xcc, 0xc6, 0xa6, 0x9f, 0x2e, 0x99, 0xb9, 0xc6, 0x2a, 0x21, 0xe6, 0x73, 0xca, 0xe6, 0x4a, 0x51, 0x1a, 0x99, 0x1c, 0x28, 0x04, 0x93, 0xd0, 0x0e, 0xa4, 0xe4, 0xda, 0x5f, 0x50, 0xfe, 0x4a, 0xfe, 0x48, 0xb5, 0xb2, 0xc1, 0xe6, 0x1f, 0x31, 0x7e, 0xef, 0x52, 0x91, 0x43, 0xc3, 0x6e, 0x77, 0xf4, 0x22, 0x6d, 0xbf, 0xe4, 0x63, 0x0e, 0xbf, 0xca, 0x36, 0xeb, 0x5c, 0x84, 0xa5, 0x48, 0x7d, 0x3b, 0x61, 0xa1, 0xdb, 0x5b, 0x2c, 0x71, 0xda, 0x45, 0xc4, 0x28, 0x00, 0x81, 0xdb, 0x31, 0xc9, 0xb4, 0xb2, 0x3b, 0x5d, 0x27, 0xa5, 0x05, 0x1b, 0xc7, 0xdb, 0x10, 0xa9, 0xbd, 0xa6, 0x93, 0x0c, 0x75, 0xe4, 0x39, 0x35, 0x41, 0x3d, 0xc5, 0x06, 0xdb, 0x8e, 0xfd, 0x46, 0x5b, 0x1d, 0x98, 0x95, 0x4f, 0x46, 0xdb, 0xd5, 0xfb, 0x29, 0x5e, 0x9d, 0x0d, 0x32, 0xeb, 0x61, 0x4f, 0xff, 0xd3, 0xf1, 0x46, 0x9a, 0x16, 0x1b, 0x91, 0x71, 0x28, 0xac, 0x4a, 0x14, 0x30, 0x3e, 0x19, 0x54, 0xb9, 0x36, 0xc7, 0x9b, 0x2d, 0xd1, 0x6c, 0x45, 0xe3, 0xdc, 0xde, 0xc8, 0x95, 0x5b, 0x87, 0xf8, 0x41, 0x1d, 0x10, 0x54, 0x01, 0x98, 0x79, 0x25, 0xd1, 0xda, 0xe9, 0xe1, 0xb5, 0x9e, 0xac, 0xeb, 0x42, 0xba, 0x8e, 0xdf, 0x8c, 0x31, 0x21, 0x70, 0xb4, 0x5d, 0xbe, 0xc5, 0x7c, 0x2b, 0xed, 0xe1, 0x94, 0x18, 0xb9, 0x51, 0x3d, 0x03, 0x2c, 0x13, 0x6b, 0xf1, 0x42, 0x6e, 0xe2, 0xb7, 0x12, 0xa0, 0xdd, 0x50, 0x9f, 0x4f, 0x6f, 0xa7, 0x6f, 0xc7, 0x03, 0x61, 0xa0, 0x83, 0xb5, 0xf3, 0x97, 0x98, 0x20, 0x9c, 0x44, 0xea, 0xd0, 0xad, 0x48, 0x64, 0x90, 0x21, 0xd8, 0x9f, 0xa7, 0xa6, 0x44, 0xca, 0x99, 0xc6, 0x36, 0xcb, 0x74, 0x5d, 0x7e, 0x5b, 0xfe, 0x31, 0x6a, 0x31, 0xf3, 0x8c, 0xd0, 0xad, 0x40, 0xa3, 0x1f, 0x7c, 0x44, 0xd6, 0x51, 0xd9, 0xe0, 0x5f, 0x9a, 0x7e, 0x41, 0x9f, 0x40, 0xf3, 0x14, 0xba, 0x85, 0xba, 0x34, 0xba, 0x2d, 0xfb, 0x34, 0xd0, 0xcf, 0x4f, 0xb0, 0xce, 0x6a, 0x51, 0xe9, 0xb0, 0x20, 0xf4, 0xf1, 0x19, 0xb2, 0xc3, 0x90, 0x11, 0x4e, 0x97, 0x55, 0x80, 0x83, 0xc4, 0x17, 0x7e, 0x4c, 0x79, 0x19, 0xfc, 0xd1, 0xe7, 0x78, 0x4b, 0x91, 0x1d, 0xae, 0x92, 0xa6, 0xf6, 0x46, 0x75, 0xe4, 0xad, 0x22, 0x1f, 0xdd, 0xa1, 0x07, 0xb3, 0x1e, 0xfe, 0xd9, 0x92, 0xeb, 0x4b, 0xed, 0xfd, 0x0a, 0xc2, 0x63, 0x27, 0xa4, 0x88, 0x17, 0x60, 0x49, 0x35, 0xdc, 0x8e, 0xa5, 0x7d, 0xab, 0xd3, 0x28, 0x90, 0x50, 0xcd, 0xed, 0x2d, 0xda, 0x15, 0x55, 0x51, 0xf1, 0x1a, 0x0a, 0xf7, 0x39, 0x5d, 0xaa, 0x77, 0x6f, 0x01, 0x8e, 0xa7, 0x7d, 0xfa, 0xff, 0x00, 0x66, 0x10, 0xa8, 0xb8, 0x63, 0x76, 0x90, 0xa8, 0x20, 0x06, 0x56, 0xdb, 0x61, 0xda, 0xbd, 0x4f, 0xcb, 0x24, 0x15, 0x0f, 0xf5, 0x66, 0xe5, 0x5f, 0x4c, 0x53, 0xc3, 0xb7, 0xce, 0x99, 0x6b, 0x17, 0xff, 0xd4, 0xf0, 0xec, 0x57, 0x6f, 0x32, 0xa5, 0xa4, 0x43, 0x76, 0x75, 0xa9, 0xf1, 0x03, 0xfa, 0x64, 0x08, 0x6c, 0x8e, 0xfb, 0x3d, 0x7f, 0xcb, 0x16, 0x2b, 0x3d, 0xbc, 0x16, 0xa3, 0x66, 0x6d, 0x98, 0xfb, 0x1e, 0xb9, 0xac, 0xc8, 0x77, 0xb7, 0x7d, 0x01, 0xb3, 0x37, 0xb8, 0xd3, 0x46, 0x95, 0x68, 0x86, 0xd2, 0x2e, 0x4e, 0xab, 0xf0, 0x23, 0x11, 0x4e, 0x5f, 0xcd, 0x98, 0xe7, 0x25, 0x96, 0x71, 0x83, 0x0f, 0xd6, 0x3c, 0xb9, 0xe7, 0x0d, 0x7c, 0x41, 0x22, 0x5e, 0xb3, 0x20, 0x0c, 0x65, 0x80, 0xc8, 0x63, 0x8e, 0xbb, 0x95, 0xa5, 0x07, 0xeb, 0xcc, 0xac, 0x73, 0x83, 0x4e, 0x5c, 0x59, 0x09, 0xd8, 0xec, 0xc8, 0x57, 0x41, 0xd3, 0x4e, 0x95, 0xa5, 0x5b, 0x4b, 0x6a, 0xcb, 0xab, 0x43, 0x10, 0x4b, 0xeb, 0x85, 0xa2, 0x2c, 0x8e, 0x3f, 0x68, 0x54, 0xf5, 0x00, 0xd3, 0x97, 0x7a, 0x65, 0x79, 0xa6, 0x24, 0x76, 0x6f, 0xd3, 0x62, 0x96, 0x30, 0x78, 0xcb, 0x21, 0xf2, 0xf4, 0x22, 0xce, 0x54, 0x8e, 0x46, 0x26, 0x10, 0x7e, 0x0a, 0xf5, 0xd8, 0xf5, 0x1f, 0x31, 0x98, 0x83, 0x73, 0xb3, 0x91, 0xcd, 0x67, 0xe6, 0x7d, 0xe8, 0x16, 0x69, 0x6f, 0x10, 0x1f, 0x54, 0x9a, 0x37, 0xf5, 0x41, 0x5e, 0x7f, 0x0a, 0x29, 0x62, 0x02, 0xf8, 0x9c, 0xc8, 0x8c, 0x77, 0x6a, 0x99, 0xa0, 0x89, 0xff, 0x00, 0x9c, 0x74, 0xd2, 0xed, 0xed, 0xfc, 0xbb, 0x7b, 0xaa, 0x9a, 0x7d, 0x62, 0xfe, 0x46, 0x2d, 0xfe, 0x4c, 0x51, 0x31, 0x11, 0xa9, 0xf6, 0xef, 0x9b, 0x30, 0x5e, 0x7b, 0x38, 0xdd, 0xf4, 0x7f, 0x95, 0x94, 0xbc, 0x12, 0x43, 0x30, 0x6a, 0xb2, 0xf3, 0x86, 0x40, 0x3e, 0xcb, 0xd7, 0x6a, 0xd7, 0xb1, 0xe9, 0x8f, 0x37, 0x19, 0x97, 0x41, 0x2c, 0x71, 0x20, 0xf5, 0x36, 0x9c, 0x55, 0x78, 0x1d, 0x8a, 0x91, 0xd7, 0x11, 0x14, 0x5a, 0x3e, 0x19, 0x03, 0x10, 0x6b, 0xca, 0xbd, 0x86, 0xf8, 0x9d, 0x95, 0x18, 0x36, 0x65, 0x2e, 0xbc, 0x54, 0x1f, 0xa2, 0x99, 0x00, 0x59, 0x2a, 0x6f, 0x5e, 0x55, 0x15, 0xe9, 0x5f, 0xc3, 0x2f, 0xb6, 0x14, 0xff, 0x00, 0xff, 0xd5, 0xf1, 0x95, 0xfe, 0x80, 0x74, 0x0d, 0x7c, 0xd9, 0x89, 0x3d, 0x78, 0x57, 0x8b, 0xc5, 0x28, 0xe8, 0x55, 0xf7, 0x1f, 0x48, 0xca, 0x38, 0xb8, 0x83, 0x9f, 0x93, 0x07, 0x85, 0x3a, 0x7a, 0x6f, 0x95, 0x66, 0x2b, 0x2c, 0x4c, 0x0d, 0x14, 0x00, 0x3e, 0x9c, 0xc3, 0x98, 0x76, 0xb8, 0x45, 0xbd, 0x02, 0xde, 0x48, 0xee, 0xdc, 0xa0, 0x15, 0xe2, 0x2b, 0xc8, 0x8a, 0x8a, 0xfd, 0x3b, 0x66, 0x3f, 0x00, 0x73, 0x84, 0x2d, 0x36, 0xb5, 0xb5, 0x9e, 0x35, 0x1c, 0x29, 0xc4, 0xfe, 0xc8, 0x04, 0x7f, 0xc4, 0x69, 0x91, 0xe1, 0x67, 0x2c, 0x4a, 0xd2, 0xe9, 0x4e, 0xe3, 0xd4, 0xf4, 0x81, 0x5a, 0x12, 0xc5, 0x41, 0x3f, 0x79, 0x38, 0x9b, 0x60, 0x20, 0x07, 0x34, 0xb0, 0xc9, 0x03, 0x5c, 0x23, 0x03, 0x53, 0x13, 0x56, 0x88, 0xdf, 0x09, 0xda, 0x9b, 0xd3, 0xb6, 0x52, 0x0e, 0xec, 0xe4, 0x29, 0x24, 0xfc, 0xd0, 0xe7, 0x75, 0xe5, 0x57, 0x6b, 0x61, 0xfb, 0xf0, 0xca, 0xaa, 0x57, 0xa8, 0xe6, 0x78, 0x1a, 0x7d, 0xf9, 0x95, 0x8a, 0x5e, 0xa0, 0xe3, 0x67, 0x8f, 0xa0, 0xbd, 0x5b, 0xf2, 0xdf, 0x4a, 0x82, 0xcb, 0x4a, 0xb3, 0xb0, 0xb4, 0x41, 0x0a, 0x70, 0x48, 0xd9, 0x57, 0x60, 0x51, 0x3a, 0x8f, 0xbc, 0xe6, 0x7b, 0xcb, 0xe4, 0x3b, 0xa7, 0x3f, 0x9b, 0x9f, 0x9a, 0xba, 0x77, 0xe5, 0x5f, 0x95, 0x9c, 0x59, 0x94, 0x9f, 0xcd, 0x37, 0x8c, 0xa9, 0xa6, 0xd9, 0x39, 0xaa, 0xd0, 0x7d, 0xa9, 0x1c, 0x03, 0x5e, 0x09, 0xff, 0x00, 0x0c, 0x76, 0xcb, 0x62, 0x2d, 0xa5, 0xf2, 0x85, 0xbf, 0xe7, 0x87, 0xe6, 0xa3, 0x5e, 0x4d, 0xa8, 0xc9, 0xe6, 0x8b, 0xd5, 0x69, 0x5c, 0xb0, 0x4a, 0xab, 0xc4, 0xb5, 0x35, 0x0a, 0xaa, 0xea, 0x40, 0x03, 0xa0, 0xf6, 0xcb, 0x40, 0x4d, 0x3e, 0xdb, 0xff, 0x00, 0x9c, 0x7f, 0xfc, 0xce, 0x4f, 0xcc, 0xbf, 0x26, 0x25, 0xe5, 0xd3, 0x2f, 0xe9, 0xdd, 0x3d, 0xfe, 0xab, 0xa9, 0xaa, 0xd2, 0xa6, 0x40, 0x2a, 0xb2, 0x71, 0x00, 0x01, 0xea, 0x0d, 0xe8, 0x3a, 0x64, 0x25, 0x16, 0x1c, 0x8b, 0xd9, 0x51, 0x39, 0x28, 0x12, 0x51, 0x41, 0xfd, 0xa3, 0xd2, 0xb9, 0x4f, 0x0d, 0x33, 0xb5, 0xf4, 0x87, 0x9d, 0x79, 0x0e, 0xb4, 0xaf, 0x6a, 0xf8, 0xf1, 0xf0, 0xc9, 0xda, 0xbf, 0xff, 0xd6, 0xf2, 0xc6, 0xb5, 0x68, 0x64, 0xd0, 0x6d, 0x35, 0x20, 0x39, 0xcd, 0x13, 0x0f, 0x5e, 0x61, 0xfc, 0x8f, 0x40, 0x8b, 0x5e, 0xe0, 0x66, 0x1c, 0x4f, 0xaa, 0x9d, 0xe6, 0xa6, 0x1e, 0x91, 0x2e, 0xa9, 0x87, 0x95, 0xee, 0x9c, 0xc5, 0x55, 0x34, 0x60, 0x40, 0xae, 0x57, 0x30, 0xd9, 0xa7, 0x95, 0xbd, 0x6f, 0xcb, 0x26, 0x39, 0x40, 0x0d, 0x4e, 0xc0, 0x9f, 0x9e, 0x50, 0x5d, 0xac, 0x79, 0x33, 0x8b, 0xbb, 0x9b, 0x3b, 0x6b, 0x35, 0x48, 0x54, 0x09, 0x29, 0x56, 0x7f, 0xe1, 0x86, 0x72, 0x00, 0x2c, 0x6e, 0xf7, 0x63, 0x3e, 0x63, 0xbd, 0xbd, 0x5d, 0x20, 0x2a, 0xb3, 0xa4, 0x33, 0x48, 0xab, 0x21, 0x43, 0xf1, 0x2c, 0x47, 0xed, 0x1d, 0xbc, 0x73, 0x18, 0x9b, 0x64, 0x28, 0x96, 0x3a, 0xc7, 0x49, 0xb0, 0xf4, 0xcc, 0xe9, 0x73, 0x6c, 0xb4, 0xf8, 0x67, 0x92, 0x32, 0x21, 0x70, 0x7b, 0x89, 0x05, 0x57, 0xef, 0x38, 0x28, 0x94, 0x4a, 0x7d, 0x13, 0x7d, 0x6a, 0xd3, 0x4c, 0xb8, 0xf2, 0xc3, 0xc8, 0x2e, 0x03, 0xf3, 0xe2, 0x7d, 0x33, 0xb7, 0xc5, 0xcc, 0x71, 0x03, 0xc6, 0xb9, 0x64, 0x06, 0xe2, 0x9a, 0xf2, 0x4f, 0xd2, 0x6d, 0xe9, 0xfe, 0x41, 0x45, 0x5b, 0x18, 0x66, 0xa5, 0x64, 0x09, 0xf4, 0xd5, 0xb7, 0xcd, 0x93, 0xc7, 0xcf, 0x9b, 0xe5, 0x6f, 0xf9, 0xc8, 0x0d, 0x56, 0xeb, 0x59, 0xfc, 0xce, 0xd5, 0x12, 0x61, 0xc4, 0x69, 0xe9, 0x0d, 0xa4, 0x4b, 0xfe, 0x48, 0x40, 0xd5, 0x3e, 0xe4, 0xb6, 0x64, 0x8e, 0x4c, 0x02, 0x61, 0x65, 0xa0, 0x14, 0xb4, 0xb6, 0xb0, 0xb1, 0xb6, 0xb2, 0x97, 0xcb, 0xf1, 0x5a, 0x2d, 0xc6, 0xa5, 0xac, 0xb4, 0x70, 0x5d, 0xc7, 0x3d, 0xc1, 0x51, 0x24, 0x91, 0xc9, 0x31, 0x75, 0x6b, 0x70, 0x9f, 0x14, 0x68, 0x01, 0x46, 0xe4, 0xb5, 0xa3, 0x17, 0xcb, 0x40, 0x61, 0x6f, 0x47, 0xff, 0x00, 0x9c, 0x3a, 0x8f, 0x5b, 0x4f, 0x3c, 0x6b, 0xb7, 0xfa, 0x30, 0x91, 0x3c, 0xa4, 0xb1, 0x95, 0xb9, 0x82, 0x42, 0x0a, 0xbc, 0x8e, 0xe4, 0xdb, 0xa9, 0xef, 0xc9, 0x17, 0x91, 0x24, 0x7c, 0xb2, 0x05, 0x64, 0xfb, 0x75, 0x64, 0x32, 0x39, 0x69, 0x5b, 0x9c, 0xad, 0xb9, 0xdb, 0xa7, 0xb5, 0x3b, 0x53, 0x2a, 0x21, 0x41, 0x44, 0xf3, 0x8b, 0x8f, 0x2e, 0x43, 0x9d, 0x2b, 0xd4, 0x57, 0x23, 0x41, 0x36, 0xff, 0x00, 0xff, 0xd7, 0xf0, 0xc0, 0xd5, 0xb5, 0x11, 0x64, 0xb6, 0x3f, 0x59, 0x90, 0xd9, 0xab, 0x06, 0xf4, 0x79, 0x7c, 0x3b, 0x74, 0xc8, 0x08, 0x8b, 0xb6, 0xe3, 0x96, 0x55, 0x57, 0xb3, 0x3e, 0xf2, 0x35, 0xc7, 0xd6, 0x0b, 0x45, 0x5d, 0xdc, 0x8a, 0x7d, 0xd9, 0x8d, 0x94, 0x3b, 0x3d, 0x1c, 0x9e, 0xc3, 0xe5, 0xc3, 0x2c, 0x7c, 0xc5, 0x0f, 0xee, 0xdb, 0x8b, 0x0c, 0xc4, 0x26, 0x9d, 0xa0, 0x9a, 0x7d, 0x2c, 0xe5, 0xe4, 0x55, 0x7f, 0xee, 0xc1, 0x15, 0x04, 0xd0, 0x12, 0x3c, 0x72, 0x89, 0x1b, 0x2c, 0xcc, 0xa8, 0x2a, 0x8b, 0x87, 0xbb, 0x63, 0x1a, 0x28, 0x65, 0xf0, 0xed, 0xf2, 0xc3, 0xc2, 0x0a, 0x06, 0x4a, 0x46, 0xc7, 0xa5, 0xa3, 0x59, 0xc8, 0xb2, 0xc7, 0x45, 0x22, 0x9c, 0x14, 0x54, 0x10, 0x46, 0xf5, 0x1d, 0x32, 0x5c, 0x14, 0x14, 0xe4, 0x32, 0x2f, 0x3a, 0xf3, 0xb6, 0x90, 0x9a, 0x6d, 0xae, 0x9f, 0x3d, 0xab, 0xb8, 0x8a, 0x3b, 0xf8, 0x39, 0x44, 0x58, 0xf0, 0x08, 0xd5, 0x14, 0xa5, 0x7b, 0x65, 0x98, 0x8e, 0xfb, 0xb5, 0x67, 0x87, 0xa5, 0xef, 0x5e, 0x44, 0x96, 0x35, 0xb5, 0xb6, 0x59, 0x36, 0xfd, 0xd8, 0xa0, 0xf1, 0x20, 0x53, 0x33, 0xc0, 0x79, 0x59, 0x73, 0x7c, 0xd7, 0xf9, 0xfb, 0xa2, 0xcd, 0x67, 0xf9, 0xa7, 0x7b, 0x72, 0xf1, 0x71, 0x83, 0x53, 0x86, 0x0b, 0x98, 0x24, 0x22, 0x8a, 0xcc, 0x88, 0x23, 0x7f, 0xb8, 0xae, 0xf9, 0x7c, 0x50, 0x1e, 0x5f, 0x7c, 0x48, 0x21, 0x44, 0x6b, 0xce, 0x9b, 0xb0, 0x1b, 0x9e, 0xf5, 0xaf, 0x8e, 0x4d, 0x5f, 0x7a, 0x7f, 0xce, 0x34, 0xf9, 0x5d, 0x3c, 0xa3, 0xf9, 0x69, 0x63, 0xa9, 0x3c, 0x27, 0xeb, 0xda, 0xe1, 0x37, 0xd7, 0x2e, 0xaa, 0xdb, 0x06, 0xda, 0x30, 0x49, 0xfe, 0x54, 0x03, 0x03, 0x49, 0xdc, 0xb3, 0xaf, 0x38, 0xfe, 0x6a, 0xf9, 0x47, 0xc9, 0x3a, 0x74, 0x97, 0xfa, 0xf6, 0xaf, 0x15, 0x85, 0xb8, 0x75, 0x89, 0xb8, 0x87, 0x9a, 0x72, 0xee, 0x2a, 0x14, 0x24, 0x60, 0xb1, 0xa8, 0xdf, 0x07, 0x0b, 0x2d, 0xcb, 0xcf, 0x7f, 0xe8, 0x6a, 0xff, 0x00, 0x26, 0xbd, 0x6a, 0x7f, 0x89, 0x2f, 0xf8, 0x52, 0x9e, 0xb7, 0xe8, 0xb9, 0xb8, 0x57, 0xc2, 0x95, 0xe9, 0x8f, 0x08, 0x5a, 0x2f, 0xff, 0xd0, 0xf0, 0x4d, 0x40, 0xaa, 0xd7, 0x00, 0x64, 0xcb, 0x3c, 0x97, 0xa8, 0xb5, 0x9e, 0xa3, 0x1a, 0xd6, 0x84, 0x95, 0x3f, 0x45, 0x72, 0x9c, 0xa2, 0xc3, 0x99, 0xa5, 0x9d, 0x49, 0xf4, 0x17, 0x97, 0xaf, 0x63, 0x17, 0x52, 0x6f, 0xf0, 0xc8, 0x43, 0x6f, 0x9a, 0xe9, 0x07, 0x70, 0x0e, 0xec, 0x83, 0x51, 0x44, 0xb8, 0x61, 0x1a, 0x9e, 0x11, 0xd3, 0x91, 0x60, 0x68, 0x6b, 0xd3, 0x31, 0x4f, 0x36, 0xd3, 0x4c, 0x52, 0xef, 0x4c, 0xd5, 0x0c, 0xc4, 0x69, 0xda, 0x94, 0xc8, 0x3a, 0xf0, 0x66, 0x07, 0x73, 0xe0, 0x40, 0xfd, 0x79, 0x93, 0x12, 0x1c, 0x9c, 0x32, 0xc7, 0xfc, 0x41, 0x33, 0xd2, 0xb4, 0x6f, 0x38, 0x98, 0x65, 0x76, 0xbf, 0x69, 0x42, 0xd0, 0xaa, 0xc9, 0xde, 0x95, 0xad, 0x28, 0x46, 0x4e, 0xac, 0x39, 0x77, 0x80, 0x11, 0xbf, 0xd8, 0xc7, 0x7c, 0xe1, 0xa5, 0xf9, 0x92, 0x4d, 0x32, 0x5b, 0x8b, 0x93, 0x27, 0xa7, 0x68, 0x56, 0xe2, 0x45, 0xda, 0x85, 0x61, 0x6e, 0x67, 0xad, 0x6b, 0xb0, 0x38, 0xc2, 0x81, 0xe4, 0xc7, 0x52, 0x31, 0x1c, 0x67, 0x86, 0x5b, 0xbd, 0x37, 0xca, 0x7a, 0x94, 0xb1, 0x69, 0xb6, 0x2e, 0xb7, 0x15, 0x48, 0xc2, 0xb4, 0x52, 0x53, 0xac, 0x32, 0xaf, 0xb1, 0xed, 0x9b, 0x10, 0x36, 0x78, 0x5c, 0x9f, 0x51, 0x64, 0x1f, 0x98, 0x3e, 0x58, 0xb6, 0xfc, 0xc8, 0xf2, 0xe5, 0xbc, 0x68, 0x52, 0x2d, 0x5a, 0xd1, 0x84, 0xb6, 0xf3, 0x95, 0x0e, 0xc0, 0x85, 0xe2, 0xcb, 0xd8, 0xd1, 0xbb, 0xe4, 0xc1, 0xa6, 0x97, 0xce, 0x17, 0x5f, 0x95, 0xde, 0x6d, 0xb6, 0xbe, 0xb7, 0x69, 0x34, 0xf3, 0x3c, 0x72, 0xcf, 0xe8, 0xa3, 0x45, 0x49, 0x95, 0x4a, 0x90, 0x3e, 0x35, 0x5a, 0x95, 0x1d, 0xfe, 0x21, 0x93, 0x4d, 0xbe, 0xd2, 0xd2, 0xf5, 0x8b, 0xbd, 0x32, 0x2d, 0x3f, 0x4c, 0x9a, 0xe4, 0xca, 0x9e, 0x90, 0x85, 0x65, 0x55, 0x08, 0x85, 0x91, 0x01, 0x3b, 0x0a, 0x05, 0xe9, 0xb0, 0xc0, 0x5a, 0xc3, 0xcd, 0x3f, 0x3b, 0x7f, 0x26, 0xec, 0xff, 0x00, 0x35, 0x6d, 0x6d, 0xb5, 0x3d, 0x16, 0xfe, 0x0d, 0x3b, 0xcd, 0x96, 0x01, 0x92, 0x46, 0x9e, 0xa2, 0x0b, 0xc8, 0xb7, 0x28, 0x92, 0x71, 0xfb, 0x2e, 0xa7, 0xec, 0x3d, 0x0f, 0xc2, 0x68, 0x71, 0x05, 0x95, 0xd3, 0xe7, 0x9f, 0xfa, 0x16, 0x2f, 0xcd, 0x7f, 0x43, 0xd6, 0xfa, 0xa5, 0x97, 0xab, 0xeb, 0x7a, 0x5f, 0x55, 0xfa, 0xec, 0x5e, 0xaf, 0x0f, 0xf7, 0xed, 0x2b, 0x4e, 0x15, 0xff, 0x00, 0x65, 0xdf, 0x8e, 0x14, 0xf1, 0xbf, 0xff, 0xd1, 0xf0, 0x5a, 0xa7, 0x18, 0x5e, 0x56, 0x1f, 0x68, 0x71, 0x5f, 0xa7, 0xbe, 0x2a, 0x98, 0xdb, 0xfa, 0x90, 0x24, 0x37, 0xb0, 0xfd, 0xb8, 0xa8, 0x58, 0x78, 0xae, 0x43, 0xc9, 0xb4, 0x6d, 0xbb, 0xda, 0x3c, 0xa1, 0xad, 0x43, 0xa8, 0xda, 0xc5, 0x2a, 0x3d, 0x26, 0x5a, 0x02, 0x2b, 0xbe, 0x60, 0x64, 0x8d, 0x17, 0x6f, 0x8b, 0x20, 0x90, 0x7a, 0x3c, 0x32, 0x8b, 0xa8, 0x02, 0xf3, 0xfd, 0xe0, 0x1b, 0x11, 0x98, 0x66, 0x3b, 0xb9, 0x62, 0x54, 0x83, 0x36, 0xf2, 0xa4, 0xe4, 0x29, 0x34, 0xeb, 0xc8, 0x74, 0xae, 0x0d, 0xc3, 0x65, 0x82, 0x13, 0x6b, 0x57, 0xba, 0x54, 0xe4, 0x8c, 0x41, 0x1b, 0x75, 0xa7, 0xe0, 0x72, 0x5c, 0x4c, 0x84, 0x50, 0x5a, 0xb3, 0xdd, 0xdd, 0xc3, 0x24, 0x33, 0xb1, 0x60, 0xe0, 0x86, 0x52, 0x45, 0x38, 0xd2, 0x87, 0x24, 0x26, 0x6d, 0x8c, 0xe1, 0x41, 0x25, 0xfc, 0xa3, 0xd7, 0x2f, 0x6f, 0x3c, 0xbf, 0x73, 0xa5, 0xb2, 0x2c, 0xd1, 0x69, 0x17, 0x2f, 0x6b, 0x14, 0x8c, 0x0f, 0x21, 0x0d, 0x79, 0x46, 0x09, 0x15, 0xed, 0xb7, 0x4e, 0xd9, 0xb9, 0x8b, 0xcb, 0xe4, 0xa2, 0x5e, 0xa3, 0xa6, 0xdf, 0x6a, 0x36, 0xe4, 0xcd, 0x69, 0x1c, 0x4e, 0x84, 0x7c, 0x76, 0xab, 0x21, 0x67, 0xa8, 0xa7, 0xd9, 0xf8, 0x4d, 0x2b, 0xf3, 0xc3, 0x4d, 0x49, 0x57, 0x98, 0x75, 0x6f, 0x31, 0xda, 0xf9, 0xa3, 0x4b, 0xfd, 0x1f, 0x69, 0x1d, 0xae, 0xa1, 0xa9, 0x7e, 0xee, 0xe6, 0xd2, 0x79, 0x18, 0xf3, 0xb5, 0x1f, 0xee, 0xd9, 0x0a, 0x01, 0x4e, 0x3f, 0xb3, 0x4d, 0xf2, 0x9c, 0xb9, 0x04, 0x05, 0xb7, 0xe2, 0x87, 0x1e, 0xdd, 0x19, 0x3e, 0xaf, 0x6b, 0xae, 0xcb, 0x6d, 0x13, 0x0d, 0x45, 0xa2, 0x8e, 0x06, 0xe5, 0x13, 0x2a, 0x02, 0x01, 0x5e, 0x82, 0xb5, 0x04, 0xe6, 0x11, 0xd4, 0xcd, 0xda, 0x43, 0x49, 0x8e, 0xb7, 0xdc, 0xb1, 0x51, 0xe6, 0x4d, 0x76, 0xd2, 0x61, 0x15, 0xaa, 0x4b, 0xa8, 0xc9, 0x6e, 0x49, 0x79, 0x20, 0xe6, 0x8c, 0x49, 0xad, 0x43, 0x16, 0xe4, 0xa7, 0xaf, 0x43, 0xd3, 0x26, 0x35, 0x75, 0xcd, 0xa8, 0xe8, 0x87, 0x46, 0xbf, 0xc7, 0x9a, 0xff, 0x00, 0xd6, 0xbf, 0x48, 0xfe, 0x88, 0xfd, 0xe7, 0x0f, 0xab, 0xfa, 0x3f, 0x58, 0x7f, 0x5f, 0x8d, 0x3f, 0x9f, 0xa7, 0x5e, 0xd4, 0xc3, 0xf9, 0xd1, 0x7c, 0xb6, 0x47, 0xe4, 0x3a, 0x5b, 0xff, 0xd2, 0xf0, 0xb7, 0xa6, 0x1e, 0xdf, 0xd3, 0xf6, 0xa5, 0x71, 0x54, 0xdb, 0x4b, 0x80, 0x3c, 0x42, 0x26, 0xee, 0x29, 0xbe, 0x51, 0x23, 0x4e, 0x44, 0x05, 0x84, 0x45, 0xa5, 0xd5, 0xf7, 0x97, 0x2e, 0xfd, 0x6b, 0x6a, 0x98, 0x09, 0xab, 0xc7, 0xfc, 0x46, 0x3b, 0x4c, 0x26, 0x32, 0x30, 0x3e, 0x4f, 0x49, 0xd0, 0xfc, 0xfb, 0x05, 0xd4, 0x4a, 0x7d, 0x40, 0xac, 0x3a, 0x8e, 0x84, 0x1c, 0xc5, 0x96, 0x2a, 0x73, 0xe1, 0x9c, 0x16, 0x6d, 0xa5, 0x79, 0x86, 0xd6, 0xec, 0x80, 0x5a, 0xa0, 0xf5, 0xca, 0xcc, 0x5c, 0xa1, 0x2b, 0x1b, 0x26, 0x30, 0x6a, 0x31, 0x46, 0xcf, 0x1c, 0x87, 0x94, 0x64, 0x9e, 0x3d, 0xb6, 0xf0, 0xca, 0xa8, 0x39, 0x51, 0x99, 0x42, 0x6b, 0x1a, 0xc5, 0xa5, 0xa5, 0x94, 0xf7, 0x92, 0xc8, 0xaa, 0xb1, 0x23, 0x30, 0x04, 0xf8, 0x0e, 0x9f, 0x4e, 0x4a, 0x11, 0xb2, 0xd5, 0x9b, 0x25, 0x06, 0x1b, 0xff, 0x00, 0x38, 0xfd, 0xad, 0xdf, 0xda, 0xf9, 0xa2, 0xfe, 0xc5, 0x42, 0xbe, 0x9b, 0x7f, 0x0b, 0xdd, 0xdd, 0x07, 0xaf, 0x14, 0x68, 0xd8, 0x71, 0x6d, 0xbb, 0x90, 0xfc, 0x73, 0x6e, 0xf2, 0xf2, 0xdd, 0xf4, 0xad, 0xa6, 0xab, 0x6d, 0x69, 0x14, 0xfa, 0xee, 0xa0, 0xe2, 0x0b, 0x0d, 0x39, 0x19, 0xfe, 0x11, 0xc5, 0x1a, 0x4a, 0x1d, 0x8f, 0x73, 0x4f, 0xf8, 0x96, 0x0b, 0x40, 0x8d, 0xec, 0xf3, 0x6d, 0x3f, 0x52, 0xba, 0xd6, 0x35, 0x8b, 0xbf, 0x36, 0x6a, 0x5f, 0x0d, 0xc5, 0xdc, 0xa8, 0xb6, 0xa8, 0x7a, 0xc5, 0x6c, 0x9b, 0x22, 0x0f, 0xa3, 0x73, 0x9a, 0xbc, 0xb3, 0xe2, 0x36, 0xed, 0xb1, 0x43, 0x80, 0x53, 0xd0, 0xa7, 0xd4, 0x44, 0xfa, 0x7a, 0xda, 0x83, 0xbd, 0x3e, 0x2f, 0xa7, 0x2b, 0xad, 0x9b, 0xb8, 0x8d, 0xa8, 0xe8, 0x91, 0xdb, 0xfa, 0x2d, 0x6f, 0xc3, 0x8a, 0x2d, 0x56, 0xa3, 0xad, 0x4f, 0x5c, 0xa4, 0x0d, 0xdc, 0xa3, 0xca, 0xd0, 0xbf, 0xa1, 0xe3, 0xfa, 0xe7, 0x0f, 0xf2, 0xb9, 0x57, 0xbf, 0x1a, 0xe4, 0xb8, 0x57, 0xc5, 0xdd, 0xff, 0xd3, 0xf0, 0xcc, 0x5d, 0x7b, 0x70, 0xc5, 0x53, 0x6d, 0x2f, 0xd5, 0xe4, 0x69, 0xfd, 0xdf, 0xec, 0xd7, 0xad, 0x7d, 0xb2, 0x8c, 0x8d, 0xd8, 0xed, 0x91, 0x9f, 0x43, 0xea, 0xe7, 0xeb, 0x94, 0xad, 0x3e, 0x1e, 0x95, 0xfc, 0x72, 0x81, 0x7d, 0x1c, 0x9d, 0xba, 0xb1, 0x7b, 0xdf, 0xa9, 0x7a, 0xdf, 0xee, 0x2f, 0xd4, 0xfa, 0xe7, 0xed, 0x7a, 0x7f, 0xdd, 0xff, 0x00, 0xb2, 0xae, 0x64, 0x0b, 0xea, 0xe3, 0x9a, 0xbf, 0x4a, 0x6f, 0xa4, 0xff, 0x00, 0x89, 0xbd, 0x45, 0xfa, 0xb5, 0x79, 0xf7, 0xeb, 0xc7, 0xe9, 0xae, 0x57, 0x2e, 0x17, 0x23, 0x1f, 0x89, 0xd1, 0x99, 0x8f, 0xf1, 0xa7, 0x11, 0xcf, 0xd3, 0xf5, 0x29, 0xb5, 0x6b, 0xd3, 0xe8, 0xcc, 0x7f, 0x45, 0xb9, 0xa3, 0xc5, 0x62, 0xbe, 0x68, 0xff, 0x00, 0x15, 0xfd, 0x4c, 0xfe, 0x90, 0xaf, 0xd4, 0xab, 0xf1, 0x7a, 0x7f, 0x62, 0x9d, 0xab, 0xdf, 0x32, 0xb1, 0x70, 0x5e, 0xdc, 0xdc, 0x2d, 0x47, 0x8b, 0x5e, 0xae, 0x4c, 0xbf, 0xf2, 0x37, 0x9f, 0x3d, 0x5b, 0xd2, 0xff, 0x00, 0x8e, 0x87, 0xee, 0x29, 0x5a, 0xf2, 0xf4, 0xaa, 0xd4, 0xa5, 0x36, 0xa7, 0x3a, 0x57, 0xfd, 0x8e, 0x64, 0x3a, 0xf2, 0xf6, 0xbf, 0xcc, 0x7f, 0x5b, 0xfc, 0x23, 0xa7, 0xfe, 0x8e, 0xff, 0x00, 0x8e, 0x37, 0xd6, 0x63, 0xfa, 0xe5, 0x2b, 0xcb, 0x87, 0xec, 0xd6, 0xbd, 0xb9, 0x7d, 0xac, 0xc7, 0xcd, 0x7c, 0x2d, 0xf8, 0x2b, 0x89, 0x26, 0x8f, 0xd4, 0xfa, 0x94, 0x3e, 0x85, 0x29, 0xc9, 0x69, 0xfc, 0x33, 0x58, 0x5d, 0x9c, 0x79, 0xb2, 0xbb, 0x0f, 0xac, 0x7a, 0x2b, 0xea, 0x75, 0xef, 0x92, 0x0c, 0x53, 0x3d, 0x2f, 0xd4, 0xfa, 0xbb, 0xfa, 0x74, 0xf5, 0x39, 0x9a, 0xd7, 0xe7, 0x80, 0x53, 0x79, 0xba, 0x5b, 0xfe, 0x97, 0xfa, 0x4b, 0xfc, 0xba, 0x7f, 0xb1, 0xc7, 0xab, 0x1e, 0x8f, 0xff, 0xd9 +}; diff --git a/webrtc/base/testclient.cc b/webrtc/base/testclient.cc new file mode 100644 index 000000000..32670e21a --- /dev/null +++ b/webrtc/base/testclient.cc @@ -0,0 +1,148 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/testclient.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +// DESIGN: Each packet received is put it into a list of packets. +// Callers can retrieve received packets from any thread by calling +// NextPacket. + +TestClient::TestClient(AsyncPacketSocket* socket) + : socket_(socket), ready_to_send_(false) { + packets_ = new std::vector(); + socket_->SignalReadPacket.connect(this, &TestClient::OnPacket); + socket_->SignalReadyToSend.connect(this, &TestClient::OnReadyToSend); +} + +TestClient::~TestClient() { + delete socket_; + for (unsigned i = 0; i < packets_->size(); i++) + delete (*packets_)[i]; + delete packets_; +} + +bool TestClient::CheckConnState(AsyncPacketSocket::State state) { + // Wait for our timeout value until the socket reaches the desired state. + uint32 end = TimeAfter(kTimeout); + while (socket_->GetState() != state && TimeUntil(end) > 0) + Thread::Current()->ProcessMessages(1); + return (socket_->GetState() == state); +} + +int TestClient::Send(const char* buf, size_t size) { + rtc::PacketOptions options; + return socket_->Send(buf, size, options); +} + +int TestClient::SendTo(const char* buf, size_t size, + const SocketAddress& dest) { + rtc::PacketOptions options; + return socket_->SendTo(buf, size, dest, options); +} + +TestClient::Packet* TestClient::NextPacket() { + // If no packets are currently available, we go into a get/dispatch loop for + // at most 1 second. If, during the loop, a packet arrives, then we can stop + // early and return it. + + // Note that the case where no packet arrives is important. We often want to + // test that a packet does not arrive. + + // Note also that we only try to pump our current thread's message queue. + // Pumping another thread's queue could lead to messages being dispatched from + // the wrong thread to non-thread-safe objects. + + uint32 end = TimeAfter(kTimeout); + while (TimeUntil(end) > 0) { + { + CritScope cs(&crit_); + if (packets_->size() != 0) { + break; + } + } + Thread::Current()->ProcessMessages(1); + } + + // Return the first packet placed in the queue. + Packet* packet = NULL; + CritScope cs(&crit_); + if (packets_->size() > 0) { + packet = packets_->front(); + packets_->erase(packets_->begin()); + } + + return packet; +} + +bool TestClient::CheckNextPacket(const char* buf, size_t size, + SocketAddress* addr) { + bool res = false; + Packet* packet = NextPacket(); + if (packet) { + res = (packet->size == size && memcmp(packet->buf, buf, size) == 0); + if (addr) + *addr = packet->addr; + delete packet; + } + return res; +} + +bool TestClient::CheckNoPacket() { + bool res; + Packet* packet = NextPacket(); + res = (packet == NULL); + delete packet; + return res; +} + +int TestClient::GetError() { + return socket_->GetError(); +} + +int TestClient::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +bool TestClient::ready_to_send() const { + return ready_to_send_; +} + +void TestClient::OnPacket(AsyncPacketSocket* socket, const char* buf, + size_t size, const SocketAddress& remote_addr, + const PacketTime& packet_time) { + CritScope cs(&crit_); + packets_->push_back(new Packet(remote_addr, buf, size)); +} + +void TestClient::OnReadyToSend(AsyncPacketSocket* socket) { + ready_to_send_ = true; +} + +TestClient::Packet::Packet(const SocketAddress& a, const char* b, size_t s) + : addr(a), buf(0), size(s) { + buf = new char[size]; + memcpy(buf, b, size); +} + +TestClient::Packet::Packet(const Packet& p) + : addr(p.addr), buf(0), size(p.size) { + buf = new char[size]; + memcpy(buf, p.buf, size); +} + +TestClient::Packet::~Packet() { + delete[] buf; +} + +} // namespace rtc diff --git a/webrtc/base/testclient.h b/webrtc/base/testclient.h new file mode 100644 index 000000000..d56f948b0 --- /dev/null +++ b/webrtc/base/testclient.h @@ -0,0 +1,93 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TESTCLIENT_H_ +#define WEBRTC_BASE_TESTCLIENT_H_ + +#include +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +// A simple client that can send TCP or UDP data and check that it receives +// what it expects to receive. Useful for testing server functionality. +class TestClient : public sigslot::has_slots<> { + public: + // Records the contents of a packet that was received. + struct Packet { + Packet(const SocketAddress& a, const char* b, size_t s); + Packet(const Packet& p); + virtual ~Packet(); + + SocketAddress addr; + char* buf; + size_t size; + }; + + // Creates a client that will send and receive with the given socket and + // will post itself messages with the given thread. + explicit TestClient(AsyncPacketSocket* socket); + ~TestClient(); + + SocketAddress address() const { return socket_->GetLocalAddress(); } + SocketAddress remote_address() const { return socket_->GetRemoteAddress(); } + + // Checks that the socket moves to the specified connect state. + bool CheckConnState(AsyncPacketSocket::State state); + + // Checks that the socket is connected to the remote side. + bool CheckConnected() { + return CheckConnState(AsyncPacketSocket::STATE_CONNECTED); + } + + // Sends using the clients socket. + int Send(const char* buf, size_t size); + + // Sends using the clients socket to the given destination. + int SendTo(const char* buf, size_t size, const SocketAddress& dest); + + // Returns the next packet received by the client or 0 if none is received + // within a reasonable amount of time. The caller must delete the packet + // when done with it. + Packet* NextPacket(); + + // Checks that the next packet has the given contents. Returns the remote + // address that the packet was sent from. + bool CheckNextPacket(const char* buf, size_t len, SocketAddress* addr); + + // Checks that no packets have arrived or will arrive in the next second. + bool CheckNoPacket(); + + int GetError(); + int SetOption(Socket::Option opt, int value); + + bool ready_to_send() const; + + private: + static const int kTimeout = 1000; + // Workaround for the fact that AsyncPacketSocket::GetConnState doesn't exist. + Socket::ConnState GetState(); + // Slot for packets read on the socket. + void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t len, + const SocketAddress& remote_addr, + const PacketTime& packet_time); + void OnReadyToSend(AsyncPacketSocket* socket); + + CriticalSection crit_; + AsyncPacketSocket* socket_; + std::vector* packets_; + bool ready_to_send_; + DISALLOW_EVIL_CONSTRUCTORS(TestClient); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TESTCLIENT_H_ diff --git a/webrtc/base/testclient_unittest.cc b/webrtc/base/testclient_unittest.cc new file mode 100644 index 000000000..c28266867 --- /dev/null +++ b/webrtc/base/testclient_unittest.cc @@ -0,0 +1,77 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testechoserver.h" +#include "webrtc/base/thread.h" + +using namespace rtc; + +void TestUdpInternal(const SocketAddress& loopback) { + Thread *main = Thread::Current(); + AsyncSocket* socket = main->socketserver() + ->CreateAsyncSocket(loopback.family(), SOCK_DGRAM); + socket->Bind(loopback); + + TestClient client(new AsyncUDPSocket(socket)); + SocketAddress addr = client.address(), from; + EXPECT_EQ(3, client.SendTo("foo", 3, addr)); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from)); + EXPECT_EQ(from, addr); + EXPECT_TRUE(client.CheckNoPacket()); +} + +void TestTcpInternal(const SocketAddress& loopback) { + Thread *main = Thread::Current(); + TestEchoServer server(main, loopback); + + AsyncSocket* socket = main->socketserver() + ->CreateAsyncSocket(loopback.family(), SOCK_STREAM); + AsyncTCPSocket* tcp_socket = AsyncTCPSocket::Create( + socket, loopback, server.address()); + ASSERT_TRUE(tcp_socket != NULL); + + TestClient client(tcp_socket); + SocketAddress addr = client.address(), from; + EXPECT_TRUE(client.CheckConnected()); + EXPECT_EQ(3, client.Send("foo", 3)); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from)); + EXPECT_EQ(from, server.address()); + EXPECT_TRUE(client.CheckNoPacket()); +} + +// Tests whether the TestClient can send UDP to itself. +TEST(TestClientTest, TestUdpIPv4) { + TestUdpInternal(SocketAddress("127.0.0.1", 0)); +} + +TEST(TestClientTest, TestUdpIPv6) { + if (HasIPv6Enabled()) { + TestUdpInternal(SocketAddress("::1", 0)); + } else { + LOG(LS_INFO) << "Skipping IPv6 test."; + } +} + +// Tests whether the TestClient can connect to a server and exchange data. +TEST(TestClientTest, TestTcpIPv4) { + TestTcpInternal(SocketAddress("127.0.0.1", 0)); +} + +TEST(TestClientTest, TestTcpIPv6) { + if (HasIPv6Enabled()) { + TestTcpInternal(SocketAddress("::1", 0)); + } else { + LOG(LS_INFO) << "Skipping IPv6 test."; + } +} diff --git a/webrtc/base/testechoserver.h b/webrtc/base/testechoserver.h new file mode 100644 index 000000000..733b320dd --- /dev/null +++ b/webrtc/base/testechoserver.h @@ -0,0 +1,73 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TESTECHOSERVER_H_ +#define WEBRTC_BASE_TESTECHOSERVER_H_ + +#include +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// A test echo server, echoes back any packets sent to it. +// Useful for unit tests. +class TestEchoServer : public sigslot::has_slots<> { + public: + TestEchoServer(Thread* thread, const SocketAddress& addr) + : server_socket_(thread->socketserver()->CreateAsyncSocket(addr.family(), + SOCK_STREAM)) { + server_socket_->Bind(addr); + server_socket_->Listen(5); + server_socket_->SignalReadEvent.connect(this, &TestEchoServer::OnAccept); + } + ~TestEchoServer() { + for (ClientList::iterator it = client_sockets_.begin(); + it != client_sockets_.end(); ++it) { + delete *it; + } + } + + SocketAddress address() const { return server_socket_->GetLocalAddress(); } + + private: + void OnAccept(AsyncSocket* socket) { + AsyncSocket* raw_socket = socket->Accept(NULL); + if (raw_socket) { + AsyncTCPSocket* packet_socket = new AsyncTCPSocket(raw_socket, false); + packet_socket->SignalReadPacket.connect(this, &TestEchoServer::OnPacket); + packet_socket->SignalClose.connect(this, &TestEchoServer::OnClose); + client_sockets_.push_back(packet_socket); + } + } + void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& remote_addr, + const PacketTime& packet_time) { + rtc::PacketOptions options; + socket->Send(buf, size, options); + } + void OnClose(AsyncPacketSocket* socket, int err) { + ClientList::iterator it = + std::find(client_sockets_.begin(), client_sockets_.end(), socket); + client_sockets_.erase(it); + Thread::Current()->Dispose(socket); + } + + typedef std::list ClientList; + scoped_ptr server_socket_; + ClientList client_sockets_; + DISALLOW_EVIL_CONSTRUCTORS(TestEchoServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TESTECHOSERVER_H_ diff --git a/webrtc/base/testutils.h b/webrtc/base/testutils.h new file mode 100644 index 000000000..a148d9161 --- /dev/null +++ b/webrtc/base/testutils.h @@ -0,0 +1,604 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TESTUTILS_H__ +#define WEBRTC_BASE_TESTUTILS_H__ + +// Utilities for testing rtc infrastructure in unittests + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include +#include + +// X defines a few macros that stomp on types that gunit.h uses. +#undef None +#undef Bool +#endif + +#include +#include +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace testing { + +using namespace rtc; + +/////////////////////////////////////////////////////////////////////////////// +// StreamSink - Monitor asynchronously signalled events from StreamInterface +// or AsyncSocket (which should probably be a StreamInterface. +/////////////////////////////////////////////////////////////////////////////// + +// Note: Any event that is an error is treaded as SSE_ERROR instead of that +// event. + +enum StreamSinkEvent { + SSE_OPEN = SE_OPEN, + SSE_READ = SE_READ, + SSE_WRITE = SE_WRITE, + SSE_CLOSE = SE_CLOSE, + SSE_ERROR = 16 +}; + +class StreamSink : public sigslot::has_slots<> { + public: + void Monitor(StreamInterface* stream) { + stream->SignalEvent.connect(this, &StreamSink::OnEvent); + events_.erase(stream); + } + void Unmonitor(StreamInterface* stream) { + stream->SignalEvent.disconnect(this); + // In case you forgot to unmonitor a previous object with this address + events_.erase(stream); + } + bool Check(StreamInterface* stream, StreamSinkEvent event, bool reset = true) { + return DoCheck(stream, event, reset); + } + int Events(StreamInterface* stream, bool reset = true) { + return DoEvents(stream, reset); + } + + void Monitor(AsyncSocket* socket) { + socket->SignalConnectEvent.connect(this, &StreamSink::OnConnectEvent); + socket->SignalReadEvent.connect(this, &StreamSink::OnReadEvent); + socket->SignalWriteEvent.connect(this, &StreamSink::OnWriteEvent); + socket->SignalCloseEvent.connect(this, &StreamSink::OnCloseEvent); + // In case you forgot to unmonitor a previous object with this address + events_.erase(socket); + } + void Unmonitor(AsyncSocket* socket) { + socket->SignalConnectEvent.disconnect(this); + socket->SignalReadEvent.disconnect(this); + socket->SignalWriteEvent.disconnect(this); + socket->SignalCloseEvent.disconnect(this); + events_.erase(socket); + } + bool Check(AsyncSocket* socket, StreamSinkEvent event, bool reset = true) { + return DoCheck(socket, event, reset); + } + int Events(AsyncSocket* socket, bool reset = true) { + return DoEvents(socket, reset); + } + + private: + typedef std::map EventMap; + + void OnEvent(StreamInterface* stream, int events, int error) { + if (error) { + events = SSE_ERROR; + } + AddEvents(stream, events); + } + void OnConnectEvent(AsyncSocket* socket) { + AddEvents(socket, SSE_OPEN); + } + void OnReadEvent(AsyncSocket* socket) { + AddEvents(socket, SSE_READ); + } + void OnWriteEvent(AsyncSocket* socket) { + AddEvents(socket, SSE_WRITE); + } + void OnCloseEvent(AsyncSocket* socket, int error) { + AddEvents(socket, (0 == error) ? SSE_CLOSE : SSE_ERROR); + } + + void AddEvents(void* obj, int events) { + EventMap::iterator it = events_.find(obj); + if (events_.end() == it) { + events_.insert(EventMap::value_type(obj, events)); + } else { + it->second |= events; + } + } + bool DoCheck(void* obj, StreamSinkEvent event, bool reset) { + EventMap::iterator it = events_.find(obj); + if ((events_.end() == it) || (0 == (it->second & event))) { + return false; + } + if (reset) { + it->second &= ~event; + } + return true; + } + int DoEvents(void* obj, bool reset) { + EventMap::iterator it = events_.find(obj); + if (events_.end() == it) + return 0; + int events = it->second; + if (reset) { + it->second = 0; + } + return events; + } + + EventMap events_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamSource - Implements stream interface and simulates asynchronous +// events on the stream, without a network. Also buffers written data. +/////////////////////////////////////////////////////////////////////////////// + +class StreamSource : public StreamInterface { +public: + StreamSource() { + Clear(); + } + + void Clear() { + readable_data_.clear(); + written_data_.clear(); + state_ = SS_CLOSED; + read_block_ = 0; + write_block_ = SIZE_UNKNOWN; + } + void QueueString(const char* data) { + QueueData(data, strlen(data)); + } + void QueueStringF(const char* format, ...) { + va_list args; + va_start(args, format); + char buffer[1024]; + size_t len = vsprintfn(buffer, sizeof(buffer), format, args); + ASSERT(len < sizeof(buffer) - 1); + va_end(args); + QueueData(buffer, len); + } + void QueueData(const char* data, size_t len) { + readable_data_.insert(readable_data_.end(), data, data + len); + if ((SS_OPEN == state_) && (readable_data_.size() == len)) { + SignalEvent(this, SE_READ, 0); + } + } + std::string ReadData() { + std::string data; + // avoid accessing written_data_[0] if it is undefined + if (written_data_.size() > 0) { + data.insert(0, &written_data_[0], written_data_.size()); + } + written_data_.clear(); + return data; + } + void SetState(StreamState state) { + int events = 0; + if ((SS_OPENING == state_) && (SS_OPEN == state)) { + events |= SE_OPEN; + if (!readable_data_.empty()) { + events |= SE_READ; + } + } else if ((SS_CLOSED != state_) && (SS_CLOSED == state)) { + events |= SE_CLOSE; + } + state_ = state; + if (events) { + SignalEvent(this, events, 0); + } + } + // Will cause Read to block when there are pos bytes in the read queue. + void SetReadBlock(size_t pos) { read_block_ = pos; } + // Will cause Write to block when there are pos bytes in the write queue. + void SetWriteBlock(size_t pos) { write_block_ = pos; } + + virtual StreamState GetState() const { return state_; } + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (SS_CLOSED == state_) { + if (error) *error = -1; + return SR_ERROR; + } + if ((SS_OPENING == state_) || (readable_data_.size() <= read_block_)) { + return SR_BLOCK; + } + size_t count = _min(buffer_len, readable_data_.size() - read_block_); + memcpy(buffer, &readable_data_[0], count); + size_t new_size = readable_data_.size() - count; + // Avoid undefined access beyond the last element of the vector. + // This only happens when new_size is 0. + if (count < readable_data_.size()) { + memmove(&readable_data_[0], &readable_data_[count], new_size); + } + readable_data_.resize(new_size); + if (read) *read = count; + return SR_SUCCESS; + } + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (SS_CLOSED == state_) { + if (error) *error = -1; + return SR_ERROR; + } + if (SS_OPENING == state_) { + return SR_BLOCK; + } + if (SIZE_UNKNOWN != write_block_) { + if (written_data_.size() >= write_block_) { + return SR_BLOCK; + } + if (data_len > (write_block_ - written_data_.size())) { + data_len = write_block_ - written_data_.size(); + } + } + if (written) *written = data_len; + const char* cdata = static_cast(data); + written_data_.insert(written_data_.end(), cdata, cdata + data_len); + return SR_SUCCESS; + } + virtual void Close() { state_ = SS_CLOSED; } + +private: + typedef std::vector Buffer; + Buffer readable_data_, written_data_; + StreamState state_; + size_t read_block_, write_block_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SocketTestClient +// Creates a simulated client for testing. Works on real and virtual networks. +/////////////////////////////////////////////////////////////////////////////// + +class SocketTestClient : public sigslot::has_slots<> { +public: + SocketTestClient() { + Init(NULL, AF_INET); + } + SocketTestClient(AsyncSocket* socket) { + Init(socket, socket->GetLocalAddress().family()); + } + SocketTestClient(const SocketAddress& address) { + Init(NULL, address.family()); + socket_->Connect(address); + } + + AsyncSocket* socket() { return socket_.get(); } + + void QueueString(const char* data) { + QueueData(data, strlen(data)); + } + void QueueStringF(const char* format, ...) { + va_list args; + va_start(args, format); + char buffer[1024]; + size_t len = vsprintfn(buffer, sizeof(buffer), format, args); + ASSERT(len < sizeof(buffer) - 1); + va_end(args); + QueueData(buffer, len); + } + void QueueData(const char* data, size_t len) { + send_buffer_.insert(send_buffer_.end(), data, data + len); + if (Socket::CS_CONNECTED == socket_->GetState()) { + Flush(); + } + } + std::string ReadData() { + std::string data(&recv_buffer_[0], recv_buffer_.size()); + recv_buffer_.clear(); + return data; + } + + bool IsConnected() const { + return (Socket::CS_CONNECTED == socket_->GetState()); + } + bool IsClosed() const { + return (Socket::CS_CLOSED == socket_->GetState()); + } + +private: + typedef std::vector Buffer; + + void Init(AsyncSocket* socket, int family) { + if (!socket) { + socket = Thread::Current()->socketserver() + ->CreateAsyncSocket(family, SOCK_STREAM); + } + socket_.reset(socket); + socket_->SignalConnectEvent.connect(this, + &SocketTestClient::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &SocketTestClient::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &SocketTestClient::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &SocketTestClient::OnCloseEvent); + } + + void Flush() { + size_t sent = 0; + while (sent < send_buffer_.size()) { + int result = socket_->Send(&send_buffer_[sent], + send_buffer_.size() - sent); + if (result > 0) { + sent += result; + } else { + break; + } + } + size_t new_size = send_buffer_.size() - sent; + memmove(&send_buffer_[0], &send_buffer_[sent], new_size); + send_buffer_.resize(new_size); + } + + void OnConnectEvent(AsyncSocket* socket) { + if (!send_buffer_.empty()) { + Flush(); + } + } + void OnReadEvent(AsyncSocket* socket) { + char data[64 * 1024]; + int result = socket_->Recv(data, ARRAY_SIZE(data)); + if (result > 0) { + recv_buffer_.insert(recv_buffer_.end(), data, data + result); + } + } + void OnWriteEvent(AsyncSocket* socket) { + if (!send_buffer_.empty()) { + Flush(); + } + } + void OnCloseEvent(AsyncSocket* socket, int error) { + } + + scoped_ptr socket_; + Buffer send_buffer_, recv_buffer_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SocketTestServer +// Creates a simulated server for testing. Works on real and virtual networks. +/////////////////////////////////////////////////////////////////////////////// + +class SocketTestServer : public sigslot::has_slots<> { + public: + SocketTestServer(const SocketAddress& address) + : socket_(Thread::Current()->socketserver() + ->CreateAsyncSocket(address.family(), SOCK_STREAM)) + { + socket_->SignalReadEvent.connect(this, &SocketTestServer::OnReadEvent); + socket_->Bind(address); + socket_->Listen(5); + } + virtual ~SocketTestServer() { + clear(); + } + + size_t size() const { return clients_.size(); } + SocketTestClient* client(size_t index) const { return clients_[index]; } + SocketTestClient* operator[](size_t index) const { return client(index); } + + void clear() { + for (size_t i=0; i(socket_->Accept(NULL)); + if (!accepted) + return; + clients_.push_back(new SocketTestClient(accepted)); + } + + scoped_ptr socket_; + std::vector clients_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Generic Utilities +/////////////////////////////////////////////////////////////////////////////// + +inline bool ReadFile(const char* filename, std::string* contents) { + FILE* fp = fopen(filename, "rb"); + if (!fp) + return false; + char buffer[1024*64]; + size_t read; + contents->clear(); + while ((read = fread(buffer, 1, sizeof(buffer), fp))) { + contents->append(buffer, read); + } + bool success = (0 != feof(fp)); + fclose(fp); + return success; +} + +/////////////////////////////////////////////////////////////////////////////// +// Unittest predicates which are similar to STREQ, but for raw memory +/////////////////////////////////////////////////////////////////////////////// + +inline AssertionResult CmpHelperMemEq(const char* expected_expression, + const char* expected_length_expression, + const char* actual_expression, + const char* actual_length_expression, + const void* expected, + size_t expected_length, + const void* actual, + size_t actual_length) +{ + if ((expected_length == actual_length) + && (0 == memcmp(expected, actual, expected_length))) { + return AssertionSuccess(); + } + + Message msg; + msg << "Value of: " << actual_expression + << " [" << actual_length_expression << "]"; + if (true) { //!actual_value.Equals(actual_expression)) { + size_t buffer_size = actual_length * 2 + 1; + char* buffer = STACK_ARRAY(char, buffer_size); + hex_encode(buffer, buffer_size, + reinterpret_cast(actual), actual_length); + msg << "\n Actual: " << buffer << " [" << actual_length << "]"; + } + + msg << "\nExpected: " << expected_expression + << " [" << expected_length_expression << "]"; + if (true) { //!expected_value.Equals(expected_expression)) { + size_t buffer_size = expected_length * 2 + 1; + char* buffer = STACK_ARRAY(char, buffer_size); + hex_encode(buffer, buffer_size, + reinterpret_cast(expected), expected_length); + msg << "\nWhich is: " << buffer << " [" << expected_length << "]"; + } + + return AssertionFailure(msg); +} + +inline AssertionResult CmpHelperFileEq(const char* expected_expression, + const char* expected_length_expression, + const char* actual_filename, + const void* expected, + size_t expected_length, + const char* filename) +{ + std::string contents; + if (!ReadFile(filename, &contents)) { + Message msg; + msg << "File '" << filename << "' could not be read."; + return AssertionFailure(msg); + } + return CmpHelperMemEq(expected_expression, expected_length_expression, + actual_filename, "", + expected, expected_length, + contents.c_str(), contents.size()); +} + +#define EXPECT_MEMEQ(expected, expected_length, actual, actual_length) \ + EXPECT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \ + actual, actual_length) + +#define ASSERT_MEMEQ(expected, expected_length, actual, actual_length) \ + ASSERT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \ + actual, actual_length) + +#define EXPECT_FILEEQ(expected, expected_length, filename) \ + EXPECT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \ + filename) + +#define ASSERT_FILEEQ(expected, expected_length, filename) \ + ASSERT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \ + filename) + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for initializing constant memory with integers in a particular byte +// order +/////////////////////////////////////////////////////////////////////////////// + +#define BYTE_CAST(x) static_cast((x) & 0xFF) + +// Declare a N-bit integer as a little-endian sequence of bytes +#define LE16(x) BYTE_CAST(((uint16)x) >> 0), BYTE_CAST(((uint16)x) >> 8) + +#define LE32(x) BYTE_CAST(((uint32)x) >> 0), BYTE_CAST(((uint32)x) >> 8), \ + BYTE_CAST(((uint32)x) >> 16), BYTE_CAST(((uint32)x) >> 24) + +#define LE64(x) BYTE_CAST(((uint64)x) >> 0), BYTE_CAST(((uint64)x) >> 8), \ + BYTE_CAST(((uint64)x) >> 16), BYTE_CAST(((uint64)x) >> 24), \ + BYTE_CAST(((uint64)x) >> 32), BYTE_CAST(((uint64)x) >> 40), \ + BYTE_CAST(((uint64)x) >> 48), BYTE_CAST(((uint64)x) >> 56) + +// Declare a N-bit integer as a big-endian (Internet) sequence of bytes +#define BE16(x) BYTE_CAST(((uint16)x) >> 8), BYTE_CAST(((uint16)x) >> 0) + +#define BE32(x) BYTE_CAST(((uint32)x) >> 24), BYTE_CAST(((uint32)x) >> 16), \ + BYTE_CAST(((uint32)x) >> 8), BYTE_CAST(((uint32)x) >> 0) + +#define BE64(x) BYTE_CAST(((uint64)x) >> 56), BYTE_CAST(((uint64)x) >> 48), \ + BYTE_CAST(((uint64)x) >> 40), BYTE_CAST(((uint64)x) >> 32), \ + BYTE_CAST(((uint64)x) >> 24), BYTE_CAST(((uint64)x) >> 16), \ + BYTE_CAST(((uint64)x) >> 8), BYTE_CAST(((uint64)x) >> 0) + +// Declare a N-bit integer as a this-endian (local machine) sequence of bytes +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 1 +#endif // BIG_ENDIAN + +#if BIG_ENDIAN +#define TE16 BE16 +#define TE32 BE32 +#define TE64 BE64 +#else // !BIG_ENDIAN +#define TE16 LE16 +#define TE32 LE32 +#define TE64 LE64 +#endif // !BIG_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// + +// Helpers for determining if X/screencasting is available (on linux). + +#define MAYBE_SKIP_SCREENCAST_TEST() \ + if (!testing::IsScreencastingAvailable()) { \ + LOG(LS_WARNING) << "Skipping test, since it doesn't have the requisite " \ + << "X environment for screen capture."; \ + return; \ + } \ + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +struct XDisplay { + XDisplay() : display_(XOpenDisplay(NULL)) { } + ~XDisplay() { if (display_) XCloseDisplay(display_); } + bool IsValid() const { return display_ != NULL; } + operator Display*() { return display_; } + private: + Display* display_; +}; +#endif + +// Returns true if screencasting is available. When false, anything that uses +// screencasting features may fail. +inline bool IsScreencastingAvailable() { +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + XDisplay display; + if (!display.IsValid()) { + LOG(LS_WARNING) << "No X Display available."; + return false; + } + int ignored_int, major_version, minor_version; + if (!XRRQueryExtension(display, &ignored_int, &ignored_int) || + !XRRQueryVersion(display, &major_version, &minor_version) || + major_version < 1 || + (major_version < 2 && minor_version < 3)) { + LOG(LS_WARNING) << "XRandr version: " << major_version << "." + << minor_version; + LOG(LS_WARNING) << "XRandr is not supported or is too old (pre 1.3)."; + return false; + } +#endif + return true; +} +} // namespace testing + +#endif // WEBRTC_BASE_TESTUTILS_H__ diff --git a/webrtc/base/thread.cc b/webrtc/base/thread.cc new file mode 100644 index 000000000..0e4f0f35f --- /dev/null +++ b/webrtc/base/thread.cc @@ -0,0 +1,567 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/thread.h" + +#ifndef __has_feature +#define __has_feature(x) 0 // Compatibility with non-clang or LLVM compilers. +#endif // __has_feature + +#if defined(WEBRTC_WIN) +#include +#elif defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/timeutils.h" + +#if !__has_feature(objc_arc) && (defined(WEBRTC_MAC)) +#include "webrtc/base/maccocoathreadhelper.h" +#include "webrtc/base/scoped_autorelease_pool.h" +#endif + +namespace rtc { + +ThreadManager* ThreadManager::Instance() { + LIBJINGLE_DEFINE_STATIC_LOCAL(ThreadManager, thread_manager, ()); + return &thread_manager; +} + +// static +Thread* Thread::Current() { + return ThreadManager::Instance()->CurrentThread(); +} + +#if defined(WEBRTC_POSIX) +ThreadManager::ThreadManager() { + pthread_key_create(&key_, NULL); +#ifndef NO_MAIN_THREAD_WRAPPING + WrapCurrentThread(); +#endif +#if !__has_feature(objc_arc) && (defined(WEBRTC_MAC)) + // Under Automatic Reference Counting (ARC), you cannot use autorelease pools + // directly. Instead, you use @autoreleasepool blocks instead. Also, we are + // maintaining thread safety using immutability within context of GCD dispatch + // queues in this case. + InitCocoaMultiThreading(); +#endif +} + +ThreadManager::~ThreadManager() { +#if __has_feature(objc_arc) + @autoreleasepool +#elif defined(WEBRTC_MAC) + // This is called during exit, at which point apparently no NSAutoreleasePools + // are available; but we might still need them to do cleanup (or we get the + // "no autoreleasepool in place, just leaking" warning when exiting). + ScopedAutoreleasePool pool; +#endif + { + UnwrapCurrentThread(); + pthread_key_delete(key_); + } +} + +Thread *ThreadManager::CurrentThread() { + return static_cast(pthread_getspecific(key_)); +} + +void ThreadManager::SetCurrentThread(Thread *thread) { + pthread_setspecific(key_, thread); +} +#endif + +#if defined(WEBRTC_WIN) +ThreadManager::ThreadManager() { + key_ = TlsAlloc(); +#ifndef NO_MAIN_THREAD_WRAPPING + WrapCurrentThread(); +#endif +} + +ThreadManager::~ThreadManager() { + UnwrapCurrentThread(); + TlsFree(key_); +} + +Thread *ThreadManager::CurrentThread() { + return static_cast(TlsGetValue(key_)); +} + +void ThreadManager::SetCurrentThread(Thread *thread) { + TlsSetValue(key_, thread); +} +#endif + +Thread *ThreadManager::WrapCurrentThread() { + Thread* result = CurrentThread(); + if (NULL == result) { + result = new Thread(); + result->WrapCurrentWithThreadManager(this); + } + return result; +} + +void ThreadManager::UnwrapCurrentThread() { + Thread* t = CurrentThread(); + if (t && !(t->IsOwned())) { + t->UnwrapCurrent(); + delete t; + } +} + +struct ThreadInit { + Thread* thread; + Runnable* runnable; +}; + +Thread::Thread(SocketServer* ss) + : MessageQueue(ss), + priority_(PRIORITY_NORMAL), + started_(false), +#if defined(WEBRTC_WIN) + thread_(NULL), + thread_id_(0), +#endif + owned_(true), + delete_self_when_complete_(false) { + SetName("Thread", this); // default name +} + +Thread::~Thread() { + Stop(); + if (active_) + Clear(NULL); +} + +bool Thread::SleepMs(int milliseconds) { +#if defined(WEBRTC_WIN) + ::Sleep(milliseconds); + return true; +#else + // POSIX has both a usleep() and a nanosleep(), but the former is deprecated, + // so we use nanosleep() even though it has greater precision than necessary. + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + int ret = nanosleep(&ts, NULL); + if (ret != 0) { + LOG_ERR(LS_WARNING) << "nanosleep() returning early"; + return false; + } + return true; +#endif +} + +bool Thread::SetName(const std::string& name, const void* obj) { + if (started_) return false; + name_ = name; + if (obj) { + char buf[16]; + sprintfn(buf, sizeof(buf), " 0x%p", obj); + name_ += buf; + } + return true; +} + +bool Thread::SetPriority(ThreadPriority priority) { +#if defined(WEBRTC_WIN) + if (started_) { + BOOL ret = FALSE; + if (priority == PRIORITY_NORMAL) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_NORMAL); + } else if (priority == PRIORITY_HIGH) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_HIGHEST); + } else if (priority == PRIORITY_ABOVE_NORMAL) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL); + } else if (priority == PRIORITY_IDLE) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_IDLE); + } + if (!ret) { + return false; + } + } + priority_ = priority; + return true; +#else + // TODO: Implement for Linux/Mac if possible. + if (started_) return false; + priority_ = priority; + return true; +#endif +} + +bool Thread::Start(Runnable* runnable) { + ASSERT(owned_); + if (!owned_) return false; + ASSERT(!started_); + if (started_) return false; + + Restart(); // reset fStop_ if the thread is being restarted + + // Make sure that ThreadManager is created on the main thread before + // we start a new thread. + ThreadManager::Instance(); + + ThreadInit* init = new ThreadInit; + init->thread = this; + init->runnable = runnable; +#if defined(WEBRTC_WIN) + DWORD flags = 0; + if (priority_ != PRIORITY_NORMAL) { + flags = CREATE_SUSPENDED; + } + thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreRun, init, flags, + &thread_id_); + if (thread_) { + started_ = true; + if (priority_ != PRIORITY_NORMAL) { + SetPriority(priority_); + ::ResumeThread(thread_); + } + } else { + return false; + } +#elif defined(WEBRTC_POSIX) + pthread_attr_t attr; + pthread_attr_init(&attr); + + // Thread priorities are not supported in NaCl. +#if !defined(__native_client__) + if (priority_ != PRIORITY_NORMAL) { + if (priority_ == PRIORITY_IDLE) { + // There is no POSIX-standard way to set a below-normal priority for an + // individual thread (only whole process), so let's not support it. + LOG(LS_WARNING) << "PRIORITY_IDLE not supported"; + } else { + // Set real-time round-robin policy. + if (pthread_attr_setschedpolicy(&attr, SCHED_RR) != 0) { + LOG(LS_ERROR) << "pthread_attr_setschedpolicy"; + } + struct sched_param param; + if (pthread_attr_getschedparam(&attr, ¶m) != 0) { + LOG(LS_ERROR) << "pthread_attr_getschedparam"; + } else { + // The numbers here are arbitrary. + if (priority_ == PRIORITY_HIGH) { + param.sched_priority = 6; // 6 = HIGH + } else { + ASSERT(priority_ == PRIORITY_ABOVE_NORMAL); + param.sched_priority = 4; // 4 = ABOVE_NORMAL + } + if (pthread_attr_setschedparam(&attr, ¶m) != 0) { + LOG(LS_ERROR) << "pthread_attr_setschedparam"; + } + } + } + } +#endif // !defined(__native_client__) + + int error_code = pthread_create(&thread_, &attr, PreRun, init); + if (0 != error_code) { + LOG(LS_ERROR) << "Unable to create pthread, error " << error_code; + return false; + } + started_ = true; +#endif + return true; +} + +void Thread::Join() { + if (started_) { + ASSERT(!IsCurrent()); +#if defined(WEBRTC_WIN) + WaitForSingleObject(thread_, INFINITE); + CloseHandle(thread_); + thread_ = NULL; + thread_id_ = 0; +#elif defined(WEBRTC_POSIX) + void *pv; + pthread_join(thread_, &pv); +#endif + started_ = false; + } +} + +#if defined(WEBRTC_WIN) +// As seen on MSDN. +// http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.71).aspx +#define MSDEV_SET_THREAD_NAME 0x406D1388 +typedef struct tagTHREADNAME_INFO { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; +} THREADNAME_INFO; + +void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = szThreadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try { + RaiseException(MSDEV_SET_THREAD_NAME, 0, sizeof(info) / sizeof(DWORD), + reinterpret_cast(&info)); + } + __except(EXCEPTION_CONTINUE_EXECUTION) { + } +} +#endif // WEBRTC_WIN + +void* Thread::PreRun(void* pv) { + ThreadInit* init = static_cast(pv); + ThreadManager::Instance()->SetCurrentThread(init->thread); +#if defined(WEBRTC_WIN) + SetThreadName(GetCurrentThreadId(), init->thread->name_.c_str()); +#elif defined(WEBRTC_POSIX) + // TODO: See if naming exists for pthreads. +#endif +#if __has_feature(objc_arc) + @autoreleasepool +#elif defined(WEBRTC_MAC) + // Make sure the new thread has an autoreleasepool + ScopedAutoreleasePool pool; +#endif + { + if (init->runnable) { + init->runnable->Run(init->thread); + } else { + init->thread->Run(); + } + if (init->thread->delete_self_when_complete_) { + init->thread->started_ = false; + delete init->thread; + } + delete init; + return NULL; + } +} + +void Thread::Run() { + ProcessMessages(kForever); +} + +bool Thread::IsOwned() { + return owned_; +} + +void Thread::Stop() { + MessageQueue::Quit(); + Join(); +} + +void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { + if (fStop_) + return; + + // Sent messages are sent to the MessageHandler directly, in the context + // of "thread", like Win32 SendMessage. If in the right context, + // call the handler directly. + + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + if (IsCurrent()) { + phandler->OnMessage(&msg); + return; + } + + AutoThread thread; + Thread *current_thread = Thread::Current(); + ASSERT(current_thread != NULL); // AutoThread ensures this + + bool ready = false; + { + CritScope cs(&crit_); + EnsureActive(); + _SendMessage smsg; + smsg.thread = current_thread; + smsg.msg = msg; + smsg.ready = &ready; + sendlist_.push_back(smsg); + } + + // Wait for a reply + + ss_->WakeUp(); + + bool waited = false; + crit_.Enter(); + while (!ready) { + crit_.Leave(); + current_thread->ReceiveSends(); + current_thread->socketserver()->Wait(kForever, false); + waited = true; + crit_.Enter(); + } + crit_.Leave(); + + // Our Wait loop above may have consumed some WakeUp events for this + // MessageQueue, that weren't relevant to this Send. Losing these WakeUps can + // cause problems for some SocketServers. + // + // Concrete example: + // Win32SocketServer on thread A calls Send on thread B. While processing the + // message, thread B Posts a message to A. We consume the wakeup for that + // Post while waiting for the Send to complete, which means that when we exit + // this loop, we need to issue another WakeUp, or else the Posted message + // won't be processed in a timely manner. + + if (waited) { + current_thread->socketserver()->WakeUp(); + } +} + +void Thread::ReceiveSends() { + // Receive a sent message. Cleanup scenarios: + // - thread sending exits: We don't allow this, since thread can exit + // only via Join, so Send must complete. + // - thread receiving exits: Wakeup/set ready in Thread::Clear() + // - object target cleared: Wakeup/set ready in Thread::Clear() + crit_.Enter(); + while (!sendlist_.empty()) { + _SendMessage smsg = sendlist_.front(); + sendlist_.pop_front(); + crit_.Leave(); + smsg.msg.phandler->OnMessage(&smsg.msg); + crit_.Enter(); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + } + crit_.Leave(); +} + +void Thread::Clear(MessageHandler *phandler, uint32 id, + MessageList* removed) { + CritScope cs(&crit_); + + // Remove messages on sendlist_ with phandler + // Object target cleared: remove from send list, wakeup/set ready + // if sender not NULL. + + std::list<_SendMessage>::iterator iter = sendlist_.begin(); + while (iter != sendlist_.end()) { + _SendMessage smsg = *iter; + if (smsg.msg.Match(phandler, id)) { + if (removed) { + removed->push_back(smsg.msg); + } else { + delete smsg.msg.pdata; + } + iter = sendlist_.erase(iter); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + continue; + } + ++iter; + } + + MessageQueue::Clear(phandler, id, removed); +} + +bool Thread::ProcessMessages(int cmsLoop) { + uint32 msEnd = (kForever == cmsLoop) ? 0 : TimeAfter(cmsLoop); + int cmsNext = cmsLoop; + + while (true) { +#if __has_feature(objc_arc) + @autoreleasepool +#elif defined(WEBRTC_MAC) + // see: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html + // Each thread is supposed to have an autorelease pool. Also for event loops + // like this, autorelease pool needs to be created and drained/released + // for each cycle. + ScopedAutoreleasePool pool; +#endif + { + Message msg; + if (!Get(&msg, cmsNext)) + return !IsQuitting(); + Dispatch(&msg); + + if (cmsLoop != kForever) { + cmsNext = TimeUntil(msEnd); + if (cmsNext < 0) + return true; + } + } + } +} + +bool Thread::WrapCurrent() { + return WrapCurrentWithThreadManager(ThreadManager::Instance()); +} + +bool Thread::WrapCurrentWithThreadManager(ThreadManager* thread_manager) { + if (started_) + return false; +#if defined(WEBRTC_WIN) + // We explicitly ask for no rights other than synchronization. + // This gives us the best chance of succeeding. + thread_ = OpenThread(SYNCHRONIZE, FALSE, GetCurrentThreadId()); + if (!thread_) { + LOG_GLE(LS_ERROR) << "Unable to get handle to thread."; + return false; + } + thread_id_ = GetCurrentThreadId(); +#elif defined(WEBRTC_POSIX) + thread_ = pthread_self(); +#endif + owned_ = false; + started_ = true; + thread_manager->SetCurrentThread(this); + return true; +} + +void Thread::UnwrapCurrent() { + // Clears the platform-specific thread-specific storage. + ThreadManager::Instance()->SetCurrentThread(NULL); +#if defined(WEBRTC_WIN) + if (!CloseHandle(thread_)) { + LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle."; + } +#endif + started_ = false; +} + + +AutoThread::AutoThread(SocketServer* ss) : Thread(ss) { + if (!ThreadManager::Instance()->CurrentThread()) { + ThreadManager::Instance()->SetCurrentThread(this); + } +} + +AutoThread::~AutoThread() { + Stop(); + if (ThreadManager::Instance()->CurrentThread() == this) { + ThreadManager::Instance()->SetCurrentThread(NULL); + } +} + +#if defined(WEBRTC_WIN) +void ComThread::Run() { + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + ASSERT(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) { + Thread::Run(); + CoUninitialize(); + } else { + LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr; + } +} +#endif + +} // namespace rtc diff --git a/webrtc/base/thread.h b/webrtc/base/thread.h new file mode 100644 index 000000000..986335d98 --- /dev/null +++ b/webrtc/base/thread.h @@ -0,0 +1,285 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_THREAD_H_ +#define WEBRTC_BASE_THREAD_H_ + +#include +#include +#include +#include + +#if defined(WEBRTC_POSIX) +#include +#endif +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/messagequeue.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +class Thread; + +class ThreadManager { + public: + ThreadManager(); + ~ThreadManager(); + + static ThreadManager* Instance(); + + Thread* CurrentThread(); + void SetCurrentThread(Thread* thread); + + // Returns a thread object with its thread_ ivar set + // to whatever the OS uses to represent the thread. + // If there already *is* a Thread object corresponding to this thread, + // this method will return that. Otherwise it creates a new Thread + // object whose wrapped() method will return true, and whose + // handle will, on Win32, be opened with only synchronization privileges - + // if you need more privilegs, rather than changing this method, please + // write additional code to adjust the privileges, or call a different + // factory method of your own devising, because this one gets used in + // unexpected contexts (like inside browser plugins) and it would be a + // shame to break it. It is also conceivable on Win32 that we won't even + // be able to get synchronization privileges, in which case the result + // will have a NULL handle. + Thread *WrapCurrentThread(); + void UnwrapCurrentThread(); + + private: +#if defined(WEBRTC_POSIX) + pthread_key_t key_; +#endif + +#if defined(WEBRTC_WIN) + DWORD key_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ThreadManager); +}; + +struct _SendMessage { + _SendMessage() {} + Thread *thread; + Message msg; + bool *ready; +}; + +enum ThreadPriority { + PRIORITY_IDLE = -1, + PRIORITY_NORMAL = 0, + PRIORITY_ABOVE_NORMAL = 1, + PRIORITY_HIGH = 2, +}; + +class Runnable { + public: + virtual ~Runnable() {} + virtual void Run(Thread* thread) = 0; + + protected: + Runnable() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Runnable); +}; + +// WARNING! SUBCLASSES MUST CALL Stop() IN THEIR DESTRUCTORS! See ~Thread(). + +class Thread : public MessageQueue { + public: + explicit Thread(SocketServer* ss = NULL); + // NOTE: ALL SUBCLASSES OF Thread MUST CALL Stop() IN THEIR DESTRUCTORS (or + // guarantee Stop() is explicitly called before the subclass is destroyed). + // This is required to avoid a data race between the destructor modifying the + // vtable, and the Thread::PreRun calling the virtual method Run(). + virtual ~Thread(); + + static Thread* Current(); + + bool IsCurrent() const { + return Current() == this; + } + + // Sleeps the calling thread for the specified number of milliseconds, during + // which time no processing is performed. Returns false if sleeping was + // interrupted by a signal (POSIX only). + static bool SleepMs(int millis); + + // Sets the thread's name, for debugging. Must be called before Start(). + // If |obj| is non-NULL, its value is appended to |name|. + const std::string& name() const { return name_; } + bool SetName(const std::string& name, const void* obj); + + // Sets the thread's priority. Must be called before Start(). + ThreadPriority priority() const { return priority_; } + bool SetPriority(ThreadPriority priority); + + // Starts the execution of the thread. + bool started() const { return started_; } + bool Start(Runnable* runnable = NULL); + + // Used for fire-and-forget threads. Deletes this thread object when the + // Run method returns. + void Release() { + delete_self_when_complete_ = true; + } + + // Tells the thread to stop and waits until it is joined. + // Never call Stop on the current thread. Instead use the inherited Quit + // function which will exit the base MessageQueue without terminating the + // underlying OS thread. + virtual void Stop(); + + // By default, Thread::Run() calls ProcessMessages(kForever). To do other + // work, override Run(). To receive and dispatch messages, call + // ProcessMessages occasionally. + virtual void Run(); + + virtual void Send(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL); + + // Convenience method to invoke a functor on another thread. Caller must + // provide the |ReturnT| template argument, which cannot (easily) be deduced. + // Uses Send() internally, which blocks the current thread until execution + // is complete. + // Ex: bool result = thread.Invoke(&MyFunctionReturningBool); + template + ReturnT Invoke(const FunctorT& functor) { + FunctorMessageHandler handler(functor); + Send(&handler); + return handler.result(); + } + + // From MessageQueue + virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY, + MessageList* removed = NULL); + virtual void ReceiveSends(); + + // ProcessMessages will process I/O and dispatch messages until: + // 1) cms milliseconds have elapsed (returns true) + // 2) Stop() is called (returns false) + bool ProcessMessages(int cms); + + // Returns true if this is a thread that we created using the standard + // constructor, false if it was created by a call to + // ThreadManager::WrapCurrentThread(). The main thread of an application + // is generally not owned, since the OS representation of the thread + // obviously exists before we can get to it. + // You cannot call Start on non-owned threads. + bool IsOwned(); + +#if defined(WEBRTC_WIN) + HANDLE GetHandle() const { + return thread_; + } + DWORD GetId() const { + return thread_id_; + } +#elif defined(WEBRTC_POSIX) + pthread_t GetPThread() { + return thread_; + } +#endif + + // This method should be called when thread is created using non standard + // method, like derived implementation of rtc::Thread and it can not be + // started by calling Start(). This will set started flag to true and + // owned to false. This must be called from the current thread. + // NOTE: These methods should be used by the derived classes only, added here + // only for testing. + bool WrapCurrent(); + void UnwrapCurrent(); + + protected: + // Blocks the calling thread until this thread has terminated. + void Join(); + + private: + static void *PreRun(void *pv); + + // ThreadManager calls this instead WrapCurrent() because + // ThreadManager::Instance() cannot be used while ThreadManager is + // being created. + bool WrapCurrentWithThreadManager(ThreadManager* thread_manager); + + std::list<_SendMessage> sendlist_; + std::string name_; + ThreadPriority priority_; + bool started_; + +#if defined(WEBRTC_POSIX) + pthread_t thread_; +#endif + +#if defined(WEBRTC_WIN) + HANDLE thread_; + DWORD thread_id_; +#endif + + bool owned_; + bool delete_self_when_complete_; + + friend class ThreadManager; + + DISALLOW_COPY_AND_ASSIGN(Thread); +}; + +// AutoThread automatically installs itself at construction +// uninstalls at destruction, if a Thread object is +// _not already_ associated with the current OS thread. + +class AutoThread : public Thread { + public: + explicit AutoThread(SocketServer* ss = 0); + virtual ~AutoThread(); + + private: + DISALLOW_COPY_AND_ASSIGN(AutoThread); +}; + +// Win32 extension for threads that need to use COM +#if defined(WEBRTC_WIN) +class ComThread : public Thread { + public: + ComThread() {} + virtual ~ComThread() { Stop(); } + + protected: + virtual void Run(); + + private: + DISALLOW_COPY_AND_ASSIGN(ComThread); +}; +#endif + +// Provides an easy way to install/uninstall a socketserver on a thread. +class SocketServerScope { + public: + explicit SocketServerScope(SocketServer* ss) { + old_ss_ = Thread::Current()->socketserver(); + Thread::Current()->set_socketserver(ss); + } + ~SocketServerScope() { + Thread::Current()->set_socketserver(old_ss_); + } + + private: + SocketServer* old_ss_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(SocketServerScope); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_THREAD_H_ diff --git a/webrtc/base/thread_unittest.cc b/webrtc/base/thread_unittest.cc new file mode 100644 index 000000000..22eb6bab7 --- /dev/null +++ b/webrtc/base/thread_unittest.cc @@ -0,0 +1,462 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/asyncinvoker.h" +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/event.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/thread.h" + +#if defined(WEBRTC_WIN) +#include // NOLINT +#endif + +using namespace rtc; + +// Generates a sequence of numbers (collaboratively). +class TestGenerator { + public: + TestGenerator() : last(0), count(0) {} + + int Next(int prev) { + int result = prev + last; + last = result; + count += 1; + return result; + } + + int last; + int count; +}; + +struct TestMessage : public MessageData { + explicit TestMessage(int v) : value(v) {} + virtual ~TestMessage() {} + + int value; +}; + +// Receives on a socket and sends by posting messages. +class SocketClient : public TestGenerator, public sigslot::has_slots<> { + public: + SocketClient(AsyncSocket* socket, const SocketAddress& addr, + Thread* post_thread, MessageHandler* phandler) + : socket_(AsyncUDPSocket::Create(socket, addr)), + post_thread_(post_thread), + post_handler_(phandler) { + socket_->SignalReadPacket.connect(this, &SocketClient::OnPacket); + } + + ~SocketClient() { + delete socket_; + } + + SocketAddress address() const { return socket_->GetLocalAddress(); } + + void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& remote_addr, + const PacketTime& packet_time) { + EXPECT_EQ(size, sizeof(uint32)); + uint32 prev = reinterpret_cast(buf)[0]; + uint32 result = Next(prev); + + post_thread_->PostDelayed(200, post_handler_, 0, new TestMessage(result)); + } + + private: + AsyncUDPSocket* socket_; + Thread* post_thread_; + MessageHandler* post_handler_; +}; + +// Receives messages and sends on a socket. +class MessageClient : public MessageHandler, public TestGenerator { + public: + MessageClient(Thread* pth, Socket* socket) + : socket_(socket) { + } + + virtual ~MessageClient() { + delete socket_; + } + + virtual void OnMessage(Message *pmsg) { + TestMessage* msg = static_cast(pmsg->pdata); + int result = Next(msg->value); + EXPECT_GE(socket_->Send(&result, sizeof(result)), 0); + delete msg; + } + + private: + Socket* socket_; +}; + +class CustomThread : public rtc::Thread { + public: + CustomThread() {} + virtual ~CustomThread() { Stop(); } + bool Start() { return false; } +}; + + +// A thread that does nothing when it runs and signals an event +// when it is destroyed. +class SignalWhenDestroyedThread : public Thread { + public: + SignalWhenDestroyedThread(Event* event) + : event_(event) { + } + + virtual ~SignalWhenDestroyedThread() { + Stop(); + event_->Set(); + } + + virtual void Run() { + // Do nothing. + } + + private: + Event* event_; +}; + +// Function objects to test Thread::Invoke. +struct FunctorA { + int operator()() { return 42; } +}; +class FunctorB { + public: + explicit FunctorB(bool* flag) : flag_(flag) {} + void operator()() { if (flag_) *flag_ = true; } + private: + bool* flag_; +}; +struct FunctorC { + int operator()() { + Thread::Current()->ProcessMessages(50); + return 24; + } +}; + +// See: https://code.google.com/p/webrtc/issues/detail?id=2409 +TEST(ThreadTest, DISABLED_Main) { + const SocketAddress addr("127.0.0.1", 0); + + // Create the messaging client on its own thread. + Thread th1; + Socket* socket = th1.socketserver()->CreateAsyncSocket(addr.family(), + SOCK_DGRAM); + MessageClient msg_client(&th1, socket); + + // Create the socket client on its own thread. + Thread th2; + AsyncSocket* asocket = + th2.socketserver()->CreateAsyncSocket(addr.family(), SOCK_DGRAM); + SocketClient sock_client(asocket, addr, &th1, &msg_client); + + socket->Connect(sock_client.address()); + + th1.Start(); + th2.Start(); + + // Get the messages started. + th1.PostDelayed(100, &msg_client, 0, new TestMessage(1)); + + // Give the clients a little while to run. + // Messages will be processed at 100, 300, 500, 700, 900. + Thread* th_main = Thread::Current(); + th_main->ProcessMessages(1000); + + // Stop the sending client. Give the receiver a bit longer to run, in case + // it is running on a machine that is under load (e.g. the build machine). + th1.Stop(); + th_main->ProcessMessages(200); + th2.Stop(); + + // Make sure the results were correct + EXPECT_EQ(5, msg_client.count); + EXPECT_EQ(34, msg_client.last); + EXPECT_EQ(5, sock_client.count); + EXPECT_EQ(55, sock_client.last); +} + +// Test that setting thread names doesn't cause a malfunction. +// There's no easy way to verify the name was set properly at this time. +TEST(ThreadTest, Names) { + // Default name + Thread *thread; + thread = new Thread(); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + thread = new Thread(); + // Name with no object parameter + EXPECT_TRUE(thread->SetName("No object", NULL)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + // Really long name + thread = new Thread(); + EXPECT_TRUE(thread->SetName("Abcdefghijklmnopqrstuvwxyz1234567890", this)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; +} + +// Test that setting thread priorities doesn't cause a malfunction. +// There's no easy way to verify the priority was set properly at this time. +TEST(ThreadTest, Priorities) { + Thread *thread; + thread = new Thread(); + EXPECT_TRUE(thread->SetPriority(PRIORITY_HIGH)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + thread = new Thread(); + EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + + thread = new Thread(); + EXPECT_TRUE(thread->Start()); +#if defined(WEBRTC_WIN) + EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL)); +#else + EXPECT_FALSE(thread->SetPriority(PRIORITY_ABOVE_NORMAL)); +#endif + thread->Stop(); + delete thread; + +} + +TEST(ThreadTest, Wrap) { + Thread* current_thread = Thread::Current(); + current_thread->UnwrapCurrent(); + CustomThread* cthread = new CustomThread(); + EXPECT_TRUE(cthread->WrapCurrent()); + EXPECT_TRUE(cthread->started()); + EXPECT_FALSE(cthread->IsOwned()); + cthread->UnwrapCurrent(); + EXPECT_FALSE(cthread->started()); + delete cthread; + current_thread->WrapCurrent(); +} + +// Test that calling Release on a thread causes it to self-destruct when +// it's finished running +TEST(ThreadTest, Release) { + scoped_ptr event(new Event(true, false)); + // Ensure the event is initialized. + event->Reset(); + + Thread* thread = new SignalWhenDestroyedThread(event.get()); + thread->Start(); + thread->Release(); + + // The event should get signaled when the thread completes, which should + // be nearly instantaneous, since it doesn't do anything. For safety, + // give it 3 seconds in case the machine is under load. + bool signaled = event->Wait(3000); + EXPECT_TRUE(signaled); +} + +TEST(ThreadTest, Invoke) { + // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functors. + EXPECT_EQ(42, thread.Invoke(FunctorA())); + bool called = false; + FunctorB f2(&called); + thread.Invoke(f2); + EXPECT_TRUE(called); + // Try calling bare functions. + struct LocalFuncs { + static int Func1() { return 999; } + static void Func2() {} + }; + EXPECT_EQ(999, thread.Invoke(&LocalFuncs::Func1)); + thread.Invoke(&LocalFuncs::Func2); +} + +class AsyncInvokeTest : public testing::Test { + public: + void IntCallback(int value) { + EXPECT_EQ(expected_thread_, Thread::Current()); + int_value_ = value; + } + void AsyncInvokeIntCallback(AsyncInvoker* invoker, Thread* thread) { + expected_thread_ = thread; + invoker->AsyncInvoke(thread, FunctorC(), + &AsyncInvokeTest::IntCallback, + static_cast(this)); + invoke_started_.Set(); + } + void SetExpectedThreadForIntCallback(Thread* thread) { + expected_thread_ = thread; + } + + protected: + enum { kWaitTimeout = 1000 }; + AsyncInvokeTest() + : int_value_(0), + invoke_started_(true, false), + expected_thread_(NULL) {} + + int int_value_; + Event invoke_started_; + Thread* expected_thread_; +}; + +TEST_F(AsyncInvokeTest, FireAndForget) { + AsyncInvoker invoker; + // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functor. + bool called = false; + invoker.AsyncInvoke(&thread, FunctorB(&called)); + EXPECT_TRUE_WAIT(called, kWaitTimeout); +} + +TEST_F(AsyncInvokeTest, WithCallback) { + AsyncInvoker invoker; + // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functor. + SetExpectedThreadForIntCallback(Thread::Current()); + invoker.AsyncInvoke(&thread, FunctorA(), + &AsyncInvokeTest::IntCallback, + static_cast(this)); + EXPECT_EQ_WAIT(42, int_value_, kWaitTimeout); +} + +TEST_F(AsyncInvokeTest, CancelInvoker) { + // Create and start the thread. + Thread thread; + thread.Start(); + // Try destroying invoker during call. + { + AsyncInvoker invoker; + invoker.AsyncInvoke(&thread, FunctorC(), + &AsyncInvokeTest::IntCallback, + static_cast(this)); + } + // With invoker gone, callback should be cancelled. + Thread::Current()->ProcessMessages(kWaitTimeout); + EXPECT_EQ(0, int_value_); +} + +TEST_F(AsyncInvokeTest, CancelCallingThread) { + AsyncInvoker invoker; + { // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functor. + thread.Invoke(Bind(&AsyncInvokeTest::AsyncInvokeIntCallback, + static_cast(this), + &invoker, Thread::Current())); + // Wait for the call to begin. + ASSERT_TRUE(invoke_started_.Wait(kWaitTimeout)); + } + // Calling thread is gone. Return message shouldn't happen. + Thread::Current()->ProcessMessages(kWaitTimeout); + EXPECT_EQ(0, int_value_); +} + +TEST_F(AsyncInvokeTest, KillInvokerBeforeExecute) { + Thread thread; + thread.Start(); + { + AsyncInvoker invoker; + // Try calling functor. + thread.Invoke(Bind(&AsyncInvokeTest::AsyncInvokeIntCallback, + static_cast(this), + &invoker, Thread::Current())); + // Wait for the call to begin. + ASSERT_TRUE(invoke_started_.Wait(kWaitTimeout)); + } + // Invoker is destroyed. Function should not execute. + Thread::Current()->ProcessMessages(kWaitTimeout); + EXPECT_EQ(0, int_value_); +} + +TEST_F(AsyncInvokeTest, Flush) { + AsyncInvoker invoker; + bool flag1 = false; + bool flag2 = false; + // Queue two async calls to the current thread. + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag1)); + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag2)); + // Because we haven't pumped messages, these should not have run yet. + EXPECT_FALSE(flag1); + EXPECT_FALSE(flag2); + // Force them to run now. + invoker.Flush(Thread::Current()); + EXPECT_TRUE(flag1); + EXPECT_TRUE(flag2); +} + +TEST_F(AsyncInvokeTest, FlushWithIds) { + AsyncInvoker invoker; + bool flag1 = false; + bool flag2 = false; + // Queue two async calls to the current thread, one with a message id. + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag1), + 5); + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag2)); + // Because we haven't pumped messages, these should not have run yet. + EXPECT_FALSE(flag1); + EXPECT_FALSE(flag2); + // Execute pending calls with id == 5. + invoker.Flush(Thread::Current(), 5); + EXPECT_TRUE(flag1); + EXPECT_FALSE(flag2); + flag1 = false; + // Execute all pending calls. The id == 5 call should not execute again. + invoker.Flush(Thread::Current()); + EXPECT_FALSE(flag1); + EXPECT_TRUE(flag2); +} + + +#if defined(WEBRTC_WIN) +class ComThreadTest : public testing::Test, public MessageHandler { + public: + ComThreadTest() : done_(false) {} + protected: + virtual void OnMessage(Message* message) { + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + // S_FALSE means the thread was already inited for a multithread apartment. + EXPECT_EQ(S_FALSE, hr); + if (SUCCEEDED(hr)) { + CoUninitialize(); + } + done_ = true; + } + bool done_; +}; + +TEST_F(ComThreadTest, ComInited) { + Thread* thread = new ComThread(); + EXPECT_TRUE(thread->Start()); + thread->Post(this, 0); + EXPECT_TRUE_WAIT(done_, 1000); + delete thread; +} +#endif diff --git a/webrtc/base/timeutils.cc b/webrtc/base/timeutils.cc new file mode 100644 index 000000000..aefa28518 --- /dev/null +++ b/webrtc/base/timeutils.cc @@ -0,0 +1,189 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#if defined(WEBRTC_POSIX) +#include +#if defined(WEBRTC_MAC) +#include +#endif +#endif + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/timeutils.h" + +#define EFFICIENT_IMPLEMENTATION 1 + +namespace rtc { + +const uint32 HALF = 0x80000000; + +uint64 TimeNanos() { + int64 ticks = 0; +#if defined(WEBRTC_MAC) + static mach_timebase_info_data_t timebase; + if (timebase.denom == 0) { + // Get the timebase if this is the first time we run. + // Recommended by Apple's QA1398. + VERIFY(KERN_SUCCESS == mach_timebase_info(&timebase)); + } + // Use timebase to convert absolute time tick units into nanoseconds. + ticks = mach_absolute_time() * timebase.numer / timebase.denom; +#elif defined(WEBRTC_POSIX) + struct timespec ts; + // TODO: Do we need to handle the case when CLOCK_MONOTONIC + // is not supported? + clock_gettime(CLOCK_MONOTONIC, &ts); + ticks = kNumNanosecsPerSec * static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec); +#elif defined(WEBRTC_WIN) + static volatile LONG last_timegettime = 0; + static volatile int64 num_wrap_timegettime = 0; + volatile LONG* last_timegettime_ptr = &last_timegettime; + DWORD now = timeGetTime(); + // Atomically update the last gotten time + DWORD old = InterlockedExchange(last_timegettime_ptr, now); + if (now < old) { + // If now is earlier than old, there may have been a race between + // threads. + // 0x0fffffff ~3.1 days, the code will not take that long to execute + // so it must have been a wrap around. + if (old > 0xf0000000 && now < 0x0fffffff) { + num_wrap_timegettime++; + } + } + ticks = now + (num_wrap_timegettime << 32); + // TODO: Calculate with nanosecond precision. Otherwise, we're just + // wasting a multiply and divide when doing Time() on Windows. + ticks = ticks * kNumNanosecsPerMillisec; +#endif + return ticks; +} + +uint32 Time() { + return static_cast(TimeNanos() / kNumNanosecsPerMillisec); +} + +uint64 TimeMicros() { + return static_cast(TimeNanos() / kNumNanosecsPerMicrosec); +} + +#if defined(WEBRTC_WIN) +static const uint64 kFileTimeToUnixTimeEpochOffset = 116444736000000000ULL; + +struct timeval { + long tv_sec, tv_usec; // NOLINT +}; + +// Emulate POSIX gettimeofday(). +// Based on breakpad/src/third_party/glog/src/utilities.cc +static int gettimeofday(struct timeval *tv, void *tz) { + // FILETIME is measured in tens of microseconds since 1601-01-01 UTC. + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + LARGE_INTEGER li; + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + // Convert to seconds and microseconds since Unix time Epoch. + int64 micros = (li.QuadPart - kFileTimeToUnixTimeEpochOffset) / 10; + tv->tv_sec = static_cast(micros / kNumMicrosecsPerSec); // NOLINT + tv->tv_usec = static_cast(micros % kNumMicrosecsPerSec); // NOLINT + + return 0; +} + +// Emulate POSIX gmtime_r(). +static struct tm *gmtime_r(const time_t *timep, struct tm *result) { + // On Windows, gmtime is thread safe. + struct tm *tm = gmtime(timep); // NOLINT + if (tm == NULL) { + return NULL; + } + *result = *tm; + return result; +} +#endif // WEBRTC_WIN + +void CurrentTmTime(struct tm *tm, int *microseconds) { + struct timeval timeval; + if (gettimeofday(&timeval, NULL) < 0) { + // Incredibly unlikely code path. + timeval.tv_sec = timeval.tv_usec = 0; + } + time_t secs = timeval.tv_sec; + gmtime_r(&secs, tm); + *microseconds = timeval.tv_usec; +} + +uint32 TimeAfter(int32 elapsed) { + ASSERT(elapsed >= 0); + ASSERT(static_cast(elapsed) < HALF); + return Time() + elapsed; +} + +bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later) { + if (earlier <= later) { + return ((earlier <= middle) && (middle <= later)); + } else { + return !((later < middle) && (middle < earlier)); + } +} + +bool TimeIsLaterOrEqual(uint32 earlier, uint32 later) { +#if EFFICIENT_IMPLEMENTATION + int32 diff = later - earlier; + return (diff >= 0 && static_cast(diff) < HALF); +#else + const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF); + return later_or_equal; +#endif +} + +bool TimeIsLater(uint32 earlier, uint32 later) { +#if EFFICIENT_IMPLEMENTATION + int32 diff = later - earlier; + return (diff > 0 && static_cast(diff) < HALF); +#else + const bool earlier_or_equal = TimeIsBetween(later, earlier, later + HALF); + return !earlier_or_equal; +#endif +} + +int32 TimeDiff(uint32 later, uint32 earlier) { +#if EFFICIENT_IMPLEMENTATION + return later - earlier; +#else + const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF); + if (later_or_equal) { + if (earlier <= later) { + return static_cast(later - earlier); + } else { + return static_cast(later + (UINT32_MAX - earlier) + 1); + } + } else { + if (later <= earlier) { + return -static_cast(earlier - later); + } else { + return -static_cast(earlier + (UINT32_MAX - later) + 1); + } + } +#endif +} + +} // namespace rtc diff --git a/webrtc/base/timeutils.h b/webrtc/base/timeutils.h new file mode 100644 index 000000000..bdf73cc03 --- /dev/null +++ b/webrtc/base/timeutils.h @@ -0,0 +1,85 @@ +/* + * Copyright 2005 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TIMEUTILS_H_ +#define WEBRTC_BASE_TIMEUTILS_H_ + +#include + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +static const int64 kNumMillisecsPerSec = INT64_C(1000); +static const int64 kNumMicrosecsPerSec = INT64_C(1000000); +static const int64 kNumNanosecsPerSec = INT64_C(1000000000); + +static const int64 kNumMicrosecsPerMillisec = kNumMicrosecsPerSec / + kNumMillisecsPerSec; +static const int64 kNumNanosecsPerMillisec = kNumNanosecsPerSec / + kNumMillisecsPerSec; +static const int64 kNumNanosecsPerMicrosec = kNumNanosecsPerSec / + kNumMicrosecsPerSec; + +// January 1970, in NTP milliseconds. +static const int64 kJan1970AsNtpMillisecs = INT64_C(2208988800000); + +typedef uint32 TimeStamp; + +// Returns the current time in milliseconds. +uint32 Time(); +// Returns the current time in microseconds. +uint64 TimeMicros(); +// Returns the current time in nanoseconds. +uint64 TimeNanos(); + +// Stores current time in *tm and microseconds in *microseconds. +void CurrentTmTime(struct tm *tm, int *microseconds); + +// Returns a future timestamp, 'elapsed' milliseconds from now. +uint32 TimeAfter(int32 elapsed); + +// Comparisons between time values, which can wrap around. +bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later); // Inclusive +bool TimeIsLaterOrEqual(uint32 earlier, uint32 later); // Inclusive +bool TimeIsLater(uint32 earlier, uint32 later); // Exclusive + +// Returns the later of two timestamps. +inline uint32 TimeMax(uint32 ts1, uint32 ts2) { + return TimeIsLaterOrEqual(ts1, ts2) ? ts2 : ts1; +} + +// Returns the earlier of two timestamps. +inline uint32 TimeMin(uint32 ts1, uint32 ts2) { + return TimeIsLaterOrEqual(ts1, ts2) ? ts1 : ts2; +} + +// Number of milliseconds that would elapse between 'earlier' and 'later' +// timestamps. The value is negative if 'later' occurs before 'earlier'. +int32 TimeDiff(uint32 later, uint32 earlier); + +// The number of milliseconds that have elapsed since 'earlier'. +inline int32 TimeSince(uint32 earlier) { + return TimeDiff(Time(), earlier); +} + +// The number of milliseconds that will elapse between now and 'later'. +inline int32 TimeUntil(uint32 later) { + return TimeDiff(later, Time()); +} + +// Converts a unix timestamp in nanoseconds to an NTP timestamp in ms. +inline int64 UnixTimestampNanosecsToNtpMillisecs(int64 unix_ts_ns) { + return unix_ts_ns / kNumNanosecsPerMillisec + kJan1970AsNtpMillisecs; +} + +} // namespace rtc + +#endif // WEBRTC_BASE_TIMEUTILS_H_ diff --git a/webrtc/base/timeutils_unittest.cc b/webrtc/base/timeutils_unittest.cc new file mode 100644 index 000000000..86a18179c --- /dev/null +++ b/webrtc/base/timeutils_unittest.cc @@ -0,0 +1,146 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +TEST(TimeTest, TimeInMs) { + uint32 ts_earlier = Time(); + Thread::SleepMs(100); + uint32 ts_now = Time(); + // Allow for the thread to wakeup ~20ms early. + EXPECT_GE(ts_now, ts_earlier + 80); + // Make sure the Time is not returning in smaller unit like microseconds. + EXPECT_LT(ts_now, ts_earlier + 1000); +} + +TEST(TimeTest, Comparison) { + // Obtain two different times, in known order + TimeStamp ts_earlier = Time(); + Thread::SleepMs(100); + TimeStamp ts_now = Time(); + EXPECT_NE(ts_earlier, ts_now); + + // Common comparisons + EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_now)); + EXPECT_TRUE( TimeIsLater( ts_earlier, ts_now)); + EXPECT_FALSE(TimeIsLaterOrEqual(ts_now, ts_earlier)); + EXPECT_FALSE(TimeIsLater( ts_now, ts_earlier)); + + // Edge cases + EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_earlier)); + EXPECT_FALSE(TimeIsLater( ts_earlier, ts_earlier)); + + // Obtain a third time + TimeStamp ts_later = TimeAfter(100); + EXPECT_NE(ts_now, ts_later); + EXPECT_TRUE( TimeIsLater(ts_now, ts_later)); + EXPECT_TRUE( TimeIsLater(ts_earlier, ts_later)); + + // Common comparisons + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_now, ts_later)); + EXPECT_FALSE(TimeIsBetween(ts_earlier, ts_later, ts_now)); + EXPECT_FALSE(TimeIsBetween(ts_now, ts_earlier, ts_later)); + EXPECT_TRUE( TimeIsBetween(ts_now, ts_later, ts_earlier)); + EXPECT_TRUE( TimeIsBetween(ts_later, ts_earlier, ts_now)); + EXPECT_FALSE(TimeIsBetween(ts_later, ts_now, ts_earlier)); + + // Edge cases + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_earlier)); + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_later)); + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_later, ts_later)); + + // Earlier of two times + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_now)); + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later)); + EXPECT_EQ(ts_earlier, TimeMin(ts_now, ts_earlier)); + EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier)); + + // Later of two times + EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_now, TimeMax(ts_earlier, ts_now)); + EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later)); + EXPECT_EQ(ts_now, TimeMax(ts_now, ts_earlier)); + EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier)); +} + +TEST(TimeTest, Intervals) { + TimeStamp ts_earlier = Time(); + TimeStamp ts_later = TimeAfter(500); + + // We can't depend on ts_later and ts_earlier to be exactly 500 apart + // since time elapses between the calls to Time() and TimeAfter(500) + EXPECT_LE(500, TimeDiff(ts_later, ts_earlier)); + EXPECT_GE(-500, TimeDiff(ts_earlier, ts_later)); + + // Time has elapsed since ts_earlier + EXPECT_GE(TimeSince(ts_earlier), 0); + + // ts_earlier is earlier than now, so TimeUntil ts_earlier is -ve + EXPECT_LE(TimeUntil(ts_earlier), 0); + + // ts_later likely hasn't happened yet, so TimeSince could be -ve + // but within 500 + EXPECT_GE(TimeSince(ts_later), -500); + + // TimeUntil ts_later is at most 500 + EXPECT_LE(TimeUntil(ts_later), 500); +} + +TEST(TimeTest, BoundaryComparison) { + // Obtain two different times, in known order + TimeStamp ts_earlier = static_cast(-50); + TimeStamp ts_later = ts_earlier + 100; + EXPECT_NE(ts_earlier, ts_later); + + // Common comparisons + EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_later)); + EXPECT_TRUE( TimeIsLater( ts_earlier, ts_later)); + EXPECT_FALSE(TimeIsLaterOrEqual(ts_later, ts_earlier)); + EXPECT_FALSE(TimeIsLater( ts_later, ts_earlier)); + + // Earlier of two times + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later)); + EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier)); + + // Later of two times + EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later)); + EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier)); + + // Interval + EXPECT_EQ(100, TimeDiff(ts_later, ts_earlier)); + EXPECT_EQ(-100, TimeDiff(ts_earlier, ts_later)); +} + +TEST(TimeTest, DISABLED_CurrentTmTime) { + struct tm tm; + int microseconds; + + time_t before = ::time(NULL); + CurrentTmTime(&tm, µseconds); + time_t after = ::time(NULL); + + // Assert that 'tm' represents a time between 'before' and 'after'. + // mktime() uses local time, so we have to compensate for that. + time_t local_delta = before - ::mktime(::gmtime(&before)); // NOLINT + time_t t = ::mktime(&tm) + local_delta; + + EXPECT_TRUE(before <= t && t <= after); + EXPECT_TRUE(0 <= microseconds && microseconds < 1000000); +} + +} // namespace rtc diff --git a/webrtc/base/timing.cc b/webrtc/base/timing.cc new file mode 100644 index 000000000..aa1fc4290 --- /dev/null +++ b/webrtc/base/timing.cc @@ -0,0 +1,112 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/timing.h" +#include "webrtc/base/timeutils.h" + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#endif +#elif defined(WEBRTC_WIN) +#include +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +Timing::Timing() { +#if defined(WEBRTC_WIN) + // This may fail, but we handle failure gracefully in the methods + // that use it (use alternative sleep method). + // + // TODO: Make it possible for user to tell if IdleWait will + // be done at lesser resolution because of this. + timer_handle_ = CreateWaitableTimer(NULL, // Security attributes. + FALSE, // Manual reset? + NULL); // Timer name. +#endif +} + +Timing::~Timing() { +#if defined(WEBRTC_WIN) + if (timer_handle_ != NULL) + CloseHandle(timer_handle_); +#endif +} + +double Timing::WallTimeNow() { +#if defined(WEBRTC_POSIX) + struct timeval time; + gettimeofday(&time, NULL); + // Convert from second (1.0) and microsecond (1e-6). + return (static_cast(time.tv_sec) + + static_cast(time.tv_usec) * 1.0e-6); + +#elif defined(WEBRTC_WIN) + struct _timeb time; + _ftime(&time); + // Convert from second (1.0) and milliseconds (1e-3). + return (static_cast(time.time) + + static_cast(time.millitm) * 1.0e-3); +#endif +} + +double Timing::TimerNow() { + return (static_cast(TimeNanos()) / kNumNanosecsPerSec); +} + +double Timing::BusyWait(double period) { + double start_time = TimerNow(); + while (TimerNow() - start_time < period) { + } + return TimerNow() - start_time; +} + +double Timing::IdleWait(double period) { + double start_time = TimerNow(); + +#if defined(WEBRTC_POSIX) + double sec_int, sec_frac = modf(period, &sec_int); + struct timespec ts; + ts.tv_sec = static_cast(sec_int); + ts.tv_nsec = static_cast(sec_frac * 1.0e9); // NOLINT + + // NOTE(liulk): for the NOLINT above, long is the appropriate POSIX + // type. + + // POSIX nanosleep may be interrupted by signals. + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) { + } + +#elif defined(WEBRTC_WIN) + if (timer_handle_ != NULL) { + LARGE_INTEGER due_time; + + // Negative indicates relative time. The unit is 100 nanoseconds. + due_time.QuadPart = -LONGLONG(period * 1.0e7); + + SetWaitableTimer(timer_handle_, &due_time, 0, NULL, NULL, TRUE); + WaitForSingleObject(timer_handle_, INFINITE); + } else { + // Still attempts to sleep with lesser resolution. + // The unit is in milliseconds. + Sleep(DWORD(period * 1.0e3)); + } +#endif + + return TimerNow() - start_time; +} + +} // namespace rtc diff --git a/webrtc/base/timing.h b/webrtc/base/timing.h new file mode 100644 index 000000000..58b17a9fb --- /dev/null +++ b/webrtc/base/timing.h @@ -0,0 +1,59 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TIMING_H_ +#define WEBRTC_BASE_TIMING_H_ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +class Timing { + public: + Timing(); + virtual ~Timing(); + + // WallTimeNow() returns the current wall-clock time in seconds, + // within 10 milliseconds resolution. + virtual double WallTimeNow(); + + // TimerNow() is like WallTimeNow(), but is monotonically + // increasing. It returns seconds in resolution of 10 microseconds + // or better. Although timer and wall-clock time have the same + // timing unit, they do not necessarily correlate because wall-clock + // time may be adjusted backwards, hence not monotonic. + // Made virtual so we can make a fake one. + virtual double TimerNow(); + + // BusyWait() exhausts CPU as long as the time elapsed is less than + // the specified interval in seconds. Returns the actual waiting + // time based on TimerNow() measurement. + double BusyWait(double period); + + // IdleWait() relinquishes control of CPU for specified period in + // seconds. It uses highest resolution sleep mechanism as possible, + // but does not otherwise guarantee the accuracy. Returns the + // actual waiting time based on TimerNow() measurement. + // + // This function is not re-entrant for an object. Create a fresh + // Timing object for each thread. + double IdleWait(double period); + + private: +#if defined(WEBRTC_WIN) + HANDLE timer_handle_; +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TIMING_H_ diff --git a/webrtc/base/transformadapter.cc b/webrtc/base/transformadapter.cc new file mode 100644 index 000000000..76b750c29 --- /dev/null +++ b/webrtc/base/transformadapter.cc @@ -0,0 +1,185 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/transformadapter.h" + +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +TransformAdapter::TransformAdapter(StreamInterface * stream, + TransformInterface * transform, + bool direction_read) + : StreamAdapterInterface(stream), transform_(transform), + direction_read_(direction_read), state_(ST_PROCESSING), len_(0) { +} + +TransformAdapter::~TransformAdapter() { + TransformAdapter::Close(); + delete transform_; +} + +StreamResult +TransformAdapter::Read(void * buffer, size_t buffer_len, + size_t * read, int * error) { + if (!direction_read_) + return SR_EOS; + + while (state_ != ST_ERROR) { + if (state_ == ST_COMPLETE) + return SR_EOS; + + // Buffer more data + if ((state_ == ST_PROCESSING) && (len_ < sizeof(buffer_))) { + size_t subread; + StreamResult result = StreamAdapterInterface::Read( + buffer_ + len_, + sizeof(buffer_) - len_, + &subread, + &error_); + if (result == SR_BLOCK) { + return SR_BLOCK; + } else if (result == SR_ERROR) { + state_ = ST_ERROR; + break; + } else if (result == SR_EOS) { + state_ = ST_FLUSHING; + } else { + len_ += subread; + } + } + + // Process buffered data + size_t in_len = len_; + size_t out_len = buffer_len; + StreamResult result = transform_->Transform(buffer_, &in_len, + buffer, &out_len, + (state_ == ST_FLUSHING)); + ASSERT(result != SR_BLOCK); + if (result == SR_EOS) { + // Note: Don't signal SR_EOS this iteration, unless out_len is zero + state_ = ST_COMPLETE; + } else if (result == SR_ERROR) { + state_ = ST_ERROR; + error_ = -1; // TODO: propagate error + break; + } else if ((out_len == 0) && (state_ == ST_FLUSHING)) { + // If there is no output AND no more input, then something is wrong + state_ = ST_ERROR; + error_ = -1; // TODO: better error code? + break; + } + + len_ -= in_len; + if (len_ > 0) + memmove(buffer_, buffer_ + in_len, len_); + + if (out_len == 0) + continue; + + if (read) + *read = out_len; + return SR_SUCCESS; + } + + if (error) + *error = error_; + return SR_ERROR; +} + +StreamResult +TransformAdapter::Write(const void * data, size_t data_len, + size_t * written, int * error) { + if (direction_read_) + return SR_EOS; + + size_t bytes_written = 0; + while (state_ != ST_ERROR) { + if (state_ == ST_COMPLETE) + return SR_EOS; + + if (len_ < sizeof(buffer_)) { + // Process buffered data + size_t in_len = data_len; + size_t out_len = sizeof(buffer_) - len_; + StreamResult result = transform_->Transform(data, &in_len, + buffer_ + len_, &out_len, + (state_ == ST_FLUSHING)); + + ASSERT(result != SR_BLOCK); + if (result == SR_EOS) { + // Note: Don't signal SR_EOS this iteration, unless no data written + state_ = ST_COMPLETE; + } else if (result == SR_ERROR) { + ASSERT(false); // When this happens, think about what should be done + state_ = ST_ERROR; + error_ = -1; // TODO: propagate error + break; + } + + len_ = out_len; + bytes_written = in_len; + } + + size_t pos = 0; + while (pos < len_) { + size_t subwritten; + StreamResult result = StreamAdapterInterface::Write(buffer_ + pos, + len_ - pos, + &subwritten, + &error_); + if (result == SR_BLOCK) { + ASSERT(false); // TODO: we should handle this + return SR_BLOCK; + } else if (result == SR_ERROR) { + state_ = ST_ERROR; + break; + } else if (result == SR_EOS) { + state_ = ST_COMPLETE; + break; + } + + pos += subwritten; + } + + len_ -= pos; + if (len_ > 0) + memmove(buffer_, buffer_ + pos, len_); + + if (bytes_written == 0) + continue; + + if (written) + *written = bytes_written; + return SR_SUCCESS; + } + + if (error) + *error = error_; + return SR_ERROR; +} + +void +TransformAdapter::Close() { + if (!direction_read_ && (state_ == ST_PROCESSING)) { + state_ = ST_FLUSHING; + do { + Write(0, 0, NULL, NULL); + } while (state_ == ST_FLUSHING); + } + state_ = ST_COMPLETE; + StreamAdapterInterface::Close(); +} + +} // namespace rtc diff --git a/webrtc/base/transformadapter.h b/webrtc/base/transformadapter.h new file mode 100644 index 000000000..ad24438ea --- /dev/null +++ b/webrtc/base/transformadapter.h @@ -0,0 +1,80 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TRANSFORMADAPTER_H__ +#define WEBRTC_BASE_TRANSFORMADAPTER_H__ + +#include "webrtc/base/stream.h" + +namespace rtc { +/////////////////////////////////////////////////////////////////////////////// + +class TransformInterface { +public: + virtual ~TransformInterface() { } + + // Transform should convert the in_len bytes of input into the out_len-sized + // output buffer. If flush is true, there will be no more data following + // input. + // After the transformation, in_len contains the number of bytes consumed, and + // out_len contains the number of bytes ready in output. + // Note: Transform should not return SR_BLOCK, as there is no asynchronous + // notification available. + virtual StreamResult Transform(const void * input, size_t * in_len, + void * output, size_t * out_len, + bool flush) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// TransformAdapter causes all data passed through to be transformed by the +// supplied TransformInterface object, which may apply compression, encryption, +// etc. + +class TransformAdapter : public StreamAdapterInterface { +public: + // Note that the transformation is unidirectional, in the direction specified + // by the constructor. Operations in the opposite direction result in SR_EOS. + TransformAdapter(StreamInterface * stream, + TransformInterface * transform, + bool direction_read); + virtual ~TransformAdapter(); + + virtual StreamResult Read(void * buffer, size_t buffer_len, + size_t * read, int * error); + virtual StreamResult Write(const void * data, size_t data_len, + size_t * written, int * error); + virtual void Close(); + + // Apriori, we can't tell what the transformation does to the stream length. + virtual bool GetAvailable(size_t* size) const { return false; } + virtual bool ReserveSize(size_t size) { return true; } + + // Transformations might not be restartable + virtual bool Rewind() { return false; } + +private: + enum State { ST_PROCESSING, ST_FLUSHING, ST_COMPLETE, ST_ERROR }; + enum { BUFFER_SIZE = 1024 }; + + TransformInterface * transform_; + bool direction_read_; + State state_; + int error_; + + char buffer_[BUFFER_SIZE]; + size_t len_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_TRANSFORMADAPTER_H__ diff --git a/webrtc/base/unittest_main.cc b/webrtc/base/unittest_main.cc new file mode 100644 index 000000000..c7adb7931 --- /dev/null +++ b/webrtc/base/unittest_main.cc @@ -0,0 +1,124 @@ +/* + * Copyright 2007 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +// +// A reuseable entry point for gunit tests. + +#if defined(WEBRTC_WIN) +#include +#endif + +#include "webrtc/base/flags.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" + +DEFINE_bool(help, false, "prints this message"); +DEFINE_string(log, "", "logging options to use"); +#if defined(WEBRTC_WIN) +DEFINE_int(crt_break_alloc, -1, "memory allocation to break on"); +DEFINE_bool(default_error_handlers, false, + "leave the default exception/dbg handler functions in place"); + +void TestInvalidParameterHandler(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t pReserved) { + LOG(LS_ERROR) << "InvalidParameter Handler called. Exiting."; + LOG(LS_ERROR) << expression << std::endl << function << std::endl << file + << std::endl << line; + exit(1); +} +void TestPureCallHandler() { + LOG(LS_ERROR) << "Purecall Handler called. Exiting."; + exit(1); +} +int TestCrtReportHandler(int report_type, char* msg, int* retval) { + LOG(LS_ERROR) << "CrtReport Handler called..."; + LOG(LS_ERROR) << msg; + if (report_type == _CRT_ASSERT) { + exit(1); + } else { + *retval = 0; + return TRUE; + } +} +#endif // WEBRTC_WIN + +rtc::Pathname GetTalkDirectory() { + // Locate talk directory. + rtc::Pathname path = rtc::Filesystem::GetCurrentDirectory(); + std::string talk_folder_name("talk"); + talk_folder_name += path.folder_delimiter(); + while (path.folder_name() != talk_folder_name && !path.empty()) { + path.SetFolder(path.parent_folder()); + } + + // If not running inside "talk" folder, then assume running in its parent + // folder. + if (path.empty()) { + path = rtc::Filesystem::GetCurrentDirectory(); + path.AppendFolder("talk"); + // Make sure the folder exist. + if (!rtc::Filesystem::IsFolder(path)) { + path.clear(); + } + } + return path; +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + FlagList::SetFlagsFromCommandLine(&argc, argv, false); + if (FLAG_help) { + FlagList::Print(NULL, false); + return 0; + } + +#if defined(WEBRTC_WIN) + if (!FLAG_default_error_handlers) { + // Make sure any errors don't throw dialogs hanging the test run. + _set_invalid_parameter_handler(TestInvalidParameterHandler); + _set_purecall_handler(TestPureCallHandler); + _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, TestCrtReportHandler); + } + +#ifdef _DEBUG // Turn on memory leak checking on Windows. + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF |_CRTDBG_LEAK_CHECK_DF); + if (FLAG_crt_break_alloc >= 0) { + _crtBreakAlloc = FLAG_crt_break_alloc; + } +#endif // _DEBUG +#endif // WEBRTC_WIN + + rtc::Filesystem::SetOrganizationName("google"); + rtc::Filesystem::SetApplicationName("unittest"); + + // By default, log timestamps. Allow overrides by used of a --log flag. + rtc::LogMessage::LogTimestamps(); + if (*FLAG_log != '\0') { + rtc::LogMessage::ConfigureLogging(FLAG_log, "unittest.log"); + } + + int res = RUN_ALL_TESTS(); + + // clean up logging so we don't appear to leak memory. + rtc::LogMessage::ConfigureLogging("", ""); + +#if defined(WEBRTC_WIN) + // Unhook crt function so that we don't ever log after statics have been + // uninitialized. + if (!FLAG_default_error_handlers) + _CrtSetReportHook2(_CRT_RPTHOOK_REMOVE, TestCrtReportHandler); +#endif + + return res; +} diff --git a/webrtc/base/unixfilesystem.cc b/webrtc/base/unixfilesystem.cc new file mode 100644 index 000000000..081d561db --- /dev/null +++ b/webrtc/base/unixfilesystem.cc @@ -0,0 +1,572 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/unixfilesystem.h" + +#include +#include +#include +#include +#include + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#include +#include "webrtc/base/macutils.h" +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +#if defined(WEBRTC_POSIX) && !defined(WEBRTC_MAC) || defined(WEBRTC_IOS) +#include +#if defined(WEBRTC_ANDROID) +#include +#elif !defined(__native_client__) +#include +#endif // !defined(__native_client__) +#include +#include +#include +#endif // WEBRTC_POSIX && !WEBRTC_MAC || WEBRTC_IOS + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include +#include +#endif + +#if defined(__native_client__) && !defined(__GLIBC__) +#include +#endif + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringutils.h" + +#if defined(WEBRTC_IOS) +// Defined in iosfilesystem.mm. No header file to discourage use +// elsewhere; other places should use GetApp{Data,Temp}Folder() in +// this file. Don't copy/paste. I mean it. +char* IOSDataDirectory(); +char* IOSTempDirectory(); +void IOSAppName(rtc::Pathname* path); +#endif + +namespace rtc { + +#if !defined(WEBRTC_ANDROID) && !defined(WEBRTC_IOS) +char* UnixFilesystem::app_temp_path_ = NULL; +#else +char* UnixFilesystem::provided_app_data_folder_ = NULL; +char* UnixFilesystem::provided_app_temp_folder_ = NULL; + +void UnixFilesystem::SetAppDataFolder(const std::string& folder) { + delete [] provided_app_data_folder_; + provided_app_data_folder_ = CopyString(folder); +} + +void UnixFilesystem::SetAppTempFolder(const std::string& folder) { + delete [] provided_app_temp_folder_; + provided_app_temp_folder_ = CopyString(folder); +} +#endif + +UnixFilesystem::UnixFilesystem() { +#if defined(WEBRTC_IOS) + if (!provided_app_data_folder_) + provided_app_data_folder_ = IOSDataDirectory(); + if (!provided_app_temp_folder_) + provided_app_temp_folder_ = IOSTempDirectory(); +#endif +} + +UnixFilesystem::~UnixFilesystem() {} + +bool UnixFilesystem::CreateFolder(const Pathname &path, mode_t mode) { + std::string pathname(path.pathname()); + int len = pathname.length(); + if ((len == 0) || (pathname[len - 1] != '/')) + return false; + + struct stat st; + int res = ::stat(pathname.c_str(), &st); + if (res == 0) { + // Something exists at this location, check if it is a directory + return S_ISDIR(st.st_mode) != 0; + } else if (errno != ENOENT) { + // Unexpected error + return false; + } + + // Directory doesn't exist, look up one directory level + do { + --len; + } while ((len > 0) && (pathname[len - 1] != '/')); + + if (!CreateFolder(Pathname(pathname.substr(0, len)), mode)) { + return false; + } + + LOG(LS_INFO) << "Creating folder: " << pathname; + return (0 == ::mkdir(pathname.c_str(), mode)); +} + +bool UnixFilesystem::CreateFolder(const Pathname &path) { + return CreateFolder(path, 0755); +} + +FileStream *UnixFilesystem::OpenFile(const Pathname &filename, + const std::string &mode) { + FileStream *fs = new FileStream(); + if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) { + delete fs; + fs = NULL; + } + return fs; +} + +bool UnixFilesystem::CreatePrivateFile(const Pathname &filename) { + int fd = open(filename.pathname().c_str(), + O_RDWR | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR); + if (fd < 0) { + LOG_ERR(LS_ERROR) << "open() failed."; + return false; + } + // Don't need to keep the file descriptor. + if (close(fd) < 0) { + LOG_ERR(LS_ERROR) << "close() failed."; + // Continue. + } + return true; +} + +bool UnixFilesystem::DeleteFile(const Pathname &filename) { + LOG(LS_INFO) << "Deleting file:" << filename.pathname(); + + if (!IsFile(filename)) { + ASSERT(IsFile(filename)); + return false; + } + return ::unlink(filename.pathname().c_str()) == 0; +} + +bool UnixFilesystem::DeleteEmptyFolder(const Pathname &folder) { + LOG(LS_INFO) << "Deleting folder" << folder.pathname(); + + if (!IsFolder(folder)) { + ASSERT(IsFolder(folder)); + return false; + } + std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1); + return ::rmdir(no_slash.c_str()) == 0; +} + +bool UnixFilesystem::GetTemporaryFolder(Pathname &pathname, bool create, + const std::string *append) { +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + FSRef fr; + if (0 != FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, + kCreateFolder, &fr)) + return false; + unsigned char buffer[NAME_MAX+1]; + if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer))) + return false; + pathname.SetPathname(reinterpret_cast(buffer), ""); +#elif defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + ASSERT(provided_app_temp_folder_ != NULL); + pathname.SetPathname(provided_app_temp_folder_, ""); +#else // !WEBRTC_MAC || WEBRTC_IOS && !WEBRTC_ANDROID + if (const char* tmpdir = getenv("TMPDIR")) { + pathname.SetPathname(tmpdir, ""); + } else if (const char* tmp = getenv("TMP")) { + pathname.SetPathname(tmp, ""); + } else { +#ifdef P_tmpdir + pathname.SetPathname(P_tmpdir, ""); +#else // !P_tmpdir + pathname.SetPathname("/tmp/", ""); +#endif // !P_tmpdir + } +#endif // !WEBRTC_MAC || WEBRTC_IOS && !WEBRTC_ANDROID + if (append) { + ASSERT(!append->empty()); + pathname.AppendFolder(*append); + } + return !create || CreateFolder(pathname); +} + +std::string UnixFilesystem::TempFilename(const Pathname &dir, + const std::string &prefix) { + int len = dir.pathname().size() + prefix.size() + 2 + 6; + char *tempname = new char[len]; + + snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(), + prefix.c_str()); + int fd = ::mkstemp(tempname); + if (fd != -1) + ::close(fd); + std::string ret(tempname); + delete[] tempname; + + return ret; +} + +bool UnixFilesystem::MoveFile(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFile(old_path)) { + ASSERT(IsFile(old_path)); + return false; + } + LOG(LS_VERBOSE) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) { + if (errno != EXDEV) + return false; + if (!CopyFile(old_path, new_path)) + return false; + if (!DeleteFile(old_path)) + return false; + } + return true; +} + +bool UnixFilesystem::MoveFolder(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFolder(old_path)) { + ASSERT(IsFolder(old_path)); + return false; + } + LOG(LS_VERBOSE) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) { + if (errno != EXDEV) + return false; + if (!CopyFolder(old_path, new_path)) + return false; + if (!DeleteFolderAndContents(old_path)) + return false; + } + return true; +} + +bool UnixFilesystem::IsFolder(const Pathname &path) { + struct stat st; + if (stat(path.pathname().c_str(), &st) < 0) + return false; + return S_ISDIR(st.st_mode); +} + +bool UnixFilesystem::CopyFile(const Pathname &old_path, + const Pathname &new_path) { + LOG(LS_VERBOSE) << "Copying " << old_path.pathname() + << " to " << new_path.pathname(); + char buf[256]; + size_t len; + + StreamInterface *source = OpenFile(old_path, "rb"); + if (!source) + return false; + + StreamInterface *dest = OpenFile(new_path, "wb"); + if (!dest) { + delete source; + return false; + } + + while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS) + dest->Write(buf, len, NULL, NULL); + + delete source; + delete dest; + return true; +} + +bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) { +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + ASSERT(provided_app_temp_folder_ != NULL); +#endif + + const char* const kTempPrefixes[] = { +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + provided_app_temp_folder_, +#else + "/tmp/", "/var/tmp/", +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + "/private/tmp/", "/private/var/tmp/", "/private/var/folders/", +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) +#endif // WEBRTC_ANDROID || WEBRTC_IOS + }; + for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) { + if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i], + strlen(kTempPrefixes[i]))) + return true; + } + return false; +} + +bool UnixFilesystem::IsFile(const Pathname& pathname) { + struct stat st; + int res = ::stat(pathname.pathname().c_str(), &st); + // Treat symlinks, named pipes, etc. all as files. + return res == 0 && !S_ISDIR(st.st_mode); +} + +bool UnixFilesystem::IsAbsent(const Pathname& pathname) { + struct stat st; + int res = ::stat(pathname.pathname().c_str(), &st); + // Note: we specifically maintain ENOTDIR as an error, because that implies + // that you could not call CreateFolder(pathname). + return res != 0 && ENOENT == errno; +} + +bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) { + struct stat st; + if (::stat(pathname.pathname().c_str(), &st) != 0) + return false; + *size = st.st_size; + return true; +} + +bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) { + struct stat st; + if (::stat(path.pathname().c_str(), &st) != 0) + return false; + switch (which) { + case FTT_CREATED: + *time = st.st_ctime; + break; + case FTT_MODIFIED: + *time = st.st_mtime; + break; + case FTT_ACCESSED: + *time = st.st_atime; + break; + default: + return false; + } + return true; +} + +bool UnixFilesystem::GetAppPathname(Pathname* path) { +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + ProcessSerialNumber psn = { 0, kCurrentProcess }; + CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn, + kProcessDictionaryIncludeAllInformationMask); + if (NULL == procinfo) + return false; + CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo, + kIOBundleExecutableKey); + std::string path8; + bool success = ToUtf8(cfpath, &path8); + CFRelease(procinfo); + if (success) + path->SetPathname(path8); + return success; +#elif defined(__native_client__) + return false; +#elif IOS + IOSAppName(path); + return true; +#else // WEBRTC_MAC && !defined(WEBRTC_IOS) + char buffer[PATH_MAX + 2]; + ssize_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1); + if ((len <= 0) || (len == PATH_MAX + 1)) + return false; + buffer[len] = '\0'; + path->SetPathname(buffer); + return true; +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) +} + +bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) { + ASSERT(!organization_name_.empty()); + ASSERT(!application_name_.empty()); + + // First get the base directory for app data. +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + if (per_user) { + // Use ~/Library/Application Support/// + FSRef fr; + if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType, + kCreateFolder, &fr)) + return false; + unsigned char buffer[NAME_MAX+1]; + if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer))) + return false; + path->SetPathname(reinterpret_cast(buffer), ""); + } else { + // TODO + return false; + } +#elif defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) // && !WEBRTC_MAC || WEBRTC_IOS + ASSERT(provided_app_data_folder_ != NULL); + path->SetPathname(provided_app_data_folder_, ""); +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) // && !WEBRTC_MAC && !WEBRTC_IOS && !WEBRTC_ANDROID + if (per_user) { + // We follow the recommendations in + // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + // It specifies separate directories for data and config files, but + // GetAppDataFolder() does not distinguish. We just return the config dir + // path. + const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); + if (xdg_config_home) { + path->SetPathname(xdg_config_home, ""); + } else { + // XDG says to default to $HOME/.config. We also support falling back to + // other synonyms for HOME if for some reason it is not defined. + const char* homedir; + if (const char* home = getenv("HOME")) { + homedir = home; + } else if (const char* dotdir = getenv("DOTDIR")) { + homedir = dotdir; + } else if (passwd* pw = getpwuid(geteuid())) { + homedir = pw->pw_dir; + } else { + return false; + } + path->SetPathname(homedir, ""); + path->AppendFolder(".config"); + } + } else { + // XDG does not define a standard directory for writable global data. Let's + // just use this. + path->SetPathname("/var/cache/", ""); + } +#endif // !WEBRTC_MAC && !WEBRTC_LINUX + + // Now add on a sub-path for our app. +#if defined(WEBRTC_MAC) || defined(WEBRTC_ANDROID) + path->AppendFolder(organization_name_); + path->AppendFolder(application_name_); +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // XDG says to use a single directory level, so we concatenate the org and app + // name with a hyphen. We also do the Linuxy thing and convert to all + // lowercase with no spaces. + std::string subdir(organization_name_); + subdir.append("-"); + subdir.append(application_name_); + replace_substrs(" ", 1, "", 0, &subdir); + std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower); + path->AppendFolder(subdir); +#endif + if (!CreateFolder(*path, 0700)) { + return false; + } +#if !defined(__native_client__) + // If the folder already exists, it may have the wrong mode or be owned by + // someone else, both of which are security problems. Setting the mode + // avoids both issues since it will fail if the path is not owned by us. + if (0 != ::chmod(path->pathname().c_str(), 0700)) { + LOG_ERR(LS_ERROR) << "Can't set mode on " << path; + return false; + } +#endif + return true; +} + +bool UnixFilesystem::GetAppTempFolder(Pathname* path) { +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + ASSERT(provided_app_temp_folder_ != NULL); + path->SetPathname(provided_app_temp_folder_); + return true; +#else + ASSERT(!application_name_.empty()); + // TODO: Consider whether we are worried about thread safety. + if (app_temp_path_ != NULL && strlen(app_temp_path_) > 0) { + path->SetPathname(app_temp_path_); + return true; + } + + // Create a random directory as /tmp/-- + char buffer[128]; + sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d", + static_cast(getpid()), + static_cast(time(0))); + std::string folder(application_name_); + folder.append(buffer); + if (!GetTemporaryFolder(*path, true, &folder)) + return false; + + delete [] app_temp_path_; + app_temp_path_ = CopyString(path->pathname()); + // TODO: atexit(DeleteFolderAndContents(app_temp_path_)); + return true; +#endif +} + +bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { +#ifdef __native_client__ + return false; +#else // __native_client__ + ASSERT(NULL != freebytes); + // TODO: Consider making relative paths absolute using cwd. + // TODO: When popping off a symlink, push back on the components of the + // symlink, so we don't jump out of the target disk inadvertently. + Pathname existing_path(path.folder(), ""); + while (!existing_path.folder().empty() && IsAbsent(existing_path)) { + existing_path.SetFolder(existing_path.parent_folder()); + } +#if defined(WEBRTC_ANDROID) + struct statfs vfs; + memset(&vfs, 0, sizeof(vfs)); + if (0 != statfs(existing_path.pathname().c_str(), &vfs)) + return false; +#else + struct statvfs vfs; + memset(&vfs, 0, sizeof(vfs)); + if (0 != statvfs(existing_path.pathname().c_str(), &vfs)) + return false; +#endif // WEBRTC_ANDROID +#if defined(WEBRTC_LINUX) + *freebytes = static_cast(vfs.f_bsize) * vfs.f_bavail; +#elif defined(WEBRTC_MAC) + *freebytes = static_cast(vfs.f_frsize) * vfs.f_bavail; +#endif + + return true; +#endif // !__native_client__ +} + +Pathname UnixFilesystem::GetCurrentDirectory() { + Pathname cwd; + char buffer[PATH_MAX]; + char *path = getcwd(buffer, PATH_MAX); + + if (!path) { + LOG_ERR(LS_ERROR) << "getcwd() failed"; + return cwd; // returns empty pathname + } + cwd.SetFolder(std::string(path)); + + return cwd; +} + +char* UnixFilesystem::CopyString(const std::string& str) { + size_t size = str.length() + 1; + + char* buf = new char[size]; + if (!buf) { + return NULL; + } + + strcpyn(buf, size, str.c_str()); + return buf; +} + +} // namespace rtc + +#if defined(__native_client__) +extern "C" int __attribute__((weak)) +link(const char* oldpath, const char* newpath) { + errno = EACCES; + return -1; +} +#endif diff --git a/webrtc/base/unixfilesystem.h b/webrtc/base/unixfilesystem.h new file mode 100644 index 000000000..7b6c20edd --- /dev/null +++ b/webrtc/base/unixfilesystem.h @@ -0,0 +1,126 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_UNIXFILESYSTEM_H_ +#define WEBRTC_BASE_UNIXFILESYSTEM_H_ + +#include + +#include "webrtc/base/fileutils.h" + +namespace rtc { + +class UnixFilesystem : public FilesystemInterface { + public: + UnixFilesystem(); + virtual ~UnixFilesystem(); + +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + // Android does not have a native code API to fetch the app data or temp + // folders. That needs to be passed into this class from Java. Similarly, iOS + // only supports an Objective-C API for fetching the folder locations, so that + // needs to be passed in here from Objective-C. Or at least that used to be + // the case; now the ctor will do the work if necessary and possible. + // TODO(fischman): add an Android version that uses JNI and drop the + // SetApp*Folder() APIs once external users stop using them. + static void SetAppDataFolder(const std::string& folder); + static void SetAppTempFolder(const std::string& folder); +#endif + + // Opens a file. Returns an open StreamInterface if function succeeds. + // Otherwise, returns NULL. + virtual FileStream *OpenFile(const Pathname &filename, + const std::string &mode); + + // Atomically creates an empty file accessible only to the current user if one + // does not already exist at the given path, otherwise fails. + virtual bool CreatePrivateFile(const Pathname &filename); + + // This will attempt to delete the file located at filename. + // It will fail with VERIY if you pass it a non-existant file, or a directory. + virtual bool DeleteFile(const Pathname &filename); + + // This will attempt to delete the folder located at 'folder' + // It ASSERTs and returns false if you pass it a non-existant folder or a + // plain file. + virtual bool DeleteEmptyFolder(const Pathname &folder); + + // Creates a directory. This will call itself recursively to create /foo/bar + // even if /foo does not exist. All created directories are created with the + // given mode. + // Returns TRUE if function succeeds + virtual bool CreateFolder(const Pathname &pathname, mode_t mode); + + // As above, with mode = 0755. + virtual bool CreateFolder(const Pathname &pathname); + + // This moves a file from old_path to new_path, where "file" can be a plain + // file or directory, which will be moved recursively. + // Returns true if function succeeds. + virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path); + virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path); + + // This copies a file from old_path to _new_path where "file" can be a plain + // file or directory, which will be copied recursively. + // Returns true if function succeeds + virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path); + + // Returns true if a pathname is a directory + virtual bool IsFolder(const Pathname& pathname); + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPath(const Pathname& pathname); + + // Returns true of pathname represents an existing file + virtual bool IsFile(const Pathname& pathname); + + // Returns true if pathname refers to no filesystem object, every parent + // directory either exists, or is also absent. + virtual bool IsAbsent(const Pathname& pathname); + + virtual std::string TempFilename(const Pathname &dir, + const std::string &prefix); + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exists) + virtual bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append); + + virtual bool GetFileSize(const Pathname& path, size_t* size); + virtual bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time); + + // Returns the path to the running application. + virtual bool GetAppPathname(Pathname* path); + + virtual bool GetAppDataFolder(Pathname* path, bool per_user); + + // Get a temporary folder that is unique to the current user and application. + virtual bool GetAppTempFolder(Pathname* path); + + virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes); + + // Returns the absolute path of the current directory. + virtual Pathname GetCurrentDirectory(); + + private: +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + static char* provided_app_data_folder_; + static char* provided_app_temp_folder_; +#else + static char* app_temp_path_; +#endif + + static char* CopyString(const std::string& str); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_UNIXFILESYSTEM_H_ diff --git a/webrtc/base/urlencode.cc b/webrtc/base/urlencode.cc new file mode 100644 index 000000000..5619e05cb --- /dev/null +++ b/webrtc/base/urlencode.cc @@ -0,0 +1,179 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/urlencode.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/stringutils.h" + +static int HexPairValue(const char * code) { + int value = 0; + const char * pch = code; + for (;;) { + int digit = *pch++; + if (digit >= '0' && digit <= '9') { + value += digit - '0'; + } + else if (digit >= 'A' && digit <= 'F') { + value += digit - 'A' + 10; + } + else if (digit >= 'a' && digit <= 'f') { + value += digit - 'a' + 10; + } + else { + return -1; + } + if (pch == code + 2) + return value; + value <<= 4; + } +} + +int InternalUrlDecode(const char *source, char *dest, + bool encode_space_as_plus) { + char * start = dest; + + while (*source) { + switch (*source) { + case '+': + if (encode_space_as_plus) { + *(dest++) = ' '; + } else { + *dest++ = *source; + } + break; + case '%': + if (source[1] && source[2]) { + int value = HexPairValue(source + 1); + if (value >= 0) { + *(dest++) = value; + source += 2; + } + else { + *dest++ = '?'; + } + } + else { + *dest++ = '?'; + } + break; + default: + *dest++ = *source; + } + source++; + } + + *dest = 0; + return static_cast(dest - start); +} + +int UrlDecode(const char *source, char *dest) { + return InternalUrlDecode(source, dest, true); +} + +int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest) { + return InternalUrlDecode(source, dest, false); +} + +bool IsValidUrlChar(char ch, bool unsafe_only) { + if (unsafe_only) { + return !(ch <= ' ' || strchr("\\\"^&`<>[]{}", ch)); + } else { + return isalnum(ch) || strchr("-_.!~*'()", ch); + } +} + +int InternalUrlEncode(const char *source, char *dest, unsigned int max, + bool encode_space_as_plus, bool unsafe_only) { + static const char *digits = "0123456789ABCDEF"; + if (max == 0) { + return 0; + } + + char *start = dest; + while (static_cast(dest - start) < max && *source) { + unsigned char ch = static_cast(*source); + if (*source == ' ' && encode_space_as_plus && !unsafe_only) { + *dest++ = '+'; + } else if (IsValidUrlChar(ch, unsafe_only)) { + *dest++ = *source; + } else { + if (static_cast(dest - start) + 4 > max) { + break; + } + *dest++ = '%'; + *dest++ = digits[(ch >> 4) & 0x0F]; + *dest++ = digits[ ch & 0x0F]; + } + source++; + } + ASSERT(static_cast(dest - start) < max); + *dest = 0; + + return static_cast(dest - start); +} + +int UrlEncode(const char *source, char *dest, unsigned max) { + return InternalUrlEncode(source, dest, max, true, false); +} + +int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest, + unsigned max) { + return InternalUrlEncode(source, dest, max, false, false); +} + +int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max) { + return InternalUrlEncode(source, dest, max, false, true); +} + +std::string +InternalUrlDecodeString(const std::string & encoded, + bool encode_space_as_plus) { + size_t needed_length = encoded.length() + 1; + char* buf = STACK_ARRAY(char, needed_length); + InternalUrlDecode(encoded.c_str(), buf, encode_space_as_plus); + return buf; +} + +std::string +UrlDecodeString(const std::string & encoded) { + return InternalUrlDecodeString(encoded, true); +} + +std::string +UrlDecodeStringWithoutEncodingSpaceAsPlus(const std::string & encoded) { + return InternalUrlDecodeString(encoded, false); +} + +std::string +InternalUrlEncodeString(const std::string & decoded, + bool encode_space_as_plus, + bool unsafe_only) { + int needed_length = static_cast(decoded.length()) * 3 + 1; + char* buf = STACK_ARRAY(char, needed_length); + InternalUrlEncode(decoded.c_str(), buf, needed_length, + encode_space_as_plus, unsafe_only); + return buf; +} + +std::string +UrlEncodeString(const std::string & decoded) { + return InternalUrlEncodeString(decoded, true, false); +} + +std::string +UrlEncodeStringWithoutEncodingSpaceAsPlus(const std::string & decoded) { + return InternalUrlEncodeString(decoded, false, false); +} + +std::string +UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded) { + return InternalUrlEncodeString(decoded, false, true); +} diff --git a/webrtc/base/urlencode.h b/webrtc/base/urlencode.h new file mode 100644 index 000000000..6195f8380 --- /dev/null +++ b/webrtc/base/urlencode.h @@ -0,0 +1,43 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef _URLENCODE_H_ +#define _URLENCODE_H_ + +#include + +// Decode all encoded characters. Also decode + as space. +int UrlDecode(const char *source, char *dest); + +// Decode all encoded characters. +int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest); + +// Encode all characters except alphas, numbers, and -_.!~*'() +// Also encode space as +. +int UrlEncode(const char *source, char *dest, unsigned max); + +// Encode all characters except alphas, numbers, and -_.!~*'() +int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest, + unsigned max); + +// Encode only unsafe chars, including \ "^&`<>[]{} +// Also encode space as %20, instead of + +int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max); + +std::string UrlDecodeString(const std::string & encoded); +std::string UrlDecodeStringWithoutEncodingSpaceAsPlus( + const std::string & encoded); +std::string UrlEncodeString(const std::string & decoded); +std::string UrlEncodeStringWithoutEncodingSpaceAsPlus( + const std::string & decoded); +std::string UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded); + +#endif + diff --git a/webrtc/base/urlencode_unittest.cc b/webrtc/base/urlencode_unittest.cc new file mode 100644 index 000000000..2214fbf2a --- /dev/null +++ b/webrtc/base/urlencode_unittest.cc @@ -0,0 +1,81 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/urlencode.h" + +TEST(Urlencode, SourceTooLong) { + char source[] = "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"; + char dest[1]; + ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_EQ('\0', dest[0]); + + dest[0] = 'a'; + ASSERT_EQ(0, UrlEncode(source, dest, 0)); + ASSERT_EQ('a', dest[0]); +} + +TEST(Urlencode, OneCharacterConversion) { + char source[] = "^"; + char dest[4]; + ASSERT_EQ(3, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_STREQ("%5E", dest); +} + +TEST(Urlencode, ShortDestinationNoEncoding) { + // In this case we have a destination that would not be + // big enough to hold an encoding but is big enough to + // hold the text given. + char source[] = "aa"; + char dest[3]; + ASSERT_EQ(2, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_STREQ("aa", dest); +} + +TEST(Urlencode, ShortDestinationEncoding) { + // In this case we have a destination that is not + // big enough to hold the encoding. + char source[] = "&"; + char dest[3]; + ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_EQ('\0', dest[0]); +} + +TEST(Urlencode, Encoding1) { + char source[] = "A^ "; + char dest[8]; + ASSERT_EQ(5, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_STREQ("A%5E+", dest); +} + +TEST(Urlencode, Encoding2) { + char source[] = "A^ "; + char dest[8]; + ASSERT_EQ(7, UrlEncodeWithoutEncodingSpaceAsPlus(source, dest, + ARRAY_SIZE(dest))); + ASSERT_STREQ("A%5E%20", dest); +} + +TEST(Urldecode, Decoding1) { + char source[] = "A%5E+"; + char dest[8]; + ASSERT_EQ(3, UrlDecode(source, dest)); + ASSERT_STREQ("A^ ", dest); +} + +TEST(Urldecode, Decoding2) { + char source[] = "A%5E+"; + char dest[8]; + ASSERT_EQ(3, UrlDecodeWithoutEncodingSpaceAsPlus(source, dest)); + ASSERT_STREQ("A^+", dest); +} diff --git a/webrtc/base/versionparsing.cc b/webrtc/base/versionparsing.cc new file mode 100644 index 000000000..c3f982ff6 --- /dev/null +++ b/webrtc/base/versionparsing.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/versionparsing.h" + +#include + +namespace rtc { + +bool ParseVersionString(const std::string& version_str, + int num_expected_segments, + int version[]) { + size_t pos = 0; + for (int i = 0;;) { + size_t dot_pos = version_str.find('.', pos); + size_t n; + if (dot_pos == std::string::npos) { + // npos here is a special value meaning "to the end of the string" + n = std::string::npos; + } else { + n = dot_pos - pos; + } + + version[i] = atoi(version_str.substr(pos, n).c_str()); + + if (++i >= num_expected_segments) break; + + if (dot_pos == std::string::npos) { + // Previous segment was not terminated by a dot, but there's supposed to + // be more segments, so that's an error. + return false; + } + pos = dot_pos + 1; + } + return true; +} + +int CompareVersions(const int version1[], + const int version2[], + int num_segments) { + for (int i = 0; i < num_segments; ++i) { + int diff = version1[i] - version2[i]; + if (diff != 0) { + return diff; + } + } + return 0; +} + +} // namespace rtc diff --git a/webrtc/base/versionparsing.h b/webrtc/base/versionparsing.h new file mode 100644 index 000000000..be2d33238 --- /dev/null +++ b/webrtc/base/versionparsing.h @@ -0,0 +1,35 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_VERSIONPARSING_H_ +#define WEBRTC_BASE_VERSIONPARSING_H_ + +#include + +namespace rtc { + +// Parses a version string into an array. "num_expected_segments" must be the +// number of numerical segments that the version is expected to have (e.g., +// "1.1.2.0" has 4). "version" must be an array of that length to hold the +// parsed numbers. +// Returns "true" iff successful. +bool ParseVersionString(const std::string& version_str, + int num_expected_segments, + int version[]); + +// Computes the lexicographical order of two versions. The return value +// indicates the order in the standard way (e.g., see strcmp()). +int CompareVersions(const int version1[], + const int version2[], + int num_segments); + +} // namespace rtc + +#endif // WEBRTC_BASE_VERSIONPARSING_H_ diff --git a/webrtc/base/versionparsing_unittest.cc b/webrtc/base/versionparsing_unittest.cc new file mode 100644 index 000000000..51156991a --- /dev/null +++ b/webrtc/base/versionparsing_unittest.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/versionparsing.h" + +#include "webrtc/base/gunit.h" + +namespace rtc { + +static const int kExampleSegments = 4; + +typedef int ExampleVersion[kExampleSegments]; + +TEST(VersionParsing, TestGoodParse) { + ExampleVersion ver; + std::string str1("1.1.2.0"); + static const ExampleVersion expect1 = {1, 1, 2, 0}; + EXPECT_TRUE(ParseVersionString(str1, kExampleSegments, ver)); + EXPECT_EQ(0, CompareVersions(ver, expect1, kExampleSegments)); + std::string str2("2.0.0.1"); + static const ExampleVersion expect2 = {2, 0, 0, 1}; + EXPECT_TRUE(ParseVersionString(str2, kExampleSegments, ver)); + EXPECT_EQ(0, CompareVersions(ver, expect2, kExampleSegments)); +} + +TEST(VersionParsing, TestBadParse) { + ExampleVersion ver; + std::string str1("1.1.2"); + EXPECT_FALSE(ParseVersionString(str1, kExampleSegments, ver)); + std::string str2(""); + EXPECT_FALSE(ParseVersionString(str2, kExampleSegments, ver)); + std::string str3("garbarge"); + EXPECT_FALSE(ParseVersionString(str3, kExampleSegments, ver)); +} + +TEST(VersionParsing, TestCompare) { + static const ExampleVersion ver1 = {1, 0, 21, 0}; + static const ExampleVersion ver2 = {1, 1, 2, 0}; + static const ExampleVersion ver3 = {1, 1, 3, 0}; + static const ExampleVersion ver4 = {1, 1, 3, 9861}; + + // Test that every combination of comparisons has the expected outcome. + EXPECT_EQ(0, CompareVersions(ver1, ver1, kExampleSegments)); + EXPECT_EQ(0, CompareVersions(ver2, ver2, kExampleSegments)); + EXPECT_EQ(0, CompareVersions(ver3, ver3, kExampleSegments)); + EXPECT_EQ(0, CompareVersions(ver4, ver4, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver1, ver2, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver2, ver1, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver1, ver3, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver3, ver1, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver1, ver4, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver4, ver1, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver2, ver3, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver3, ver2, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver2, ver4, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver4, ver2, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver3, ver4, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver4, ver3, kExampleSegments)); +} + +} // namespace rtc diff --git a/webrtc/base/virtualsocket_unittest.cc b/webrtc/base/virtualsocket_unittest.cc new file mode 100644 index 000000000..253d2c5be --- /dev/null +++ b/webrtc/base/virtualsocket_unittest.cc @@ -0,0 +1,1001 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/logging.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/virtualsocketserver.h" + +using namespace rtc; + +// Sends at a constant rate but with random packet sizes. +struct Sender : public MessageHandler { + Sender(Thread* th, AsyncSocket* s, uint32 rt) + : thread(th), socket(new AsyncUDPSocket(s)), + done(false), rate(rt), count(0) { + last_send = rtc::Time(); + thread->PostDelayed(NextDelay(), this, 1); + } + + uint32 NextDelay() { + uint32 size = (rand() % 4096) + 1; + return 1000 * size / rate; + } + + void OnMessage(Message* pmsg) { + ASSERT_EQ(1u, pmsg->message_id); + + if (done) + return; + + uint32 cur_time = rtc::Time(); + uint32 delay = cur_time - last_send; + uint32 size = rate * delay / 1000; + size = std::min(size, 4096); + size = std::max(size, sizeof(uint32)); + + count += size; + memcpy(dummy, &cur_time, sizeof(cur_time)); + socket->Send(dummy, size, options); + + last_send = cur_time; + thread->PostDelayed(NextDelay(), this, 1); + } + + Thread* thread; + scoped_ptr socket; + rtc::PacketOptions options; + bool done; + uint32 rate; // bytes per second + uint32 count; + uint32 last_send; + char dummy[4096]; +}; + +struct Receiver : public MessageHandler, public sigslot::has_slots<> { + Receiver(Thread* th, AsyncSocket* s, uint32 bw) + : thread(th), socket(new AsyncUDPSocket(s)), bandwidth(bw), done(false), + count(0), sec_count(0), sum(0), sum_sq(0), samples(0) { + socket->SignalReadPacket.connect(this, &Receiver::OnReadPacket); + thread->PostDelayed(1000, this, 1); + } + + ~Receiver() { + thread->Clear(this); + } + + void OnReadPacket(AsyncPacketSocket* s, const char* data, size_t size, + const SocketAddress& remote_addr, + const PacketTime& packet_time) { + ASSERT_EQ(socket.get(), s); + ASSERT_GE(size, 4U); + + count += size; + sec_count += size; + + uint32 send_time = *reinterpret_cast(data); + uint32 recv_time = rtc::Time(); + uint32 delay = recv_time - send_time; + sum += delay; + sum_sq += delay * delay; + samples += 1; + } + + void OnMessage(Message* pmsg) { + ASSERT_EQ(1u, pmsg->message_id); + + if (done) + return; + + // It is always possible for us to receive more than expected because + // packets can be further delayed in delivery. + if (bandwidth > 0) + ASSERT_TRUE(sec_count <= 5 * bandwidth / 4); + sec_count = 0; + thread->PostDelayed(1000, this, 1); + } + + Thread* thread; + scoped_ptr socket; + uint32 bandwidth; + bool done; + size_t count; + size_t sec_count; + double sum; + double sum_sq; + uint32 samples; +}; + +class VirtualSocketServerTest : public testing::Test { + public: + VirtualSocketServerTest() : ss_(new VirtualSocketServer(NULL)), + kIPv4AnyAddress(IPAddress(INADDR_ANY), 0), + kIPv6AnyAddress(IPAddress(in6addr_any), 0) { + } + + void CheckAddressIncrementalization(const SocketAddress& post, + const SocketAddress& pre) { + EXPECT_EQ(post.port(), pre.port() + 1); + IPAddress post_ip = post.ipaddr(); + IPAddress pre_ip = pre.ipaddr(); + EXPECT_EQ(pre_ip.family(), post_ip.family()); + if (post_ip.family() == AF_INET) { + in_addr pre_ipv4 = pre_ip.ipv4_address(); + in_addr post_ipv4 = post_ip.ipv4_address(); + int difference = ntohl(post_ipv4.s_addr) - ntohl(pre_ipv4.s_addr); + EXPECT_EQ(1, difference); + } else if (post_ip.family() == AF_INET6) { + in6_addr post_ip6 = post_ip.ipv6_address(); + in6_addr pre_ip6 = pre_ip.ipv6_address(); + uint32* post_as_ints = reinterpret_cast(&post_ip6.s6_addr); + uint32* pre_as_ints = reinterpret_cast(&pre_ip6.s6_addr); + EXPECT_EQ(post_as_ints[3], pre_as_ints[3] + 1); + } + } + + void BasicTest(const SocketAddress& initial_addr) { + AsyncSocket* socket = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_DGRAM); + socket->Bind(initial_addr); + SocketAddress server_addr = socket->GetLocalAddress(); + // Make sure VSS didn't switch families on us. + EXPECT_EQ(server_addr.family(), initial_addr.family()); + + TestClient* client1 = new TestClient(new AsyncUDPSocket(socket)); + AsyncSocket* socket2 = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2)); + + SocketAddress client2_addr; + EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr)); + + SocketAddress client1_addr; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr)); + EXPECT_EQ(client1_addr, server_addr); + + SocketAddress empty = EmptySocketAddressWithFamily(initial_addr.family()); + for (int i = 0; i < 10; i++) { + client2 = new TestClient(AsyncUDPSocket::Create(ss_, empty)); + + SocketAddress next_client2_addr; + EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &next_client2_addr)); + CheckAddressIncrementalization(next_client2_addr, client2_addr); + // EXPECT_EQ(next_client2_addr.port(), client2_addr.port() + 1); + + SocketAddress server_addr2; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, next_client2_addr)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &server_addr2)); + EXPECT_EQ(server_addr2, server_addr); + + client2_addr = next_client2_addr; + } + } + + // initial_addr should be made from either INADDR_ANY or in6addr_any. + void ConnectTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + SocketAddress accept_addr; + const SocketAddress kEmptyAddr = + EmptySocketAddressWithFamily(initial_addr.family()); + + // Create client + AsyncSocket* client = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(client); + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(client->GetLocalAddress().IsNil()); + + // Create server + AsyncSocket* server = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(server); + EXPECT_NE(0, server->Listen(5)); // Bind required + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(server->GetState(), AsyncSocket::CS_CONNECTING); + + // No pending server connections + EXPECT_FALSE(sink.Check(server, testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_EQ(AF_UNSPEC, accept_addr.family()); + + // Attempt connect to listening socket + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + EXPECT_NE(client->GetLocalAddress(), kEmptyAddr); // Implicit Bind + EXPECT_NE(AF_UNSPEC, client->GetLocalAddress().family()); // Implicit Bind + EXPECT_NE(client->GetLocalAddress(), server->GetLocalAddress()); + + // Client is connecting + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE)); + + ss_->ProcessMessagesUntilIdle(); + + // Client still connecting + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE)); + + // Server has pending connection + EXPECT_TRUE(sink.Check(server, testing::SSE_READ)); + Socket* accepted = server->Accept(&accept_addr); + EXPECT_TRUE(NULL != accepted); + EXPECT_NE(accept_addr, kEmptyAddr); + EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr); + + EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_EQ(accepted->GetLocalAddress(), server->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + ss_->ProcessMessagesUntilIdle(); + + // Client has connected + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + } + + void ConnectToNonListenerTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + SocketAddress accept_addr; + const SocketAddress nil_addr; + const SocketAddress empty_addr = + EmptySocketAddressWithFamily(initial_addr.family()); + + // Create client + AsyncSocket* client = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(client); + + // Create server + AsyncSocket* server = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(server); + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + // Attempt connect to non-listening socket + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + // No pending server connections + EXPECT_FALSE(sink.Check(server, testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_EQ(accept_addr, nil_addr); + + // Connection failed + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR)); + EXPECT_EQ(client->GetRemoteAddress(), nil_addr); + } + + void CloseDuringConnectTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + SocketAddress accept_addr; + const SocketAddress empty_addr = + EmptySocketAddressWithFamily(initial_addr.family()); + + // Create client and server + scoped_ptr client(ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM)); + sink.Monitor(client.get()); + scoped_ptr server(ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM)); + sink.Monitor(server.get()); + + // Initiate connect + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Server close before socket enters accept queue + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + server->Close(); + + ss_->ProcessMessagesUntilIdle(); + + // Result: connection failed + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + + server.reset(ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + + // Initiate connect + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + // Server close while socket is in accept queue + EXPECT_TRUE(sink.Check(server.get(), testing::SSE_READ)); + server->Close(); + + ss_->ProcessMessagesUntilIdle(); + + // Result: connection failed + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + + // New server + server.reset(ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + + // Initiate connect + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + // Server accepts connection + EXPECT_TRUE(sink.Check(server.get(), testing::SSE_READ)); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(NULL != accepted.get()); + sink.Monitor(accepted.get()); + + // Client closes before connection complets + EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED); + + // Connected message has not been processed yet. + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING); + client->Close(); + + ss_->ProcessMessagesUntilIdle(); + + // Result: accepted socket closes + EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(sink.Check(accepted.get(), testing::SSE_CLOSE)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + } + + void CloseTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + const SocketAddress kEmptyAddr; + + // Create clients + AsyncSocket* a = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM); + sink.Monitor(a); + a->Bind(initial_addr); + EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family()); + + + scoped_ptr b(ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM)); + sink.Monitor(b.get()); + b->Bind(initial_addr); + EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, a->Connect(b->GetLocalAddress())); + EXPECT_EQ(0, b->Connect(a->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + EXPECT_TRUE(sink.Check(a, testing::SSE_OPEN)); + EXPECT_EQ(a->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_EQ(a->GetRemoteAddress(), b->GetLocalAddress()); + + EXPECT_TRUE(sink.Check(b.get(), testing::SSE_OPEN)); + EXPECT_EQ(b->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_EQ(b->GetRemoteAddress(), a->GetLocalAddress()); + + EXPECT_EQ(1, a->Send("a", 1)); + b->Close(); + EXPECT_EQ(1, a->Send("b", 1)); + + ss_->ProcessMessagesUntilIdle(); + + char buffer[10]; + EXPECT_FALSE(sink.Check(b.get(), testing::SSE_READ)); + EXPECT_EQ(-1, b->Recv(buffer, 10)); + + EXPECT_TRUE(sink.Check(a, testing::SSE_CLOSE)); + EXPECT_EQ(a->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_EQ(a->GetRemoteAddress(), kEmptyAddr); + + // No signal for Closer + EXPECT_FALSE(sink.Check(b.get(), testing::SSE_CLOSE)); + EXPECT_EQ(b->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_EQ(b->GetRemoteAddress(), kEmptyAddr); + } + + void TcpSendTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + const SocketAddress kEmptyAddr; + + // Connect two sockets + AsyncSocket* a = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM); + sink.Monitor(a); + a->Bind(initial_addr); + EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family()); + + AsyncSocket* b = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM); + sink.Monitor(b); + b->Bind(initial_addr); + EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, a->Connect(b->GetLocalAddress())); + EXPECT_EQ(0, b->Connect(a->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + const size_t kBufferSize = 2000; + ss_->set_send_buffer_capacity(kBufferSize); + ss_->set_recv_buffer_capacity(kBufferSize); + + const size_t kDataSize = 5000; + char send_buffer[kDataSize], recv_buffer[kDataSize]; + for (size_t i = 0; i < kDataSize; ++i) + send_buffer[i] = static_cast(i % 256); + memset(recv_buffer, 0, sizeof(recv_buffer)); + size_t send_pos = 0, recv_pos = 0; + + // Can't send more than send buffer in one write + int result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(static_cast(kBufferSize), result); + send_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE)); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Receive buffer is already filled, fill send buffer again + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(static_cast(kBufferSize), result); + send_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE)); + EXPECT_FALSE(sink.Check(b, testing::SSE_READ)); + + // No more room in send or receive buffer + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(-1, result); + EXPECT_TRUE(a->IsBlocking()); + + // Read a subset of the data + result = b->Recv(recv_buffer + recv_pos, 500); + EXPECT_EQ(500, result); + recv_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(a, testing::SSE_WRITE)); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Room for more on the sending side + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(500, result); + send_pos += result; + + // Empty the recv buffer + while (true) { + result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos); + if (result < 0) { + EXPECT_EQ(-1, result); + EXPECT_TRUE(b->IsBlocking()); + break; + } + recv_pos += result; + } + + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Continue to empty the recv buffer + while (true) { + result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos); + if (result < 0) { + EXPECT_EQ(-1, result); + EXPECT_TRUE(b->IsBlocking()); + break; + } + recv_pos += result; + } + + // Send last of the data + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(500, result); + send_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Receive the last of the data + while (true) { + result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos); + if (result < 0) { + EXPECT_EQ(-1, result); + EXPECT_TRUE(b->IsBlocking()); + break; + } + recv_pos += result; + } + + ss_->ProcessMessagesUntilIdle(); + EXPECT_FALSE(sink.Check(b, testing::SSE_READ)); + + // The received data matches the sent data + EXPECT_EQ(kDataSize, send_pos); + EXPECT_EQ(kDataSize, recv_pos); + EXPECT_EQ(0, memcmp(recv_buffer, send_buffer, kDataSize)); + } + + void TcpSendsPacketsInOrderTest(const SocketAddress& initial_addr) { + const SocketAddress kEmptyAddr; + + // Connect two sockets + AsyncSocket* a = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + AsyncSocket* b = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + a->Bind(initial_addr); + EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family()); + + b->Bind(initial_addr); + EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, a->Connect(b->GetLocalAddress())); + EXPECT_EQ(0, b->Connect(a->GetLocalAddress())); + ss_->ProcessMessagesUntilIdle(); + + // First, deliver all packets in 0 ms. + char buffer[2] = { 0, 0 }; + const char cNumPackets = 10; + for (char i = 0; i < cNumPackets; ++i) { + buffer[0] = '0' + i; + EXPECT_EQ(1, a->Send(buffer, 1)); + } + + ss_->ProcessMessagesUntilIdle(); + + for (char i = 0; i < cNumPackets; ++i) { + EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer))); + EXPECT_EQ(static_cast('0' + i), buffer[0]); + } + + // Next, deliver packets at random intervals + const uint32 mean = 50; + const uint32 stddev = 50; + + ss_->set_delay_mean(mean); + ss_->set_delay_stddev(stddev); + ss_->UpdateDelayDistribution(); + + for (char i = 0; i < cNumPackets; ++i) { + buffer[0] = 'A' + i; + EXPECT_EQ(1, a->Send(buffer, 1)); + } + + ss_->ProcessMessagesUntilIdle(); + + for (char i = 0; i < cNumPackets; ++i) { + EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer))); + EXPECT_EQ(static_cast('A' + i), buffer[0]); + } + } + + void BandwidthTest(const SocketAddress& initial_addr) { + AsyncSocket* send_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + AsyncSocket* recv_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + ASSERT_EQ(0, send_socket->Bind(initial_addr)); + ASSERT_EQ(0, recv_socket->Bind(initial_addr)); + EXPECT_EQ(send_socket->GetLocalAddress().family(), initial_addr.family()); + EXPECT_EQ(recv_socket->GetLocalAddress().family(), initial_addr.family()); + ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress())); + + uint32 bandwidth = 64 * 1024; + ss_->set_bandwidth(bandwidth); + + Thread* pthMain = Thread::Current(); + Sender sender(pthMain, send_socket, 80 * 1024); + Receiver receiver(pthMain, recv_socket, bandwidth); + + pthMain->ProcessMessages(5000); + sender.done = true; + pthMain->ProcessMessages(5000); + + ASSERT_TRUE(receiver.count >= 5 * 3 * bandwidth / 4); + ASSERT_TRUE(receiver.count <= 6 * bandwidth); // queue could drain for 1s + + ss_->set_bandwidth(0); + } + + void DelayTest(const SocketAddress& initial_addr) { + time_t seed = ::time(NULL); + LOG(LS_VERBOSE) << "seed = " << seed; + srand(static_cast(seed)); + + const uint32 mean = 2000; + const uint32 stddev = 500; + + ss_->set_delay_mean(mean); + ss_->set_delay_stddev(stddev); + ss_->UpdateDelayDistribution(); + + AsyncSocket* send_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + AsyncSocket* recv_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + ASSERT_EQ(0, send_socket->Bind(initial_addr)); + ASSERT_EQ(0, recv_socket->Bind(initial_addr)); + EXPECT_EQ(send_socket->GetLocalAddress().family(), initial_addr.family()); + EXPECT_EQ(recv_socket->GetLocalAddress().family(), initial_addr.family()); + ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress())); + + Thread* pthMain = Thread::Current(); + // Avg packet size is 2K, so at 200KB/s for 10s, we should see about + // 1000 packets, which is necessary to get a good distribution. + Sender sender(pthMain, send_socket, 100 * 2 * 1024); + Receiver receiver(pthMain, recv_socket, 0); + + pthMain->ProcessMessages(10000); + sender.done = receiver.done = true; + ss_->ProcessMessagesUntilIdle(); + + const double sample_mean = receiver.sum / receiver.samples; + double num = + receiver.samples * receiver.sum_sq - receiver.sum * receiver.sum; + double den = receiver.samples * (receiver.samples - 1); + const double sample_stddev = sqrt(num / den); + LOG(LS_VERBOSE) << "mean=" << sample_mean << " stddev=" << sample_stddev; + + EXPECT_LE(500u, receiver.samples); + // We initially used a 0.1 fudge factor, but on the build machine, we + // have seen the value differ by as much as 0.13. + EXPECT_NEAR(mean, sample_mean, 0.15 * mean); + EXPECT_NEAR(stddev, sample_stddev, 0.15 * stddev); + + ss_->set_delay_mean(0); + ss_->set_delay_stddev(0); + ss_->UpdateDelayDistribution(); + } + + // Test cross-family communication between a client bound to client_addr and a + // server bound to server_addr. shouldSucceed indicates if communication is + // expected to work or not. + void CrossFamilyConnectionTest(const SocketAddress& client_addr, + const SocketAddress& server_addr, + bool shouldSucceed) { + testing::StreamSink sink; + SocketAddress accept_address; + const SocketAddress kEmptyAddr; + + // Client gets a IPv4 address + AsyncSocket* client = ss_->CreateAsyncSocket(client_addr.family(), + SOCK_STREAM); + sink.Monitor(client); + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr); + client->Bind(client_addr); + + // Server gets a non-mapped non-any IPv6 address. + // IPv4 sockets should not be able to connect to this. + AsyncSocket* server = ss_->CreateAsyncSocket(server_addr.family(), + SOCK_STREAM); + sink.Monitor(server); + server->Bind(server_addr); + server->Listen(5); + + if (shouldSucceed) { + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(server, testing::SSE_READ)); + Socket* accepted = server->Accept(&accept_address); + EXPECT_TRUE(NULL != accepted); + EXPECT_NE(kEmptyAddr, accept_address); + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + } else { + // Check that the connection failed. + EXPECT_EQ(-1, client->Connect(server->GetLocalAddress())); + ss_->ProcessMessagesUntilIdle(); + + EXPECT_FALSE(sink.Check(server, testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_address)); + EXPECT_EQ(accept_address, kEmptyAddr); + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr); + } + } + + // Test cross-family datagram sending between a client bound to client_addr + // and a server bound to server_addr. shouldSucceed indicates if sending is + // expected to succed or not. + void CrossFamilyDatagramTest(const SocketAddress& client_addr, + const SocketAddress& server_addr, + bool shouldSucceed) { + AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM); + socket->Bind(server_addr); + SocketAddress bound_server_addr = socket->GetLocalAddress(); + TestClient* client1 = new TestClient(new AsyncUDPSocket(socket)); + + AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM); + socket2->Bind(client_addr); + TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2)); + SocketAddress client2_addr; + + if (shouldSucceed) { + EXPECT_EQ(3, client2->SendTo("foo", 3, bound_server_addr)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr)); + SocketAddress client1_addr; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr)); + EXPECT_EQ(client1_addr, bound_server_addr); + } else { + EXPECT_EQ(-1, client2->SendTo("foo", 3, bound_server_addr)); + EXPECT_FALSE(client1->CheckNextPacket("foo", 3, 0)); + } + } + + protected: + virtual void SetUp() { + Thread::Current()->set_socketserver(ss_); + } + virtual void TearDown() { + Thread::Current()->set_socketserver(NULL); + } + + VirtualSocketServer* ss_; + const SocketAddress kIPv4AnyAddress; + const SocketAddress kIPv6AnyAddress; +}; + +TEST_F(VirtualSocketServerTest, basic_v4) { + SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 5000); + BasicTest(ipv4_test_addr); +} + +TEST_F(VirtualSocketServerTest, basic_v6) { + SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 5000); + BasicTest(ipv6_test_addr); +} + +TEST_F(VirtualSocketServerTest, connect_v4) { + ConnectTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, connect_v6) { + ConnectTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, connect_to_non_listener_v4) { + ConnectToNonListenerTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, connect_to_non_listener_v6) { + ConnectToNonListenerTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_during_connect_v4) { + CloseDuringConnectTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_during_connect_v6) { + CloseDuringConnectTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_v4) { + CloseTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_v6) { + CloseTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, tcp_send_v4) { + TcpSendTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, tcp_send_v6) { + TcpSendTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v4) { + TcpSendsPacketsInOrderTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v6) { + TcpSendsPacketsInOrderTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, bandwidth_v4) { + SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000); + BandwidthTest(ipv4_test_addr); +} + +TEST_F(VirtualSocketServerTest, bandwidth_v6) { + SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000); + BandwidthTest(ipv6_test_addr); +} + +TEST_F(VirtualSocketServerTest, delay_v4) { + SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000); + DelayTest(ipv4_test_addr); +} + +// See: https://code.google.com/p/webrtc/issues/detail?id=2409 +TEST_F(VirtualSocketServerTest, DISABLED_delay_v6) { + SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000); + DelayTest(ipv6_test_addr); +} + +// Works, receiving socket sees 127.0.0.2. +TEST_F(VirtualSocketServerTest, CanConnectFromMappedIPv6ToIPv4Any) { + CrossFamilyConnectionTest(SocketAddress("::ffff:127.0.0.2", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +// Fails. +TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToIPv4Any) { + CrossFamilyConnectionTest(SocketAddress("::2", 0), + SocketAddress("0.0.0.0", 5000), + false); +} + +// Fails. +TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToMappedIPv6) { + CrossFamilyConnectionTest(SocketAddress("::2", 0), + SocketAddress("::ffff:127.0.0.1", 5000), + false); +} + +// Works. receiving socket sees ::ffff:127.0.0.2. +TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToIPv6Any) { + CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::", 5000), + true); +} + +// Fails. +TEST_F(VirtualSocketServerTest, CantConnectFromIPv4ToUnMappedIPv6) { + CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::1", 5000), + false); +} + +// Works. Receiving socket sees ::ffff:127.0.0.1. +TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToMappedIPv6) { + CrossFamilyConnectionTest(SocketAddress("127.0.0.1", 0), + SocketAddress("::ffff:127.0.0.2", 5000), + true); +} + +// Works, receiving socket sees a result from GetNextIP. +TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv6ToIPv4Any) { + CrossFamilyConnectionTest(SocketAddress("::", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +// Works, receiving socket sees whatever GetNextIP gave the client. +TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv4ToIPv6Any) { + CrossFamilyConnectionTest(SocketAddress("0.0.0.0", 0), + SocketAddress("::", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv4ToIPv6Any) { + CrossFamilyDatagramTest(SocketAddress("0.0.0.0", 0), + SocketAddress("::", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromMappedIPv6ToIPv4Any) { + CrossFamilyDatagramTest(SocketAddress("::ffff:127.0.0.1", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToIPv4Any) { + CrossFamilyDatagramTest(SocketAddress("::2", 0), + SocketAddress("0.0.0.0", 5000), + false); +} + +TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToMappedIPv6) { + CrossFamilyDatagramTest(SocketAddress("::2", 0), + SocketAddress("::ffff:127.0.0.1", 5000), + false); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToIPv6Any) { + CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CantSendDatagramFromIPv4ToUnMappedIPv6) { + CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::1", 5000), + false); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToMappedIPv6) { + CrossFamilyDatagramTest(SocketAddress("127.0.0.1", 0), + SocketAddress("::ffff:127.0.0.2", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv6ToIPv4Any) { + CrossFamilyDatagramTest(SocketAddress("::", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CreatesStandardDistribution) { + const uint32 kTestMean[] = { 10, 100, 333, 1000 }; + const double kTestDev[] = { 0.25, 0.1, 0.01 }; + // TODO: The current code only works for 1000 data points or more. + const uint32 kTestSamples[] = { /*10, 100,*/ 1000 }; + for (size_t midx = 0; midx < ARRAY_SIZE(kTestMean); ++midx) { + for (size_t didx = 0; didx < ARRAY_SIZE(kTestDev); ++didx) { + for (size_t sidx = 0; sidx < ARRAY_SIZE(kTestSamples); ++sidx) { + ASSERT_LT(0u, kTestSamples[sidx]); + const uint32 kStdDev = + static_cast(kTestDev[didx] * kTestMean[midx]); + VirtualSocketServer::Function* f = + VirtualSocketServer::CreateDistribution(kTestMean[midx], + kStdDev, + kTestSamples[sidx]); + ASSERT_TRUE(NULL != f); + ASSERT_EQ(kTestSamples[sidx], f->size()); + double sum = 0; + for (uint32 i = 0; i < f->size(); ++i) { + sum += (*f)[i].second; + } + const double mean = sum / f->size(); + double sum_sq_dev = 0; + for (uint32 i = 0; i < f->size(); ++i) { + double dev = (*f)[i].second - mean; + sum_sq_dev += dev * dev; + } + const double stddev = sqrt(sum_sq_dev / f->size()); + EXPECT_NEAR(kTestMean[midx], mean, 0.1 * kTestMean[midx]) + << "M=" << kTestMean[midx] + << " SD=" << kStdDev + << " N=" << kTestSamples[sidx]; + EXPECT_NEAR(kStdDev, stddev, 0.1 * kStdDev) + << "M=" << kTestMean[midx] + << " SD=" << kStdDev + << " N=" << kTestSamples[sidx]; + delete f; + } + } + } +} diff --git a/webrtc/base/virtualsocketserver.cc b/webrtc/base/virtualsocketserver.cc new file mode 100644 index 000000000..f8e8ddeb8 --- /dev/null +++ b/webrtc/base/virtualsocketserver.cc @@ -0,0 +1,1101 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/virtualsocketserver.h" + +#include +#include + +#include +#include +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddresspair.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { +#if defined(WEBRTC_WIN) +const in_addr kInitialNextIPv4 = { {0x01, 0, 0, 0} }; +#else +// This value is entirely arbitrary, hence the lack of concern about endianness. +const in_addr kInitialNextIPv4 = { 0x01000000 }; +#endif +// Starts at ::2 so as to not cause confusion with ::1. +const in6_addr kInitialNextIPv6 = { { { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 + } } }; + +const uint16 kFirstEphemeralPort = 49152; +const uint16 kLastEphemeralPort = 65535; +const uint16 kEphemeralPortCount = kLastEphemeralPort - kFirstEphemeralPort + 1; +const uint32 kDefaultNetworkCapacity = 64 * 1024; +const uint32 kDefaultTcpBufferSize = 32 * 1024; + +const uint32 UDP_HEADER_SIZE = 28; // IP + UDP headers +const uint32 TCP_HEADER_SIZE = 40; // IP + TCP headers +const uint32 TCP_MSS = 1400; // Maximum segment size + +// Note: The current algorithm doesn't work for sample sizes smaller than this. +const int NUM_SAMPLES = 1000; + +enum { + MSG_ID_PACKET, + MSG_ID_CONNECT, + MSG_ID_DISCONNECT, +}; + +// Packets are passed between sockets as messages. We copy the data just like +// the kernel does. +class Packet : public MessageData { + public: + Packet(const char* data, size_t size, const SocketAddress& from) + : size_(size), consumed_(0), from_(from) { + ASSERT(NULL != data); + data_ = new char[size_]; + memcpy(data_, data, size_); + } + + virtual ~Packet() { + delete[] data_; + } + + const char* data() const { return data_ + consumed_; } + size_t size() const { return size_ - consumed_; } + const SocketAddress& from() const { return from_; } + + // Remove the first size bytes from the data. + void Consume(size_t size) { + ASSERT(size + consumed_ < size_); + consumed_ += size; + } + + private: + char* data_; + size_t size_, consumed_; + SocketAddress from_; +}; + +struct MessageAddress : public MessageData { + explicit MessageAddress(const SocketAddress& a) : addr(a) { } + SocketAddress addr; +}; + +// Implements the socket interface using the virtual network. Packets are +// passed as messages using the message queue of the socket server. +class VirtualSocket : public AsyncSocket, public MessageHandler { + public: + VirtualSocket(VirtualSocketServer* server, int family, int type, bool async) + : server_(server), family_(family), type_(type), async_(async), + state_(CS_CLOSED), error_(0), listen_queue_(NULL), + write_enabled_(false), + network_size_(0), recv_buffer_size_(0), bound_(false), was_any_(false) { + ASSERT((type_ == SOCK_DGRAM) || (type_ == SOCK_STREAM)); + ASSERT(async_ || (type_ != SOCK_STREAM)); // We only support async streams + } + + virtual ~VirtualSocket() { + Close(); + + for (RecvBuffer::iterator it = recv_buffer_.begin(); + it != recv_buffer_.end(); ++it) { + delete *it; + } + } + + virtual SocketAddress GetLocalAddress() const { + return local_addr_; + } + + virtual SocketAddress GetRemoteAddress() const { + return remote_addr_; + } + + // Used by server sockets to set the local address without binding. + void SetLocalAddress(const SocketAddress& addr) { + local_addr_ = addr; + } + + virtual int Bind(const SocketAddress& addr) { + if (!local_addr_.IsNil()) { + error_ = EINVAL; + return -1; + } + local_addr_ = addr; + int result = server_->Bind(this, &local_addr_); + if (result != 0) { + local_addr_.Clear(); + error_ = EADDRINUSE; + } else { + bound_ = true; + was_any_ = addr.IsAnyIP(); + } + return result; + } + + virtual int Connect(const SocketAddress& addr) { + return InitiateConnect(addr, true); + } + + virtual int Close() { + if (!local_addr_.IsNil() && bound_) { + // Remove from the binding table. + server_->Unbind(local_addr_, this); + bound_ = false; + } + + if (SOCK_STREAM == type_) { + // Cancel pending sockets + if (listen_queue_) { + while (!listen_queue_->empty()) { + SocketAddress addr = listen_queue_->front(); + + // Disconnect listening socket. + server_->Disconnect(server_->LookupBinding(addr)); + listen_queue_->pop_front(); + } + delete listen_queue_; + listen_queue_ = NULL; + } + // Disconnect stream sockets + if (CS_CONNECTED == state_) { + // Disconnect remote socket, check if it is a child of a server socket. + VirtualSocket* socket = + server_->LookupConnection(local_addr_, remote_addr_); + if (!socket) { + // Not a server socket child, then see if it is bound. + // TODO: If this is indeed a server socket that has no + // children this will cause the server socket to be + // closed. This might lead to unexpected results, how to fix this? + socket = server_->LookupBinding(remote_addr_); + } + server_->Disconnect(socket); + + // Remove mapping for both directions. + server_->RemoveConnection(remote_addr_, local_addr_); + server_->RemoveConnection(local_addr_, remote_addr_); + } + // Cancel potential connects + MessageList msgs; + if (server_->msg_queue_) { + server_->msg_queue_->Clear(this, MSG_ID_CONNECT, &msgs); + } + for (MessageList::iterator it = msgs.begin(); it != msgs.end(); ++it) { + ASSERT(NULL != it->pdata); + MessageAddress* data = static_cast(it->pdata); + + // Lookup remote side. + VirtualSocket* socket = server_->LookupConnection(local_addr_, + data->addr); + if (socket) { + // Server socket, remote side is a socket retreived by + // accept. Accepted sockets are not bound so we will not + // find it by looking in the bindings table. + server_->Disconnect(socket); + server_->RemoveConnection(local_addr_, data->addr); + } else { + server_->Disconnect(server_->LookupBinding(data->addr)); + } + delete data; + } + // Clear incoming packets and disconnect messages + if (server_->msg_queue_) { + server_->msg_queue_->Clear(this); + } + } + + state_ = CS_CLOSED; + local_addr_.Clear(); + remote_addr_.Clear(); + return 0; + } + + virtual int Send(const void *pv, size_t cb) { + if (CS_CONNECTED != state_) { + error_ = ENOTCONN; + return -1; + } + if (SOCK_DGRAM == type_) { + return SendUdp(pv, cb, remote_addr_); + } else { + return SendTcp(pv, cb); + } + } + + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + if (SOCK_DGRAM == type_) { + return SendUdp(pv, cb, addr); + } else { + if (CS_CONNECTED != state_) { + error_ = ENOTCONN; + return -1; + } + return SendTcp(pv, cb); + } + } + + virtual int Recv(void *pv, size_t cb) { + SocketAddress addr; + return RecvFrom(pv, cb, &addr); + } + + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + // If we don't have a packet, then either error or wait for one to arrive. + if (recv_buffer_.empty()) { + if (async_) { + error_ = EAGAIN; + return -1; + } + while (recv_buffer_.empty()) { + Message msg; + server_->msg_queue_->Get(&msg); + server_->msg_queue_->Dispatch(&msg); + } + } + + // Return the packet at the front of the queue. + Packet* packet = recv_buffer_.front(); + size_t data_read = _min(cb, packet->size()); + memcpy(pv, packet->data(), data_read); + *paddr = packet->from(); + + if (data_read < packet->size()) { + packet->Consume(data_read); + } else { + recv_buffer_.pop_front(); + delete packet; + } + + if (SOCK_STREAM == type_) { + bool was_full = (recv_buffer_size_ == server_->recv_buffer_capacity_); + recv_buffer_size_ -= data_read; + if (was_full) { + VirtualSocket* sender = server_->LookupBinding(remote_addr_); + ASSERT(NULL != sender); + server_->SendTcp(sender); + } + } + + return static_cast(data_read); + } + + virtual int Listen(int backlog) { + ASSERT(SOCK_STREAM == type_); + ASSERT(CS_CLOSED == state_); + if (local_addr_.IsNil()) { + error_ = EINVAL; + return -1; + } + ASSERT(NULL == listen_queue_); + listen_queue_ = new ListenQueue; + state_ = CS_CONNECTING; + return 0; + } + + virtual VirtualSocket* Accept(SocketAddress *paddr) { + if (NULL == listen_queue_) { + error_ = EINVAL; + return NULL; + } + while (!listen_queue_->empty()) { + VirtualSocket* socket = new VirtualSocket(server_, AF_INET, type_, + async_); + + // Set the new local address to the same as this server socket. + socket->SetLocalAddress(local_addr_); + // Sockets made from a socket that 'was Any' need to inherit that. + socket->set_was_any(was_any_); + SocketAddress remote_addr(listen_queue_->front()); + int result = socket->InitiateConnect(remote_addr, false); + listen_queue_->pop_front(); + if (result != 0) { + delete socket; + continue; + } + socket->CompleteConnect(remote_addr, false); + if (paddr) { + *paddr = remote_addr; + } + return socket; + } + error_ = EWOULDBLOCK; + return NULL; + } + + virtual int GetError() const { + return error_; + } + + virtual void SetError(int error) { + error_ = error; + } + + virtual ConnState GetState() const { + return state_; + } + + virtual int GetOption(Option opt, int* value) { + OptionsMap::const_iterator it = options_map_.find(opt); + if (it == options_map_.end()) { + return -1; + } + *value = it->second; + return 0; // 0 is success to emulate getsockopt() + } + + virtual int SetOption(Option opt, int value) { + options_map_[opt] = value; + return 0; // 0 is success to emulate setsockopt() + } + + virtual int EstimateMTU(uint16* mtu) { + if (CS_CONNECTED != state_) + return ENOTCONN; + else + return 65536; + } + + void OnMessage(Message *pmsg) { + if (pmsg->message_id == MSG_ID_PACKET) { + //ASSERT(!local_addr_.IsAny()); + ASSERT(NULL != pmsg->pdata); + Packet* packet = static_cast(pmsg->pdata); + + recv_buffer_.push_back(packet); + + if (async_) { + SignalReadEvent(this); + } + } else if (pmsg->message_id == MSG_ID_CONNECT) { + ASSERT(NULL != pmsg->pdata); + MessageAddress* data = static_cast(pmsg->pdata); + if (listen_queue_ != NULL) { + listen_queue_->push_back(data->addr); + if (async_) { + SignalReadEvent(this); + } + } else if ((SOCK_STREAM == type_) && (CS_CONNECTING == state_)) { + CompleteConnect(data->addr, true); + } else { + LOG(LS_VERBOSE) << "Socket at " << local_addr_ << " is not listening"; + server_->Disconnect(server_->LookupBinding(data->addr)); + } + delete data; + } else if (pmsg->message_id == MSG_ID_DISCONNECT) { + ASSERT(SOCK_STREAM == type_); + if (CS_CLOSED != state_) { + int error = (CS_CONNECTING == state_) ? ECONNREFUSED : 0; + state_ = CS_CLOSED; + remote_addr_.Clear(); + if (async_) { + SignalCloseEvent(this, error); + } + } + } else { + ASSERT(false); + } + } + + bool was_any() { return was_any_; } + void set_was_any(bool was_any) { was_any_ = was_any; } + + private: + struct NetworkEntry { + size_t size; + uint32 done_time; + }; + + typedef std::deque ListenQueue; + typedef std::deque NetworkQueue; + typedef std::vector SendBuffer; + typedef std::list RecvBuffer; + typedef std::map OptionsMap; + + int InitiateConnect(const SocketAddress& addr, bool use_delay) { + if (!remote_addr_.IsNil()) { + error_ = (CS_CONNECTED == state_) ? EISCONN : EINPROGRESS; + return -1; + } + if (local_addr_.IsNil()) { + // If there's no local address set, grab a random one in the correct AF. + int result = 0; + if (addr.ipaddr().family() == AF_INET) { + result = Bind(SocketAddress("0.0.0.0", 0)); + } else if (addr.ipaddr().family() == AF_INET6) { + result = Bind(SocketAddress("::", 0)); + } + if (result != 0) { + return result; + } + } + if (type_ == SOCK_DGRAM) { + remote_addr_ = addr; + state_ = CS_CONNECTED; + } else { + int result = server_->Connect(this, addr, use_delay); + if (result != 0) { + error_ = EHOSTUNREACH; + return -1; + } + state_ = CS_CONNECTING; + } + return 0; + } + + void CompleteConnect(const SocketAddress& addr, bool notify) { + ASSERT(CS_CONNECTING == state_); + remote_addr_ = addr; + state_ = CS_CONNECTED; + server_->AddConnection(remote_addr_, local_addr_, this); + if (async_ && notify) { + SignalConnectEvent(this); + } + } + + int SendUdp(const void* pv, size_t cb, const SocketAddress& addr) { + // If we have not been assigned a local port, then get one. + if (local_addr_.IsNil()) { + local_addr_ = EmptySocketAddressWithFamily(addr.ipaddr().family()); + int result = server_->Bind(this, &local_addr_); + if (result != 0) { + local_addr_.Clear(); + error_ = EADDRINUSE; + return result; + } + } + + // Send the data in a message to the appropriate socket. + return server_->SendUdp(this, static_cast(pv), cb, addr); + } + + int SendTcp(const void* pv, size_t cb) { + size_t capacity = server_->send_buffer_capacity_ - send_buffer_.size(); + if (0 == capacity) { + write_enabled_ = true; + error_ = EWOULDBLOCK; + return -1; + } + size_t consumed = _min(cb, capacity); + const char* cpv = static_cast(pv); + send_buffer_.insert(send_buffer_.end(), cpv, cpv + consumed); + server_->SendTcp(this); + return static_cast(consumed); + } + + VirtualSocketServer* server_; + int family_; + int type_; + bool async_; + ConnState state_; + int error_; + SocketAddress local_addr_; + SocketAddress remote_addr_; + + // Pending sockets which can be Accepted + ListenQueue* listen_queue_; + + // Data which tcp has buffered for sending + SendBuffer send_buffer_; + bool write_enabled_; + + // Critical section to protect the recv_buffer and queue_ + CriticalSection crit_; + + // Network model that enforces bandwidth and capacity constraints + NetworkQueue network_; + size_t network_size_; + + // Data which has been received from the network + RecvBuffer recv_buffer_; + // The amount of data which is in flight or in recv_buffer_ + size_t recv_buffer_size_; + + // Is this socket bound? + bool bound_; + + // When we bind a socket to Any, VSS's Bind gives it another address. For + // dual-stack sockets, we want to distinguish between sockets that were + // explicitly given a particular address and sockets that had one picked + // for them by VSS. + bool was_any_; + + // Store the options that are set + OptionsMap options_map_; + + friend class VirtualSocketServer; +}; + +VirtualSocketServer::VirtualSocketServer(SocketServer* ss) + : server_(ss), server_owned_(false), msg_queue_(NULL), stop_on_idle_(false), + network_delay_(Time()), next_ipv4_(kInitialNextIPv4), + next_ipv6_(kInitialNextIPv6), next_port_(kFirstEphemeralPort), + bindings_(new AddressMap()), connections_(new ConnectionMap()), + bandwidth_(0), network_capacity_(kDefaultNetworkCapacity), + send_buffer_capacity_(kDefaultTcpBufferSize), + recv_buffer_capacity_(kDefaultTcpBufferSize), + delay_mean_(0), delay_stddev_(0), delay_samples_(NUM_SAMPLES), + delay_dist_(NULL), drop_prob_(0.0) { + if (!server_) { + server_ = new PhysicalSocketServer(); + server_owned_ = true; + } + UpdateDelayDistribution(); +} + +VirtualSocketServer::~VirtualSocketServer() { + delete bindings_; + delete connections_; + delete delay_dist_; + if (server_owned_) { + delete server_; + } +} + +IPAddress VirtualSocketServer::GetNextIP(int family) { + if (family == AF_INET) { + IPAddress next_ip(next_ipv4_); + next_ipv4_.s_addr = + HostToNetwork32(NetworkToHost32(next_ipv4_.s_addr) + 1); + return next_ip; + } else if (family == AF_INET6) { + IPAddress next_ip(next_ipv6_); + uint32* as_ints = reinterpret_cast(&next_ipv6_.s6_addr); + as_ints[3] += 1; + return next_ip; + } + return IPAddress(); +} + +uint16 VirtualSocketServer::GetNextPort() { + uint16 port = next_port_; + if (next_port_ < kLastEphemeralPort) { + ++next_port_; + } else { + next_port_ = kFirstEphemeralPort; + } + return port; +} + +Socket* VirtualSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* VirtualSocketServer::CreateSocket(int family, int type) { + return CreateSocketInternal(family, type); +} + +AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int family, int type) { + return CreateSocketInternal(family, type); +} + +VirtualSocket* VirtualSocketServer::CreateSocketInternal(int family, int type) { + return new VirtualSocket(this, family, type, true); +} + +void VirtualSocketServer::SetMessageQueue(MessageQueue* msg_queue) { + msg_queue_ = msg_queue; + if (msg_queue_) { + msg_queue_->SignalQueueDestroyed.connect(this, + &VirtualSocketServer::OnMessageQueueDestroyed); + } +} + +bool VirtualSocketServer::Wait(int cmsWait, bool process_io) { + ASSERT(msg_queue_ == Thread::Current()); + if (stop_on_idle_ && Thread::Current()->empty()) { + return false; + } + return socketserver()->Wait(cmsWait, process_io); +} + +void VirtualSocketServer::WakeUp() { + socketserver()->WakeUp(); +} + +bool VirtualSocketServer::ProcessMessagesUntilIdle() { + ASSERT(msg_queue_ == Thread::Current()); + stop_on_idle_ = true; + while (!msg_queue_->empty()) { + Message msg; + if (msg_queue_->Get(&msg, kForever)) { + msg_queue_->Dispatch(&msg); + } + } + stop_on_idle_ = false; + return !msg_queue_->IsQuitting(); +} + +int VirtualSocketServer::Bind(VirtualSocket* socket, + const SocketAddress& addr) { + ASSERT(NULL != socket); + // Address must be completely specified at this point + ASSERT(!IPIsUnspec(addr.ipaddr())); + ASSERT(addr.port() != 0); + + // Normalize the address (turns v6-mapped addresses into v4-addresses). + SocketAddress normalized(addr.ipaddr().Normalized(), addr.port()); + + AddressMap::value_type entry(normalized, socket); + return bindings_->insert(entry).second ? 0 : -1; +} + +int VirtualSocketServer::Bind(VirtualSocket* socket, SocketAddress* addr) { + ASSERT(NULL != socket); + + if (IPIsAny(addr->ipaddr())) { + addr->SetIP(GetNextIP(addr->ipaddr().family())); + } else if (!IPIsUnspec(addr->ipaddr())) { + addr->SetIP(addr->ipaddr().Normalized()); + } else { + ASSERT(false); + } + + if (addr->port() == 0) { + for (int i = 0; i < kEphemeralPortCount; ++i) { + addr->SetPort(GetNextPort()); + if (bindings_->find(*addr) == bindings_->end()) { + break; + } + } + } + + return Bind(socket, *addr); +} + +VirtualSocket* VirtualSocketServer::LookupBinding(const SocketAddress& addr) { + SocketAddress normalized(addr.ipaddr().Normalized(), + addr.port()); + AddressMap::iterator it = bindings_->find(normalized); + return (bindings_->end() != it) ? it->second : NULL; +} + +int VirtualSocketServer::Unbind(const SocketAddress& addr, + VirtualSocket* socket) { + SocketAddress normalized(addr.ipaddr().Normalized(), + addr.port()); + ASSERT((*bindings_)[normalized] == socket); + bindings_->erase(bindings_->find(normalized)); + return 0; +} + +void VirtualSocketServer::AddConnection(const SocketAddress& local, + const SocketAddress& remote, + VirtualSocket* remote_socket) { + // Add this socket pair to our routing table. This will allow + // multiple clients to connect to the same server address. + SocketAddress local_normalized(local.ipaddr().Normalized(), + local.port()); + SocketAddress remote_normalized(remote.ipaddr().Normalized(), + remote.port()); + SocketAddressPair address_pair(local_normalized, remote_normalized); + connections_->insert(std::pair(address_pair, remote_socket)); +} + +VirtualSocket* VirtualSocketServer::LookupConnection( + const SocketAddress& local, + const SocketAddress& remote) { + SocketAddress local_normalized(local.ipaddr().Normalized(), + local.port()); + SocketAddress remote_normalized(remote.ipaddr().Normalized(), + remote.port()); + SocketAddressPair address_pair(local_normalized, remote_normalized); + ConnectionMap::iterator it = connections_->find(address_pair); + return (connections_->end() != it) ? it->second : NULL; +} + +void VirtualSocketServer::RemoveConnection(const SocketAddress& local, + const SocketAddress& remote) { + SocketAddress local_normalized(local.ipaddr().Normalized(), + local.port()); + SocketAddress remote_normalized(remote.ipaddr().Normalized(), + remote.port()); + SocketAddressPair address_pair(local_normalized, remote_normalized); + connections_->erase(address_pair); +} + +static double Random() { + return static_cast(rand()) / RAND_MAX; +} + +int VirtualSocketServer::Connect(VirtualSocket* socket, + const SocketAddress& remote_addr, + bool use_delay) { + uint32 delay = use_delay ? GetRandomTransitDelay() : 0; + VirtualSocket* remote = LookupBinding(remote_addr); + if (!CanInteractWith(socket, remote)) { + LOG(LS_INFO) << "Address family mismatch between " + << socket->GetLocalAddress() << " and " << remote_addr; + return -1; + } + if (remote != NULL) { + SocketAddress addr = socket->GetLocalAddress(); + msg_queue_->PostDelayed(delay, remote, MSG_ID_CONNECT, + new MessageAddress(addr)); + } else { + LOG(LS_INFO) << "No one listening at " << remote_addr; + msg_queue_->PostDelayed(delay, socket, MSG_ID_DISCONNECT); + } + return 0; +} + +bool VirtualSocketServer::Disconnect(VirtualSocket* socket) { + if (socket) { + // Remove the mapping. + msg_queue_->Post(socket, MSG_ID_DISCONNECT); + return true; + } + return false; +} + +int VirtualSocketServer::SendUdp(VirtualSocket* socket, + const char* data, size_t data_size, + const SocketAddress& remote_addr) { + // See if we want to drop this packet. + if (Random() < drop_prob_) { + LOG(LS_VERBOSE) << "Dropping packet: bad luck"; + return static_cast(data_size); + } + + VirtualSocket* recipient = LookupBinding(remote_addr); + if (!recipient) { + // Make a fake recipient for address family checking. + scoped_ptr dummy_socket( + CreateSocketInternal(AF_INET, SOCK_DGRAM)); + dummy_socket->SetLocalAddress(remote_addr); + if (!CanInteractWith(socket, dummy_socket.get())) { + LOG(LS_VERBOSE) << "Incompatible address families: " + << socket->GetLocalAddress() << " and " << remote_addr; + return -1; + } + LOG(LS_VERBOSE) << "No one listening at " << remote_addr; + return static_cast(data_size); + } + + if (!CanInteractWith(socket, recipient)) { + LOG(LS_VERBOSE) << "Incompatible address families: " + << socket->GetLocalAddress() << " and " << remote_addr; + return -1; + } + + CritScope cs(&socket->crit_); + + uint32 cur_time = Time(); + PurgeNetworkPackets(socket, cur_time); + + // Determine whether we have enough bandwidth to accept this packet. To do + // this, we need to update the send queue. Once we know it's current size, + // we know whether we can fit this packet. + // + // NOTE: There are better algorithms for maintaining such a queue (such as + // "Derivative Random Drop"); however, this algorithm is a more accurate + // simulation of what a normal network would do. + + size_t packet_size = data_size + UDP_HEADER_SIZE; + if (socket->network_size_ + packet_size > network_capacity_) { + LOG(LS_VERBOSE) << "Dropping packet: network capacity exceeded"; + return static_cast(data_size); + } + + AddPacketToNetwork(socket, recipient, cur_time, data, data_size, + UDP_HEADER_SIZE, false); + + return static_cast(data_size); +} + +void VirtualSocketServer::SendTcp(VirtualSocket* socket) { + // TCP can't send more data than will fill up the receiver's buffer. + // We track the data that is in the buffer plus data in flight using the + // recipient's recv_buffer_size_. Anything beyond that must be stored in the + // sender's buffer. We will trigger the buffered data to be sent when data + // is read from the recv_buffer. + + // Lookup the local/remote pair in the connections table. + VirtualSocket* recipient = LookupConnection(socket->local_addr_, + socket->remote_addr_); + if (!recipient) { + LOG(LS_VERBOSE) << "Sending data to no one."; + return; + } + + CritScope cs(&socket->crit_); + + uint32 cur_time = Time(); + PurgeNetworkPackets(socket, cur_time); + + while (true) { + size_t available = recv_buffer_capacity_ - recipient->recv_buffer_size_; + size_t max_data_size = _min(available, TCP_MSS - TCP_HEADER_SIZE); + size_t data_size = _min(socket->send_buffer_.size(), max_data_size); + if (0 == data_size) + break; + + AddPacketToNetwork(socket, recipient, cur_time, &socket->send_buffer_[0], + data_size, TCP_HEADER_SIZE, true); + recipient->recv_buffer_size_ += data_size; + + size_t new_buffer_size = socket->send_buffer_.size() - data_size; + // Avoid undefined access beyond the last element of the vector. + // This only happens when new_buffer_size is 0. + if (data_size < socket->send_buffer_.size()) { + // memmove is required for potentially overlapping source/destination. + memmove(&socket->send_buffer_[0], &socket->send_buffer_[data_size], + new_buffer_size); + } + socket->send_buffer_.resize(new_buffer_size); + } + + if (socket->write_enabled_ + && (socket->send_buffer_.size() < send_buffer_capacity_)) { + socket->write_enabled_ = false; + socket->SignalWriteEvent(socket); + } +} + +void VirtualSocketServer::AddPacketToNetwork(VirtualSocket* sender, + VirtualSocket* recipient, + uint32 cur_time, + const char* data, + size_t data_size, + size_t header_size, + bool ordered) { + VirtualSocket::NetworkEntry entry; + entry.size = data_size + header_size; + + sender->network_size_ += entry.size; + uint32 send_delay = SendDelay(static_cast(sender->network_size_)); + entry.done_time = cur_time + send_delay; + sender->network_.push_back(entry); + + // Find the delay for crossing the many virtual hops of the network. + uint32 transit_delay = GetRandomTransitDelay(); + + // Post the packet as a message to be delivered (on our own thread) + Packet* p = new Packet(data, data_size, sender->local_addr_); + uint32 ts = TimeAfter(send_delay + transit_delay); + if (ordered) { + // Ensure that new packets arrive after previous ones + // TODO: consider ordering on a per-socket basis, since this + // introduces artifical delay. + ts = TimeMax(ts, network_delay_); + } + msg_queue_->PostAt(ts, recipient, MSG_ID_PACKET, p); + network_delay_ = TimeMax(ts, network_delay_); +} + +void VirtualSocketServer::PurgeNetworkPackets(VirtualSocket* socket, + uint32 cur_time) { + while (!socket->network_.empty() && + (socket->network_.front().done_time <= cur_time)) { + ASSERT(socket->network_size_ >= socket->network_.front().size); + socket->network_size_ -= socket->network_.front().size; + socket->network_.pop_front(); + } +} + +uint32 VirtualSocketServer::SendDelay(uint32 size) { + if (bandwidth_ == 0) + return 0; + else + return 1000 * size / bandwidth_; +} + +#if 0 +void PrintFunction(std::vector >* f) { + return; + double sum = 0; + for (uint32 i = 0; i < f->size(); ++i) { + std::cout << (*f)[i].first << '\t' << (*f)[i].second << std::endl; + sum += (*f)[i].second; + } + if (!f->empty()) { + const double mean = sum / f->size(); + double sum_sq_dev = 0; + for (uint32 i = 0; i < f->size(); ++i) { + double dev = (*f)[i].second - mean; + sum_sq_dev += dev * dev; + } + std::cout << "Mean = " << mean << " StdDev = " + << sqrt(sum_sq_dev / f->size()) << std::endl; + } +} +#endif // + +void VirtualSocketServer::UpdateDelayDistribution() { + Function* dist = CreateDistribution(delay_mean_, delay_stddev_, + delay_samples_); + // We take a lock just to make sure we don't leak memory. + { + CritScope cs(&delay_crit_); + delete delay_dist_; + delay_dist_ = dist; + } +} + +static double PI = 4 * atan(1.0); + +static double Normal(double x, double mean, double stddev) { + double a = (x - mean) * (x - mean) / (2 * stddev * stddev); + return exp(-a) / (stddev * sqrt(2 * PI)); +} + +#if 0 // static unused gives a warning +static double Pareto(double x, double min, double k) { + if (x < min) + return 0; + else + return k * std::pow(min, k) / std::pow(x, k+1); +} +#endif + +VirtualSocketServer::Function* VirtualSocketServer::CreateDistribution( + uint32 mean, uint32 stddev, uint32 samples) { + Function* f = new Function(); + + if (0 == stddev) { + f->push_back(Point(mean, 1.0)); + } else { + double start = 0; + if (mean >= 4 * static_cast(stddev)) + start = mean - 4 * static_cast(stddev); + double end = mean + 4 * static_cast(stddev); + + for (uint32 i = 0; i < samples; i++) { + double x = start + (end - start) * i / (samples - 1); + double y = Normal(x, mean, stddev); + f->push_back(Point(x, y)); + } + } + return Resample(Invert(Accumulate(f)), 0, 1, samples); +} + +uint32 VirtualSocketServer::GetRandomTransitDelay() { + size_t index = rand() % delay_dist_->size(); + double delay = (*delay_dist_)[index].second; + //LOG_F(LS_INFO) << "random[" << index << "] = " << delay; + return static_cast(delay); +} + +struct FunctionDomainCmp { + bool operator()(const VirtualSocketServer::Point& p1, + const VirtualSocketServer::Point& p2) { + return p1.first < p2.first; + } + bool operator()(double v1, const VirtualSocketServer::Point& p2) { + return v1 < p2.first; + } + bool operator()(const VirtualSocketServer::Point& p1, double v2) { + return p1.first < v2; + } +}; + +VirtualSocketServer::Function* VirtualSocketServer::Accumulate(Function* f) { + ASSERT(f->size() >= 1); + double v = 0; + for (Function::size_type i = 0; i < f->size() - 1; ++i) { + double dx = (*f)[i + 1].first - (*f)[i].first; + double avgy = ((*f)[i + 1].second + (*f)[i].second) / 2; + (*f)[i].second = v; + v = v + dx * avgy; + } + (*f)[f->size()-1].second = v; + return f; +} + +VirtualSocketServer::Function* VirtualSocketServer::Invert(Function* f) { + for (Function::size_type i = 0; i < f->size(); ++i) + std::swap((*f)[i].first, (*f)[i].second); + + std::sort(f->begin(), f->end(), FunctionDomainCmp()); + return f; +} + +VirtualSocketServer::Function* VirtualSocketServer::Resample( + Function* f, double x1, double x2, uint32 samples) { + Function* g = new Function(); + + for (size_t i = 0; i < samples; i++) { + double x = x1 + (x2 - x1) * i / (samples - 1); + double y = Evaluate(f, x); + g->push_back(Point(x, y)); + } + + delete f; + return g; +} + +double VirtualSocketServer::Evaluate(Function* f, double x) { + Function::iterator iter = + std::lower_bound(f->begin(), f->end(), x, FunctionDomainCmp()); + if (iter == f->begin()) { + return (*f)[0].second; + } else if (iter == f->end()) { + ASSERT(f->size() >= 1); + return (*f)[f->size() - 1].second; + } else if (iter->first == x) { + return iter->second; + } else { + double x1 = (iter - 1)->first; + double y1 = (iter - 1)->second; + double x2 = iter->first; + double y2 = iter->second; + return y1 + (y2 - y1) * (x - x1) / (x2 - x1); + } +} + +bool VirtualSocketServer::CanInteractWith(VirtualSocket* local, + VirtualSocket* remote) { + if (!local || !remote) { + return false; + } + IPAddress local_ip = local->GetLocalAddress().ipaddr(); + IPAddress remote_ip = remote->GetLocalAddress().ipaddr(); + IPAddress local_normalized = local_ip.Normalized(); + IPAddress remote_normalized = remote_ip.Normalized(); + // Check if the addresses are the same family after Normalization (turns + // mapped IPv6 address into IPv4 addresses). + // This will stop unmapped V6 addresses from talking to mapped V6 addresses. + if (local_normalized.family() == remote_normalized.family()) { + return true; + } + + // If ip1 is IPv4 and ip2 is :: and ip2 is not IPV6_V6ONLY. + int remote_v6_only = 0; + remote->GetOption(Socket::OPT_IPV6_V6ONLY, &remote_v6_only); + if (local_ip.family() == AF_INET && !remote_v6_only && IPIsAny(remote_ip)) { + return true; + } + // Same check, backwards. + int local_v6_only = 0; + local->GetOption(Socket::OPT_IPV6_V6ONLY, &local_v6_only); + if (remote_ip.family() == AF_INET && !local_v6_only && IPIsAny(local_ip)) { + return true; + } + + // Check to see if either socket was explicitly bound to IPv6-any. + // These sockets can talk with anyone. + if (local_ip.family() == AF_INET6 && local->was_any()) { + return true; + } + if (remote_ip.family() == AF_INET6 && remote->was_any()) { + return true; + } + + return false; +} + +} // namespace rtc diff --git a/webrtc/base/virtualsocketserver.h b/webrtc/base/virtualsocketserver.h new file mode 100644 index 000000000..87e35364c --- /dev/null +++ b/webrtc/base/virtualsocketserver.h @@ -0,0 +1,234 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_VIRTUALSOCKETSERVER_H_ +#define WEBRTC_BASE_VIRTUALSOCKETSERVER_H_ + +#include + +#include +#include + +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/socketserver.h" + +namespace rtc { + +class VirtualSocket; +class SocketAddressPair; + +// Simulates a network in the same manner as a loopback interface. The +// interface can create as many addresses as you want. All of the sockets +// created by this network will be able to communicate with one another, unless +// they are bound to addresses from incompatible families. +class VirtualSocketServer : public SocketServer, public sigslot::has_slots<> { + public: + // TODO: Add "owned" parameter. + // If "owned" is set, the supplied socketserver will be deleted later. + explicit VirtualSocketServer(SocketServer* ss); + virtual ~VirtualSocketServer(); + + SocketServer* socketserver() { return server_; } + + // Limits the network bandwidth (maximum bytes per second). Zero means that + // all sends occur instantly. Defaults to 0. + uint32 bandwidth() const { return bandwidth_; } + void set_bandwidth(uint32 bandwidth) { bandwidth_ = bandwidth; } + + // Limits the amount of data which can be in flight on the network without + // packet loss (on a per sender basis). Defaults to 64 KB. + uint32 network_capacity() const { return network_capacity_; } + void set_network_capacity(uint32 capacity) { + network_capacity_ = capacity; + } + + // The amount of data which can be buffered by tcp on the sender's side + uint32 send_buffer_capacity() const { return send_buffer_capacity_; } + void set_send_buffer_capacity(uint32 capacity) { + send_buffer_capacity_ = capacity; + } + + // The amount of data which can be buffered by tcp on the receiver's side + uint32 recv_buffer_capacity() const { return recv_buffer_capacity_; } + void set_recv_buffer_capacity(uint32 capacity) { + recv_buffer_capacity_ = capacity; + } + + // Controls the (transit) delay for packets sent in the network. This does + // not inclue the time required to sit in the send queue. Both of these + // values are measured in milliseconds. Defaults to no delay. + uint32 delay_mean() const { return delay_mean_; } + uint32 delay_stddev() const { return delay_stddev_; } + uint32 delay_samples() const { return delay_samples_; } + void set_delay_mean(uint32 delay_mean) { delay_mean_ = delay_mean; } + void set_delay_stddev(uint32 delay_stddev) { + delay_stddev_ = delay_stddev; + } + void set_delay_samples(uint32 delay_samples) { + delay_samples_ = delay_samples; + } + + // If the (transit) delay parameters are modified, this method should be + // called to recompute the new distribution. + void UpdateDelayDistribution(); + + // Controls the (uniform) probability that any sent packet is dropped. This + // is separate from calculations to drop based on queue size. + double drop_probability() { return drop_prob_; } + void set_drop_probability(double drop_prob) { + assert((0 <= drop_prob) && (drop_prob <= 1)); + drop_prob_ = drop_prob; + } + + // SocketFactory: + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + // SocketServer: + virtual void SetMessageQueue(MessageQueue* queue); + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + typedef std::pair Point; + typedef std::vector Function; + + static Function* CreateDistribution(uint32 mean, uint32 stddev, + uint32 samples); + + // Similar to Thread::ProcessMessages, but it only processes messages until + // there are no immediate messages or pending network traffic. Returns false + // if Thread::Stop() was called. + bool ProcessMessagesUntilIdle(); + + protected: + // Returns a new IP not used before in this network. + IPAddress GetNextIP(int family); + uint16 GetNextPort(); + + VirtualSocket* CreateSocketInternal(int family, int type); + + // Binds the given socket to addr, assigning and IP and Port if necessary + int Bind(VirtualSocket* socket, SocketAddress* addr); + + // Binds the given socket to the given (fully-defined) address. + int Bind(VirtualSocket* socket, const SocketAddress& addr); + + // Find the socket bound to the given address + VirtualSocket* LookupBinding(const SocketAddress& addr); + + int Unbind(const SocketAddress& addr, VirtualSocket* socket); + + // Adds a mapping between this socket pair and the socket. + void AddConnection(const SocketAddress& client, + const SocketAddress& server, + VirtualSocket* socket); + + // Find the socket pair corresponding to this server address. + VirtualSocket* LookupConnection(const SocketAddress& client, + const SocketAddress& server); + + void RemoveConnection(const SocketAddress& client, + const SocketAddress& server); + + // Connects the given socket to the socket at the given address + int Connect(VirtualSocket* socket, const SocketAddress& remote_addr, + bool use_delay); + + // Sends a disconnect message to the socket at the given address + bool Disconnect(VirtualSocket* socket); + + // Sends the given packet to the socket at the given address (if one exists). + int SendUdp(VirtualSocket* socket, const char* data, size_t data_size, + const SocketAddress& remote_addr); + + // Moves as much data as possible from the sender's buffer to the network + void SendTcp(VirtualSocket* socket); + + // Places a packet on the network. + void AddPacketToNetwork(VirtualSocket* socket, VirtualSocket* recipient, + uint32 cur_time, const char* data, size_t data_size, + size_t header_size, bool ordered); + + // Removes stale packets from the network + void PurgeNetworkPackets(VirtualSocket* socket, uint32 cur_time); + + // Computes the number of milliseconds required to send a packet of this size. + uint32 SendDelay(uint32 size); + + // Returns a random transit delay chosen from the appropriate distribution. + uint32 GetRandomTransitDelay(); + + // Basic operations on functions. Those that return a function also take + // ownership of the function given (and hence, may modify or delete it). + static Function* Accumulate(Function* f); + static Function* Invert(Function* f); + static Function* Resample(Function* f, double x1, double x2, uint32 samples); + static double Evaluate(Function* f, double x); + + // NULL out our message queue if it goes away. Necessary in the case where + // our lifetime is greater than that of the thread we are using, since we + // try to send Close messages for all connected sockets when we shutdown. + void OnMessageQueueDestroyed() { msg_queue_ = NULL; } + + // Determine if two sockets should be able to communicate. + // We don't (currently) specify an address family for sockets; instead, + // the currently bound address is used to infer the address family. + // Any socket that is not explicitly bound to an IPv4 address is assumed to be + // dual-stack capable. + // This function tests if two addresses can communicate, as well as the + // sockets to which they may be bound (the addresses may or may not yet be + // bound to the sockets). + // First the addresses are tested (after normalization): + // If both have the same family, then communication is OK. + // If only one is IPv4 then false, unless the other is bound to ::. + // This applies even if the IPv4 address is 0.0.0.0. + // The socket arguments are optional; the sockets are checked to see if they + // were explicitly bound to IPv6-any ('::'), and if so communication is + // permitted. + // NB: This scheme doesn't permit non-dualstack IPv6 sockets. + static bool CanInteractWith(VirtualSocket* local, VirtualSocket* remote); + + private: + friend class VirtualSocket; + + typedef std::map AddressMap; + typedef std::map ConnectionMap; + + SocketServer* server_; + bool server_owned_; + MessageQueue* msg_queue_; + bool stop_on_idle_; + uint32 network_delay_; + in_addr next_ipv4_; + in6_addr next_ipv6_; + uint16 next_port_; + AddressMap* bindings_; + ConnectionMap* connections_; + + uint32 bandwidth_; + uint32 network_capacity_; + uint32 send_buffer_capacity_; + uint32 recv_buffer_capacity_; + uint32 delay_mean_; + uint32 delay_stddev_; + uint32 delay_samples_; + Function* delay_dist_; + CriticalSection delay_crit_; + + double drop_prob_; + DISALLOW_EVIL_CONSTRUCTORS(VirtualSocketServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_VIRTUALSOCKETSERVER_H_ diff --git a/webrtc/base/win32.cc b/webrtc/base/win32.cc new file mode 100644 index 000000000..8f5661225 --- /dev/null +++ b/webrtc/base/win32.cc @@ -0,0 +1,456 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/win32.h" + +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +// Helper function declarations for inet_ntop/inet_pton. +static const char* inet_ntop_v4(const void* src, char* dst, socklen_t size); +static const char* inet_ntop_v6(const void* src, char* dst, socklen_t size); +static int inet_pton_v4(const char* src, void* dst); +static int inet_pton_v6(const char* src, void* dst); + +// Implementation of inet_ntop (create a printable representation of an +// ip address). XP doesn't have its own inet_ntop, and +// WSAAddressToString requires both IPv6 to be installed and for Winsock +// to be initialized. +const char* win32_inet_ntop(int af, const void *src, + char* dst, socklen_t size) { + if (!src || !dst) { + return NULL; + } + switch (af) { + case AF_INET: { + return inet_ntop_v4(src, dst, size); + } + case AF_INET6: { + return inet_ntop_v6(src, dst, size); + } + } + return NULL; +} + +// As above, but for inet_pton. Implements inet_pton for v4 and v6. +// Note that our inet_ntop will output normal 'dotted' v4 addresses only. +int win32_inet_pton(int af, const char* src, void* dst) { + if (!src || !dst) { + return 0; + } + if (af == AF_INET) { + return inet_pton_v4(src, dst); + } else if (af == AF_INET6) { + return inet_pton_v6(src, dst); + } + return -1; +} + +// Helper function for inet_ntop for IPv4 addresses. +// Outputs "dotted-quad" decimal notation. +const char* inet_ntop_v4(const void* src, char* dst, socklen_t size) { + if (size < INET_ADDRSTRLEN) { + return NULL; + } + const struct in_addr* as_in_addr = + reinterpret_cast(src); + rtc::sprintfn(dst, size, "%d.%d.%d.%d", + as_in_addr->S_un.S_un_b.s_b1, + as_in_addr->S_un.S_un_b.s_b2, + as_in_addr->S_un.S_un_b.s_b3, + as_in_addr->S_un.S_un_b.s_b4); + return dst; +} + +// Helper function for inet_ntop for IPv6 addresses. +const char* inet_ntop_v6(const void* src, char* dst, socklen_t size) { + if (size < INET6_ADDRSTRLEN) { + return NULL; + } + const uint16* as_shorts = + reinterpret_cast(src); + int runpos[8]; + int current = 1; + int max = 1; + int maxpos = -1; + int run_array_size = ARRAY_SIZE(runpos); + // Run over the address marking runs of 0s. + for (int i = 0; i < run_array_size; ++i) { + if (as_shorts[i] == 0) { + runpos[i] = current; + if (current > max) { + maxpos = i; + max = current; + } + ++current; + } else { + runpos[i] = -1; + current =1; + } + } + + if (max > 1) { + int tmpmax = maxpos; + // Run back through, setting -1 for all but the longest run. + for (int i = run_array_size - 1; i >= 0; i--) { + if (i > tmpmax) { + runpos[i] = -1; + } else if (runpos[i] == -1) { + // We're less than maxpos, we hit a -1, so the 'good' run is done. + // Setting tmpmax -1 means all remaining positions get set to -1. + tmpmax = -1; + } + } + } + + char* cursor = dst; + // Print IPv4 compatible and IPv4 mapped addresses using the IPv4 helper. + // These addresses have an initial run of either eight zero-bytes followed + // by 0xFFFF, or an initial run of ten zero-bytes. + if (runpos[0] == 1 && (maxpos == 5 || + (maxpos == 4 && as_shorts[5] == 0xFFFF))) { + *cursor++ = ':'; + *cursor++ = ':'; + if (maxpos == 4) { + cursor += rtc::sprintfn(cursor, INET6_ADDRSTRLEN - 2, "ffff:"); + } + const struct in_addr* as_v4 = + reinterpret_cast(&(as_shorts[6])); + inet_ntop_v4(as_v4, cursor, + static_cast(INET6_ADDRSTRLEN - (cursor - dst))); + } else { + for (int i = 0; i < run_array_size; ++i) { + if (runpos[i] == -1) { + cursor += rtc::sprintfn(cursor, + INET6_ADDRSTRLEN - (cursor - dst), + "%x", NetworkToHost16(as_shorts[i])); + if (i != 7 && runpos[i + 1] != 1) { + *cursor++ = ':'; + } + } else if (runpos[i] == 1) { + // Entered the run; print the colons and skip the run. + *cursor++ = ':'; + *cursor++ = ':'; + i += (max - 1); + } + } + } + return dst; +} + +// Helper function for inet_pton for IPv4 addresses. +// |src| points to a character string containing an IPv4 network address in +// dotted-decimal format, "ddd.ddd.ddd.ddd", where ddd is a decimal number +// of up to three digits in the range 0 to 255. +// The address is converted and copied to dst, +// which must be sizeof(struct in_addr) (4) bytes (32 bits) long. +int inet_pton_v4(const char* src, void* dst) { + const int kIpv4AddressSize = 4; + int found = 0; + const char* src_pos = src; + unsigned char result[kIpv4AddressSize] = {0}; + + while (*src_pos != '\0') { + // strtol won't treat whitespace characters in the begining as an error, + // so check to ensure this is started with digit before passing to strtol. + if (!isdigit(*src_pos)) { + return 0; + } + char* end_pos; + long value = strtol(src_pos, &end_pos, 10); + if (value < 0 || value > 255 || src_pos == end_pos) { + return 0; + } + ++found; + if (found > kIpv4AddressSize) { + return 0; + } + result[found - 1] = static_cast(value); + src_pos = end_pos; + if (*src_pos == '.') { + // There's more. + ++src_pos; + } else if (*src_pos != '\0') { + // If it's neither '.' nor '\0' then return fail. + return 0; + } + } + if (found != kIpv4AddressSize) { + return 0; + } + memcpy(dst, result, sizeof(result)); + return 1; +} + +// Helper function for inet_pton for IPv6 addresses. +int inet_pton_v6(const char* src, void* dst) { + // sscanf will pick any other invalid chars up, but it parses 0xnnnn as hex. + // Check for literal x in the input string. + const char* readcursor = src; + char c = *readcursor++; + while (c) { + if (c == 'x') { + return 0; + } + c = *readcursor++; + } + readcursor = src; + + struct in6_addr an_addr; + memset(&an_addr, 0, sizeof(an_addr)); + + uint16* addr_cursor = reinterpret_cast(&an_addr.s6_addr[0]); + uint16* addr_end = reinterpret_cast(&an_addr.s6_addr[16]); + bool seencompressed = false; + + // Addresses that start with "::" (i.e., a run of initial zeros) or + // "::ffff:" can potentially be IPv4 mapped or compatibility addresses. + // These have dotted-style IPv4 addresses on the end (e.g. "::192.168.7.1"). + if (*readcursor == ':' && *(readcursor+1) == ':' && + *(readcursor + 2) != 0) { + // Check for periods, which we'll take as a sign of v4 addresses. + const char* addrstart = readcursor + 2; + if (rtc::strchr(addrstart, ".")) { + const char* colon = rtc::strchr(addrstart, "::"); + if (colon) { + uint16 a_short; + int bytesread = 0; + if (sscanf(addrstart, "%hx%n", &a_short, &bytesread) != 1 || + a_short != 0xFFFF || bytesread != 4) { + // Colons + periods means has to be ::ffff:a.b.c.d. But it wasn't. + return 0; + } else { + an_addr.s6_addr[10] = 0xFF; + an_addr.s6_addr[11] = 0xFF; + addrstart = colon + 1; + } + } + struct in_addr v4; + if (inet_pton_v4(addrstart, &v4.s_addr)) { + memcpy(&an_addr.s6_addr[12], &v4, sizeof(v4)); + memcpy(dst, &an_addr, sizeof(an_addr)); + return 1; + } else { + // Invalid v4 address. + return 0; + } + } + } + + // For addresses without a trailing IPv4 component ('normal' IPv6 addresses). + while (*readcursor != 0 && addr_cursor < addr_end) { + if (*readcursor == ':') { + if (*(readcursor + 1) == ':') { + if (seencompressed) { + // Can only have one compressed run of zeroes ("::") per address. + return 0; + } + // Hit a compressed run. Count colons to figure out how much of the + // address is skipped. + readcursor += 2; + const char* coloncounter = readcursor; + int coloncount = 0; + if (*coloncounter == 0) { + // Special case - trailing ::. + addr_cursor = addr_end; + } else { + while (*coloncounter) { + if (*coloncounter == ':') { + ++coloncount; + } + ++coloncounter; + } + // (coloncount + 1) is the number of shorts left in the address. + addr_cursor = addr_end - (coloncount + 1); + seencompressed = true; + } + } else { + ++readcursor; + } + } else { + uint16 word; + int bytesread = 0; + if (sscanf(readcursor, "%hx%n", &word, &bytesread) != 1) { + return 0; + } else { + *addr_cursor = HostToNetwork16(word); + ++addr_cursor; + readcursor += bytesread; + if (*readcursor != ':' && *readcursor != '\0') { + return 0; + } + } + } + } + + if (*readcursor != '\0' || addr_cursor < addr_end) { + // Catches addresses too short or too long. + return 0; + } + memcpy(dst, &an_addr, sizeof(an_addr)); + return 1; +} + +// +// Unix time is in seconds relative to 1/1/1970. So we compute the windows +// FILETIME of that time/date, then we add/subtract in appropriate units to +// convert to/from unix time. +// The units of FILETIME are 100ns intervals, so by multiplying by or dividing +// by 10000000, we can convert to/from seconds. +// +// FileTime = UnixTime*10000000 + FileTime(1970) +// UnixTime = (FileTime-FileTime(1970))/10000000 +// + +void FileTimeToUnixTime(const FILETIME& ft, time_t* ut) { + ASSERT(NULL != ut); + + // FILETIME has an earlier date base than time_t (1/1/1970), so subtract off + // the difference. + SYSTEMTIME base_st; + memset(&base_st, 0, sizeof(base_st)); + base_st.wDay = 1; + base_st.wMonth = 1; + base_st.wYear = 1970; + + FILETIME base_ft; + SystemTimeToFileTime(&base_st, &base_ft); + + ULARGE_INTEGER base_ul, current_ul; + memcpy(&base_ul, &base_ft, sizeof(FILETIME)); + memcpy(¤t_ul, &ft, sizeof(FILETIME)); + + // Divide by big number to convert to seconds, then subtract out the 1970 + // base date value. + const ULONGLONG RATIO = 10000000; + *ut = static_cast((current_ul.QuadPart - base_ul.QuadPart) / RATIO); +} + +void UnixTimeToFileTime(const time_t& ut, FILETIME* ft) { + ASSERT(NULL != ft); + + // FILETIME has an earlier date base than time_t (1/1/1970), so add in + // the difference. + SYSTEMTIME base_st; + memset(&base_st, 0, sizeof(base_st)); + base_st.wDay = 1; + base_st.wMonth = 1; + base_st.wYear = 1970; + + FILETIME base_ft; + SystemTimeToFileTime(&base_st, &base_ft); + + ULARGE_INTEGER base_ul; + memcpy(&base_ul, &base_ft, sizeof(FILETIME)); + + // Multiply by big number to convert to 100ns units, then add in the 1970 + // base date value. + const ULONGLONG RATIO = 10000000; + ULARGE_INTEGER current_ul; + current_ul.QuadPart = base_ul.QuadPart + static_cast(ut) * RATIO; + memcpy(ft, ¤t_ul, sizeof(FILETIME)); +} + +bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename) { + // TODO: Integrate into fileutils.h + // TODO: Handle wide and non-wide cases via TCHAR? + // TODO: Skip \\?\ processing if the length is not > MAX_PATH? + // TODO: Write unittests + + // Convert to Utf16 + int wlen = ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), + static_cast(utf8.length() + 1), NULL, + 0); + if (0 == wlen) { + return false; + } + wchar_t* wfilename = STACK_ARRAY(wchar_t, wlen); + if (0 == ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), + static_cast(utf8.length() + 1), + wfilename, wlen)) { + return false; + } + // Replace forward slashes with backslashes + std::replace(wfilename, wfilename + wlen, L'/', L'\\'); + // Convert to complete filename + DWORD full_len = ::GetFullPathName(wfilename, 0, NULL, NULL); + if (0 == full_len) { + return false; + } + wchar_t* filepart = NULL; + wchar_t* full_filename = STACK_ARRAY(wchar_t, full_len + 6); + wchar_t* start = full_filename + 6; + if (0 == ::GetFullPathName(wfilename, full_len, start, &filepart)) { + return false; + } + // Add long-path prefix + const wchar_t kLongPathPrefix[] = L"\\\\?\\UNC"; + if ((start[0] != L'\\') || (start[1] != L'\\')) { + // Non-unc path: + // Becomes: \\?\ + start -= 4; + ASSERT(start >= full_filename); + memcpy(start, kLongPathPrefix, 4 * sizeof(wchar_t)); + } else if (start[2] != L'?') { + // Unc path: \\\ + // Becomes: \\?\UNC\\ + start -= 6; + ASSERT(start >= full_filename); + memcpy(start, kLongPathPrefix, 7 * sizeof(wchar_t)); + } else { + // Already in long-path form. + } + filename->assign(start); + return true; +} + +bool GetOsVersion(int* major, int* minor, int* build) { + OSVERSIONINFO info = {0}; + info.dwOSVersionInfoSize = sizeof(info); + if (GetVersionEx(&info)) { + if (major) *major = info.dwMajorVersion; + if (minor) *minor = info.dwMinorVersion; + if (build) *build = info.dwBuildNumber; + return true; + } + return false; +} + +bool GetCurrentProcessIntegrityLevel(int* level) { + bool ret = false; + HANDLE process = ::GetCurrentProcess(), token; + if (OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, &token)) { + DWORD size; + if (!GetTokenInformation(token, TokenIntegrityLevel, NULL, 0, &size) && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + + char* buf = STACK_ARRAY(char, size); + TOKEN_MANDATORY_LABEL* til = + reinterpret_cast(buf); + if (GetTokenInformation(token, TokenIntegrityLevel, til, size, &size)) { + + DWORD count = *GetSidSubAuthorityCount(til->Label.Sid); + *level = *GetSidSubAuthority(til->Label.Sid, count - 1); + ret = true; + } + } + CloseHandle(token); + } + return ret; +} +} // namespace rtc diff --git a/webrtc/base/win32.h b/webrtc/base/win32.h new file mode 100644 index 000000000..bf5da254c --- /dev/null +++ b/webrtc/base/win32.h @@ -0,0 +1,129 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WIN32_H_ +#define WEBRTC_BASE_WIN32_H_ + +#if defined(WEBRTC_WIN) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +// Make sure we don't get min/max macros +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include + +#ifndef SECURITY_MANDATORY_LABEL_AUTHORITY +// Add defines that we use if we are compiling against older sdks +#define SECURITY_MANDATORY_MEDIUM_RID (0x00002000L) +#define TokenIntegrityLevel static_cast(0x19) +typedef struct _TOKEN_MANDATORY_LABEL { + SID_AND_ATTRIBUTES Label; +} TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL; +#endif // SECURITY_MANDATORY_LABEL_AUTHORITY + +#undef SetPort + +#include + +#include "webrtc/base/stringutils.h" +#include "webrtc/base/basictypes.h" + +namespace rtc { + +const char* win32_inet_ntop(int af, const void *src, char* dst, socklen_t size); +int win32_inet_pton(int af, const char* src, void *dst); + +/////////////////////////////////////////////////////////////////////////////// + +inline std::wstring ToUtf16(const char* utf8, size_t len) { + int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast(len), + NULL, 0); + wchar_t* ws = STACK_ARRAY(wchar_t, len16); + ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast(len), ws, len16); + return std::wstring(ws, len16); +} + +inline std::wstring ToUtf16(const std::string& str) { + return ToUtf16(str.data(), str.length()); +} + +inline std::string ToUtf8(const wchar_t* wide, size_t len) { + int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast(len), + NULL, 0, NULL, NULL); + char* ns = STACK_ARRAY(char, len8); + ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast(len), ns, len8, + NULL, NULL); + return std::string(ns, len8); +} + +inline std::string ToUtf8(const wchar_t* wide) { + return ToUtf8(wide, wcslen(wide)); +} + +inline std::string ToUtf8(const std::wstring& wstr) { + return ToUtf8(wstr.data(), wstr.length()); +} + +// Convert FILETIME to time_t +void FileTimeToUnixTime(const FILETIME& ft, time_t* ut); + +// Convert time_t to FILETIME +void UnixTimeToFileTime(const time_t& ut, FILETIME * ft); + +// Convert a Utf8 path representation to a non-length-limited Unicode pathname. +bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename); + +// Convert a FILETIME to a UInt64 +inline uint64 ToUInt64(const FILETIME& ft) { + ULARGE_INTEGER r = {ft.dwLowDateTime, ft.dwHighDateTime}; + return r.QuadPart; +} + +enum WindowsMajorVersions { + kWindows2000 = 5, + kWindowsVista = 6, +}; +bool GetOsVersion(int* major, int* minor, int* build); + +inline bool IsWindowsVistaOrLater() { + int major; + return (GetOsVersion(&major, NULL, NULL) && major >= kWindowsVista); +} + +inline bool IsWindowsXpOrLater() { + int major, minor; + return (GetOsVersion(&major, &minor, NULL) && + (major >= kWindowsVista || + (major == kWindows2000 && minor >= 1))); +} + +// Determine the current integrity level of the process. +bool GetCurrentProcessIntegrityLevel(int* level); + +inline bool IsCurrentProcessLowIntegrity() { + int level; + return (GetCurrentProcessIntegrityLevel(&level) && + level < SECURITY_MANDATORY_MEDIUM_RID); +} + +bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_WIN +#endif // WEBRTC_BASE_WIN32_H_ diff --git a/webrtc/base/win32_unittest.cc b/webrtc/base/win32_unittest.cc new file mode 100644 index 000000000..0050c7726 --- /dev/null +++ b/webrtc/base/win32_unittest.cc @@ -0,0 +1,62 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/win32.h" +#include "webrtc/base/winping.h" + +#if !defined(WEBRTC_WIN) +#error Only for Windows +#endif + +namespace rtc { + +class Win32Test : public testing::Test { + public: + Win32Test() { + } +}; + +TEST_F(Win32Test, FileTimeToUInt64Test) { + FILETIME ft; + ft.dwHighDateTime = 0xBAADF00D; + ft.dwLowDateTime = 0xFEED3456; + + uint64 expected = 0xBAADF00DFEED3456; + EXPECT_EQ(expected, ToUInt64(ft)); +} + +TEST_F(Win32Test, WinPingTest) { + WinPing ping; + ASSERT_TRUE(ping.IsValid()); + + // Test valid ping cases. + WinPing::PingResult result = ping.Ping(IPAddress(INADDR_LOOPBACK), 20, 50, 1, + false); + ASSERT_EQ(WinPing::PING_SUCCESS, result); + if (HasIPv6Enabled()) { + WinPing::PingResult v6result = ping.Ping(IPAddress(in6addr_loopback), 20, + 50, 1, false); + ASSERT_EQ(WinPing::PING_SUCCESS, v6result); + } + + // Test invalid parameter cases. + ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping( + IPAddress(INADDR_LOOPBACK), 0, 50, 1, false)); + ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping( + IPAddress(INADDR_LOOPBACK), 20, 0, 1, false)); + ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping( + IPAddress(INADDR_LOOPBACK), 20, 50, 0, false)); +} + +} // namespace rtc diff --git a/webrtc/base/win32filesystem.cc b/webrtc/base/win32filesystem.cc new file mode 100644 index 000000000..73f8ef0cf --- /dev/null +++ b/webrtc/base/win32filesystem.cc @@ -0,0 +1,460 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/win32filesystem.h" + +#include "webrtc/base/win32.h" +#include +#include +#include + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringutils.h" + +// In several places in this file, we test the integrity level of the process +// before calling GetLongPathName. We do this because calling GetLongPathName +// when running under protected mode IE (a low integrity process) can result in +// a virtualized path being returned, which is wrong if you only plan to read. +// TODO: Waiting to hear back from IE team on whether this is the +// best approach; IEIsProtectedModeProcess is another possible solution. + +namespace rtc { + +bool Win32Filesystem::CreateFolder(const Pathname &pathname) { + if (pathname.pathname().empty() || !pathname.filename().empty()) + return false; + + std::wstring path16; + if (!Utf8ToWindowsFilename(pathname.pathname(), &path16)) + return false; + + DWORD res = ::GetFileAttributes(path16.c_str()); + if (res != INVALID_FILE_ATTRIBUTES) { + // Something exists at this location, check if it is a directory + return ((res & FILE_ATTRIBUTE_DIRECTORY) != 0); + } else if ((GetLastError() != ERROR_FILE_NOT_FOUND) + && (GetLastError() != ERROR_PATH_NOT_FOUND)) { + // Unexpected error + return false; + } + + // Directory doesn't exist, look up one directory level + if (!pathname.parent_folder().empty()) { + Pathname parent(pathname); + parent.SetFolder(pathname.parent_folder()); + if (!CreateFolder(parent)) { + return false; + } + } + + return (::CreateDirectory(path16.c_str(), NULL) != 0); +} + +FileStream *Win32Filesystem::OpenFile(const Pathname &filename, + const std::string &mode) { + FileStream *fs = new FileStream(); + if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) { + delete fs; + fs = NULL; + } + return fs; +} + +bool Win32Filesystem::CreatePrivateFile(const Pathname &filename) { + // To make the file private to the current user, we first must construct a + // SECURITY_DESCRIPTOR specifying an ACL. This code is mostly based upon + // http://msdn.microsoft.com/en-us/library/ms707085%28VS.85%29.aspx + + // Get the current process token. + HANDLE process_token = INVALID_HANDLE_VALUE; + if (!::OpenProcessToken(::GetCurrentProcess(), + TOKEN_QUERY, + &process_token)) { + LOG_ERR(LS_ERROR) << "OpenProcessToken() failed"; + return false; + } + + // Get the size of its TOKEN_USER structure. Return value is not checked + // because we expect it to fail. + DWORD token_user_size = 0; + (void)::GetTokenInformation(process_token, + TokenUser, + NULL, + 0, + &token_user_size); + + // Get the TOKEN_USER structure. + scoped_ptr token_user_bytes(new char[token_user_size]); + PTOKEN_USER token_user = reinterpret_cast( + token_user_bytes.get()); + memset(token_user, 0, token_user_size); + BOOL success = ::GetTokenInformation(process_token, + TokenUser, + token_user, + token_user_size, + &token_user_size); + // We're now done with this. + ::CloseHandle(process_token); + if (!success) { + LOG_ERR(LS_ERROR) << "GetTokenInformation() failed"; + return false; + } + + if (!IsValidSid(token_user->User.Sid)) { + LOG_ERR(LS_ERROR) << "Current process has invalid user SID"; + return false; + } + + // Compute size needed for an ACL that allows access to just this user. + int acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + + GetLengthSid(token_user->User.Sid); + + // Allocate it. + scoped_ptr acl_bytes(new char[acl_size]); + PACL acl = reinterpret_cast(acl_bytes.get()); + memset(acl, 0, acl_size); + if (!::InitializeAcl(acl, acl_size, ACL_REVISION)) { + LOG_ERR(LS_ERROR) << "InitializeAcl() failed"; + return false; + } + + // Allow access to only the current user. + if (!::AddAccessAllowedAce(acl, + ACL_REVISION, + GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL, + token_user->User.Sid)) { + LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed"; + return false; + } + + // Now make the security descriptor. + SECURITY_DESCRIPTOR security_descriptor; + if (!::InitializeSecurityDescriptor(&security_descriptor, + SECURITY_DESCRIPTOR_REVISION)) { + LOG_ERR(LS_ERROR) << "InitializeSecurityDescriptor() failed"; + return false; + } + + // Put the ACL in it. + if (!::SetSecurityDescriptorDacl(&security_descriptor, + TRUE, + acl, + FALSE)) { + LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed"; + return false; + } + + // Finally create the file. + SECURITY_ATTRIBUTES security_attributes; + security_attributes.nLength = sizeof(security_attributes); + security_attributes.lpSecurityDescriptor = &security_descriptor; + security_attributes.bInheritHandle = FALSE; + HANDLE handle = ::CreateFile( + ToUtf16(filename.pathname()).c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + &security_attributes, + CREATE_NEW, + 0, + NULL); + if (INVALID_HANDLE_VALUE == handle) { + LOG_ERR(LS_ERROR) << "CreateFile() failed"; + return false; + } + if (!::CloseHandle(handle)) { + LOG_ERR(LS_ERROR) << "CloseFile() failed"; + // Continue. + } + return true; +} + +bool Win32Filesystem::DeleteFile(const Pathname &filename) { + LOG(LS_INFO) << "Deleting file " << filename.pathname(); + if (!IsFile(filename)) { + ASSERT(IsFile(filename)); + return false; + } + return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0; +} + +bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) { + LOG(LS_INFO) << "Deleting folder " << folder.pathname(); + + std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1); + return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0; +} + +bool Win32Filesystem::GetTemporaryFolder(Pathname &pathname, bool create, + const std::string *append) { + wchar_t buffer[MAX_PATH + 1]; + if (!::GetTempPath(ARRAY_SIZE(buffer), buffer)) + return false; + if (!IsCurrentProcessLowIntegrity() && + !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + size_t len = strlen(buffer); + if ((len > 0) && (buffer[len-1] != '\\')) { + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, L"\\"); + } + if (len >= ARRAY_SIZE(buffer) - 1) + return false; + pathname.clear(); + pathname.SetFolder(ToUtf8(buffer)); + if (append != NULL) { + ASSERT(!append->empty()); + pathname.AppendFolder(*append); + } + return !create || CreateFolder(pathname); +} + +std::string Win32Filesystem::TempFilename(const Pathname &dir, + const std::string &prefix) { + wchar_t filename[MAX_PATH]; + if (::GetTempFileName(ToUtf16(dir.pathname()).c_str(), + ToUtf16(prefix).c_str(), 0, filename) != 0) + return ToUtf8(filename); + ASSERT(false); + return ""; +} + +bool Win32Filesystem::MoveFile(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFile(old_path)) { + ASSERT(IsFile(old_path)); + return false; + } + LOG(LS_INFO) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + return ::MoveFile(ToUtf16(old_path.pathname()).c_str(), + ToUtf16(new_path.pathname()).c_str()) != 0; +} + +bool Win32Filesystem::MoveFolder(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFolder(old_path)) { + ASSERT(IsFolder(old_path)); + return false; + } + LOG(LS_INFO) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + if (::MoveFile(ToUtf16(old_path.pathname()).c_str(), + ToUtf16(new_path.pathname()).c_str()) == 0) { + if (::GetLastError() != ERROR_NOT_SAME_DEVICE) { + LOG_GLE(LS_ERROR) << "Failed to move file"; + return false; + } + if (!CopyFolder(old_path, new_path)) + return false; + if (!DeleteFolderAndContents(old_path)) + return false; + } + return true; +} + +bool Win32Filesystem::IsFolder(const Pathname &path) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data)) + return false; + return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == + FILE_ATTRIBUTE_DIRECTORY; +} + +bool Win32Filesystem::IsFile(const Pathname &path) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data)) + return false; + return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +bool Win32Filesystem::IsAbsent(const Pathname& path) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data)) + return false; + DWORD err = ::GetLastError(); + return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err); +} + +bool Win32Filesystem::CopyFile(const Pathname &old_path, + const Pathname &new_path) { + return ::CopyFile(ToUtf16(old_path.pathname()).c_str(), + ToUtf16(new_path.pathname()).c_str(), TRUE) != 0; +} + +bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) { + TCHAR buffer[MAX_PATH + 1]; + if (!::GetTempPath(ARRAY_SIZE(buffer), buffer)) + return false; + if (!IsCurrentProcessLowIntegrity() && + !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + return (::strnicmp(ToUtf16(pathname.pathname()).c_str(), + buffer, strlen(buffer)) == 0); +} + +bool Win32Filesystem::GetFileSize(const Pathname &pathname, size_t *size) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(), + GetFileExInfoStandard, &data) == 0) + return false; + *size = data.nFileSizeLow; + return true; +} + +bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data) == 0) + return false; + switch (which) { + case FTT_CREATED: + FileTimeToUnixTime(data.ftCreationTime, time); + break; + case FTT_MODIFIED: + FileTimeToUnixTime(data.ftLastWriteTime, time); + break; + case FTT_ACCESSED: + FileTimeToUnixTime(data.ftLastAccessTime, time); + break; + default: + return false; + } + return true; +} + +bool Win32Filesystem::GetAppPathname(Pathname* path) { + TCHAR buffer[MAX_PATH + 1]; + if (0 == ::GetModuleFileName(NULL, buffer, ARRAY_SIZE(buffer))) + return false; + path->SetPathname(ToUtf8(buffer)); + return true; +} + +bool Win32Filesystem::GetAppDataFolder(Pathname* path, bool per_user) { + ASSERT(!organization_name_.empty()); + ASSERT(!application_name_.empty()); + TCHAR buffer[MAX_PATH + 1]; + int csidl = per_user ? CSIDL_LOCAL_APPDATA : CSIDL_COMMON_APPDATA; + if (!::SHGetSpecialFolderPath(NULL, buffer, csidl, TRUE)) + return false; + if (!IsCurrentProcessLowIntegrity() && + !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + size_t len = strcatn(buffer, ARRAY_SIZE(buffer), __T("\\")); + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + ToUtf16(organization_name_).c_str()); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\")); + } + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + ToUtf16(application_name_).c_str()); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\")); + } + if (len >= ARRAY_SIZE(buffer) - 1) + return false; + path->clear(); + path->SetFolder(ToUtf8(buffer)); + return CreateFolder(*path); +} + +bool Win32Filesystem::GetAppTempFolder(Pathname* path) { + if (!GetAppPathname(path)) + return false; + std::string filename(path->filename()); + return GetTemporaryFolder(*path, true, &filename); +} + +bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { + if (!freebytes) { + return false; + } + char drive[4]; + std::wstring drive16; + const wchar_t* target_drive = NULL; + if (path.GetDrive(drive, sizeof(drive))) { + drive16 = ToUtf16(drive); + target_drive = drive16.c_str(); + } else if (path.folder().substr(0, 2) == "\\\\") { + // UNC path, fail. + // TODO: Handle UNC paths. + return false; + } else { + // The path is probably relative. GetDriveType and GetDiskFreeSpaceEx + // use the current drive if NULL is passed as the drive name. + // TODO: Add method to Pathname to determine if the path is relative. + // TODO: Add method to Pathname to convert a path to absolute. + } + UINT driveType = ::GetDriveType(target_drive); + if ( (driveType & DRIVE_REMOTE) || (driveType & DRIVE_UNKNOWN) ) { + LOG(LS_VERBOSE) << " remove or unknown drive " << drive; + return false; + } + + int64 totalNumberOfBytes; // receives the number of bytes on disk + int64 totalNumberOfFreeBytes; // receives the free bytes on disk + // make sure things won't change in 64 bit machine + // TODO replace with compile time assert + ASSERT(sizeof(ULARGE_INTEGER) == sizeof(uint64)); //NOLINT + if (::GetDiskFreeSpaceEx(target_drive, + (PULARGE_INTEGER)freebytes, + (PULARGE_INTEGER)&totalNumberOfBytes, + (PULARGE_INTEGER)&totalNumberOfFreeBytes)) { + return true; + } else { + LOG(LS_VERBOSE) << " GetDiskFreeSpaceEx returns error "; + return false; + } +} + +Pathname Win32Filesystem::GetCurrentDirectory() { + Pathname cwd; + int path_len = 0; + scoped_ptr path; + do { + int needed = ::GetCurrentDirectory(path_len, path.get()); + if (needed == 0) { + // Error. + LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed"; + return cwd; // returns empty pathname + } + if (needed <= path_len) { + // It wrote successfully. + break; + } + // Else need to re-alloc for "needed". + path.reset(new wchar_t[needed]); + path_len = needed; + } while (true); + cwd.SetFolder(ToUtf8(path.get())); + return cwd; +} + +// TODO: Consider overriding DeleteFolderAndContents for speed and potentially +// better OS integration (recycle bin?) +/* + std::wstring temp_path16 = ToUtf16(temp_path.pathname()); + temp_path16.append(1, '*'); + temp_path16.append(1, '\0'); + + SHFILEOPSTRUCT file_op = { 0 }; + file_op.wFunc = FO_DELETE; + file_op.pFrom = temp_path16.c_str(); + file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT; + return (0 == SHFileOperation(&file_op)); +*/ + +} // namespace rtc diff --git a/webrtc/base/win32filesystem.h b/webrtc/base/win32filesystem.h new file mode 100644 index 000000000..3cd5373e3 --- /dev/null +++ b/webrtc/base/win32filesystem.h @@ -0,0 +1,101 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef _WEBRTC_BASE_WIN32FILESYSTEM_H__ +#define _WEBRTC_BASE_WIN32FILESYSTEM_H__ + +#include "fileutils.h" + +namespace rtc { + +class Win32Filesystem : public FilesystemInterface { + public: + // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise, + // returns NULL. + virtual FileStream *OpenFile(const Pathname &filename, + const std::string &mode); + + // Atomically creates an empty file accessible only to the current user if one + // does not already exist at the given path, otherwise fails. + virtual bool CreatePrivateFile(const Pathname &filename); + + // This will attempt to delete the path located at filename. + // If the path points to a folder, it will fail with VERIFY + virtual bool DeleteFile(const Pathname &filename); + + // This will attempt to delete an empty folder. If the path does not point to + // a folder, it fails with VERIFY. If the folder is not empty, it fails normally + virtual bool DeleteEmptyFolder(const Pathname &folder); + + // Creates a directory. This will call itself recursively to create /foo/bar even if + // /foo does not exist. + // Returns TRUE if function succeeds + virtual bool CreateFolder(const Pathname &pathname); + + // This moves a file from old_path to new_path. If the new path is on a + // different volume than the old, it will attempt to copy and then delete + // the folder + // Returns true if the file is successfully moved + virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path); + + // Moves a folder from old_path to new_path. If the new path is on a different + // volume from the old, it will attempt to Copy and then Delete the folder + // Returns true if the folder is successfully moved + virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path); + + // This copies a file from old_path to _new_path + // Returns true if function succeeds + virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path); + + // Returns true if a pathname is a directory + virtual bool IsFolder(const Pathname& pathname); + + // Returns true if a file exists at path + virtual bool IsFile(const Pathname &path); + + // Returns true if pathname refers to no filesystem object, every parent + // directory either exists, or is also absent. + virtual bool IsAbsent(const Pathname& pathname); + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPath(const Pathname& pathname); + + // All of the following functions set pathname and return true if successful. + // Returned paths always include a trailing backslash. + // If create is true, the path will be recursively created. + // If append is non-NULL, it will be appended (and possibly created). + + virtual std::string TempFilename(const Pathname &dir, const std::string &prefix); + + virtual bool GetFileSize(const Pathname& path, size_t* size); + virtual bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time); + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exists) + virtual bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append); + + // Returns the path to the running application. + virtual bool GetAppPathname(Pathname* path); + + virtual bool GetAppDataFolder(Pathname* path, bool per_user); + + // Get a temporary folder that is unique to the current user and application. + virtual bool GetAppTempFolder(Pathname* path); + + virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes); + + virtual Pathname GetCurrentDirectory(); +}; + +} // namespace rtc + +#endif // WEBRTC_WINFILESYSTEM_H__ diff --git a/webrtc/base/win32regkey.cc b/webrtc/base/win32regkey.cc new file mode 100644 index 000000000..1ed0d4ea2 --- /dev/null +++ b/webrtc/base/win32regkey.cc @@ -0,0 +1,1102 @@ +/* + * Copyright 2003 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Registry configuration wrapers class implementation +// +// Change made by S. Ganesh - ganesh@google.com: +// Use SHQueryValueEx instead of RegQueryValueEx throughout. +// A call to the SHLWAPI function is essentially a call to the standard +// function but with post-processing: +// * to fix REG_SZ or REG_EXPAND_SZ data that is not properly null-terminated; +// * to expand REG_EXPAND_SZ data. + +#include "webrtc/base/win32regkey.h" + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +RegKey::RegKey() { + h_key_ = NULL; +} + +RegKey::~RegKey() { + Close(); +} + +HRESULT RegKey::Create(HKEY parent_key, const wchar_t* key_name) { + return Create(parent_key, + key_name, + REG_NONE, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, + NULL, + NULL); +} + +HRESULT RegKey::Open(HKEY parent_key, const wchar_t* key_name) { + return Open(parent_key, key_name, KEY_ALL_ACCESS); +} + +bool RegKey::HasValue(const TCHAR* value_name) const { + return (ERROR_SUCCESS == ::RegQueryValueEx(h_key_, value_name, NULL, + NULL, NULL, NULL)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_DWORD, &value); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64 value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_QWORD, &value); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, + REG_BINARY, &value, sizeof(value)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, + REG_BINARY, &value, sizeof(value)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const TCHAR* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return SetValueStaticHelper(full_key_name, value_name, + REG_SZ, const_cast(value)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const uint8* value, + DWORD byte_count) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_BINARY, + const_cast(value), byte_count); +} + +HRESULT RegKey::SetValueMultiSZ(const wchar_t* full_key_name, + const wchar_t* value_name, + const uint8* value, + DWORD byte_count) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ, + const_cast(value), byte_count); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_DWORD, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_QWORD, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float* value) { + ASSERT(value != NULL); + ASSERT(full_key_name != NULL); + + DWORD byte_count = 0; + scoped_ptr buffer; + HRESULT hr = GetValueStaticHelper(full_key_name, value_name, + REG_BINARY, buffer.accept(), &byte_count); + if (SUCCEEDED(hr)) { + ASSERT(byte_count == sizeof(*value)); + if (byte_count == sizeof(*value)) { + *value = *reinterpret_cast(buffer.get()); + } + } + return hr; +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double* value) { + ASSERT(value != NULL); + ASSERT(full_key_name != NULL); + + DWORD byte_count = 0; + scoped_ptr buffer; + HRESULT hr = GetValueStaticHelper(full_key_name, value_name, + REG_BINARY, buffer.accept(), &byte_count); + if (SUCCEEDED(hr)) { + ASSERT(byte_count == sizeof(*value)); + if (byte_count == sizeof(*value)) { + *value = *reinterpret_cast(buffer.get()); + } + } + return hr; +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + wchar_t** value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_SZ, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::wstring* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + scoped_ptr buffer; + HRESULT hr = RegKey::GetValue(full_key_name, value_name, buffer.accept()); + if (SUCCEEDED(hr)) { + value->assign(buffer.get()); + } + return hr; +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::vector* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + uint8** value, + DWORD* byte_count) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + ASSERT(byte_count != NULL); + + return GetValueStaticHelper(full_key_name, value_name, + REG_BINARY, value, byte_count); +} + +HRESULT RegKey::DeleteSubKey(const wchar_t* key_name) { + ASSERT(key_name != NULL); + ASSERT(h_key_ != NULL); + + LONG res = ::RegDeleteKey(h_key_, key_name); + HRESULT hr = HRESULT_FROM_WIN32(res); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { + hr = S_FALSE; + } + return hr; +} + +HRESULT RegKey::DeleteValue(const wchar_t* value_name) { + ASSERT(h_key_ != NULL); + + LONG res = ::RegDeleteValue(h_key_, value_name); + HRESULT hr = HRESULT_FROM_WIN32(res); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { + hr = S_FALSE; + } + return hr; +} + +HRESULT RegKey::Close() { + HRESULT hr = S_OK; + if (h_key_ != NULL) { + LONG res = ::RegCloseKey(h_key_); + hr = HRESULT_FROM_WIN32(res); + h_key_ = NULL; + } + return hr; +} + +HRESULT RegKey::Create(HKEY parent_key, + const wchar_t* key_name, + wchar_t* lpszClass, + DWORD options, + REGSAM sam_desired, + LPSECURITY_ATTRIBUTES lpSecAttr, + LPDWORD lpdwDisposition) { + ASSERT(key_name != NULL); + ASSERT(parent_key != NULL); + + DWORD dw = 0; + HKEY h_key = NULL; + LONG res = ::RegCreateKeyEx(parent_key, key_name, 0, lpszClass, options, + sam_desired, lpSecAttr, &h_key, &dw); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (lpdwDisposition) { + *lpdwDisposition = dw; + } + + // we have to close the currently opened key + // before replacing it with the new one + if (hr == S_OK) { + hr = Close(); + ASSERT(hr == S_OK); + h_key_ = h_key; + } + return hr; +} + +HRESULT RegKey::Open(HKEY parent_key, + const wchar_t* key_name, + REGSAM sam_desired) { + ASSERT(key_name != NULL); + ASSERT(parent_key != NULL); + + HKEY h_key = NULL; + LONG res = ::RegOpenKeyEx(parent_key, key_name, 0, sam_desired, &h_key); + HRESULT hr = HRESULT_FROM_WIN32(res); + + // we have to close the currently opened key + // before replacing it with the new one + if (hr == S_OK) { + // close the currently opened key if any + hr = Close(); + ASSERT(hr == S_OK); + h_key_ = h_key; + } + return hr; +} + +// save the key and all of its subkeys and values to a file +HRESULT RegKey::Save(const wchar_t* full_key_name, const wchar_t* file_name) { + ASSERT(full_key_name != NULL); + ASSERT(file_name != NULL); + + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + if (!h_key) { + return E_FAIL; + } + + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (FAILED(hr)) { + return hr; + } + + AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, true); + LONG res = ::RegSaveKey(key.h_key_, file_name, NULL); + AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, false); + + return HRESULT_FROM_WIN32(res); +} + +// restore the key and all of its subkeys and values which are saved into a file +HRESULT RegKey::Restore(const wchar_t* full_key_name, + const wchar_t* file_name) { + ASSERT(full_key_name != NULL); + ASSERT(file_name != NULL); + + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + if (!h_key) { + return E_FAIL; + } + + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_WRITE); + if (FAILED(hr)) { + return hr; + } + + AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, true); + LONG res = ::RegRestoreKey(key.h_key_, file_name, REG_FORCE_RESTORE); + AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, false); + + return HRESULT_FROM_WIN32(res); +} + +// check if the current key has the specified subkey +bool RegKey::HasSubkey(const wchar_t* key_name) const { + ASSERT(key_name != NULL); + + RegKey key; + HRESULT hr = key.Open(h_key_, key_name, KEY_READ); + key.Close(); + return hr == S_OK; +} + +// static flush key +HRESULT RegKey::FlushKey(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + LONG res = ::RegFlushKey(h_key); + hr = HRESULT_FROM_WIN32(res); + } + return hr; +} + +// static SET helper +HRESULT RegKey::SetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD byte_count) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + hr = key.Create(h_key, key_name.c_str()); + if (hr == S_OK) { + switch (type) { + case REG_DWORD: + hr = key.SetValue(value_name, *(static_cast(value))); + break; + case REG_QWORD: + hr = key.SetValue(value_name, *(static_cast(value))); + break; + case REG_SZ: + hr = key.SetValue(value_name, static_cast(value)); + break; + case REG_BINARY: + hr = key.SetValue(value_name, static_cast(value), + byte_count); + break; + case REG_MULTI_SZ: + hr = key.SetValue(value_name, static_cast(value), + byte_count, type); + break; + default: + ASSERT(false); + hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + break; + } + // close the key after writing + HRESULT temp_hr = key.Close(); + if (hr == S_OK) { + hr = temp_hr; + } + } + } + return hr; +} + +// static GET helper +HRESULT RegKey::GetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD* byte_count) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (hr == S_OK) { + switch (type) { + case REG_DWORD: + hr = key.GetValue(value_name, reinterpret_cast(value)); + break; + case REG_QWORD: + hr = key.GetValue(value_name, reinterpret_cast(value)); + break; + case REG_SZ: + hr = key.GetValue(value_name, reinterpret_cast(value)); + break; + case REG_MULTI_SZ: + hr = key.GetValue(value_name, reinterpret_cast< + std::vector*>(value)); + break; + case REG_BINARY: + hr = key.GetValue(value_name, reinterpret_cast(value), + byte_count); + break; + default: + ASSERT(false); + hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + break; + } + // close the key after writing + HRESULT temp_hr = key.Close(); + if (hr == S_OK) { + hr = temp_hr; + } + } + } + return hr; +} + +// GET helper +HRESULT RegKey::GetValueHelper(const wchar_t* value_name, + DWORD* type, + uint8** value, + DWORD* byte_count) const { + ASSERT(byte_count != NULL); + ASSERT(value != NULL); + ASSERT(type != NULL); + + // init return buffer + *value = NULL; + + // get the size of the return data buffer + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, type, NULL, byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (hr == S_OK) { + // if the value length is 0, nothing to do + if (*byte_count != 0) { + // allocate the buffer + *value = new byte[*byte_count]; + ASSERT(*value != NULL); + + // make the call again to get the data + res = ::SHQueryValueEx(h_key_, value_name, NULL, + type, *value, byte_count); + hr = HRESULT_FROM_WIN32(res); + ASSERT(hr == S_OK); + } + } + return hr; +} + +// Int32 Get +HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD* value) const { + ASSERT(value != NULL); + + DWORD type = 0; + DWORD byte_count = sizeof(DWORD); + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + value, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + ASSERT((hr != S_OK) || (type == REG_DWORD)); + ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD))); + return hr; +} + +// Int64 Get +HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD64* value) const { + ASSERT(value != NULL); + + DWORD type = 0; + DWORD byte_count = sizeof(DWORD64); + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + value, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + ASSERT((hr != S_OK) || (type == REG_QWORD)); + ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD64))); + return hr; +} + +// String Get +HRESULT RegKey::GetValue(const wchar_t* value_name, wchar_t** value) const { + ASSERT(value != NULL); + + DWORD byte_count = 0; + DWORD type = 0; + + // first get the size of the string buffer + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, + &type, NULL, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (hr == S_OK) { + // allocate room for the string and a terminating \0 + *value = new wchar_t[(byte_count / sizeof(wchar_t)) + 1]; + + if ((*value) != NULL) { + if (byte_count != 0) { + // make the call again + res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + *value, &byte_count); + hr = HRESULT_FROM_WIN32(res); + } else { + (*value)[0] = L'\0'; + } + + ASSERT((hr != S_OK) || (type == REG_SZ) || + (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ)); + } else { + hr = E_OUTOFMEMORY; + } + } + + return hr; +} + +// get a string value +HRESULT RegKey::GetValue(const wchar_t* value_name, std::wstring* value) const { + ASSERT(value != NULL); + + DWORD byte_count = 0; + DWORD type = 0; + + // first get the size of the string buffer + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, + &type, NULL, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (hr == S_OK) { + if (byte_count != 0) { + // Allocate some memory and make the call again + value->resize(byte_count / sizeof(wchar_t) + 1); + res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + &value->at(0), &byte_count); + hr = HRESULT_FROM_WIN32(res); + value->resize(wcslen(value->data())); + } else { + value->clear(); + } + + ASSERT((hr != S_OK) || (type == REG_SZ) || + (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ)); + } + + return hr; +} + +// convert REG_MULTI_SZ bytes to string array +HRESULT RegKey::MultiSZBytesToStringArray(const uint8* buffer, + DWORD byte_count, + std::vector* value) { + ASSERT(buffer != NULL); + ASSERT(value != NULL); + + const wchar_t* data = reinterpret_cast(buffer); + DWORD data_len = byte_count / sizeof(wchar_t); + value->clear(); + if (data_len > 1) { + // must be terminated by two null characters + if (data[data_len - 1] != 0 || data[data_len - 2] != 0) { + return E_INVALIDARG; + } + + // put null-terminated strings into arrays + while (*data) { + std::wstring str(data); + value->push_back(str); + data += str.length() + 1; + } + } + return S_OK; +} + +// get a std::vector value from REG_MULTI_SZ type +HRESULT RegKey::GetValue(const wchar_t* value_name, + std::vector* value) const { + ASSERT(value != NULL); + + DWORD byte_count = 0; + DWORD type = 0; + uint8* buffer = 0; + + // first get the size of the buffer + HRESULT hr = GetValueHelper(value_name, &type, &buffer, &byte_count); + ASSERT((hr != S_OK) || (type == REG_MULTI_SZ)); + + if (SUCCEEDED(hr)) { + hr = MultiSZBytesToStringArray(buffer, byte_count, value); + } + + return hr; +} + +// Binary data Get +HRESULT RegKey::GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count) const { + ASSERT(byte_count != NULL); + ASSERT(value != NULL); + + DWORD type = 0; + HRESULT hr = GetValueHelper(value_name, &type, value, byte_count); + ASSERT((hr != S_OK) || (type == REG_MULTI_SZ) || (type == REG_BINARY)); + return hr; +} + +// Raw data get +HRESULT RegKey::GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count, + DWORD*type) const { + ASSERT(type != NULL); + ASSERT(byte_count != NULL); + ASSERT(value != NULL); + + return GetValueHelper(value_name, type, value, byte_count); +} + +// Int32 set +HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD value) const { + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_DWORD, + reinterpret_cast(&value), + sizeof(DWORD)); + return HRESULT_FROM_WIN32(res); +} + +// Int64 set +HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD64 value) const { + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_QWORD, + reinterpret_cast(&value), + sizeof(DWORD64)); + return HRESULT_FROM_WIN32(res); +} + +// String set +HRESULT RegKey::SetValue(const wchar_t* value_name, + const wchar_t* value) const { + ASSERT(value != NULL); + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_SZ, + reinterpret_cast(value), + (lstrlen(value) + 1) * sizeof(wchar_t)); + return HRESULT_FROM_WIN32(res); +} + +// Binary data set +HRESULT RegKey::SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count) const { + ASSERT(h_key_ != NULL); + + // special case - if 'value' is NULL make sure byte_count is zero + if (value == NULL) { + byte_count = 0; + } + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, + REG_BINARY, value, byte_count); + return HRESULT_FROM_WIN32(res); +} + +// Raw data set +HRESULT RegKey::SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count, + DWORD type) const { + ASSERT(value != NULL); + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, type, value, byte_count); + return HRESULT_FROM_WIN32(res); +} + +bool RegKey::HasKey(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + key.Close(); + return S_OK == hr; + } + return false; +} + +// static version of HasValue +bool RegKey::HasValue(const wchar_t* full_key_name, const wchar_t* value_name) { + ASSERT(full_key_name != NULL); + + bool has_value = false; + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + if (key.Open(h_key, key_name.c_str(), KEY_READ) == S_OK) { + has_value = key.HasValue(value_name); + key.Close(); + } + } + return has_value; +} + +HRESULT RegKey::GetValueType(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value_type) { + ASSERT(full_key_name != NULL); + ASSERT(value_type != NULL); + + *value_type = REG_NONE; + + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (SUCCEEDED(hr)) { + LONG res = ::SHQueryValueEx(key.h_key_, value_name, NULL, value_type, + NULL, NULL); + if (res != ERROR_SUCCESS) { + hr = HRESULT_FROM_WIN32(res); + } + } + + return hr; +} + +HRESULT RegKey::DeleteKey(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + return DeleteKey(full_key_name, true); +} + +HRESULT RegKey::DeleteKey(const wchar_t* full_key_name, bool recursively) { + ASSERT(full_key_name != NULL); + + // need to open the parent key first + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + // get the parent key + std::wstring parent_key(GetParentKeyInfo(&key_name)); + + RegKey key; + HRESULT hr = key.Open(h_key, parent_key.c_str()); + + if (hr == S_OK) { + hr = recursively ? key.RecurseDeleteSubKey(key_name.c_str()) + : key.DeleteSubKey(key_name.c_str()); + } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { + hr = S_FALSE; + } + + key.Close(); + return hr; +} + +HRESULT RegKey::DeleteValue(const wchar_t* full_key_name, + const wchar_t* value_name) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + hr = key.Open(h_key, key_name.c_str()); + if (hr == S_OK) { + hr = key.DeleteValue(value_name); + key.Close(); + } + } + return hr; +} + +HRESULT RegKey::RecurseDeleteSubKey(const wchar_t* key_name) { + ASSERT(key_name != NULL); + + RegKey key; + HRESULT hr = key.Open(h_key_, key_name); + + if (hr == S_OK) { + // enumerate all subkeys of this key and recursivelly delete them + FILETIME time = {0}; + wchar_t key_name_buf[kMaxKeyNameChars] = {0}; + DWORD key_name_buf_size = kMaxKeyNameChars; + while (hr == S_OK && + ::RegEnumKeyEx(key.h_key_, 0, key_name_buf, &key_name_buf_size, + NULL, NULL, NULL, &time) == ERROR_SUCCESS) { + hr = key.RecurseDeleteSubKey(key_name_buf); + + // restore the buffer size + key_name_buf_size = kMaxKeyNameChars; + } + // close the top key + key.Close(); + } + + if (hr == S_OK) { + // the key has no more children keys + // delete the key and all of its values + hr = DeleteSubKey(key_name); + } + + return hr; +} + +HKEY RegKey::GetRootKeyInfo(std::wstring* full_key_name) { + ASSERT(full_key_name != NULL); + + HKEY h_key = NULL; + // get the root HKEY + size_t index = full_key_name->find(L'\\'); + std::wstring root_key; + + if (index == -1) { + root_key = *full_key_name; + *full_key_name = L""; + } else { + root_key = full_key_name->substr(0, index); + *full_key_name = full_key_name->substr(index + 1, + full_key_name->length() - index - 1); + } + + for (std::wstring::iterator iter = root_key.begin(); + iter != root_key.end(); ++iter) { + *iter = toupper(*iter); + } + + if (!root_key.compare(L"HKLM") || + !root_key.compare(L"HKEY_LOCAL_MACHINE")) { + h_key = HKEY_LOCAL_MACHINE; + } else if (!root_key.compare(L"HKCU") || + !root_key.compare(L"HKEY_CURRENT_USER")) { + h_key = HKEY_CURRENT_USER; + } else if (!root_key.compare(L"HKU") || + !root_key.compare(L"HKEY_USERS")) { + h_key = HKEY_USERS; + } else if (!root_key.compare(L"HKCR") || + !root_key.compare(L"HKEY_CLASSES_ROOT")) { + h_key = HKEY_CLASSES_ROOT; + } + + return h_key; +} + + +// Returns true if this key name is 'safe' for deletion +// (doesn't specify a key root) +bool RegKey::SafeKeyNameForDeletion(const wchar_t* key_name) { + ASSERT(key_name != NULL); + std::wstring key(key_name); + + HKEY root_key = GetRootKeyInfo(&key); + + if (!root_key) { + key = key_name; + } + if (key.empty()) { + return false; + } + bool found_subkey = false, backslash_found = false; + for (size_t i = 0 ; i < key.length() ; ++i) { + if (key[i] == L'\\') { + backslash_found = true; + } else if (backslash_found) { + found_subkey = true; + break; + } + } + return (root_key == HKEY_USERS) ? found_subkey : true; +} + +std::wstring RegKey::GetParentKeyInfo(std::wstring* key_name) { + ASSERT(key_name != NULL); + + // get the parent key + size_t index = key_name->rfind(L'\\'); + std::wstring parent_key; + if (index == -1) { + parent_key = L""; + } else { + parent_key = key_name->substr(0, index); + *key_name = key_name->substr(index + 1, key_name->length() - index - 1); + } + + return parent_key; +} + +// get the number of values for this key +uint32 RegKey::GetValueCount() { + DWORD num_values = 0; + + if (ERROR_SUCCESS != ::RegQueryInfoKey( + h_key_, // key handle + NULL, // buffer for class name + NULL, // size of class string + NULL, // reserved + NULL, // number of subkeys + NULL, // longest subkey size + NULL, // longest class string + &num_values, // number of values for this key + NULL, // longest value name + NULL, // longest value data + NULL, // security descriptor + NULL)) { // last write time + ASSERT(false); + } + return num_values; +} + +// Enumerators for the value_names for this key + +// Called to get the value name for the given value name index +// Use GetValueCount() to get the total value_name count for this key +// Returns failure if no key at the specified index +HRESULT RegKey::GetValueNameAt(int index, std::wstring* value_name, + DWORD* type) { + ASSERT(value_name != NULL); + + LONG res = ERROR_SUCCESS; + wchar_t value_name_buf[kMaxValueNameChars] = {0}; + DWORD value_name_buf_size = kMaxValueNameChars; + res = ::RegEnumValue(h_key_, index, value_name_buf, &value_name_buf_size, + NULL, type, NULL, NULL); + + if (res == ERROR_SUCCESS) { + value_name->assign(value_name_buf); + } + + return HRESULT_FROM_WIN32(res); +} + +uint32 RegKey::GetSubkeyCount() { + // number of values for key + DWORD num_subkeys = 0; + + if (ERROR_SUCCESS != ::RegQueryInfoKey( + h_key_, // key handle + NULL, // buffer for class name + NULL, // size of class string + NULL, // reserved + &num_subkeys, // number of subkeys + NULL, // longest subkey size + NULL, // longest class string + NULL, // number of values for this key + NULL, // longest value name + NULL, // longest value data + NULL, // security descriptor + NULL)) { // last write time + ASSERT(false); + } + return num_subkeys; +} + +HRESULT RegKey::GetSubkeyNameAt(int index, std::wstring* key_name) { + ASSERT(key_name != NULL); + + LONG res = ERROR_SUCCESS; + wchar_t key_name_buf[kMaxKeyNameChars] = {0}; + DWORD key_name_buf_size = kMaxKeyNameChars; + + res = ::RegEnumKeyEx(h_key_, index, key_name_buf, &key_name_buf_size, + NULL, NULL, NULL, NULL); + + if (res == ERROR_SUCCESS) { + key_name->assign(key_name_buf); + } + + return HRESULT_FROM_WIN32(res); +} + +// Is the key empty: having no sub-keys and values +bool RegKey::IsKeyEmpty(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + bool is_empty = true; + + // Get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + // Open the key to check + if (h_key != NULL) { + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (SUCCEEDED(hr)) { + is_empty = key.GetSubkeyCount() == 0 && key.GetValueCount() == 0; + key.Close(); + } + } + + return is_empty; +} + +bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable) { + ASSERT(privilege != NULL); + + bool ret = false; + HANDLE token; + if (::OpenProcessToken(::GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) { + LUID luid; + memset(&luid, 0, sizeof(luid)); + if (::LookupPrivilegeValue(NULL, privilege, &luid)) { + TOKEN_PRIVILEGES privs; + privs.PrivilegeCount = 1; + privs.Privileges[0].Luid = luid; + privs.Privileges[0].Attributes = to_enable ? SE_PRIVILEGE_ENABLED : 0; + if (::AdjustTokenPrivileges(token, FALSE, &privs, 0, NULL, 0)) { + ret = true; + } else { + LOG_GLE(LS_ERROR) << "AdjustTokenPrivileges failed"; + } + } else { + LOG_GLE(LS_ERROR) << "LookupPrivilegeValue failed"; + } + CloseHandle(token); + } else { + LOG_GLE(LS_ERROR) << "OpenProcessToken(GetCurrentProcess) failed"; + } + + return ret; +} + +} // namespace rtc diff --git a/webrtc/base/win32regkey.h b/webrtc/base/win32regkey.h new file mode 100644 index 000000000..b33d4dc2b --- /dev/null +++ b/webrtc/base/win32regkey.h @@ -0,0 +1,337 @@ +/* + * Copyright 2003 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Registry configuration wrappers class +// +// Offers static functions for convenient +// fast access for individual values +// +// Also provides a wrapper class for efficient +// batch operations on values of a given registry key. +// + +#ifndef WEBRTC_BASE_WIN32REGKEY_H_ +#define WEBRTC_BASE_WIN32REGKEY_H_ + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/win32.h" + +namespace rtc { + +// maximum sizes registry key and value names +const int kMaxKeyNameChars = 255 + 1; +const int kMaxValueNameChars = 16383 + 1; + +class RegKey { + public: + // constructor + RegKey(); + + // destructor + ~RegKey(); + + // create a reg key + HRESULT Create(HKEY parent_key, const wchar_t* key_name); + + HRESULT Create(HKEY parent_key, + const wchar_t* key_name, + wchar_t* reg_class, + DWORD options, + REGSAM sam_desired, + LPSECURITY_ATTRIBUTES lp_sec_attr, + LPDWORD lp_disposition); + + // open an existing reg key + HRESULT Open(HKEY parent_key, const wchar_t* key_name); + + HRESULT Open(HKEY parent_key, const wchar_t* key_name, REGSAM sam_desired); + + // close this reg key + HRESULT Close(); + + // check if the key has a specified value + bool HasValue(const wchar_t* value_name) const; + + // get the number of values for this key + uint32 GetValueCount(); + + // Called to get the value name for the given value name index + // Use GetValueCount() to get the total value_name count for this key + // Returns failure if no key at the specified index + // If you modify the key while enumerating, the indexes will be out of order. + // Since the index order is not guaranteed, you need to reset your counting + // loop. + // 'type' refers to REG_DWORD, REG_QWORD, etc.. + // 'type' can be NULL if not interested in the value type + HRESULT GetValueNameAt(int index, std::wstring* value_name, DWORD* type); + + // check if the current key has the specified subkey + bool HasSubkey(const wchar_t* key_name) const; + + // get the number of subkeys for this key + uint32 GetSubkeyCount(); + + // Called to get the key name for the given key index + // Use GetSubkeyCount() to get the total count for this key + // Returns failure if no key at the specified index + // If you modify the key while enumerating, the indexes will be out of order. + // Since the index order is not guaranteed, you need to reset your counting + // loop. + HRESULT GetSubkeyNameAt(int index, std::wstring* key_name); + + // SETTERS + + // set an int32 value - use when reading multiple values from a key + HRESULT SetValue(const wchar_t* value_name, DWORD value) const; + + // set an int64 value + HRESULT SetValue(const wchar_t* value_name, DWORD64 value) const; + + // set a string value + HRESULT SetValue(const wchar_t* value_name, const wchar_t* value) const; + + // set binary data + HRESULT SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count) const; + + // set raw data, including type + HRESULT SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count, + DWORD type) const; + + // GETTERS + + // get an int32 value + HRESULT GetValue(const wchar_t* value_name, DWORD* value) const; + + // get an int64 value + HRESULT GetValue(const wchar_t* value_name, DWORD64* value) const; + + // get a string value - the caller must free the return buffer + HRESULT GetValue(const wchar_t* value_name, wchar_t** value) const; + + // get a string value + HRESULT GetValue(const wchar_t* value_name, std::wstring* value) const; + + // get a std::vector value from REG_MULTI_SZ type + HRESULT GetValue(const wchar_t* value_name, + std::vector* value) const; + + // get binary data - the caller must free the return buffer + HRESULT GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count) const; + + // get raw data, including type - the caller must free the return buffer + HRESULT GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count, + DWORD* type) const; + + // STATIC VERSIONS + + // flush + static HRESULT FlushKey(const wchar_t* full_key_name); + + // check if a key exists + static bool HasKey(const wchar_t* full_key_name); + + // check if the key has a specified value + static bool HasValue(const wchar_t* full_key_name, const wchar_t* value_name); + + // SETTERS + + // STATIC int32 set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD value); + + // STATIC int64 set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64 value); + + // STATIC float set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float value); + + // STATIC double set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double value); + + // STATIC string set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const wchar_t* value); + + // STATIC binary data set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const uint8* value, + DWORD byte_count); + + // STATIC multi-string set + static HRESULT SetValueMultiSZ(const wchar_t* full_key_name, + const TCHAR* value_name, + const uint8* value, + DWORD byte_count); + + // GETTERS + + // STATIC int32 get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value); + + // STATIC int64 get + // + // Note: if you are using time64 you should + // likely use GetLimitedTimeValue (util.h) instead of this method. + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64* value); + + // STATIC float get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float* value); + + // STATIC double get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double* value); + + // STATIC string get + // Note: the caller must free the return buffer for wchar_t* version + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + wchar_t** value); + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::wstring* value); + + // STATIC REG_MULTI_SZ get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::vector* value); + + // STATIC get binary data - the caller must free the return buffer + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + uint8** value, + DWORD* byte_count); + + // Get type of a registry value + static HRESULT GetValueType(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value_type); + + // delete a subkey of the current key (with no subkeys) + HRESULT DeleteSubKey(const wchar_t* key_name); + + // recursively delete a sub key of the current key (and all its subkeys) + HRESULT RecurseDeleteSubKey(const wchar_t* key_name); + + // STATIC version of delete key - handles nested keys also + // delete a key and all its sub-keys recursively + // Returns S_FALSE if key didn't exist, S_OK if deletion was successful, + // and failure otherwise. + static HRESULT DeleteKey(const wchar_t* full_key_name); + + // STATIC version of delete key + // delete a key recursively or non-recursively + // Returns S_FALSE if key didn't exist, S_OK if deletion was successful, + // and failure otherwise. + static HRESULT DeleteKey(const wchar_t* full_key_name, bool recursive); + + // delete the specified value + HRESULT DeleteValue(const wchar_t* value_name); + + // STATIC version of delete value + // Returns S_FALSE if key didn't exist, S_OK if deletion was successful, + // and failure otherwise. + static HRESULT DeleteValue(const wchar_t* full_key_name, + const wchar_t* value_name); + + // Peek inside (use a RegKey as a smart wrapper around a registry handle) + HKEY key() { return h_key_; } + + // helper function to get the HKEY and the root key from a string + // modifies the argument in place and returns the key name + // e.g. HKLM\\Software\\Google\... returns HKLM, "Software\\Google\..." + // Necessary for the static versions that use the full name of the reg key + static HKEY GetRootKeyInfo(std::wstring* full_key_name); + + // Returns true if this key name is 'safe' for deletion (doesn't specify a key + // root) + static bool SafeKeyNameForDeletion(const wchar_t* key_name); + + // save the key and all of its subkeys and values to a file + static HRESULT Save(const wchar_t* full_key_name, const wchar_t* file_name); + + // restore the key and all of its subkeys and values which are saved into a + // file + static HRESULT Restore(const wchar_t* full_key_name, + const wchar_t* file_name); + + // Is the key empty: having no sub-keys and values + static bool IsKeyEmpty(const wchar_t* full_key_name); + + private: + + // helper function to get any value from the registry + // used when the size of the data is unknown + HRESULT GetValueHelper(const wchar_t* value_name, + DWORD* type, uint8** value, + DWORD* byte_count) const; + + // helper function to get the parent key name and the subkey from a string + // modifies the argument in place and returns the key name + // Necessary for the static versions that use the full name of the reg key + static std::wstring GetParentKeyInfo(std::wstring* key_name); + + // common SET Helper for the static case + static HRESULT SetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD byte_count = 0); + + // common GET Helper for the static case + static HRESULT GetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD* byte_count = NULL); + + // convert REG_MULTI_SZ bytes to string array + static HRESULT MultiSZBytesToStringArray(const uint8* buffer, + DWORD byte_count, + std::vector* value); + + // the HKEY for the current key + HKEY h_key_; + + // for unittest + friend void RegKeyHelperFunctionsTest(); + + DISALLOW_EVIL_CONSTRUCTORS(RegKey); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32REGKEY_H_ diff --git a/webrtc/base/win32regkey_unittest.cc b/webrtc/base/win32regkey_unittest.cc new file mode 100644 index 000000000..1e7738182 --- /dev/null +++ b/webrtc/base/win32regkey_unittest.cc @@ -0,0 +1,590 @@ +/* + * Copyright 2003 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Unittest for registry access API + +#include "webrtc/base/gunit.h" +#include "webrtc/base/common.h" +#include "webrtc/base/win32regkey.h" + +namespace rtc { + +#ifndef EXPECT_SUCCEEDED +#define EXPECT_SUCCEEDED(x) EXPECT_TRUE(SUCCEEDED(x)) +#endif + +#ifndef EXPECT_FAILED +#define EXPECT_FAILED(x) EXPECT_TRUE(FAILED(x)) +#endif + +#define kBaseKey L"Software\\Google\\__TEST" +#define kSubkeyName L"subkey_test" + +const wchar_t kRkey1[] = kBaseKey; +const wchar_t kRkey1SubkeyName[] = kSubkeyName; +const wchar_t kRkey1Subkey[] = kBaseKey L"\\" kSubkeyName; +const wchar_t kFullRkey1[] = L"HKCU\\" kBaseKey; +const wchar_t kFullRkey1Subkey[] = L"HKCU\\" kBaseKey L"\\" kSubkeyName; + +const wchar_t kValNameInt[] = L"Int32 Value"; +const DWORD kIntVal = 20; +const DWORD kIntVal2 = 30; + +const wchar_t kValNameInt64[] = L"Int64 Value"; +const DWORD64 kIntVal64 = 119600064000000000uI64; + +const wchar_t kValNameFloat[] = L"Float Value"; +const float kFloatVal = 12.3456789f; + +const wchar_t kValNameDouble[] = L"Double Value"; +const double kDoubleVal = 98.7654321; + +const wchar_t kValNameStr[] = L"Str Value"; +const wchar_t kStrVal[] = L"Some string data 1"; +const wchar_t kStrVal2[] = L"Some string data 2"; + +const wchar_t kValNameBinary[] = L"Binary Value"; +const char kBinaryVal[] = "Some binary data abcdefghi 1"; +const char kBinaryVal2[] = "Some binary data abcdefghi 2"; + +const wchar_t kValNameMultiStr[] = L"MultiStr Value"; +const wchar_t kMultiSZ[] = L"abc\0def\0P12345\0"; +const wchar_t kEmptyMultiSZ[] = L""; +const wchar_t kInvalidMultiSZ[] = {L'6', L'7', L'8'}; + +// friend function of RegKey +void RegKeyHelperFunctionsTest() { + // Try out some dud values + std::wstring temp_key = L""; + EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL); + EXPECT_STREQ(temp_key.c_str(), L""); + + temp_key = L"a"; + EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL); + EXPECT_STREQ(temp_key.c_str(), L""); + + // The basics + temp_key = L"HKLM\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_LOCAL_MACHINE\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKCU\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_CURRENT_USER\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKU\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_USERS\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKCR\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_CLASSES_ROOT\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + // Make sure it is case insensitive + temp_key = L"hkcr\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"hkey_CLASSES_ROOT\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + // + // Test RegKey::GetParentKeyInfo + // + + // dud cases + temp_key = L""; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L""); + EXPECT_STREQ(temp_key.c_str(), L""); + + temp_key = L"a"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L""); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"a\\b"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"a"); + EXPECT_STREQ(temp_key.c_str(), L"b"); + + temp_key = L"\\b"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L""); + EXPECT_STREQ(temp_key.c_str(), L"b"); + + // Some regular cases + temp_key = L"HKEY_CLASSES_ROOT\\moon"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), + L"HKEY_CLASSES_ROOT"); + EXPECT_STREQ(temp_key.c_str(), L"moon"); + + temp_key = L"HKEY_CLASSES_ROOT\\moon\\doggy"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), + L"HKEY_CLASSES_ROOT\\moon"); + EXPECT_STREQ(temp_key.c_str(), L"doggy"); + + // + // Test MultiSZBytesToStringArray + // + + std::vector result; + EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray( + reinterpret_cast(kMultiSZ), sizeof(kMultiSZ), &result)); + EXPECT_EQ(result.size(), 3); + EXPECT_STREQ(result[0].c_str(), L"abc"); + EXPECT_STREQ(result[1].c_str(), L"def"); + EXPECT_STREQ(result[2].c_str(), L"P12345"); + + EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray( + reinterpret_cast(kEmptyMultiSZ), + sizeof(kEmptyMultiSZ), &result)); + EXPECT_EQ(result.size(), 0); + EXPECT_FALSE(SUCCEEDED(RegKey::MultiSZBytesToStringArray( + reinterpret_cast(kInvalidMultiSZ), + sizeof(kInvalidMultiSZ), &result))); +} + +TEST(RegKeyTest, RegKeyHelperFunctionsTest) { + RegKeyHelperFunctionsTest(); +} + +TEST(RegKeyTest, RegKeyNonStaticFunctionsTest) { + DWORD int_val = 0; + DWORD64 int64_val = 0; + wchar_t* str_val = NULL; + uint8* binary_val = NULL; + DWORD uint8_count = 0; + + // Just in case... + // make sure the no test key residue is left from previous aborted runs + RegKey::DeleteKey(kFullRkey1); + + // initial state + RegKey r_key; + EXPECT_TRUE(r_key.key() == NULL); + + // create a reg key + EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1)); + + // do the create twice - it should return the already created one + EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1)); + + // now do an open - should work just fine + EXPECT_SUCCEEDED(r_key.Open(HKEY_CURRENT_USER, kRkey1)); + + // get an in-existent value + EXPECT_EQ(r_key.GetValue(kValNameInt, &int_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // set and get some values + + // set an INT 32 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameInt)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal); + + // set it again! + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal2)); + + // read it again + EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal2); + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameInt)); + + // set an INT 64 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameInt64)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt64, &int64_val)); + EXPECT_EQ(int64_val, kIntVal64); + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt64)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameInt64)); + + // set a string + EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameStr)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val)); + EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0); + delete[] str_val; + + // set it again + EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal2)); + + // read it again + EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val)); + EXPECT_TRUE(lstrcmp(str_val, kStrVal2) == 0); + delete[] str_val; + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameStr)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameInt)); + + // set a binary value + EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary, + reinterpret_cast(kBinaryVal), sizeof(kBinaryVal) - 1)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count)); + EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal) - 1) == 0); + delete[] binary_val; + + // set it again + EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary, + reinterpret_cast(kBinaryVal2), sizeof(kBinaryVal) - 1)); + + // read it again + EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count)); + EXPECT_TRUE(memcmp(binary_val, kBinaryVal2, sizeof(kBinaryVal2) - 1) == 0); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameBinary)); + + // set some values and check the total count + + // set an INT 32 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal)); + + // set an INT 64 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64)); + + // set a string + EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal)); + + // set a binary value + EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary, + reinterpret_cast(kBinaryVal), sizeof(kBinaryVal) - 1)); + + // get the value count + uint32 value_count = r_key.GetValueCount(); + EXPECT_EQ(value_count, 4); + + // check the value names + std::wstring value_name; + DWORD type = 0; + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(0, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameInt); + EXPECT_EQ(type, REG_DWORD); + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(1, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameInt64); + EXPECT_EQ(type, REG_QWORD); + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(2, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameStr); + EXPECT_EQ(type, REG_SZ); + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(3, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameBinary); + EXPECT_EQ(type, REG_BINARY); + + // check that there are no more values + EXPECT_FAILED(r_key.GetValueNameAt(4, &value_name, &type)); + + uint32 subkey_count = r_key.GetSubkeyCount(); + EXPECT_EQ(subkey_count, 0); + + // now create a subkey and make sure we can get the name + RegKey temp_key; + EXPECT_SUCCEEDED(temp_key.Create(HKEY_CURRENT_USER, kRkey1Subkey)); + + // check the subkey exists + EXPECT_TRUE(r_key.HasSubkey(kRkey1SubkeyName)); + + // check the name + EXPECT_EQ(r_key.GetSubkeyCount(), 1); + + std::wstring subkey_name; + EXPECT_SUCCEEDED(r_key.GetSubkeyNameAt(0, &subkey_name)); + EXPECT_STREQ(subkey_name.c_str(), kRkey1SubkeyName); + + // delete the key + EXPECT_SUCCEEDED(r_key.DeleteSubKey(kRkey1)); + + // close this key + EXPECT_SUCCEEDED(r_key.Close()); + + // whack the whole key + EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1)); +} + +TEST(RegKeyTest, RegKeyStaticFunctionsTest) { + DWORD int_val = 0; + DWORD64 int64_val = 0; + float float_val = 0; + double double_val = 0; + wchar_t* str_val = NULL; + std::wstring wstr_val; + uint8* binary_val = NULL; + DWORD uint8_count = 0; + + // Just in case... + // make sure the no test key residue is left from previous aborted runs + RegKey::DeleteKey(kFullRkey1); + + // get an in-existent value from an un-existent key + EXPECT_EQ(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // set int32 + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt, kIntVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt)); + + // get an in-existent value from an existent key + EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &int_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt)); + + // set int64 + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt64, kIntVal64)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt64)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt64, &int64_val)); + EXPECT_EQ(int64_val, kIntVal64); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt64)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt64)); + + // set float + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameFloat, kFloatVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameFloat)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val)); + EXPECT_EQ(float_val, kFloatVal); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameFloat)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameFloat)); + EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val)); + + // set double + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameDouble, kDoubleVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameDouble)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val)); + EXPECT_EQ(double_val, kDoubleVal); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameDouble)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameDouble)); + EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val)); + + // set string + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameStr, kStrVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameStr)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &str_val)); + EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0); + delete[] str_val; + + // read it back in std::wstring + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &wstr_val)); + EXPECT_STREQ(wstr_val.c_str(), kStrVal); + + // get an in-existent value from an existent key + EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &str_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameStr)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameStr)); + + // set binary + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, + reinterpret_cast(kBinaryVal), sizeof(kBinaryVal)-1)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary, + &binary_val, &uint8_count)); + EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal)-1) == 0); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // special case - set a binary value with length 0 + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, + reinterpret_cast(kBinaryVal), 0)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary, + &binary_val, &uint8_count)); + EXPECT_EQ(uint8_count, 0); + EXPECT_TRUE(binary_val == NULL); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // special case - set a NULL binary value + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, NULL, 100)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary, + &binary_val, &uint8_count)); + EXPECT_EQ(uint8_count, 0); + EXPECT_TRUE(binary_val == NULL); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // test read/write REG_MULTI_SZ value + std::vector result; + EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr, + reinterpret_cast(kMultiSZ), sizeof(kMultiSZ))); + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result)); + EXPECT_EQ(result.size(), 3); + EXPECT_STREQ(result[0].c_str(), L"abc"); + EXPECT_STREQ(result[1].c_str(), L"def"); + EXPECT_STREQ(result[2].c_str(), L"P12345"); + EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr, + reinterpret_cast(kEmptyMultiSZ), sizeof(kEmptyMultiSZ))); + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result)); + EXPECT_EQ(result.size(), 0); + // writing REG_MULTI_SZ value will automatically add ending null characters + EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr, + reinterpret_cast(kInvalidMultiSZ), sizeof(kInvalidMultiSZ))); + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result)); + EXPECT_EQ(result.size(), 1); + EXPECT_STREQ(result[0].c_str(), L"678"); + + // Run the following test only in dev machine + // This is because the build machine might not have admin privilege +#ifdef IS_PRIVATE_BUILD + // get a temp file name + wchar_t temp_path[MAX_PATH] = {0}; + EXPECT_LT(::GetTempPath(ARRAY_SIZE(temp_path), temp_path), + static_cast(ARRAY_SIZE(temp_path))); + wchar_t temp_file[MAX_PATH] = {0}; + EXPECT_NE(::GetTempFileName(temp_path, L"rkut_", + ::GetTickCount(), temp_file), 0); + + // test save + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt, kIntVal)); + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt64, kIntVal64)); + EXPECT_SUCCEEDED(RegKey::Save(kFullRkey1Subkey, temp_file)); + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt)); + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt64)); + + // test restore + EXPECT_SUCCEEDED(RegKey::Restore(kFullRkey1Subkey, temp_file)); + int_val = 0; + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey, kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal); + int64_val = 0; + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey, + kValNameInt64, + &int64_val)); + EXPECT_EQ(int64_val, kIntVal64); + + // delete the temp file + EXPECT_EQ(TRUE, ::DeleteFile(temp_file)); +#endif + + // whack the whole key + EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1)); +} + +} // namespace rtc diff --git a/webrtc/base/win32securityerrors.cc b/webrtc/base/win32securityerrors.cc new file mode 100644 index 000000000..71fe466a9 --- /dev/null +++ b/webrtc/base/win32securityerrors.cc @@ -0,0 +1,49 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/win32.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +extern const ConstantLabel SECURITY_ERRORS[]; + +const ConstantLabel SECURITY_ERRORS[] = { + KLABEL(SEC_I_COMPLETE_AND_CONTINUE), + KLABEL(SEC_I_COMPLETE_NEEDED), + KLABEL(SEC_I_CONTEXT_EXPIRED), + KLABEL(SEC_I_CONTINUE_NEEDED), + KLABEL(SEC_I_INCOMPLETE_CREDENTIALS), + KLABEL(SEC_I_RENEGOTIATE), + KLABEL(SEC_E_CERT_EXPIRED), + KLABEL(SEC_E_INCOMPLETE_MESSAGE), + KLABEL(SEC_E_INSUFFICIENT_MEMORY), + KLABEL(SEC_E_INTERNAL_ERROR), + KLABEL(SEC_E_INVALID_HANDLE), + KLABEL(SEC_E_INVALID_TOKEN), + KLABEL(SEC_E_LOGON_DENIED), + KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY), + KLABEL(SEC_E_NO_CREDENTIALS), + KLABEL(SEC_E_NOT_OWNER), + KLABEL(SEC_E_OK), + KLABEL(SEC_E_SECPKG_NOT_FOUND), + KLABEL(SEC_E_TARGET_UNKNOWN), + KLABEL(SEC_E_UNKNOWN_CREDENTIALS), + KLABEL(SEC_E_UNSUPPORTED_FUNCTION), + KLABEL(SEC_E_UNTRUSTED_ROOT), + KLABEL(SEC_E_WRONG_PRINCIPAL), + LASTLABEL +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/win32socketinit.cc b/webrtc/base/win32socketinit.cc new file mode 100644 index 000000000..02a6c26f4 --- /dev/null +++ b/webrtc/base/win32socketinit.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/win32socketinit.h" + +#include "webrtc/base/win32.h" + +namespace rtc { + +// Please don't remove this function. +void EnsureWinsockInit() { + // The default implementation uses a global initializer, so WSAStartup + // happens at module load time. Thus we don't need to do anything here. + // The hook is provided so that a client that statically links with + // libjingle can override it, to provide its own initialization. +} + +#if defined(WEBRTC_WIN) +class WinsockInitializer { + public: + WinsockInitializer() { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(1, 0); + err_ = WSAStartup(wVersionRequested, &wsaData); + } + ~WinsockInitializer() { + if (!err_) + WSACleanup(); + } + int error() { + return err_; + } + private: + int err_; +}; +WinsockInitializer g_winsockinit; +#endif + +} // namespace rtc diff --git a/webrtc/base/win32socketinit.h b/webrtc/base/win32socketinit.h new file mode 100644 index 000000000..46d27cba0 --- /dev/null +++ b/webrtc/base/win32socketinit.h @@ -0,0 +1,20 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WIN32SOCKETINIT_H_ +#define WEBRTC_BASE_WIN32SOCKETINIT_H_ + +namespace rtc { + +void EnsureWinsockInit(); + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32SOCKETINIT_H_ diff --git a/webrtc/base/win32socketserver.cc b/webrtc/base/win32socketserver.cc new file mode 100644 index 000000000..d0b736c58 --- /dev/null +++ b/webrtc/base/win32socketserver.cc @@ -0,0 +1,850 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/win32socketserver.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/winping.h" +#include "webrtc/base/win32window.h" +#include // NOLINT + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Socket +/////////////////////////////////////////////////////////////////////////////// + +// TODO: Move this to a common place where PhysicalSocketServer can +// share it. +// Standard MTUs +static const uint16 PACKET_MAXIMUMS[] = { + 65535, // Theoretical maximum, Hyperchannel + 32000, // Nothing + 17914, // 16Mb IBM Token Ring + 8166, // IEEE 802.4 + // 4464 // IEEE 802.5 (4Mb max) + 4352, // FDDI + // 2048, // Wideband Network + 2002, // IEEE 802.5 (4Mb recommended) + // 1536, // Expermental Ethernet Networks + // 1500, // Ethernet, Point-to-Point (default) + 1492, // IEEE 802.3 + 1006, // SLIP, ARPANET + // 576, // X.25 Networks + // 544, // DEC IP Portal + // 512, // NETBIOS + 508, // IEEE 802/Source-Rt Bridge, ARCNET + 296, // Point-to-Point (low delay) + 68, // Official minimum + 0, // End of list marker +}; + +static const int IP_HEADER_SIZE = 20u; +static const int ICMP_HEADER_SIZE = 8u; +static const int ICMP_PING_TIMEOUT_MILLIS = 10000u; + +// TODO: Enable for production builds also? Use FormatMessage? +#ifdef _DEBUG +LPCSTR WSAErrorToString(int error, LPCSTR *description_result) { + LPCSTR string = "Unspecified"; + LPCSTR description = "Unspecified description"; + switch (error) { + case ERROR_SUCCESS: + string = "SUCCESS"; + description = "Operation succeeded"; + break; + case WSAEWOULDBLOCK: + string = "WSAEWOULDBLOCK"; + description = "Using a non-blocking socket, will notify later"; + break; + case WSAEACCES: + string = "WSAEACCES"; + description = "Access denied, or sharing violation"; + break; + case WSAEADDRNOTAVAIL: + string = "WSAEADDRNOTAVAIL"; + description = "Address is not valid in this context"; + break; + case WSAENETDOWN: + string = "WSAENETDOWN"; + description = "Network is down"; + break; + case WSAENETUNREACH: + string = "WSAENETUNREACH"; + description = "Network is up, but unreachable"; + break; + case WSAENETRESET: + string = "WSANETRESET"; + description = "Connection has been reset due to keep-alive activity"; + break; + case WSAECONNABORTED: + string = "WSAECONNABORTED"; + description = "Aborted by host"; + break; + case WSAECONNRESET: + string = "WSAECONNRESET"; + description = "Connection reset by host"; + break; + case WSAETIMEDOUT: + string = "WSAETIMEDOUT"; + description = "Timed out, host failed to respond"; + break; + case WSAECONNREFUSED: + string = "WSAECONNREFUSED"; + description = "Host actively refused connection"; + break; + case WSAEHOSTDOWN: + string = "WSAEHOSTDOWN"; + description = "Host is down"; + break; + case WSAEHOSTUNREACH: + string = "WSAEHOSTUNREACH"; + description = "Host is unreachable"; + break; + case WSAHOST_NOT_FOUND: + string = "WSAHOST_NOT_FOUND"; + description = "No such host is known"; + break; + } + if (description_result) { + *description_result = description; + } + return string; +} + +void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) { + LPCSTR description_string; + LPCSTR error_string = WSAErrorToString(error, &description_string); + LOG(LS_INFO) << context << " = " << error + << " (" << error_string << ":" << description_string << ") [" + << address.ToString() << "]"; +} +#else +void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {} +#endif + +///////////////////////////////////////////////////////////////////////////// +// Win32Socket::EventSink +///////////////////////////////////////////////////////////////////////////// + +#define WM_SOCKETNOTIFY (WM_USER + 50) +#define WM_DNSNOTIFY (WM_USER + 51) + +struct Win32Socket::DnsLookup { + HANDLE handle; + uint16 port; + char buffer[MAXGETHOSTSTRUCT]; +}; + +class Win32Socket::EventSink : public Win32Window { + public: + explicit EventSink(Win32Socket * parent) : parent_(parent) { } + + void Dispose(); + + virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result); + virtual void OnNcDestroy(); + + private: + bool OnSocketNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result); + bool OnDnsNotify(WPARAM wParam, LPARAM lParam, LRESULT& result); + + Win32Socket * parent_; +}; + +void Win32Socket::EventSink::Dispose() { + parent_ = NULL; + if (::IsWindow(handle())) { + ::DestroyWindow(handle()); + } else { + delete this; + } +} + +bool Win32Socket::EventSink::OnMessage(UINT uMsg, WPARAM wParam, + LPARAM lParam, LRESULT& result) { + switch (uMsg) { + case WM_SOCKETNOTIFY: + case WM_TIMER: + return OnSocketNotify(uMsg, wParam, lParam, result); + case WM_DNSNOTIFY: + return OnDnsNotify(wParam, lParam, result); + } + return false; +} + +bool Win32Socket::EventSink::OnSocketNotify(UINT uMsg, WPARAM wParam, + LPARAM lParam, LRESULT& result) { + result = 0; + + int wsa_event = WSAGETSELECTEVENT(lParam); + int wsa_error = WSAGETSELECTERROR(lParam); + + // Treat connect timeouts as close notifications + if (uMsg == WM_TIMER) { + wsa_event = FD_CLOSE; + wsa_error = WSAETIMEDOUT; + } + + if (parent_) + parent_->OnSocketNotify(static_cast(wParam), wsa_event, wsa_error); + return true; +} + +bool Win32Socket::EventSink::OnDnsNotify(WPARAM wParam, LPARAM lParam, + LRESULT& result) { + result = 0; + + int error = WSAGETASYNCERROR(lParam); + if (parent_) + parent_->OnDnsNotify(reinterpret_cast(wParam), error); + return true; +} + +void Win32Socket::EventSink::OnNcDestroy() { + if (parent_) { + LOG(LS_ERROR) << "EventSink hwnd is being destroyed, but the event sink" + " hasn't yet been disposed."; + } else { + delete this; + } +} + +///////////////////////////////////////////////////////////////////////////// +// Win32Socket +///////////////////////////////////////////////////////////////////////////// + +Win32Socket::Win32Socket() + : socket_(INVALID_SOCKET), error_(0), state_(CS_CLOSED), connect_time_(0), + closing_(false), close_error_(0), sink_(NULL), dns_(NULL) { +} + +Win32Socket::~Win32Socket() { + Close(); +} + +bool Win32Socket::CreateT(int family, int type) { + Close(); + int proto = (SOCK_DGRAM == type) ? IPPROTO_UDP : IPPROTO_TCP; + socket_ = ::WSASocket(family, type, proto, NULL, NULL, 0); + if (socket_ == INVALID_SOCKET) { + UpdateLastError(); + return false; + } + if ((SOCK_DGRAM == type) && !SetAsync(FD_READ | FD_WRITE)) { + return false; + } + return true; +} + +int Win32Socket::Attach(SOCKET s) { + ASSERT(socket_ == INVALID_SOCKET); + if (socket_ != INVALID_SOCKET) + return SOCKET_ERROR; + + ASSERT(s != INVALID_SOCKET); + if (s == INVALID_SOCKET) + return SOCKET_ERROR; + + socket_ = s; + state_ = CS_CONNECTED; + + if (!SetAsync(FD_READ | FD_WRITE | FD_CLOSE)) + return SOCKET_ERROR; + + return 0; +} + +void Win32Socket::SetTimeout(int ms) { + if (sink_) + ::SetTimer(sink_->handle(), 1, ms, 0); +} + +SocketAddress Win32Socket::GetLocalAddress() const { + sockaddr_storage addr = {0}; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(socket_, reinterpret_cast(&addr), + &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } else { + LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket=" + << socket_; + } + return address; +} + +SocketAddress Win32Socket::GetRemoteAddress() const { + sockaddr_storage addr = {0}; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(socket_, reinterpret_cast(&addr), + &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } else { + LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket=" + << socket_; + } + return address; +} + +int Win32Socket::Bind(const SocketAddress& addr) { + ASSERT(socket_ != INVALID_SOCKET); + if (socket_ == INVALID_SOCKET) + return SOCKET_ERROR; + + sockaddr_storage saddr; + size_t len = addr.ToSockAddrStorage(&saddr); + int err = ::bind(socket_, + reinterpret_cast(&saddr), + static_cast(len)); + UpdateLastError(); + return err; +} + +int Win32Socket::Connect(const SocketAddress& addr) { + if (state_ != CS_CLOSED) { + SetError(EALREADY); + return SOCKET_ERROR; + } + + if (!addr.IsUnresolvedIP()) { + return DoConnect(addr); + } + + LOG_F(LS_INFO) << "async dns lookup (" << addr.hostname() << ")"; + DnsLookup * dns = new DnsLookup; + if (!sink_) { + // Explicitly create the sink ourselves here; we can't rely on SetAsync + // because we don't have a socket_ yet. + CreateSink(); + } + // TODO: Replace with IPv6 compatible lookup. + dns->handle = WSAAsyncGetHostByName(sink_->handle(), WM_DNSNOTIFY, + addr.hostname().c_str(), dns->buffer, + sizeof(dns->buffer)); + + if (!dns->handle) { + LOG_F(LS_ERROR) << "WSAAsyncGetHostByName error: " << WSAGetLastError(); + delete dns; + UpdateLastError(); + Close(); + return SOCKET_ERROR; + } + + dns->port = addr.port(); + dns_ = dns; + state_ = CS_CONNECTING; + return 0; +} + +int Win32Socket::DoConnect(const SocketAddress& addr) { + if ((socket_ == INVALID_SOCKET) && !CreateT(addr.family(), SOCK_STREAM)) { + return SOCKET_ERROR; + } + if (!SetAsync(FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE)) { + return SOCKET_ERROR; + } + + sockaddr_storage saddr = {0}; + size_t len = addr.ToSockAddrStorage(&saddr); + connect_time_ = Time(); + int result = connect(socket_, + reinterpret_cast(&saddr), + static_cast(len)); + if (result != SOCKET_ERROR) { + state_ = CS_CONNECTED; + } else { + int code = WSAGetLastError(); + if (code == WSAEWOULDBLOCK) { + state_ = CS_CONNECTING; + } else { + ReportWSAError("WSAAsync:connect", code, addr); + error_ = code; + Close(); + return SOCKET_ERROR; + } + } + addr_ = addr; + + return 0; +} + +int Win32Socket::GetError() const { + return error_; +} + +void Win32Socket::SetError(int error) { + error_ = error; +} + +Socket::ConnState Win32Socket::GetState() const { + return state_; +} + +int Win32Socket::GetOption(Option opt, int* value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + + char* p = reinterpret_cast(value); + int optlen = sizeof(value); + return ::getsockopt(socket_, slevel, sopt, p, &optlen); +} + +int Win32Socket::SetOption(Option opt, int value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + + const char* p = reinterpret_cast(&value); + return ::setsockopt(socket_, slevel, sopt, p, sizeof(value)); +} + +int Win32Socket::Send(const void* buffer, size_t length) { + int sent = ::send(socket_, + reinterpret_cast(buffer), + static_cast(length), + 0); + UpdateLastError(); + return sent; +} + +int Win32Socket::SendTo(const void* buffer, size_t length, + const SocketAddress& addr) { + sockaddr_storage saddr; + size_t addr_len = addr.ToSockAddrStorage(&saddr); + int sent = ::sendto(socket_, reinterpret_cast(buffer), + static_cast(length), 0, + reinterpret_cast(&saddr), + static_cast(addr_len)); + UpdateLastError(); + return sent; +} + +int Win32Socket::Recv(void* buffer, size_t length) { + int received = ::recv(socket_, static_cast(buffer), + static_cast(length), 0); + UpdateLastError(); + if (closing_ && received <= static_cast(length)) + PostClosed(); + return received; +} + +int Win32Socket::RecvFrom(void* buffer, size_t length, + SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + int received = ::recvfrom(socket_, static_cast(buffer), + static_cast(length), 0, + reinterpret_cast(&saddr), &addr_len); + UpdateLastError(); + if (received != SOCKET_ERROR) + SocketAddressFromSockAddrStorage(saddr, out_addr); + if (closing_ && received <= static_cast(length)) + PostClosed(); + return received; +} + +int Win32Socket::Listen(int backlog) { + int err = ::listen(socket_, backlog); + if (!SetAsync(FD_ACCEPT)) + return SOCKET_ERROR; + + UpdateLastError(); + if (err == 0) + state_ = CS_CONNECTING; + return err; +} + +Win32Socket* Win32Socket::Accept(SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + SOCKET s = ::accept(socket_, reinterpret_cast(&saddr), &addr_len); + UpdateLastError(); + if (s == INVALID_SOCKET) + return NULL; + if (out_addr) + SocketAddressFromSockAddrStorage(saddr, out_addr); + Win32Socket* socket = new Win32Socket; + if (0 == socket->Attach(s)) + return socket; + delete socket; + return NULL; +} + +int Win32Socket::Close() { + int err = 0; + if (socket_ != INVALID_SOCKET) { + err = ::closesocket(socket_); + socket_ = INVALID_SOCKET; + closing_ = false; + close_error_ = 0; + UpdateLastError(); + } + if (dns_) { + WSACancelAsyncRequest(dns_->handle); + delete dns_; + dns_ = NULL; + } + if (sink_) { + sink_->Dispose(); + sink_ = NULL; + } + addr_.Clear(); + state_ = CS_CLOSED; + return err; +} + +int Win32Socket::EstimateMTU(uint16* mtu) { + SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + error_ = ENOTCONN; + return -1; + } + + WinPing ping; + if (!ping.IsValid()) { + error_ = EINVAL; // can't think of a better error ID + return -1; + } + + for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) { + int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE; + WinPing::PingResult result = ping.Ping(addr.ipaddr(), size, + ICMP_PING_TIMEOUT_MILLIS, 1, false); + if (result == WinPing::PING_FAIL) { + error_ = EINVAL; // can't think of a better error ID + return -1; + } + if (result != WinPing::PING_TOO_LARGE) { + *mtu = PACKET_MAXIMUMS[level]; + return 0; + } + } + + ASSERT(false); + return 0; +} + +void Win32Socket::CreateSink() { + ASSERT(NULL == sink_); + + // Create window + sink_ = new EventSink(this); + sink_->Create(NULL, L"EventSink", 0, 0, 0, 0, 10, 10); +} + +bool Win32Socket::SetAsync(int events) { + if (NULL == sink_) { + CreateSink(); + ASSERT(NULL != sink_); + } + + // start the async select + if (WSAAsyncSelect(socket_, sink_->handle(), WM_SOCKETNOTIFY, events) + == SOCKET_ERROR) { + UpdateLastError(); + Close(); + return false; + } + + return true; +} + +bool Win32Socket::HandleClosed(int close_error) { + // WM_CLOSE will be received before all data has been read, so we need to + // hold on to it until the read buffer has been drained. + char ch; + closing_ = true; + close_error_ = close_error; + return (::recv(socket_, &ch, 1, MSG_PEEK) <= 0); +} + +void Win32Socket::PostClosed() { + // If we see that the buffer is indeed drained, then send the close. + closing_ = false; + ::PostMessage(sink_->handle(), WM_SOCKETNOTIFY, + socket_, WSAMAKESELECTREPLY(FD_CLOSE, close_error_)); +} + +void Win32Socket::UpdateLastError() { + error_ = WSAGetLastError(); +} + +int Win32Socket::TranslateOption(Option opt, int* slevel, int* sopt) { + switch (opt) { + case OPT_DONTFRAGMENT: + *slevel = IPPROTO_IP; + *sopt = IP_DONTFRAGMENT; + break; + case OPT_RCVBUF: + *slevel = SOL_SOCKET; + *sopt = SO_RCVBUF; + break; + case OPT_SNDBUF: + *slevel = SOL_SOCKET; + *sopt = SO_SNDBUF; + break; + case OPT_NODELAY: + *slevel = IPPROTO_TCP; + *sopt = TCP_NODELAY; + break; + case OPT_DSCP: + LOG(LS_WARNING) << "Socket::OPT_DSCP not supported."; + return -1; + default: + ASSERT(false); + return -1; + } + return 0; +} + +void Win32Socket::OnSocketNotify(SOCKET socket, int event, int error) { + // Ignore events if we're already closed. + if (socket != socket_) + return; + + error_ = error; + switch (event) { + case FD_CONNECT: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:connect notify", error, addr_); +#ifdef _DEBUG + int32 duration = TimeSince(connect_time_); + LOG(LS_INFO) << "WSAAsync:connect error (" << duration + << " ms), faking close"; +#endif + state_ = CS_CLOSED; + // If you get an error connecting, close doesn't really do anything + // and it certainly doesn't send back any close notification, but + // we really only maintain a few states, so it is easiest to get + // back into a known state by pretending that a close happened, even + // though the connect event never did occur. + SignalCloseEvent(this, error); + } else { +#ifdef _DEBUG + int32 duration = TimeSince(connect_time_); + LOG(LS_INFO) << "WSAAsync:connect (" << duration << " ms)"; +#endif + state_ = CS_CONNECTED; + SignalConnectEvent(this); + } + break; + + case FD_ACCEPT: + case FD_READ: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:read notify", error, addr_); + } else { + SignalReadEvent(this); + } + break; + + case FD_WRITE: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:write notify", error, addr_); + } else { + SignalWriteEvent(this); + } + break; + + case FD_CLOSE: + if (HandleClosed(error)) { + ReportWSAError("WSAAsync:close notify", error, addr_); + state_ = CS_CLOSED; + SignalCloseEvent(this, error); + } + break; + } +} + +void Win32Socket::OnDnsNotify(HANDLE task, int error) { + if (!dns_ || dns_->handle != task) + return; + + uint32 ip = 0; + if (error == 0) { + hostent* pHost = reinterpret_cast(dns_->buffer); + uint32 net_ip = *reinterpret_cast(pHost->h_addr_list[0]); + ip = NetworkToHost32(net_ip); + } + + LOG_F(LS_INFO) << "(" << IPAddress(ip).ToSensitiveString() + << ", " << error << ")"; + + if (error == 0) { + SocketAddress address(ip, dns_->port); + error = DoConnect(address); + } else { + Close(); + } + + if (error) { + error_ = error; + SignalCloseEvent(this, error_); + } else { + delete dns_; + dns_ = NULL; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Win32SocketServer +// Provides cricket base services on top of a win32 gui thread +/////////////////////////////////////////////////////////////////////////////// + +static UINT s_wm_wakeup_id = 0; +const TCHAR Win32SocketServer::kWindowName[] = L"libjingle Message Window"; + +Win32SocketServer::Win32SocketServer(MessageQueue* message_queue) + : message_queue_(message_queue), + wnd_(this), + posted_(false), + hdlg_(NULL) { + if (s_wm_wakeup_id == 0) + s_wm_wakeup_id = RegisterWindowMessage(L"WM_WAKEUP"); + if (!wnd_.Create(NULL, kWindowName, 0, 0, 0, 0, 0, 0)) { + LOG_GLE(LS_ERROR) << "Failed to create message window."; + } +} + +Win32SocketServer::~Win32SocketServer() { + if (wnd_.handle() != NULL) { + KillTimer(wnd_.handle(), 1); + wnd_.Destroy(); + } +} + +Socket* Win32SocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* Win32SocketServer::CreateSocket(int family, int type) { + return CreateAsyncSocket(family, type); +} + +AsyncSocket* Win32SocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* Win32SocketServer::CreateAsyncSocket(int family, int type) { + Win32Socket* socket = new Win32Socket; + if (socket->CreateT(family, type)) { + return socket; + } + delete socket; + return NULL; +} + +void Win32SocketServer::SetMessageQueue(MessageQueue* queue) { + message_queue_ = queue; +} + +bool Win32SocketServer::Wait(int cms, bool process_io) { + BOOL b; + if (process_io) { + // Spin the Win32 message pump at least once, and as long as requested. + // This is the Thread::ProcessMessages case. + uint32 start = Time(); + do { + MSG msg; + SetTimer(wnd_.handle(), 0, cms, NULL); + // Get the next available message. If we have a modeless dialog, give + // give the message to IsDialogMessage, which will return true if it + // was a message for the dialog that it handled internally. + // Otherwise, dispatch as usual via Translate/DispatchMessage. + b = GetMessage(&msg, NULL, 0, 0); + if (b == -1) { + LOG_GLE(LS_ERROR) << "GetMessage failed."; + return false; + } else if(b) { + if (!hdlg_ || !IsDialogMessage(hdlg_, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + KillTimer(wnd_.handle(), 0); + } while (b && TimeSince(start) < cms); + } else if (cms != 0) { + // Sit and wait forever for a WakeUp. This is the Thread::Send case. + ASSERT(cms == -1); + MSG msg; + b = GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id); + { + CritScope scope(&cs_); + posted_ = false; + } + } else { + // No-op (cms == 0 && !process_io). This is the Pump case. + b = TRUE; + } + return (b != FALSE); +} + +void Win32SocketServer::WakeUp() { + if (wnd_.handle()) { + // Set the "message pending" flag, if not already set. + { + CritScope scope(&cs_); + if (posted_) + return; + posted_ = true; + } + + PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0); + } +} + +void Win32SocketServer::Pump() { + // Clear the "message pending" flag. + { + CritScope scope(&cs_); + posted_ = false; + } + + // Dispatch all the messages that are currently in our queue. If new messages + // are posted during the dispatch, they will be handled in the next Pump. + // We use max(1, ...) to make sure we try to dispatch at least once, since + // this allow us to process "sent" messages, not included in the size() count. + Message msg; + for (size_t max_messages_to_process = _max(1, message_queue_->size()); + max_messages_to_process > 0 && message_queue_->Get(&msg, 0, false); + --max_messages_to_process) { + message_queue_->Dispatch(&msg); + } + + // Anything remaining? + int delay = message_queue_->GetDelay(); + if (delay == -1) { + KillTimer(wnd_.handle(), 1); + } else { + SetTimer(wnd_.handle(), 1, delay, NULL); + } +} + +bool Win32SocketServer::MessageWindow::OnMessage(UINT wm, WPARAM wp, + LPARAM lp, LRESULT& lr) { + bool handled = false; + if (wm == s_wm_wakeup_id || (wm == WM_TIMER && wp == 1)) { + ss_->Pump(); + lr = 0; + handled = true; + } + return handled; +} + +} // namespace rtc diff --git a/webrtc/base/win32socketserver.h b/webrtc/base/win32socketserver.h new file mode 100644 index 000000000..a03f6c028 --- /dev/null +++ b/webrtc/base/win32socketserver.h @@ -0,0 +1,164 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WIN32SOCKETSERVER_H_ +#define WEBRTC_BASE_WIN32SOCKETSERVER_H_ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/socketfactory.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/win32window.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Socket +/////////////////////////////////////////////////////////////////////////////// + +class Win32Socket : public AsyncSocket { + public: + Win32Socket(); + virtual ~Win32Socket(); + + bool CreateT(int family, int type); + + int Attach(SOCKET s); + void SetTimeout(int ms); + + // AsyncSocket Interface + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Bind(const SocketAddress& addr); + virtual int Connect(const SocketAddress& addr); + virtual int Send(const void *buffer, size_t length); + virtual int SendTo(const void *buffer, size_t length, const SocketAddress& addr); + virtual int Recv(void *buffer, size_t length); + virtual int RecvFrom(void *buffer, size_t length, SocketAddress *out_addr); + virtual int Listen(int backlog); + virtual Win32Socket *Accept(SocketAddress *out_addr); + virtual int Close(); + virtual int GetError() const; + virtual void SetError(int error); + virtual ConnState GetState() const; + virtual int EstimateMTU(uint16* mtu); + virtual int GetOption(Option opt, int* value); + virtual int SetOption(Option opt, int value); + + private: + void CreateSink(); + bool SetAsync(int events); + int DoConnect(const SocketAddress& addr); + bool HandleClosed(int close_error); + void PostClosed(); + void UpdateLastError(); + static int TranslateOption(Option opt, int* slevel, int* sopt); + + void OnSocketNotify(SOCKET socket, int event, int error); + void OnDnsNotify(HANDLE task, int error); + + SOCKET socket_; + int error_; + ConnState state_; + SocketAddress addr_; // address that we connected to (see DoConnect) + uint32 connect_time_; + bool closing_; + int close_error_; + + class EventSink; + friend class EventSink; + EventSink * sink_; + + struct DnsLookup; + DnsLookup * dns_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Win32SocketServer +/////////////////////////////////////////////////////////////////////////////// + +class Win32SocketServer : public SocketServer { + public: + explicit Win32SocketServer(MessageQueue* message_queue); + virtual ~Win32SocketServer(); + + void set_modeless_dialog(HWND hdlg) { + hdlg_ = hdlg; + } + + // SocketServer Interface + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual void SetMessageQueue(MessageQueue* queue); + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + void Pump(); + + HWND handle() { return wnd_.handle(); } + + private: + class MessageWindow : public Win32Window { + public: + explicit MessageWindow(Win32SocketServer* ss) : ss_(ss) {} + private: + virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result); + Win32SocketServer* ss_; + }; + + static const TCHAR kWindowName[]; + MessageQueue *message_queue_; + MessageWindow wnd_; + CriticalSection cs_; + bool posted_; + HWND hdlg_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Win32Thread. Automatically pumps Windows messages. +/////////////////////////////////////////////////////////////////////////////// + +class Win32Thread : public Thread { + public: + Win32Thread() : ss_(this), id_(0) { + set_socketserver(&ss_); + } + virtual ~Win32Thread() { + Stop(); + set_socketserver(NULL); + } + virtual void Run() { + id_ = GetCurrentThreadId(); + Thread::Run(); + id_ = 0; + } + virtual void Quit() { + PostThreadMessage(id_, WM_QUIT, 0, 0); + } + private: + Win32SocketServer ss_; + DWORD id_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_WIN + +#endif // WEBRTC_BASE_WIN32SOCKETSERVER_H_ diff --git a/webrtc/base/win32socketserver_unittest.cc b/webrtc/base/win32socketserver_unittest.cc new file mode 100644 index 000000000..1d3ef2ea3 --- /dev/null +++ b/webrtc/base/win32socketserver_unittest.cc @@ -0,0 +1,157 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "webrtc/base/gunit.h" +#include "webrtc/base/socket_unittest.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/win32socketserver.h" + +namespace rtc { + +// Test that Win32SocketServer::Wait works as expected. +TEST(Win32SocketServerTest, TestWait) { + Win32SocketServer server(NULL); + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that Win32Socket::Pump does not touch general Windows messages. +TEST(Win32SocketServerTest, TestPump) { + Win32SocketServer server(NULL); + SocketServerScope scope(&server); + EXPECT_EQ(TRUE, PostMessage(NULL, WM_USER, 999, 0)); + server.Pump(); + MSG msg; + EXPECT_EQ(TRUE, PeekMessage(&msg, NULL, WM_USER, 0, PM_REMOVE)); + EXPECT_EQ(WM_USER, msg.message); + EXPECT_EQ(999, msg.wParam); +} + +// Test that Win32Socket passes all the generic Socket tests. +class Win32SocketTest : public SocketTest { + protected: + Win32SocketTest() : server_(NULL), scope_(&server_) {} + Win32SocketServer server_; + SocketServerScope scope_; +}; + +TEST_F(Win32SocketTest, TestConnectIPv4) { + SocketTest::TestConnectIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectIPv6) { + SocketTest::TestConnectIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupIPv4) { + SocketTest::TestConnectWithDnsLookupIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupIPv6) { + SocketTest::TestConnectWithDnsLookupIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectFailIPv4) { + SocketTest::TestConnectFailIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectFailIPv6) { + SocketTest::TestConnectFailIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupFailIPv4) { + SocketTest::TestConnectWithDnsLookupFailIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupFailIPv6) { + SocketTest::TestConnectWithDnsLookupFailIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWithClosedSocketIPv4) { + SocketTest::TestConnectWithClosedSocketIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWithClosedSocketIPv6) { + SocketTest::TestConnectWithClosedSocketIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWhileNotClosedIPv4) { + SocketTest::TestConnectWhileNotClosedIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWhileNotClosedIPv6) { + SocketTest::TestConnectWhileNotClosedIPv6(); +} + +TEST_F(Win32SocketTest, TestServerCloseDuringConnectIPv4) { + SocketTest::TestServerCloseDuringConnectIPv4(); +} + +TEST_F(Win32SocketTest, TestServerCloseDuringConnectIPv6) { + SocketTest::TestServerCloseDuringConnectIPv6(); +} + +TEST_F(Win32SocketTest, TestClientCloseDuringConnectIPv4) { + SocketTest::TestClientCloseDuringConnectIPv4(); +} + +TEST_F(Win32SocketTest, TestClientCloseDuringConnectIPv6) { + SocketTest::TestClientCloseDuringConnectIPv6(); +} + +TEST_F(Win32SocketTest, TestServerCloseIPv4) { + SocketTest::TestServerCloseIPv4(); +} + +TEST_F(Win32SocketTest, TestServerCloseIPv6) { + SocketTest::TestServerCloseIPv6(); +} + +TEST_F(Win32SocketTest, TestCloseInClosedCallbackIPv4) { + SocketTest::TestCloseInClosedCallbackIPv4(); +} + +TEST_F(Win32SocketTest, TestCloseInClosedCallbackIPv6) { + SocketTest::TestCloseInClosedCallbackIPv6(); +} + +TEST_F(Win32SocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(Win32SocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} + +TEST_F(Win32SocketTest, TestTcpIPv4) { + SocketTest::TestTcpIPv4(); +} + +TEST_F(Win32SocketTest, TestTcpIPv6) { + SocketTest::TestTcpIPv6(); +} + +TEST_F(Win32SocketTest, TestUdpIPv4) { + SocketTest::TestUdpIPv4(); +} + +TEST_F(Win32SocketTest, TestUdpIPv6) { + SocketTest::TestUdpIPv6(); +} + +TEST_F(Win32SocketTest, TestGetSetOptionsIPv4) { + SocketTest::TestGetSetOptionsIPv4(); +} + +TEST_F(Win32SocketTest, TestGetSetOptionsIPv6) { + SocketTest::TestGetSetOptionsIPv6(); +} + +} // namespace rtc diff --git a/webrtc/base/win32toolhelp.h b/webrtc/base/win32toolhelp.h new file mode 100644 index 000000000..dfafdb317 --- /dev/null +++ b/webrtc/base/win32toolhelp.h @@ -0,0 +1,172 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef WEBRTC_BASE_WIN32TOOLHELP_H_ +#define WEBRTC_BASE_WIN32TOOLHELP_H_ + +#if !defined(WEBRTC_WIN) +#error WEBRTC_WIN Only +#endif + +#include "webrtc/base/win32.h" + +// Should be included first, but that causes redefinitions. +#include + +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +// The toolhelp api used to enumerate processes and their modules +// on Windows is very repetetive and clunky to use. This little +// template wraps it to make it a little more programmer friendly. +// +// Traits: Traits type that adapts the enumerator to the corresponding +// win32 toolhelp api. Each traits class need to: +// - define the type of the enumerated data as a public symbol Type +// +// - implement bool First(HANDLE, T*) normally calls a +// Xxxx32First method in the toolhelp API. Ex Process32First(...) +// +// - implement bool Next(HANDLE, T*) normally calls a +// Xxxx32Next method in the toolhelp API. Ex Process32Next(...) +// +// - implement bool CloseHandle(HANDLE) +// +template +class ToolhelpEnumeratorBase { + public: + ToolhelpEnumeratorBase(HANDLE snapshot) + : snapshot_(snapshot), broken_(false), first_(true) { + + // Clear out the Traits::Type structure instance. + Zero(¤t_); + } + + virtual ~ToolhelpEnumeratorBase() { + Close(); + } + + // Moves forward to the next object using the First and Next + // pointers. If either First or Next ever indicates an failure + // all subsequent calls to this method will fail; the enumerator + // object is considered broken. + bool Next() { + if (!Valid()) { + return false; + } + + // Move the iteration forward. + current_.dwSize = sizeof(typename Traits::Type); + bool incr_ok = false; + if (first_) { + incr_ok = Traits::First(snapshot_, ¤t_); + first_ = false; + } else { + incr_ok = Traits::Next(snapshot_, ¤t_); + } + + if (!incr_ok) { + Zero(¤t_); + broken_ = true; + } + + return incr_ok; + } + + const typename Traits::Type& current() const { + return current_; + } + + void Close() { + if (snapshot_ != INVALID_HANDLE_VALUE) { + Traits::CloseHandle(snapshot_); + snapshot_ = INVALID_HANDLE_VALUE; + } + } + + private: + // Checks the state of the snapshot handle. + bool Valid() { + return snapshot_ != INVALID_HANDLE_VALUE && !broken_; + } + + static void Zero(typename Traits::Type* buff) { + ZeroMemory(buff, sizeof(typename Traits::Type)); + } + + HANDLE snapshot_; + typename Traits::Type current_; + bool broken_; + bool first_; +}; + +class ToolhelpTraits { + public: + static HANDLE CreateSnapshot(uint32 flags, uint32 process_id) { + return CreateToolhelp32Snapshot(flags, process_id); + } + + static bool CloseHandle(HANDLE handle) { + return ::CloseHandle(handle) == TRUE; + } +}; + +class ToolhelpProcessTraits : public ToolhelpTraits { + public: + typedef PROCESSENTRY32 Type; + + static bool First(HANDLE handle, Type* t) { + return ::Process32First(handle, t) == TRUE; + } + + static bool Next(HANDLE handle, Type* t) { + return ::Process32Next(handle, t) == TRUE; + } +}; + +class ProcessEnumerator : public ToolhelpEnumeratorBase { + public: + ProcessEnumerator() + : ToolhelpEnumeratorBase( + ToolhelpProcessTraits::CreateSnapshot(TH32CS_SNAPPROCESS, 0)) { + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ProcessEnumerator); +}; + +class ToolhelpModuleTraits : public ToolhelpTraits { + public: + typedef MODULEENTRY32 Type; + + static bool First(HANDLE handle, Type* t) { + return ::Module32First(handle, t) == TRUE; + } + + static bool Next(HANDLE handle, Type* t) { + return ::Module32Next(handle, t) == TRUE; + } +}; + +class ModuleEnumerator : public ToolhelpEnumeratorBase { + public: + explicit ModuleEnumerator(uint32 process_id) + : ToolhelpEnumeratorBase( + ToolhelpModuleTraits::CreateSnapshot(TH32CS_SNAPMODULE, + process_id)) { + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ModuleEnumerator); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32TOOLHELP_H_ diff --git a/webrtc/base/win32toolhelp_unittest.cc b/webrtc/base/win32toolhelp_unittest.cc new file mode 100644 index 000000000..280f2ec98 --- /dev/null +++ b/webrtc/base/win32toolhelp_unittest.cc @@ -0,0 +1,278 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/win32toolhelp.h" + +namespace rtc { + +typedef struct { + // Required to match the toolhelp api struct 'design'. + DWORD dwSize; + int a; + uint32 b; +} TestData; + +class Win32ToolhelpTest : public testing::Test { + public: + Win32ToolhelpTest() { + } + + HANDLE AsHandle() { + return reinterpret_cast(this); + } + + static Win32ToolhelpTest* AsFixture(HANDLE handle) { + return reinterpret_cast(handle); + } + + static bool First(HANDLE handle, TestData* d) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + // This method should be called only once for every test. + // If it is called more than once it return false which + // should break the test. + EXPECT_EQ(0, tst->first_called_); // Just to be safe. + if (tst->first_called_ > 0) { + return false; + } + + *d = kTestData[0]; + tst->index_ = 1; + ++(tst->first_called_); + return true; + } + + static bool Next(HANDLE handle, TestData* d) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + ++(tst->next_called_); + + if (tst->index_ >= kTestDataSize) { + return FALSE; + } + + *d = kTestData[tst->index_]; + ++(tst->index_); + return true; + } + + static bool Fail(HANDLE handle, TestData* d) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + ++(tst->fail_called_); + return false; + } + + static bool CloseHandle(HANDLE handle) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + ++(tst->close_handle_called_); + return true; + } + + protected: + virtual void SetUp() { + fail_called_ = 0; + first_called_ = 0; + next_called_ = 0; + close_handle_called_ = 0; + index_ = 0; + } + + static bool AllZero(const TestData& data) { + return data.dwSize == 0 && data.a == 0 && data.b == 0; + } + + static bool Equals(const TestData& expected, const TestData& actual) { + return expected.dwSize == actual.dwSize + && expected.a == actual.a + && expected.b == actual.b; + } + + bool CheckCallCounters(int first, int next, int fail, int close) { + bool match = first_called_ == first && next_called_ == next + && fail_called_ == fail && close_handle_called_ == close; + + if (!match) { + LOG(LS_ERROR) << "Expected: (" + << first << ", " + << next << ", " + << fail << ", " + << close << ")"; + + LOG(LS_ERROR) << "Actual: (" + << first_called_ << ", " + << next_called_ << ", " + << fail_called_ << ", " + << close_handle_called_ << ")"; + } + return match; + } + + static const int kTestDataSize = 3; + static const TestData kTestData[]; + int index_; + int first_called_; + int fail_called_; + int next_called_; + int close_handle_called_; +}; + +const TestData Win32ToolhelpTest::kTestData[] = { + {1, 1, 1}, {2, 2, 2}, {3, 3, 3} +}; + + +class TestTraits { + public: + typedef TestData Type; + + static bool First(HANDLE handle, Type* t) { + return Win32ToolhelpTest::First(handle, t); + } + + static bool Next(HANDLE handle, Type* t) { + return Win32ToolhelpTest::Next(handle, t); + } + + static bool CloseHandle(HANDLE handle) { + return Win32ToolhelpTest::CloseHandle(handle); + } +}; + +class BadFirstTraits { + public: + typedef TestData Type; + + static bool First(HANDLE handle, Type* t) { + return Win32ToolhelpTest::Fail(handle, t); + } + + static bool Next(HANDLE handle, Type* t) { + // This should never be called. + ADD_FAILURE(); + return false; + } + + static bool CloseHandle(HANDLE handle) { + return Win32ToolhelpTest::CloseHandle(handle); + } +}; + +class BadNextTraits { + public: + typedef TestData Type; + + static bool First(HANDLE handle, Type* t) { + return Win32ToolhelpTest::First(handle, t); + } + + static bool Next(HANDLE handle, Type* t) { + return Win32ToolhelpTest::Fail(handle, t); + } + + static bool CloseHandle(HANDLE handle) { + return Win32ToolhelpTest::CloseHandle(handle); + } +}; + +// The toolhelp in normally inherited but most of +// these tests only excercise the methods from the +// traits therefore I use a typedef to make the +// test code easier to read. +typedef rtc::ToolhelpEnumeratorBase EnumeratorForTest; + +TEST_F(Win32ToolhelpTest, TestNextWithInvalidCtorHandle) { + EnumeratorForTest t(INVALID_HANDLE_VALUE); + + EXPECT_FALSE(t.Next()); + EXPECT_TRUE(CheckCallCounters(0, 0, 0, 0)); +} + +// Tests that Next() returns false if the first-pointer +// function fails. +TEST_F(Win32ToolhelpTest, TestNextFirstFails) { + typedef rtc::ToolhelpEnumeratorBase BadEnumerator; + rtc::scoped_ptr t(new BadEnumerator(AsHandle())); + + // If next ever fails it shall always fail. + EXPECT_FALSE(t->Next()); + EXPECT_FALSE(t->Next()); + EXPECT_FALSE(t->Next()); + t.reset(); + EXPECT_TRUE(CheckCallCounters(0, 0, 1, 1)); +} + +// Tests that Next() returns false if the next-pointer +// function fails. +TEST_F(Win32ToolhelpTest, TestNextNextFails) { + typedef rtc::ToolhelpEnumeratorBase BadEnumerator; + rtc::scoped_ptr t(new BadEnumerator(AsHandle())); + + // If next ever fails it shall always fail. No more calls + // shall be dispatched to Next(...). + EXPECT_TRUE(t->Next()); + EXPECT_FALSE(t->Next()); + EXPECT_FALSE(t->Next()); + t.reset(); + EXPECT_TRUE(CheckCallCounters(1, 0, 1, 1)); +} + + +// Tests that current returns an object is all zero's +// if Next() hasn't been called. +TEST_F(Win32ToolhelpTest, TestCurrentNextNotCalled) { + rtc::scoped_ptr t(new EnumeratorForTest(AsHandle())); + EXPECT_TRUE(AllZero(t->current())); + t.reset(); + EXPECT_TRUE(CheckCallCounters(0, 0, 0, 1)); +} + +// Tests the simple everything works path through the code. +TEST_F(Win32ToolhelpTest, TestCurrentNextCalled) { + rtc::scoped_ptr t(new EnumeratorForTest(AsHandle())); + + EXPECT_TRUE(t->Next()); + EXPECT_TRUE(Equals(t->current(), kTestData[0])); + EXPECT_TRUE(t->Next()); + EXPECT_TRUE(Equals(t->current(), kTestData[1])); + EXPECT_TRUE(t->Next()); + EXPECT_TRUE(Equals(t->current(), kTestData[2])); + EXPECT_FALSE(t->Next()); + t.reset(); + EXPECT_TRUE(CheckCallCounters(1, 3, 0, 1)); +} + +TEST_F(Win32ToolhelpTest, TestCurrentProcess) { + WCHAR buf[MAX_PATH]; + GetModuleFileName(NULL, buf, ARRAY_SIZE(buf)); + std::wstring name = ToUtf16(Pathname(ToUtf8(buf)).filename()); + + rtc::ProcessEnumerator processes; + bool found = false; + while (processes.Next()) { + if (!name.compare(processes.current().szExeFile)) { + found = true; + break; + } + } + EXPECT_TRUE(found); + + rtc::ModuleEnumerator modules(processes.current().th32ProcessID); + found = false; + while (modules.Next()) { + if (!name.compare(modules.current().szModule)) { + found = true; + break; + } + } + EXPECT_TRUE(found); +} + +} // namespace rtc diff --git a/webrtc/base/win32window.cc b/webrtc/base/win32window.cc new file mode 100644 index 000000000..4d4101405 --- /dev/null +++ b/webrtc/base/win32window.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/win32window.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Window +/////////////////////////////////////////////////////////////////////////////// + +static const wchar_t kWindowBaseClassName[] = L"WindowBaseClass"; +HINSTANCE Win32Window::instance_ = NULL; +ATOM Win32Window::window_class_ = 0; + +Win32Window::Win32Window() : wnd_(NULL) { +} + +Win32Window::~Win32Window() { + ASSERT(NULL == wnd_); +} + +bool Win32Window::Create(HWND parent, const wchar_t* title, DWORD style, + DWORD exstyle, int x, int y, int cx, int cy) { + if (wnd_) { + // Window already exists. + return false; + } + + if (!window_class_) { + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&Win32Window::WndProc), + &instance_)) { + LOG_GLE(LS_ERROR) << "GetModuleHandleEx failed"; + return false; + } + + // Class not registered, register it. + WNDCLASSEX wcex; + memset(&wcex, 0, sizeof(wcex)); + wcex.cbSize = sizeof(wcex); + wcex.hInstance = instance_; + wcex.lpfnWndProc = &Win32Window::WndProc; + wcex.lpszClassName = kWindowBaseClassName; + window_class_ = ::RegisterClassEx(&wcex); + if (!window_class_) { + LOG_GLE(LS_ERROR) << "RegisterClassEx failed"; + return false; + } + } + wnd_ = ::CreateWindowEx(exstyle, kWindowBaseClassName, title, style, + x, y, cx, cy, parent, NULL, instance_, this); + return (NULL != wnd_); +} + +void Win32Window::Destroy() { + VERIFY(::DestroyWindow(wnd_) != FALSE); +} + +void Win32Window::Shutdown() { + if (window_class_) { + ::UnregisterClass(MAKEINTATOM(window_class_), instance_); + window_class_ = 0; + } +} + +bool Win32Window::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result) { + switch (uMsg) { + case WM_CLOSE: + if (!OnClose()) { + result = 0; + return true; + } + break; + } + return false; +} + +LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + Win32Window* that = reinterpret_cast( + ::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (!that && (WM_CREATE == uMsg)) { + CREATESTRUCT* cs = reinterpret_cast(lParam); + that = static_cast(cs->lpCreateParams); + that->wnd_ = hwnd; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(that)); + } + if (that) { + LRESULT result; + bool handled = that->OnMessage(uMsg, wParam, lParam, result); + if (WM_DESTROY == uMsg) { + for (HWND child = ::GetWindow(hwnd, GW_CHILD); child; + child = ::GetWindow(child, GW_HWNDNEXT)) { + LOG(LS_INFO) << "Child window: " << static_cast(child); + } + } + if (WM_NCDESTROY == uMsg) { + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL); + that->wnd_ = NULL; + that->OnNcDestroy(); + } + if (handled) { + return result; + } + } + return ::DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +} // namespace rtc diff --git a/webrtc/base/win32window.h b/webrtc/base/win32window.h new file mode 100644 index 000000000..c0ba6b23d --- /dev/null +++ b/webrtc/base/win32window.h @@ -0,0 +1,60 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WIN32WINDOW_H_ +#define WEBRTC_BASE_WIN32WINDOW_H_ + +#if defined(WEBRTC_WIN) + +#include "webrtc/base/win32.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Window +/////////////////////////////////////////////////////////////////////////////// + +class Win32Window { + public: + Win32Window(); + virtual ~Win32Window(); + + HWND handle() const { return wnd_; } + + bool Create(HWND parent, const wchar_t* title, DWORD style, DWORD exstyle, + int x, int y, int cx, int cy); + void Destroy(); + + // Call this when your DLL unloads. + static void Shutdown(); + + protected: + virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result); + + virtual bool OnClose() { return true; } + virtual void OnNcDestroy() { } + + private: + static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + + HWND wnd_; + static HINSTANCE instance_; + static ATOM window_class_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_WIN + +#endif // WEBRTC_BASE_WIN32WINDOW_H_ diff --git a/webrtc/base/win32window_unittest.cc b/webrtc/base/win32window_unittest.cc new file mode 100644 index 000000000..5dba67eb5 --- /dev/null +++ b/webrtc/base/win32window_unittest.cc @@ -0,0 +1,66 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/common.h" +#include "webrtc/base/win32window.h" +#include "webrtc/base/logging.h" + +static LRESULT kDummyResult = 0x1234ABCD; + +class TestWindow : public rtc::Win32Window { + public: + TestWindow() : destroyed_(false) { memset(&msg_, 0, sizeof(msg_)); } + const MSG& msg() const { return msg_; } + bool destroyed() const { return destroyed_; } + + virtual bool OnMessage(UINT uMsg, WPARAM wParam, + LPARAM lParam, LRESULT& result) { + msg_.message = uMsg; + msg_.wParam = wParam; + msg_.lParam = lParam; + result = kDummyResult; + return true; + } + virtual void OnNcDestroy() { + destroyed_ = true; + } + + private: + MSG msg_; + bool destroyed_; +}; + +TEST(Win32WindowTest, Basics) { + TestWindow wnd; + EXPECT_TRUE(wnd.handle() == NULL); + EXPECT_FALSE(wnd.destroyed()); + EXPECT_TRUE(wnd.Create(0, L"Test", 0, 0, 0, 0, 100, 100)); + EXPECT_TRUE(wnd.handle() != NULL); + EXPECT_EQ(kDummyResult, ::SendMessage(wnd.handle(), WM_USER, 1, 2)); + EXPECT_EQ(WM_USER, wnd.msg().message); + EXPECT_EQ(1, wnd.msg().wParam); + EXPECT_EQ(2, wnd.msg().lParam); + wnd.Destroy(); + EXPECT_TRUE(wnd.handle() == NULL); + EXPECT_TRUE(wnd.destroyed()); +} + +TEST(Win32WindowTest, MultipleWindows) { + TestWindow wnd1, wnd2; + EXPECT_TRUE(wnd1.Create(0, L"Test", 0, 0, 0, 0, 100, 100)); + EXPECT_TRUE(wnd2.Create(0, L"Test", 0, 0, 0, 0, 100, 100)); + EXPECT_TRUE(wnd1.handle() != NULL); + EXPECT_TRUE(wnd2.handle() != NULL); + wnd1.Destroy(); + wnd2.Destroy(); + EXPECT_TRUE(wnd2.handle() == NULL); + EXPECT_TRUE(wnd1.handle() == NULL); +} diff --git a/webrtc/base/win32windowpicker.cc b/webrtc/base/win32windowpicker.cc new file mode 100644 index 000000000..b4550ae4a --- /dev/null +++ b/webrtc/base/win32windowpicker.cc @@ -0,0 +1,143 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "webrtc/base/win32windowpicker.h" + +#include +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +namespace { + +// Window class names that we want to filter out. +const char kProgramManagerClass[] = "Progman"; +const char kButtonClass[] = "Button"; + +} // namespace + +BOOL CALLBACK Win32WindowPicker::EnumProc(HWND hwnd, LPARAM l_param) { + WindowDescriptionList* descriptions = + reinterpret_cast(l_param); + + // Skip windows that are invisible, minimized, have no title, or are owned, + // unless they have the app window style set. Except for minimized windows, + // this is what Alt-Tab does. + // TODO: Figure out how to grab a thumbnail of a minimized window and + // include them in the list. + int len = GetWindowTextLength(hwnd); + HWND owner = GetWindow(hwnd, GW_OWNER); + LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) || + (owner && !(exstyle & WS_EX_APPWINDOW))) { + // TODO: Investigate if windows without title still could be + // interesting to share. We could use the name of the process as title: + // + // GetWindowThreadProcessId() + // OpenProcess() + // QueryFullProcessImageName() + return TRUE; + } + + // Skip the Program Manager window and the Start button. + TCHAR class_name_w[500]; + ::GetClassName(hwnd, class_name_w, 500); + std::string class_name = ToUtf8(class_name_w); + if (class_name == kProgramManagerClass || class_name == kButtonClass) { + // We don't want the Program Manager window nor the Start button. + return TRUE; + } + + TCHAR window_title[500]; + GetWindowText(hwnd, window_title, ARRAY_SIZE(window_title)); + std::string title = ToUtf8(window_title); + + WindowId id(hwnd); + WindowDescription desc(id, title); + descriptions->push_back(desc); + return TRUE; +} + +BOOL CALLBACK Win32WindowPicker::MonitorEnumProc(HMONITOR h_monitor, + HDC hdc_monitor, + LPRECT lprc_monitor, + LPARAM l_param) { + DesktopDescriptionList* desktop_desc = + reinterpret_cast(l_param); + + DesktopId id(h_monitor, static_cast(desktop_desc->size())); + // TODO: Figure out an appropriate desktop title. + DesktopDescription desc(id, ""); + + // Determine whether it's the primary monitor. + MONITORINFO monitor_info = {0}; + monitor_info.cbSize = sizeof(monitor_info); + bool primary = (GetMonitorInfo(h_monitor, &monitor_info) && + (monitor_info.dwFlags & MONITORINFOF_PRIMARY) != 0); + desc.set_primary(primary); + + desktop_desc->push_back(desc); + return TRUE; +} + +Win32WindowPicker::Win32WindowPicker() { +} + +bool Win32WindowPicker::Init() { + return true; +} +// TODO: Consider changing enumeration to clear() descriptions +// before append(). +bool Win32WindowPicker::GetWindowList(WindowDescriptionList* descriptions) { + LPARAM desc = reinterpret_cast(descriptions); + return EnumWindows(Win32WindowPicker::EnumProc, desc) != FALSE; +} + +bool Win32WindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { + // Create a fresh WindowDescriptionList so that we can use desktop_desc.size() + // in MonitorEnumProc to compute the desktop index. + DesktopDescriptionList desktop_desc; + HDC hdc = GetDC(NULL); + bool success = false; + if (EnumDisplayMonitors(hdc, NULL, Win32WindowPicker::MonitorEnumProc, + reinterpret_cast(&desktop_desc)) != FALSE) { + // Append the desktop descriptions to the end of the returned descriptions. + descriptions->insert(descriptions->end(), desktop_desc.begin(), + desktop_desc.end()); + success = true; + } + ReleaseDC(NULL, hdc); + return success; +} + +bool Win32WindowPicker::GetDesktopDimensions(const DesktopId& id, + int* width, + int* height) { + MONITORINFOEX monitor_info; + monitor_info.cbSize = sizeof(MONITORINFOEX); + if (!GetMonitorInfo(id.id(), &monitor_info)) { + return false; + } + *width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left; + *height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top; + return true; +} + +bool Win32WindowPicker::IsVisible(const WindowId& id) { + return (::IsWindow(id.id()) != FALSE && ::IsWindowVisible(id.id()) != FALSE); +} + +bool Win32WindowPicker::MoveToFront(const WindowId& id) { + return SetForegroundWindow(id.id()) != FALSE; +} + +} // namespace rtc diff --git a/webrtc/base/win32windowpicker.h b/webrtc/base/win32windowpicker.h new file mode 100644 index 000000000..9c84bfd98 --- /dev/null +++ b/webrtc/base/win32windowpicker.h @@ -0,0 +1,39 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef WEBRTC_BASE_WIN32WINDOWPICKER_H_ +#define WEBRTC_BASE_WIN32WINDOWPICKER_H_ + +#include "webrtc/base/win32.h" +#include "webrtc/base/windowpicker.h" + +namespace rtc { + +class Win32WindowPicker : public WindowPicker { + public: + Win32WindowPicker(); + virtual bool Init(); + virtual bool IsVisible(const WindowId& id); + virtual bool MoveToFront(const WindowId& id); + virtual bool GetWindowList(WindowDescriptionList* descriptions); + virtual bool GetDesktopList(DesktopDescriptionList* descriptions); + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height); + + protected: + static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM l_param); + static BOOL CALLBACK MonitorEnumProc(HMONITOR h_monitor, + HDC hdc_monitor, + LPRECT lprc_monitor, + LPARAM l_param); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32WINDOWPICKER_H_ diff --git a/webrtc/base/win32windowpicker_unittest.cc b/webrtc/base/win32windowpicker_unittest.cc new file mode 100644 index 000000000..71e8af6bf --- /dev/null +++ b/webrtc/base/win32windowpicker_unittest.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "webrtc/base/gunit.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/win32window.h" +#include "webrtc/base/win32windowpicker.h" +#include "webrtc/base/windowpicker.h" + +#if !defined(WEBRTC_WIN) +#error Only for Windows +#endif + +namespace rtc { + +static const TCHAR* kVisibleWindowTitle = L"Visible Window"; +static const TCHAR* kInvisibleWindowTitle = L"Invisible Window"; + +class Win32WindowPickerForTest : public Win32WindowPicker { + public: + Win32WindowPickerForTest() { + EXPECT_TRUE(visible_window_.Create(NULL, kVisibleWindowTitle, WS_VISIBLE, + 0, 0, 0, 0, 0)); + EXPECT_TRUE(invisible_window_.Create(NULL, kInvisibleWindowTitle, 0, + 0, 0, 0, 0, 0)); + } + + ~Win32WindowPickerForTest() { + visible_window_.Destroy(); + invisible_window_.Destroy(); + } + + virtual bool GetWindowList(WindowDescriptionList* descriptions) { + if (!Win32WindowPicker::EnumProc(visible_window_.handle(), + reinterpret_cast(descriptions))) { + return false; + } + if (!Win32WindowPicker::EnumProc(invisible_window_.handle(), + reinterpret_cast(descriptions))) { + return false; + } + return true; + } + + Win32Window* visible_window() { + return &visible_window_; + } + + Win32Window* invisible_window() { + return &invisible_window_; + } + + private: + Win32Window visible_window_; + Win32Window invisible_window_; +}; + +TEST(Win32WindowPickerTest, TestGetWindowList) { + Win32WindowPickerForTest window_picker; + WindowDescriptionList descriptions; + EXPECT_TRUE(window_picker.GetWindowList(&descriptions)); + EXPECT_EQ(1, descriptions.size()); + WindowDescription desc = descriptions.front(); + EXPECT_EQ(window_picker.visible_window()->handle(), desc.id().id()); + TCHAR window_title[500]; + GetWindowText(window_picker.visible_window()->handle(), window_title, + ARRAY_SIZE(window_title)); + EXPECT_EQ(0, wcscmp(window_title, kVisibleWindowTitle)); +} + +TEST(Win32WindowPickerTest, TestIsVisible) { + Win32WindowPickerForTest window_picker; + HWND visible_id = window_picker.visible_window()->handle(); + HWND invisible_id = window_picker.invisible_window()->handle(); + EXPECT_TRUE(window_picker.IsVisible(WindowId(visible_id))); + EXPECT_FALSE(window_picker.IsVisible(WindowId(invisible_id))); +} + +TEST(Win32WindowPickerTest, TestMoveToFront) { + Win32WindowPickerForTest window_picker; + HWND visible_id = window_picker.visible_window()->handle(); + HWND invisible_id = window_picker.invisible_window()->handle(); + + // There are a number of condition where SetForegroundWindow might + // fail depending on the state of the calling process. To be on the + // safe side we doesn't expect MoveToFront to return true, just test + // that we don't crash. + window_picker.MoveToFront(WindowId(visible_id)); + window_picker.MoveToFront(WindowId(invisible_id)); +} + +} // namespace rtc diff --git a/webrtc/base/window.h b/webrtc/base/window.h new file mode 100644 index 000000000..d96102652 --- /dev/null +++ b/webrtc/base/window.h @@ -0,0 +1,124 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WINDOW_H_ +#define WEBRTC_BASE_WINDOW_H_ + +#include "webrtc/base/stringencode.h" + +// Define platform specific window types. +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +typedef unsigned long Window; // Avoid include . +#elif defined(WEBRTC_WIN) +// We commonly include win32.h in webrtc/base so just include it here. +#include "webrtc/base/win32.h" // Include HWND, HMONITOR. +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +typedef unsigned int CGWindowID; +typedef unsigned int CGDirectDisplayID; +#endif + +namespace rtc { + +class WindowId { + public: + // Define WindowT for each platform. +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + typedef Window WindowT; +#elif defined(WEBRTC_WIN) + typedef HWND WindowT; +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + typedef CGWindowID WindowT; +#else + typedef unsigned int WindowT; +#endif + + static WindowId Cast(uint64 id) { +#if defined(WEBRTC_WIN) + return WindowId(reinterpret_cast(id)); +#else + return WindowId(static_cast(id)); +#endif + } + + static uint64 Format(const WindowT& id) { +#if defined(WEBRTC_WIN) + return static_cast(reinterpret_cast(id)); +#else + return static_cast(id); +#endif + } + + WindowId() : id_(0) {} + WindowId(const WindowT& id) : id_(id) {} // NOLINT + const WindowT& id() const { return id_; } + bool IsValid() const { return id_ != 0; } + bool Equals(const WindowId& other) const { + return id_ == other.id(); + } + + private: + WindowT id_; +}; + +class DesktopId { + public: + // Define DesktopT for each platform. +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + typedef Window DesktopT; +#elif defined(WEBRTC_WIN) + typedef HMONITOR DesktopT; +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + typedef CGDirectDisplayID DesktopT; +#else + typedef unsigned int DesktopT; +#endif + + static DesktopId Cast(int id, int index) { +#if defined(WEBRTC_WIN) + return DesktopId(reinterpret_cast(id), index); +#else + return DesktopId(static_cast(id), index); +#endif + } + + DesktopId() : id_(0), index_(-1) {} + DesktopId(const DesktopT& id, int index) // NOLINT + : id_(id), index_(index) { + } + const DesktopT& id() const { return id_; } + int index() const { return index_; } + bool IsValid() const { return index_ != -1; } + bool Equals(const DesktopId& other) const { + return id_ == other.id() && index_ == other.index(); + } + + private: + // Id is the platform specific desktop identifier. + DesktopT id_; + // Index is the desktop index as enumerated by each platform. + // Desktop capturer typically takes the index instead of id. + int index_; +}; + +// Window event types. +enum WindowEvent { + WE_RESIZE = 0, + WE_CLOSE = 1, + WE_MINIMIZE = 2, + WE_RESTORE = 3, +}; + +inline std::string ToString(const WindowId& window) { + return ToString(window.id()); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_WINDOW_H_ diff --git a/webrtc/base/windowpicker.h b/webrtc/base/windowpicker.h new file mode 100644 index 000000000..3ae7b0e49 --- /dev/null +++ b/webrtc/base/windowpicker.h @@ -0,0 +1,84 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WINDOWPICKER_H_ +#define WEBRTC_BASE_WINDOWPICKER_H_ + +#include +#include + +#include "webrtc/base/window.h" + +namespace rtc { + +class WindowDescription { + public: + WindowDescription() : id_() {} + WindowDescription(const WindowId& id, const std::string& title) + : id_(id), title_(title) { + } + const WindowId& id() const { return id_; } + void set_id(const WindowId& id) { id_ = id; } + const std::string& title() const { return title_; } + void set_title(const std::string& title) { title_ = title; } + + private: + WindowId id_; + std::string title_; +}; + +class DesktopDescription { + public: + DesktopDescription() : id_() {} + DesktopDescription(const DesktopId& id, const std::string& title) + : id_(id), title_(title), primary_(false) { + } + const DesktopId& id() const { return id_; } + void set_id(const DesktopId& id) { id_ = id; } + const std::string& title() const { return title_; } + void set_title(const std::string& title) { title_ = title; } + // Indicates whether it is the primary desktop in the system. + bool primary() const { return primary_; } + void set_primary(bool primary) { primary_ = primary; } + + private: + DesktopId id_; + std::string title_; + bool primary_; +}; + +typedef std::vector WindowDescriptionList; +typedef std::vector DesktopDescriptionList; + +class WindowPicker { + public: + virtual ~WindowPicker() {} + virtual bool Init() = 0; + + // TODO: Move this two methods to window.h when we no longer need to load + // CoreGraphics dynamically. + virtual bool IsVisible(const WindowId& id) = 0; + virtual bool MoveToFront(const WindowId& id) = 0; + + // Gets a list of window description and appends to descriptions. + // Returns true if successful. + virtual bool GetWindowList(WindowDescriptionList* descriptions) = 0; + // Gets a list of desktop descriptions and appends to descriptions. + // Returns true if successful. + virtual bool GetDesktopList(DesktopDescriptionList* descriptions) = 0; + // Gets the width and height of a desktop. + // Returns true if successful. + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height) = 0; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WINDOWPICKER_H_ diff --git a/webrtc/base/windowpicker_unittest.cc b/webrtc/base/windowpicker_unittest.cc new file mode 100644 index 000000000..edd01bc0b --- /dev/null +++ b/webrtc/base/windowpicker_unittest.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "webrtc/base/gunit.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/window.h" +#include "webrtc/base/windowpicker.h" +#include "webrtc/base/windowpickerfactory.h" + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +# define DISABLE_ON_MAC(name) DISABLED_ ## name +#else +# define DISABLE_ON_MAC(name) name +#endif + +TEST(WindowPickerTest, GetWindowList) { + MAYBE_SKIP_SCREENCAST_TEST(); + if (!rtc::WindowPickerFactory::IsSupported()) { + LOG(LS_INFO) << "skipping test: window capturing is not supported with " + << "current configuration."; + } + rtc::scoped_ptr picker( + rtc::WindowPickerFactory::CreateWindowPicker()); + EXPECT_TRUE(picker->Init()); + rtc::WindowDescriptionList descriptions; + EXPECT_TRUE(picker->GetWindowList(&descriptions)); +} + +// TODO(hughv) Investigate why this fails on pulse but not locally after +// upgrading to XCode 4.5. The failure is GetDesktopList returning FALSE. +TEST(WindowPickerTest, DISABLE_ON_MAC(GetDesktopList)) { + MAYBE_SKIP_SCREENCAST_TEST(); + if (!rtc::WindowPickerFactory::IsSupported()) { + LOG(LS_INFO) << "skipping test: window capturing is not supported with " + << "current configuration."; + } + rtc::scoped_ptr picker( + rtc::WindowPickerFactory::CreateWindowPicker()); + EXPECT_TRUE(picker->Init()); + rtc::DesktopDescriptionList descriptions; + EXPECT_TRUE(picker->GetDesktopList(&descriptions)); + if (descriptions.size() > 0) { + int width = 0; + int height = 0; + EXPECT_TRUE(picker->GetDesktopDimensions(descriptions[0].id(), &width, + &height)); + EXPECT_GT(width, 0); + EXPECT_GT(height, 0); + + // Test |IsPrimaryDesktop|. Only one desktop should be a primary. + bool found_primary = false; + for (rtc::DesktopDescriptionList::iterator it = descriptions.begin(); + it != descriptions.end(); ++it) { + if (it->primary()) { + EXPECT_FALSE(found_primary); + found_primary = true; + } + } + EXPECT_TRUE(found_primary); + } +} diff --git a/webrtc/base/windowpickerfactory.h b/webrtc/base/windowpickerfactory.h new file mode 100644 index 000000000..00a4cc469 --- /dev/null +++ b/webrtc/base/windowpickerfactory.h @@ -0,0 +1,59 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WINDOWPICKERFACTORY_H_ +#define WEBRTC_BASE_WINDOWPICKERFACTORY_H_ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32windowpicker.h" +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "webrtc/base/macutils.h" +#include "webrtc/base/macwindowpicker.h" +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include "webrtc/base/linuxwindowpicker.h" +#endif + +#include "webrtc/base/windowpicker.h" + +namespace rtc { + +class WindowPickerFactory { + public: + virtual ~WindowPickerFactory() {} + + // Instance method for dependency injection. + virtual WindowPicker* Create() { + return CreateWindowPicker(); + } + + static WindowPicker* CreateWindowPicker() { +#if defined(WEBRTC_WIN) + return new Win32WindowPicker(); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + return new MacWindowPicker(); +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) && defined(HAVE_X11) + return new LinuxWindowPicker(); +#else + return NULL; +#endif + } + + static bool IsSupported() { +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + return GetOSVersionName() >= kMacOSLeopard; +#else + return true; +#endif + } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WINDOWPICKERFACTORY_H_ diff --git a/webrtc/base/winfirewall.cc b/webrtc/base/winfirewall.cc new file mode 100644 index 000000000..97e6d1518 --- /dev/null +++ b/webrtc/base/winfirewall.cc @@ -0,0 +1,155 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/winfirewall.h" + +#include "webrtc/base/win32.h" + +#include +#include + +#define RELEASE(lpUnk) do { \ + if ((lpUnk) != NULL) { \ + (lpUnk)->Release(); \ + (lpUnk) = NULL; \ + } \ +} while (0) + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// WinFirewall +////////////////////////////////////////////////////////////////////// + +WinFirewall::WinFirewall() : mgr_(NULL), policy_(NULL), profile_(NULL) { +} + +WinFirewall::~WinFirewall() { + Shutdown(); +} + +bool WinFirewall::Initialize(HRESULT* result) { + if (mgr_) { + if (result) { + *result = S_OK; + } + return true; + } + + HRESULT hr = CoCreateInstance(__uuidof(NetFwMgr), + 0, CLSCTX_INPROC_SERVER, + __uuidof(INetFwMgr), + reinterpret_cast(&mgr_)); + if (SUCCEEDED(hr) && (mgr_ != NULL)) + hr = mgr_->get_LocalPolicy(&policy_); + if (SUCCEEDED(hr) && (policy_ != NULL)) + hr = policy_->get_CurrentProfile(&profile_); + + if (result) + *result = hr; + return SUCCEEDED(hr) && (profile_ != NULL); +} + +void WinFirewall::Shutdown() { + RELEASE(profile_); + RELEASE(policy_); + RELEASE(mgr_); +} + +bool WinFirewall::Enabled() const { + if (!profile_) + return false; + + VARIANT_BOOL fwEnabled = VARIANT_FALSE; + profile_->get_FirewallEnabled(&fwEnabled); + return (fwEnabled != VARIANT_FALSE); +} + +bool WinFirewall::QueryAuthorized(const char* filename, bool* authorized) + const { + return QueryAuthorizedW(ToUtf16(filename).c_str(), authorized); +} + +bool WinFirewall::QueryAuthorizedW(const wchar_t* filename, bool* authorized) + const { + *authorized = false; + bool success = false; + + if (!profile_) + return false; + + _bstr_t bfilename = filename; + + INetFwAuthorizedApplications* apps = NULL; + HRESULT hr = profile_->get_AuthorizedApplications(&apps); + if (SUCCEEDED(hr) && (apps != NULL)) { + INetFwAuthorizedApplication* app = NULL; + hr = apps->Item(bfilename, &app); + if (SUCCEEDED(hr) && (app != NULL)) { + VARIANT_BOOL fwEnabled = VARIANT_FALSE; + hr = app->get_Enabled(&fwEnabled); + app->Release(); + + if (SUCCEEDED(hr)) { + success = true; + *authorized = (fwEnabled != VARIANT_FALSE); + } + } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + // No entry in list of authorized apps + success = true; + } else { + // Unexpected error + } + apps->Release(); + } + + return success; +} + +bool WinFirewall::AddApplication(const char* filename, + const char* friendly_name, + bool authorized, + HRESULT* result) { + return AddApplicationW(ToUtf16(filename).c_str(), + ToUtf16(friendly_name).c_str(), authorized, result); +} + +bool WinFirewall::AddApplicationW(const wchar_t* filename, + const wchar_t* friendly_name, + bool authorized, + HRESULT* result) { + INetFwAuthorizedApplications* apps = NULL; + HRESULT hr = profile_->get_AuthorizedApplications(&apps); + if (SUCCEEDED(hr) && (apps != NULL)) { + INetFwAuthorizedApplication* app = NULL; + hr = CoCreateInstance(__uuidof(NetFwAuthorizedApplication), + 0, CLSCTX_INPROC_SERVER, + __uuidof(INetFwAuthorizedApplication), + reinterpret_cast(&app)); + if (SUCCEEDED(hr) && (app != NULL)) { + _bstr_t bstr = filename; + hr = app->put_ProcessImageFileName(bstr); + bstr = friendly_name; + if (SUCCEEDED(hr)) + hr = app->put_Name(bstr); + if (SUCCEEDED(hr)) + hr = app->put_Enabled(authorized ? VARIANT_TRUE : VARIANT_FALSE); + if (SUCCEEDED(hr)) + hr = apps->Add(app); + app->Release(); + } + apps->Release(); + } + if (result) + *result = hr; + return SUCCEEDED(hr); +} + +} // namespace rtc diff --git a/webrtc/base/winfirewall.h b/webrtc/base/winfirewall.h new file mode 100644 index 000000000..a74631baf --- /dev/null +++ b/webrtc/base/winfirewall.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WINFIREWALL_H_ +#define WEBRTC_BASE_WINFIREWALL_H_ + +#ifndef _HRESULT_DEFINED +#define _HRESULT_DEFINED +typedef long HRESULT; // Can't forward declare typedef, but don't need all win +#endif // !_HRESULT_DEFINED + +struct INetFwMgr; +struct INetFwPolicy; +struct INetFwProfile; + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// WinFirewall +////////////////////////////////////////////////////////////////////// + +class WinFirewall { + public: + WinFirewall(); + ~WinFirewall(); + + bool Initialize(HRESULT* result); + void Shutdown(); + + bool Enabled() const; + bool QueryAuthorized(const char* filename, bool* authorized) const; + bool QueryAuthorizedW(const wchar_t* filename, bool* authorized) const; + + bool AddApplication(const char* filename, const char* friendly_name, + bool authorized, HRESULT* result); + bool AddApplicationW(const wchar_t* filename, const wchar_t* friendly_name, + bool authorized, HRESULT* result); + + private: + INetFwMgr* mgr_; + INetFwPolicy* policy_; + INetFwProfile* profile_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_WINFIREWALL_H_ diff --git a/webrtc/base/winfirewall_unittest.cc b/webrtc/base/winfirewall_unittest.cc new file mode 100644 index 000000000..e5c3d6ac1 --- /dev/null +++ b/webrtc/base/winfirewall_unittest.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/winfirewall.h" + +#include + +namespace rtc { + +TEST(WinFirewallTest, ReadStatus) { + ::CoInitialize(NULL); + WinFirewall fw; + HRESULT hr; + bool authorized; + + EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized)); + EXPECT_TRUE(fw.Initialize(&hr)); + EXPECT_EQ(S_OK, hr); + + EXPECT_TRUE(fw.QueryAuthorized("bogus.exe", &authorized)); + + // Unless we mock out INetFwMgr we can't really have an expectation either way + // about whether we're authorized. It will depend on the settings of the + // machine running the test. Same goes for AddApplication. + + fw.Shutdown(); + EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized)); + + ::CoUninitialize(); +} + +} // namespace rtc diff --git a/webrtc/base/winping.cc b/webrtc/base/winping.cc new file mode 100644 index 000000000..cbb0847bb --- /dev/null +++ b/webrtc/base/winping.cc @@ -0,0 +1,359 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/winping.h" + +#include +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/ipaddress.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Found in IPExport.h +////////////////////////////////////////////////////////////////////// + +typedef struct icmp_echo_reply { + ULONG Address; // Replying address + ULONG Status; // Reply IP_STATUS + ULONG RoundTripTime; // RTT in milliseconds + USHORT DataSize; // Reply data size in bytes + USHORT Reserved; // Reserved for system use + PVOID Data; // Pointer to the reply data + struct ip_option_information Options; // Reply options +} ICMP_ECHO_REPLY, * PICMP_ECHO_REPLY; + +typedef struct icmpv6_echo_reply_lh { + sockaddr_in6 Address; + ULONG Status; + unsigned int RoundTripTime; +} ICMPV6_ECHO_REPLY, *PICMPV6_ECHO_REPLY; + +// +// IP_STATUS codes returned from IP APIs +// + +#define IP_STATUS_BASE 11000 + +#define IP_SUCCESS 0 +#define IP_BUF_TOO_SMALL (IP_STATUS_BASE + 1) +#define IP_DEST_NET_UNREACHABLE (IP_STATUS_BASE + 2) +#define IP_DEST_HOST_UNREACHABLE (IP_STATUS_BASE + 3) +#define IP_DEST_PROT_UNREACHABLE (IP_STATUS_BASE + 4) +#define IP_DEST_PORT_UNREACHABLE (IP_STATUS_BASE + 5) +#define IP_NO_RESOURCES (IP_STATUS_BASE + 6) +#define IP_BAD_OPTION (IP_STATUS_BASE + 7) +#define IP_HW_ERROR (IP_STATUS_BASE + 8) +#define IP_PACKET_TOO_BIG (IP_STATUS_BASE + 9) +#define IP_REQ_TIMED_OUT (IP_STATUS_BASE + 10) +#define IP_BAD_REQ (IP_STATUS_BASE + 11) +#define IP_BAD_ROUTE (IP_STATUS_BASE + 12) +#define IP_TTL_EXPIRED_TRANSIT (IP_STATUS_BASE + 13) +#define IP_TTL_EXPIRED_REASSEM (IP_STATUS_BASE + 14) +#define IP_PARAM_PROBLEM (IP_STATUS_BASE + 15) +#define IP_SOURCE_QUENCH (IP_STATUS_BASE + 16) +#define IP_OPTION_TOO_BIG (IP_STATUS_BASE + 17) +#define IP_BAD_DESTINATION (IP_STATUS_BASE + 18) + +#define IP_ADDR_DELETED (IP_STATUS_BASE + 19) +#define IP_SPEC_MTU_CHANGE (IP_STATUS_BASE + 20) +#define IP_MTU_CHANGE (IP_STATUS_BASE + 21) +#define IP_UNLOAD (IP_STATUS_BASE + 22) +#define IP_ADDR_ADDED (IP_STATUS_BASE + 23) +#define IP_MEDIA_CONNECT (IP_STATUS_BASE + 24) +#define IP_MEDIA_DISCONNECT (IP_STATUS_BASE + 25) +#define IP_BIND_ADAPTER (IP_STATUS_BASE + 26) +#define IP_UNBIND_ADAPTER (IP_STATUS_BASE + 27) +#define IP_DEVICE_DOES_NOT_EXIST (IP_STATUS_BASE + 28) +#define IP_DUPLICATE_ADDRESS (IP_STATUS_BASE + 29) +#define IP_INTERFACE_METRIC_CHANGE (IP_STATUS_BASE + 30) +#define IP_RECONFIG_SECFLTR (IP_STATUS_BASE + 31) +#define IP_NEGOTIATING_IPSEC (IP_STATUS_BASE + 32) +#define IP_INTERFACE_WOL_CAPABILITY_CHANGE (IP_STATUS_BASE + 33) +#define IP_DUPLICATE_IPADD (IP_STATUS_BASE + 34) + +#define IP_GENERAL_FAILURE (IP_STATUS_BASE + 50) +#define MAX_IP_STATUS IP_GENERAL_FAILURE +#define IP_PENDING (IP_STATUS_BASE + 255) + +// +// Values used in the IP header Flags field. +// +#define IP_FLAG_DF 0x2 // Don't fragment this packet. + +// +// Supported IP Option Types. +// +// These types define the options which may be used in the OptionsData field +// of the ip_option_information structure. See RFC 791 for a complete +// description of each. +// +#define IP_OPT_EOL 0 // End of list option +#define IP_OPT_NOP 1 // No operation +#define IP_OPT_SECURITY 0x82 // Security option +#define IP_OPT_LSRR 0x83 // Loose source route +#define IP_OPT_SSRR 0x89 // Strict source route +#define IP_OPT_RR 0x7 // Record route +#define IP_OPT_TS 0x44 // Timestamp +#define IP_OPT_SID 0x88 // Stream ID (obsolete) +#define IP_OPT_ROUTER_ALERT 0x94 // Router Alert Option + +#define MAX_OPT_SIZE 40 // Maximum length of IP options in bytes + +////////////////////////////////////////////////////////////////////// +// Global Constants and Types +////////////////////////////////////////////////////////////////////// + +const char * const ICMP_DLL_NAME = "Iphlpapi.dll"; +const char * const ICMP_CREATE_FUNC = "IcmpCreateFile"; +const char * const ICMP_CLOSE_FUNC = "IcmpCloseHandle"; +const char * const ICMP_SEND_FUNC = "IcmpSendEcho"; +const char * const ICMP6_CREATE_FUNC = "Icmp6CreateFile"; +const char * const ICMP6_CLOSE_FUNC = "Icmp6CloseHandle"; +const char * const ICMP6_SEND_FUNC = "Icmp6SendEcho2"; + +inline uint32 ReplySize(uint32 data_size, int family) { + if (family == AF_INET) { + // A ping error message is 8 bytes long, so make sure we allow for at least + // 8 bytes of reply data. + return sizeof(ICMP_ECHO_REPLY) + rtc::_max(8, data_size); + } else if (family == AF_INET6) { + // Per MSDN, Send6IcmpEcho2 needs at least one ICMPV6_ECHO_REPLY, + // 8 bytes for ICMP header, _and_ an IO_BLOCK_STATUS (2 pointers), + // in addition to the data size. + return sizeof(ICMPV6_ECHO_REPLY) + data_size + 8 + (2 * sizeof(DWORD*)); + } else { + return 0; + } +} + +////////////////////////////////////////////////////////////////////// +// WinPing +////////////////////////////////////////////////////////////////////// + +WinPing::WinPing() + : dll_(0), hping_(INVALID_HANDLE_VALUE), create_(0), close_(0), send_(0), + create6_(0), send6_(0), data_(0), dlen_(0), reply_(0), + rlen_(0), valid_(false) { + + dll_ = LoadLibraryA(ICMP_DLL_NAME); + if (!dll_) { + LOG(LERROR) << "LoadLibrary: " << GetLastError(); + return; + } + + create_ = (PIcmpCreateFile) GetProcAddress(dll_, ICMP_CREATE_FUNC); + close_ = (PIcmpCloseHandle) GetProcAddress(dll_, ICMP_CLOSE_FUNC); + send_ = (PIcmpSendEcho) GetProcAddress(dll_, ICMP_SEND_FUNC); + if (!create_ || !close_ || !send_) { + LOG(LERROR) << "GetProcAddress(ICMP_*): " << GetLastError(); + return; + } + hping_ = create_(); + if (hping_ == INVALID_HANDLE_VALUE) { + LOG(LERROR) << "IcmpCreateFile: " << GetLastError(); + return; + } + + if (HasIPv6Enabled()) { + create6_ = (PIcmp6CreateFile) GetProcAddress(dll_, ICMP6_CREATE_FUNC); + send6_ = (PIcmp6SendEcho2) GetProcAddress(dll_, ICMP6_SEND_FUNC); + if (!create6_ || !send6_) { + LOG(LERROR) << "GetProcAddress(ICMP6_*): " << GetLastError(); + return; + } + hping6_ = create6_(); + if (hping6_ == INVALID_HANDLE_VALUE) { + LOG(LERROR) << "Icmp6CreateFile: " << GetLastError(); + } + } + + dlen_ = 0; + rlen_ = ReplySize(dlen_, AF_INET); + data_ = new char[dlen_]; + reply_ = new char[rlen_]; + + valid_ = true; +} + +WinPing::~WinPing() { + if ((hping_ != INVALID_HANDLE_VALUE) && close_) { + if (!close_(hping_)) + LOG(WARNING) << "IcmpCloseHandle: " << GetLastError(); + } + if ((hping6_ != INVALID_HANDLE_VALUE) && close_) { + if (!close_(hping6_)) { + LOG(WARNING) << "Icmp6CloseHandle: " << GetLastError(); + } + } + + if (dll_) + FreeLibrary(dll_); + + delete[] data_; + delete[] reply_; +} + +WinPing::PingResult WinPing::Ping( + IPAddress ip, uint32 data_size, uint32 timeout, uint8 ttl, + bool allow_fragments) { + + if (data_size == 0 || timeout == 0 || ttl == 0) { + LOG(LERROR) << "IcmpSendEcho: data_size/timeout/ttl is 0."; + return PING_INVALID_PARAMS; + } + + assert(IsValid()); + + IP_OPTION_INFORMATION ipopt; + memset(&ipopt, 0, sizeof(ipopt)); + if (!allow_fragments) + ipopt.Flags |= IP_FLAG_DF; + ipopt.Ttl = ttl; + + uint32 reply_size = ReplySize(data_size, ip.family()); + + if (data_size > dlen_) { + delete [] data_; + dlen_ = data_size; + data_ = new char[dlen_]; + memset(data_, 'z', dlen_); + } + + if (reply_size > rlen_) { + delete [] reply_; + rlen_ = reply_size; + reply_ = new char[rlen_]; + } + DWORD result = 0; + if (ip.family() == AF_INET) { + result = send_(hping_, ip.ipv4_address().S_un.S_addr, + data_, uint16(data_size), &ipopt, + reply_, reply_size, timeout); + } else if (ip.family() == AF_INET6) { + sockaddr_in6 src = {0}; + sockaddr_in6 dst = {0}; + src.sin6_family = AF_INET6; + dst.sin6_family = AF_INET6; + dst.sin6_addr = ip.ipv6_address(); + result = send6_(hping6_, NULL, NULL, NULL, + &src, &dst, + data_, int16(data_size), &ipopt, + reply_, reply_size, timeout); + } + if (result == 0) { + DWORD error = GetLastError(); + if (error == IP_PACKET_TOO_BIG) + return PING_TOO_LARGE; + if (error == IP_REQ_TIMED_OUT) + return PING_TIMEOUT; + LOG(LERROR) << "IcmpSendEcho(" << ip.ToSensitiveString() + << ", " << data_size << "): " << error; + return PING_FAIL; + } + + return PING_SUCCESS; +} + +////////////////////////////////////////////////////////////////////// +// Microsoft Documenation +////////////////////////////////////////////////////////////////////// +// +// Routine Name: +// +// IcmpCreateFile +// +// Routine Description: +// +// Opens a handle on which ICMP Echo Requests can be issued. +// +// Arguments: +// +// None. +// +// Return Value: +// +// An open file handle or INVALID_HANDLE_VALUE. Extended error information +// is available by calling GetLastError(). +// +////////////////////////////////////////////////////////////////////// +// +// Routine Name: +// +// IcmpCloseHandle +// +// Routine Description: +// +// Closes a handle opened by ICMPOpenFile. +// +// Arguments: +// +// IcmpHandle - The handle to close. +// +// Return Value: +// +// TRUE if the handle was closed successfully, otherwise FALSE. Extended +// error information is available by calling GetLastError(). +// +////////////////////////////////////////////////////////////////////// +// +// Routine Name: +// +// IcmpSendEcho +// +// Routine Description: +// +// Sends an ICMP Echo request and returns any replies. The +// call returns when the timeout has expired or the reply buffer +// is filled. +// +// Arguments: +// +// IcmpHandle - An open handle returned by ICMPCreateFile. +// +// DestinationAddress - The destination of the echo request. +// +// RequestData - A buffer containing the data to send in the +// request. +// +// RequestSize - The number of bytes in the request data buffer. +// +// RequestOptions - Pointer to the IP header options for the request. +// May be NULL. +// +// ReplyBuffer - A buffer to hold any replies to the request. +// On return, the buffer will contain an array of +// ICMP_ECHO_REPLY structures followed by the +// options and data for the replies. The buffer +// should be large enough to hold at least one +// ICMP_ECHO_REPLY structure plus +// MAX(RequestSize, 8) bytes of data since an ICMP +// error message contains 8 bytes of data. +// +// ReplySize - The size in bytes of the reply buffer. +// +// Timeout - The time in milliseconds to wait for replies. +// +// Return Value: +// +// Returns the number of ICMP_ECHO_REPLY structures stored in ReplyBuffer. +// The status of each reply is contained in the structure. If the return +// value is zero, extended error information is available via +// GetLastError(). +// +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/winping.h b/webrtc/base/winping.h new file mode 100644 index 000000000..75f82b7b4 --- /dev/null +++ b/webrtc/base/winping.h @@ -0,0 +1,103 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WINPING_H__ +#define WEBRTC_BASE_WINPING_H__ + +#if defined(WEBRTC_WIN) + +#include "webrtc/base/win32.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/IPAddress.h" + +namespace rtc { + +// This class wraps a Win32 API for doing ICMP pinging. This API, unlike the +// the normal socket APIs (as implemented on Win9x), will return an error if +// an ICMP packet with the dont-fragment bit set is too large. This means this +// class can be used to detect the MTU to a given address. + +typedef struct ip_option_information { + UCHAR Ttl; // Time To Live + UCHAR Tos; // Type Of Service + UCHAR Flags; // IP header flags + UCHAR OptionsSize; // Size in bytes of options data + PUCHAR OptionsData; // Pointer to options data +} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION; + +typedef HANDLE (WINAPI *PIcmpCreateFile)(); + +typedef BOOL (WINAPI *PIcmpCloseHandle)(HANDLE icmp_handle); + +typedef HANDLE (WINAPI *PIcmp6CreateFile)(); + +typedef BOOL (WINAPI *PIcmp6CloseHandle)(HANDLE icmp_handle); + +typedef DWORD (WINAPI *PIcmpSendEcho)( + HANDLE IcmpHandle, + ULONG DestinationAddress, + LPVOID RequestData, + WORD RequestSize, + PIP_OPTION_INFORMATION RequestOptions, + LPVOID ReplyBuffer, + DWORD ReplySize, + DWORD Timeout); + +typedef DWORD (WINAPI *PIcmp6SendEcho2)( + HANDLE IcmpHandle, + HANDLE Event, + FARPROC ApcRoutine, + PVOID ApcContext, + struct sockaddr_in6 *SourceAddress, + struct sockaddr_in6 *DestinationAddress, + LPVOID RequestData, + WORD RequestSize, + PIP_OPTION_INFORMATION RequestOptions, + LPVOID ReplyBuffer, + DWORD ReplySize, + DWORD Timeout +); + +class WinPing { +public: + WinPing(); + ~WinPing(); + + // Determines whether the class was initialized correctly. + bool IsValid() { return valid_; } + + // Attempts to send a ping with the given parameters. + enum PingResult { PING_FAIL, PING_INVALID_PARAMS, + PING_TOO_LARGE, PING_TIMEOUT, PING_SUCCESS }; + PingResult Ping( + IPAddress ip, uint32 data_size, uint32 timeout_millis, uint8 ttl, + bool allow_fragments); + +private: + HMODULE dll_; + HANDLE hping_; + HANDLE hping6_; + PIcmpCreateFile create_; + PIcmpCloseHandle close_; + PIcmpSendEcho send_; + PIcmp6CreateFile create6_; + PIcmp6SendEcho2 send6_; + char* data_; + uint32 dlen_; + char* reply_; + uint32 rlen_; + bool valid_; +}; + +} // namespace rtc + +#endif // WEBRTC_WIN + +#endif // WEBRTC_BASE_WINPING_H__ diff --git a/webrtc/base/worker.cc b/webrtc/base/worker.cc new file mode 100644 index 000000000..1b48b9b1d --- /dev/null +++ b/webrtc/base/worker.cc @@ -0,0 +1,75 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/worker.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +enum { + MSG_HAVEWORK = 0, +}; + +Worker::Worker() : worker_thread_(NULL) {} + +Worker::~Worker() { + // We need to already be stopped before being destroyed. We cannot call + // StopWork() from here because the subclass's data has already been + // destructed, so OnStop() cannot be called. + ASSERT(!worker_thread_); +} + +bool Worker::StartWork() { + rtc::Thread *me = rtc::Thread::Current(); + if (worker_thread_) { + if (worker_thread_ == me) { + // Already working on this thread, so nothing to do. + return true; + } else { + LOG(LS_ERROR) << "Automatically switching threads is not supported"; + ASSERT(false); + return false; + } + } + worker_thread_ = me; + OnStart(); + return true; +} + +bool Worker::StopWork() { + if (!worker_thread_) { + // Already not working, so nothing to do. + return true; + } else if (worker_thread_ != rtc::Thread::Current()) { + LOG(LS_ERROR) << "Stopping from a different thread is not supported"; + ASSERT(false); + return false; + } + OnStop(); + worker_thread_->Clear(this, MSG_HAVEWORK); + worker_thread_ = NULL; + return true; +} + +void Worker::HaveWork() { + ASSERT(worker_thread_ != NULL); + worker_thread_->Post(this, MSG_HAVEWORK); +} + +void Worker::OnMessage(rtc::Message *msg) { + ASSERT(msg->message_id == MSG_HAVEWORK); + ASSERT(worker_thread_ == rtc::Thread::Current()); + OnHaveWork(); +} + +} // namespace rtc diff --git a/webrtc/base/worker.h b/webrtc/base/worker.h new file mode 100644 index 000000000..694a78057 --- /dev/null +++ b/webrtc/base/worker.h @@ -0,0 +1,72 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_WORKER_H_ +#define WEBRTC_BASE_WORKER_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/messagehandler.h" + +namespace rtc { + +class Thread; + +// A worker is an object that performs some specific long-lived task in an +// event-driven manner. +// The only method that should be considered thread-safe is HaveWork(), which +// allows you to signal the availability of work from any thread. All other +// methods are thread-hostile. Specifically: +// StartWork()/StopWork() should not be called concurrently with themselves or +// each other, and it is an error to call them while the worker is running on +// a different thread. +// The destructor may not be called if the worker is currently running +// (regardless of the thread), but you can call StopWork() in a subclass's +// destructor. +class Worker : private MessageHandler { + public: + Worker(); + + // Destroys this Worker, but it must have already been stopped via StopWork(). + virtual ~Worker(); + + // Attaches the worker to the current thread and begins processing work if not + // already doing so. + bool StartWork(); + // Stops processing work if currently doing so and detaches from the current + // thread. + bool StopWork(); + + protected: + // Signal that work is available to be done. May only be called within the + // lifetime of a OnStart()/OnStop() pair. + void HaveWork(); + + // These must be implemented by a subclass. + // Called on the worker thread to start working. + virtual void OnStart() = 0; + // Called on the worker thread when work has been signalled via HaveWork(). + virtual void OnHaveWork() = 0; + // Called on the worker thread to stop working. Upon return, any pending + // OnHaveWork() calls are cancelled. + virtual void OnStop() = 0; + + private: + // Inherited from MessageHandler. + virtual void OnMessage(Message *msg); + + // The thread that is currently doing the work. + Thread *worker_thread_; + + DISALLOW_COPY_AND_ASSIGN(Worker); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WORKER_H_ diff --git a/webrtc/build/common.gypi b/webrtc/build/common.gypi index a07711401..233b1b8c0 100644 --- a/webrtc/build/common.gypi +++ b/webrtc/build/common.gypi @@ -89,6 +89,9 @@ 'build_libyuv%': 1, 'build_libvpx%': 1, + # Disable by default + 'have_dbus_glib%': 0, + # Enable to use the Mozilla internal settings. 'build_with_mozilla%': 0, @@ -144,9 +147,6 @@ }, 'target_defaults': { 'include_dirs': [ - # Allow includes to be prefixed with webrtc/ in case it is not an - # immediate subdirectory of <(DEPTH). - '../..', # To include the top-level directory when building in Chrome, so we can # use full paths (e.g. headers inside testing/ or third_party/). '<(DEPTH)', @@ -161,6 +161,14 @@ 'WEBRTC_MOZILLA_BUILD', ], }], + ['have_dbus_glib==1', { + 'defines': [ + 'HAVE_DBUS_GLIB', + ], + 'cflags': [ + '= 1600 +#include +#else +typedef unsigned __int64 uint64; +typedef __int64 int64; +#endif +#ifndef INT64_C +#define INT64_C(x) x ## I64 +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## UI64 +#endif +#define INT64_F "I64" +#else // COMPILER_MSVC +#ifndef INT64_C +#define INT64_C(x) x ## LL +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## ULL +#endif +#ifndef INT64_F +#define INT64_F "ll" +#endif +#endif // COMPILER_MSVC +#endif // INT_TYPES_DEFINED + +// Detect compiler is for x86 or x64. +#if defined(__x86_64__) || defined(_M_X64) || \ + defined(__i386__) || defined(_M_IX86) +#define CPU_X86 1 +#endif +// Detect compiler is for arm. +#if defined(__arm__) || defined(_M_ARM) +#define CPU_ARM 1 +#endif +#if defined(CPU_X86) && defined(CPU_ARM) +#error CPU_X86 and CPU_ARM both defined. +#endif +#if !defined(ARCH_CPU_BIG_ENDIAN) && !defined(ARCH_CPU_LITTLE_ENDIAN) +// x86, arm or GCC provided __BYTE_ORDER__ macros +#if CPU_X86 || CPU_ARM || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define ARCH_CPU_LITTLE_ENDIAN +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define ARCH_CPU_BIG_ENDIAN +#else +#error ARCH_CPU_BIG_ENDIAN or ARCH_CPU_LITTLE_ENDIAN should be defined. +#endif +#endif +#if defined(ARCH_CPU_BIG_ENDIAN) && defined(ARCH_CPU_LITTLE_ENDIAN) +#error ARCH_CPU_BIG_ENDIAN and ARCH_CPU_LITTLE_ENDIAN both defined. +#endif + +#if defined(WEBRTC_WIN) +typedef int socklen_t; +#endif + +namespace rtc { +template inline T _min(T a, T b) { return (a > b) ? b : a; } +template inline T _max(T a, T b) { return (a < b) ? b : a; } + +// For wait functions that take a number of milliseconds, kForever indicates +// unlimited time. +const int kForever = -1; +} + +#if defined(WEBRTC_WIN) +#if _MSC_VER < 1700 + #define alignof(t) __alignof(t) +#endif +#else // !WEBRTC_WIN +#define alignof(t) __alignof__(t) +#endif // !WEBRTC_WIN +#define IS_ALIGNED(p, a) (0==(reinterpret_cast(p) & ((a)-1))) +#define ALIGNP(p, t) \ + (reinterpret_cast(((reinterpret_cast(p) + \ + ((t)-1)) & ~((t)-1)))) + +// LIBJINGLE_DEFINE_STATIC_LOCAL() is a libjingle's copy +// of CR_DEFINE_STATIC_LOCAL(). +#define LIBJINGLE_DEFINE_STATIC_LOCAL(type, name, arguments) \ + CR_DEFINE_STATIC_LOCAL(type, name, arguments) + +#endif // OVERRIDES_WEBRTC_BASE_BASICTYPES_H__ diff --git a/webrtc/overrides/webrtc/base/constructormagic.h b/webrtc/overrides/webrtc/base/constructormagic.h new file mode 100644 index 000000000..bb89f91f1 --- /dev/null +++ b/webrtc/overrides/webrtc/base/constructormagic.h @@ -0,0 +1,20 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This file overrides the inclusion of webrtc/base/constructormagic.h +// We do this because constructor magic defines DISALLOW_EVIL_CONSTRUCTORS, +// but we want to use the version from Chromium. + +#ifndef OVERRIDES_WEBRTC_BASE_CONSTRUCTORMAGIC_H__ +#define OVERRIDES_WEBRTC_BASE_CONSTRUCTORMAGIC_H__ + +#include "base/basictypes.h" + +#endif // OVERRIDES_WEBRTC_BASE_CONSTRUCTORMAGIC_H__ diff --git a/webrtc/overrides/webrtc/base/logging.cc b/webrtc/overrides/webrtc/base/logging.cc new file mode 100644 index 000000000..2e43c5016 --- /dev/null +++ b/webrtc/overrides/webrtc/base/logging.cc @@ -0,0 +1,317 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "third_party/webrtc/overrides/webrtc/base/logging.h" + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#endif // OS_MACOSX + +#include + +#include "base/atomicops.h" +#include "base/strings/string_util.h" +#include "base/threading/platform_thread.h" +#include "third_party/webrtc/base/ipaddress.h" +#include "third_party/webrtc/base/stream.h" +#include "third_party/webrtc/base/stringencode.h" +#include "third_party/webrtc/base/stringutils.h" +#include "third_party/webrtc/base/timeutils.h" + +// From this file we can't use VLOG since it expands into usage of the __FILE__ +// macro (for correct filtering). The actual logging call from DIAGNOSTIC_LOG in +// ~DiagnosticLogMessage. Note that the second parameter to the LAZY_STREAM +// macro is true since the filter check has already been done for +// DIAGNOSTIC_LOG. +#define LOG_LAZY_STREAM_DIRECT(file_name, line_number, sev) \ + LAZY_STREAM(logging::LogMessage(file_name, line_number, \ + -sev).stream(), true) + +namespace rtc { + +void (*g_logging_delegate_function)(const std::string&) = NULL; +void (*g_extra_logging_init_function)( + void (*logging_delegate_function)(const std::string&)) = NULL; +#ifndef NDEBUG +COMPILE_ASSERT(sizeof(base::subtle::Atomic32) == sizeof(base::PlatformThreadId), + atomic32_not_same_size_as_platformthreadid); +base::subtle::Atomic32 g_init_logging_delegate_thread_id = 0; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Constant Labels +///////////////////////////////////////////////////////////////////////////// + +const char* FindLabel(int value, const ConstantLabel entries[]) { + for (int i = 0; entries[i].label; ++i) { + if (value == entries[i].value) return entries[i].label; + } + return 0; +} + +std::string ErrorName(int err, const ConstantLabel* err_table) { + if (err == 0) + return "No error"; + + if (err_table != 0) { + if (const char * value = FindLabel(err, err_table)) + return value; + } + + char buffer[16]; + base::snprintf(buffer, sizeof(buffer), "0x%08x", err); + return buffer; +} + +///////////////////////////////////////////////////////////////////////////// +// Log helper functions +///////////////////////////////////////////////////////////////////////////// + +// Generates extra information for LOG_E. +static std::string GenerateExtra(LogErrorContext err_ctx, + int err, + const char* module) { + if (err_ctx != ERRCTX_NONE) { + std::ostringstream tmp; + tmp << ": "; + tmp << "[0x" << std::setfill('0') << std::hex << std::setw(8) << err << "]"; + switch (err_ctx) { + case ERRCTX_ERRNO: + tmp << " " << strerror(err); + break; +#if defined(WEBRTC_WIN) + case ERRCTX_HRESULT: { + char msgbuf[256]; + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM; + HMODULE hmod = GetModuleHandleA(module); + if (hmod) + flags |= FORMAT_MESSAGE_FROM_HMODULE; + if (DWORD len = FormatMessageA( + flags, hmod, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), NULL)) { + while ((len > 0) && + isspace(static_cast(msgbuf[len-1]))) { + msgbuf[--len] = 0; + } + tmp << " " << msgbuf; + } + break; + } +#endif // OS_WIN +#if defined(WEBRTC_IOS) + case ERRCTX_OSSTATUS: + tmp << " " << "Unknown LibJingle error: " << err; + break; +#elif defined(WEBRTC_MAC) + case ERRCTX_OSSTATUS: { + tmp << " " << nonnull(GetMacOSStatusErrorString(err), "Unknown error"); + if (const char* desc = GetMacOSStatusCommentString(err)) { + tmp << ": " << desc; + } + break; + } +#endif // OS_MACOSX + default: + break; + } + return tmp.str(); + } + return ""; +} + +DiagnosticLogMessage::DiagnosticLogMessage(const char* file, + int line, + LoggingSeverity severity, + bool log_to_chrome, + LogErrorContext err_ctx, + int err) + : file_name_(file), + line_(line), + severity_(severity), + log_to_chrome_(log_to_chrome) { + extra_ = GenerateExtra(err_ctx, err, NULL); +} + +DiagnosticLogMessage::DiagnosticLogMessage(const char* file, + int line, + LoggingSeverity severity, + bool log_to_chrome, + LogErrorContext err_ctx, + int err, + const char* module) + : file_name_(file), + line_(line), + severity_(severity), + log_to_chrome_(log_to_chrome) { + extra_ = GenerateExtra(err_ctx, err, module); +} + +DiagnosticLogMessage::~DiagnosticLogMessage() { + print_stream_ << extra_; + const std::string& str = print_stream_.str(); + if (log_to_chrome_) + LOG_LAZY_STREAM_DIRECT(file_name_, line_, severity_) << str; + if (g_logging_delegate_function && severity_ <= LS_INFO) { + g_logging_delegate_function(str); + } +} + +// Note: this function is a copy from the overriden libjingle implementation. +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state) { + if (!LOG_CHECK_LEVEL_V(level)) + return; + + const char * direction = (input ? " << " : " >> "); + + // NULL data means to flush our count of unprintable characters. + if (!data) { + if (state && state->unprintable_count_[input]) { + LOG_V(level) << label << direction << "## " + << state->unprintable_count_[input] + << " consecutive unprintable ##"; + state->unprintable_count_[input] = 0; + } + return; + } + + // The ctype classification functions want unsigned chars. + const unsigned char* udata = static_cast(data); + + if (hex_mode) { + const size_t LINE_SIZE = 24; + char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1]; + while (len > 0) { + memset(asc_line, ' ', sizeof(asc_line)); + memset(hex_line, ' ', sizeof(hex_line)); + size_t line_len = _min(len, LINE_SIZE); + for (size_t i = 0; i < line_len; ++i) { + unsigned char ch = udata[i]; + asc_line[i] = isprint(ch) ? ch : '.'; + hex_line[i*2 + i/4] = hex_encode(ch >> 4); + hex_line[i*2 + i/4 + 1] = hex_encode(ch & 0xf); + } + asc_line[sizeof(asc_line)-1] = 0; + hex_line[sizeof(hex_line)-1] = 0; + LOG_V(level) << label << direction + << asc_line << " " << hex_line << " "; + udata += line_len; + len -= line_len; + } + return; + } + + size_t consecutive_unprintable = state ? state->unprintable_count_[input] : 0; + + const unsigned char* end = udata + len; + while (udata < end) { + const unsigned char* line = udata; + const unsigned char* end_of_line = strchrn(udata, + end - udata, + '\n'); + if (!end_of_line) { + udata = end_of_line = end; + } else { + udata = end_of_line + 1; + } + + bool is_printable = true; + + // If we are in unprintable mode, we need to see a line of at least + // kMinPrintableLine characters before we'll switch back. + const ptrdiff_t kMinPrintableLine = 4; + if (consecutive_unprintable && ((end_of_line - line) < kMinPrintableLine)) { + is_printable = false; + } else { + // Determine if the line contains only whitespace and printable + // characters. + bool is_entirely_whitespace = true; + for (const unsigned char* pos = line; pos < end_of_line; ++pos) { + if (isspace(*pos)) + continue; + is_entirely_whitespace = false; + if (!isprint(*pos)) { + is_printable = false; + break; + } + } + // Treat an empty line following unprintable data as unprintable. + if (consecutive_unprintable && is_entirely_whitespace) { + is_printable = false; + } + } + if (!is_printable) { + consecutive_unprintable += (udata - line); + continue; + } + // Print out the current line, but prefix with a count of prior unprintable + // characters. + if (consecutive_unprintable) { + LOG_V(level) << label << direction << "## " << consecutive_unprintable + << " consecutive unprintable ##"; + consecutive_unprintable = 0; + } + // Strip off trailing whitespace. + while ((end_of_line > line) && isspace(*(end_of_line-1))) { + --end_of_line; + } + // Filter out any private data + std::string substr(reinterpret_cast(line), end_of_line - line); + std::string::size_type pos_private = substr.find("Email"); + if (pos_private == std::string::npos) { + pos_private = substr.find("Passwd"); + } + if (pos_private == std::string::npos) { + LOG_V(level) << label << direction << substr; + } else { + LOG_V(level) << label << direction << "## omitted for privacy ##"; + } + } + + if (state) { + state->unprintable_count_[input] = consecutive_unprintable; + } +} + +void InitDiagnosticLoggingDelegateFunction( + void (*delegate)(const std::string&)) { +#ifndef NDEBUG + // Ensure that this function is always called from the same thread. + base::subtle::NoBarrier_CompareAndSwap(&g_init_logging_delegate_thread_id, 0, + static_cast(base::PlatformThread::CurrentId())); + DCHECK_EQ(g_init_logging_delegate_thread_id, + base::PlatformThread::CurrentId()); +#endif + CHECK(delegate); + // This function may be called with the same argument several times if the + // page is reloaded or there are several PeerConnections on one page with + // logging enabled. This is OK, we simply don't have to do anything. + if (delegate == g_logging_delegate_function) + return; + CHECK(!g_logging_delegate_function); +#ifdef NDEBUG + IPAddress::set_strip_sensitive(true); +#endif + g_logging_delegate_function = delegate; + + if (g_extra_logging_init_function) + g_extra_logging_init_function(delegate); +} + +void SetExtraLoggingInit( + void (*function)(void (*delegate)(const std::string&))) { + CHECK(function); + CHECK(!g_extra_logging_init_function); + g_extra_logging_init_function = function; +} + +} // namespace rtc diff --git a/webrtc/overrides/webrtc/base/logging.h b/webrtc/overrides/webrtc/base/logging.h new file mode 100644 index 000000000..d8dfca2ce --- /dev/null +++ b/webrtc/overrides/webrtc/base/logging.h @@ -0,0 +1,221 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This file overrides the logging macros in libjingle (webrtc/base/logging.h). +// Instead of using libjingle's logging implementation, the libjingle macros are +// mapped to the corresponding base/logging.h macro (chromium's VLOG). +// If this file is included outside of libjingle (e.g. in wrapper code) it +// should be included after base/logging.h (if any) or compiler error or +// unexpected behavior may occur (macros that have the same name in libjingle as +// in chromium will use the libjingle definition if this file is included +// first). + +// Setting the LoggingSeverity (and lower) that should be written to file should +// be done via command line by specifying the flags: +// --vmodule or --v please see base/logging.h for details on how to use them. +// Specifying what file to write to is done using InitLogging also in +// base/logging.h. + +// The macros and classes declared in here are not described as they are +// NOT TO BE USED outside of libjingle. + +#ifndef THIRD_PARTY_LIBJINGLE_OVERRIDES_WEBRTC_BASE_LOGGING_H_ +#define THIRD_PARTY_LIBJINGLE_OVERRIDES_WEBRTC_BASE_LOGGING_H_ + +#include +#include + +#include "base/logging.h" +#include "third_party/webrtc/base/scoped_ref_ptr.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// ConstantLabel can be used to easily generate string names from constant +// values. This can be useful for logging descriptive names of error messages. +// Usage: +// const ConstantLabel LIBRARY_ERRORS[] = { +// KLABEL(SOME_ERROR), +// KLABEL(SOME_OTHER_ERROR), +// ... +// LASTLABEL +// } +// +// int err = LibraryFunc(); +// LOG(LS_ERROR) << "LibraryFunc returned: " +// << ErrorName(err, LIBRARY_ERRORS); + +struct ConstantLabel { + int value; + const char* label; +}; +#define KLABEL(x) { x, #x } +#define LASTLABEL { 0, 0 } + +const char* FindLabel(int value, const ConstantLabel entries[]); +std::string ErrorName(int err, const ConstantLabel* err_table); + +////////////////////////////////////////////////////////////////////// +// Note that the non-standard LoggingSeverity aliases exist because they are +// still in broad use. The meanings of the levels are: +// LS_SENSITIVE: Information which should only be logged with the consent +// of the user, due to privacy concerns. +// LS_VERBOSE: This level is for data which we do not want to appear in the +// normal debug log, but should appear in diagnostic logs. +// LS_INFO: Chatty level used in debugging for all sorts of things, the default +// in debug builds. +// LS_WARNING: Something that may warrant investigation. +// LS_ERROR: Something that should not have occurred. +// Note that LoggingSeverity is mapped over to chromiums verbosity levels where +// anything lower than or equal to the current verbosity level is written to +// file which is the opposite of logging severity in libjingle where higher +// severity numbers than or equal to the current severity level are written to +// file. Also, note that the values are explicitly defined here for convenience +// since the command line flag must be set using numerical values. +enum LoggingSeverity { LS_ERROR = 1, + LS_WARNING = 2, + LS_INFO = 3, + LS_VERBOSE = 4, + LS_SENSITIVE = 5, + INFO = LS_INFO, + WARNING = LS_WARNING, + LERROR = LS_ERROR }; + +// LogErrorContext assists in interpreting the meaning of an error value. +enum LogErrorContext { + ERRCTX_NONE, + ERRCTX_ERRNO, // System-local errno + ERRCTX_HRESULT, // Windows HRESULT + ERRCTX_OSSTATUS, // MacOS OSStatus + + // Abbreviations for LOG_E macro + ERRCTX_EN = ERRCTX_ERRNO, // LOG_E(sev, EN, x) + ERRCTX_HR = ERRCTX_HRESULT, // LOG_E(sev, HR, x) + ERRCTX_OS = ERRCTX_OSSTATUS, // LOG_E(sev, OS, x) +}; + +// Class that writes a log message to the logging delegate ("WebRTC logging +// stream" in Chrome) and to Chrome's logging stream. +class DiagnosticLogMessage { + public: + DiagnosticLogMessage(const char* file, int line, LoggingSeverity severity, + bool log_to_chrome, LogErrorContext err_ctx, int err); + DiagnosticLogMessage(const char* file, int line, LoggingSeverity severity, + bool log_to_chrome, LogErrorContext err_ctx, int err, + const char* module); + ~DiagnosticLogMessage(); + + void CreateTimestamp(); + + std::ostream& stream() { return print_stream_; } + + private: + const char* file_name_; + const int line_; + const LoggingSeverity severity_; + const bool log_to_chrome_; + + std::string extra_; + + std::ostringstream print_stream_; +}; + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". +class LogMessageVoidify { + public: + LogMessageVoidify() { } + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream&) { } +}; + +////////////////////////////////////////////////////////////////////// +// Logging Helpers +////////////////////////////////////////////////////////////////////// + +class LogMultilineState { + public: + size_t unprintable_count_[2]; + LogMultilineState() { + unprintable_count_[0] = unprintable_count_[1] = 0; + } +}; + +// When possible, pass optional state variable to track various data across +// multiple calls to LogMultiline. Otherwise, pass NULL. +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state); + +// TODO(grunell): Change name to InitDiagnosticLoggingDelegate or +// InitDiagnosticLogging. Change also in init_webrtc.h/cc. +// TODO(grunell): typedef the delegate function. +void InitDiagnosticLoggingDelegateFunction( + void (*delegate)(const std::string&)); + +void SetExtraLoggingInit( + void (*function)(void (*delegate)(const std::string&))); +} // namespace rtc + +////////////////////////////////////////////////////////////////////// +// Libjingle macros which are mapped over to their VLOG equivalent in +// base/logging.h +////////////////////////////////////////////////////////////////////// + +#if defined(LOGGING_INSIDE_WEBRTC) + +#define DIAGNOSTIC_LOG(sev, ctx, err, ...) \ + rtc::DiagnosticLogMessage( \ + __FILE__, __LINE__, sev, VLOG_IS_ON(sev), \ + rtc::ERRCTX_ ## ctx, err, ##__VA_ARGS__).stream() + +#define LOG_CHECK_LEVEL(sev) VLOG_IS_ON(rtc::sev) +#define LOG_CHECK_LEVEL_V(sev) VLOG_IS_ON(sev) + +#define LOG_V(sev) DIAGNOSTIC_LOG(sev, NONE, 0) +#undef LOG +#define LOG(sev) DIAGNOSTIC_LOG(rtc::sev, NONE, 0) + +// The _F version prefixes the message with the current function name. +#if defined(__GNUC__) && defined(_DEBUG) +#define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": " +#else +#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": " +#endif + +#define LOG_E(sev, ctx, err, ...) \ + DIAGNOSTIC_LOG(rtc::sev, ctx, err, ##__VA_ARGS__) + +#undef LOG_ERRNO_EX +#define LOG_ERRNO_EX(sev, err) LOG_E(sev, ERRNO, err) +#undef LOG_ERRNO +#define LOG_ERRNO(sev) LOG_ERRNO_EX(sev, errno) + +#if defined(WEBRTC_WIN) +#define LOG_GLE_EX(sev, err) LOG_E(sev, HRESULT, err) +#define LOG_GLE(sev) LOG_GLE_EX(sev, GetLastError()) +#define LOG_GLEM(sev, mod) LOG_E(sev, HRESULT, GetLastError(), mod) +#define LOG_ERR_EX(sev, err) LOG_GLE_EX(sev, err) +#define LOG_ERR(sev) LOG_GLE(sev) +#define LAST_SYSTEM_ERROR (::GetLastError()) +#else +#define LOG_ERR_EX(sev, err) LOG_ERRNO_EX(sev, err) +#define LOG_ERR(sev) LOG_ERRNO(sev) +#define LAST_SYSTEM_ERROR (errno) +#endif // OS_WIN + +#undef PLOG +#define PLOG(sev, err) LOG_ERR_EX(sev, err) + +#endif // LOGGING_INSIDE_WEBRTC + +#endif // THIRD_PARTY_LIBJINGLE_OVERRIDES_WEBRTC_BASE_LOGGING_H_ diff --git a/webrtc/overrides/webrtc/base/win32socketinit.cc b/webrtc/overrides/webrtc/base/win32socketinit.cc new file mode 100644 index 000000000..929ce8d36 --- /dev/null +++ b/webrtc/overrides/webrtc/base/win32socketinit.cc @@ -0,0 +1,28 @@ +/* + * Copyright 2006 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Redirect Libjingle's winsock initialization activity into Chromium's +// singleton object that managest precisely that for the browser. + +#include "webrtc/base/win32socketinit.h" + +#include "net/base/winsock_init.h" + +#if !defined(WEBRTC_WIN) +#error "Only compile this on Windows" +#endif + +namespace rtc { + +void EnsureWinsockInit() { + net::EnsureWinsockInit(); +} + +} // namespace rtc diff --git a/webrtc/system_wrappers/source/system_wrappers.gyp b/webrtc/system_wrappers/source/system_wrappers.gyp index dd25de674..60b41dbd6 100644 --- a/webrtc/system_wrappers/source/system_wrappers.gyp +++ b/webrtc/system_wrappers/source/system_wrappers.gyp @@ -16,6 +16,9 @@ 'spreadsortlib', '../interface', ], + 'dependencies': [ + '../../base/base.gyp:webrtc_base', + ], 'direct_dependent_settings': { 'include_dirs': [ '../interface', diff --git a/webrtc/webrtc.gyp b/webrtc/webrtc.gyp index e78dba6fa..f376c0643 100644 --- a/webrtc/webrtc.gyp +++ b/webrtc/webrtc.gyp @@ -19,6 +19,7 @@ ], 'variables': { 'webrtc_all_dependencies': [ + 'base/base.gyp:*', 'common_audio/common_audio.gyp:*', 'common_video/common_video.gyp:*', 'modules/modules.gyp:*', @@ -39,6 +40,7 @@ 'conditions': [ ['include_tests==1', { 'dependencies': [ + 'base/base_tests.gyp:*', 'common_video/common_video_unittests.gyp:*', 'system_wrappers/source/system_wrappers_tests.gyp:*', 'test/metrics.gyp:*',