diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-09 20:42:35 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-09 20:42:35 +0000 |
commit | 1b121768708093cf6f50919e8a6a045b83e62d43 (patch) | |
tree | 3ba699e75530e546f63c85061ef914631974e7a4 /remoting | |
parent | 938f6d228fcd0dbd57065ba680465492f72a743b (diff) | |
download | chromium_src-1b121768708093cf6f50919e8a6a045b83e62d43.zip chromium_src-1b121768708093cf6f50919e8a6a045b83e62d43.tar.gz chromium_src-1b121768708093cf6f50919e8a6a045b83e62d43.tar.bz2 |
Resolve addresses of STUN/Relay servers in JingleInfoRequest.
BUG=83242
TEST=NAT traversal works
Review URL: http://codereview.chromium.org/7598004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96060 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/client/DEPS | 2 | ||||
-rw-r--r-- | remoting/client/ipc_host_resolver.cc | 69 | ||||
-rw-r--r-- | remoting/client/ipc_host_resolver.h | 35 | ||||
-rw-r--r-- | remoting/client/plugin/chromoting_instance.cc | 7 | ||||
-rw-r--r-- | remoting/jingle_glue/host_resolver.cc | 13 | ||||
-rw-r--r-- | remoting/jingle_glue/host_resolver.h | 45 | ||||
-rw-r--r-- | remoting/jingle_glue/jingle_info_request.cc | 46 | ||||
-rw-r--r-- | remoting/jingle_glue/jingle_info_request.h | 22 | ||||
-rw-r--r-- | remoting/protocol/connection_to_host.cc | 4 | ||||
-rw-r--r-- | remoting/protocol/connection_to_host.h | 3 | ||||
-rw-r--r-- | remoting/protocol/jingle_session_manager.cc | 10 | ||||
-rw-r--r-- | remoting/protocol/jingle_session_manager.h | 4 | ||||
-rw-r--r-- | remoting/remoting.gyp | 4 |
13 files changed, 244 insertions, 20 deletions
diff --git a/remoting/client/DEPS b/remoting/client/DEPS index ab0c15a..2a3269f 100644 --- a/remoting/client/DEPS +++ b/remoting/client/DEPS @@ -1,7 +1,7 @@ include_rules = [ "+ppapi", "+jingle/glue", - "+third_party/npapi", + "+net", "+ui/gfx", "+remoting/protocol", diff --git a/remoting/client/ipc_host_resolver.cc b/remoting/client/ipc_host_resolver.cc new file mode 100644 index 0000000..7bac609 --- /dev/null +++ b/remoting/client/ipc_host_resolver.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/client/ipc_host_resolver.h" + +#include "base/bind.h" +#include "content/renderer/p2p/host_address_request.h" +#include "content/renderer/p2p/socket_dispatcher.h" +#include "net/base/ip_endpoint.h" +#include "jingle/glue/utils.h" + +namespace remoting { + +class IpcHostResolver : public HostResolver { + public: + IpcHostResolver(P2PSocketDispatcher* socket_dispatcher) + : socket_dispatcher_(socket_dispatcher) { + } + + virtual ~IpcHostResolver() { + if (request_) + request_->Cancel(); + } + + virtual void Resolve(const talk_base::SocketAddress& address) OVERRIDE { + if (address.IsUnresolved()) { + port_ = address.port(); + request_ = new P2PHostAddressRequest(socket_dispatcher_); + request_->Request(address.hostname(), base::Bind( + &IpcHostResolver::OnDone, base::Unretained(this))); + } else { + SignalDone(this, address); + } + } + + private: + void OnDone(const net::IPAddressNumber& address) { + talk_base::SocketAddress socket_address; + if (address.empty() || + !jingle_glue::IPEndPointToSocketAddress( + net::IPEndPoint(address, port_), &socket_address)) { + // Return nil address if the request has failed. + SignalDone(this, talk_base::SocketAddress()); + return; + } + + request_ = NULL; + SignalDone(this, socket_address); + } + + P2PSocketDispatcher* socket_dispatcher_; + scoped_refptr<P2PHostAddressRequest> request_; + uint16 port_; +}; + +IpcHostResolverFactory::IpcHostResolverFactory( + P2PSocketDispatcher* socket_dispatcher) + : socket_dispatcher_(socket_dispatcher) { +} + +IpcHostResolverFactory::~IpcHostResolverFactory() { +} + +HostResolver* IpcHostResolverFactory::CreateHostResolver() { + return new IpcHostResolver(socket_dispatcher_); +} + +} // namespace remoting diff --git a/remoting/client/ipc_host_resolver.h b/remoting/client/ipc_host_resolver.h new file mode 100644 index 0000000..8bcb028 --- /dev/null +++ b/remoting/client/ipc_host_resolver.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_CLIENT_IPC_HOST_RESOLVER_H_ +#define REMOTING_CLIENT_IPC_HOST_ADDRESS_RESOLVER_H_ + +#include "base/compiler_specific.h" +#include "remoting/jingle_glue/host_resolver.h" + +class P2PSocketDispatcher; + +namespace remoting { + +// Implementation of HostResolverFactory interface that works in +// renderer. +// +// TODO(sergeyu): Move this class to content/renderer/p2p after +// HostResolver interface is moved to libjingle. +class IpcHostResolverFactory : public HostResolverFactory { + public: + IpcHostResolverFactory(P2PSocketDispatcher* socket_dispatcher); + virtual ~IpcHostResolverFactory(); + + virtual HostResolver* CreateHostResolver() OVERRIDE; + + private: + P2PSocketDispatcher* socket_dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(IpcHostResolverFactory); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_HOST_RESOLVER_H_ diff --git a/remoting/client/plugin/chromoting_instance.cc b/remoting/client/plugin/chromoting_instance.cc index efc5e78..cbbfa1e 100644 --- a/remoting/client/plugin/chromoting_instance.cc +++ b/remoting/client/plugin/chromoting_instance.cc @@ -32,7 +32,7 @@ #include "remoting/base/util.h" #include "remoting/client/client_config.h" #include "remoting/client/chromoting_client.h" -#include "remoting/client/rectangle_update_decoder.h" +#include "remoting/client/ipc_host_resolver.h" #include "remoting/client/plugin/chromoting_scriptable_object.h" #include "remoting/client/plugin/pepper_input_handler.h" #include "remoting/client/plugin/pepper_port_allocator_session.h" @@ -40,6 +40,7 @@ #include "remoting/client/plugin/pepper_view_proxy.h" #include "remoting/client/plugin/pepper_util.h" #include "remoting/client/plugin/pepper_xmpp_proxy.h" +#include "remoting/client/rectangle_update_decoder.h" #include "remoting/proto/auth.pb.h" #include "remoting/protocol/connection_to_host.h" #include "remoting/protocol/host_stub.h" @@ -180,6 +181,7 @@ void ChromotingInstance::Connect(const ClientConfig& config) { IpcNetworkManager* network_manager = NULL; IpcPacketSocketFactory* socket_factory = NULL; + HostResolverFactory* host_resolver_factory = NULL; PortAllocatorSessionFactory* session_factory = CreatePepperPortAllocatorSessionFactory(this); @@ -189,11 +191,12 @@ void ChromotingInstance::Connect(const ClientConfig& config) { VLOG(1) << "Creating IpcNetworkManager and IpcPacketSocketFactory."; network_manager = new IpcNetworkManager(socket_dispatcher); socket_factory = new IpcPacketSocketFactory(socket_dispatcher); + host_resolver_factory = new IpcHostResolverFactory(socket_dispatcher); } host_connection_.reset(new protocol::ConnectionToHost( context_.network_message_loop(), network_manager, socket_factory, - session_factory, enable_client_nat_traversal_)); + host_resolver_factory, session_factory, enable_client_nat_traversal_)); input_handler_.reset(new PepperInputHandler(&context_, host_connection_.get(), view_proxy_)); diff --git a/remoting/jingle_glue/host_resolver.cc b/remoting/jingle_glue/host_resolver.cc new file mode 100644 index 0000000..5ad3316 --- /dev/null +++ b/remoting/jingle_glue/host_resolver.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/jingle_glue/host_resolver.h" + +namespace remoting { + +HostResolver::HostResolver() { } + +HostResolver::~HostResolver() { } + +} // namespace remoting diff --git a/remoting/jingle_glue/host_resolver.h b/remoting/jingle_glue/host_resolver.h new file mode 100644 index 0000000..d74ae2b --- /dev/null +++ b/remoting/jingle_glue/host_resolver.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_JINGLE_GLUE_HOST_RESOLVER_H_ +#define REMOTING_JINGLE_GLUE_HOST_RESOLVER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "third_party/libjingle/source/talk/base/sigslot.h" +#include "third_party/libjingle/source/talk/base/socketaddress.h" + +namespace remoting { + +// TODO(sergeyu): Move HostResolver and HostResolverFactory to +// libjingle and use them in StunPort. + +class HostResolver { + public: + HostResolver(); + virtual ~HostResolver(); + + virtual void Resolve(const talk_base::SocketAddress& address) = 0; + + sigslot::signal2<HostResolver*, const talk_base::SocketAddress&> SignalDone; + + private: + DISALLOW_COPY_AND_ASSIGN(HostResolver); +}; + +class HostResolverFactory { + public: + HostResolverFactory() { } + virtual ~HostResolverFactory() { } + + virtual HostResolver* CreateHostResolver() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(HostResolverFactory); +}; + +} // namespace remoting + +#endif // REMOTING_JINGLE_GLUE_HOST_RESOLVER_H_ diff --git a/remoting/jingle_glue/jingle_info_request.cc b/remoting/jingle_glue/jingle_info_request.cc index b8866b5..1dabf26 100644 --- a/remoting/jingle_glue/jingle_info_request.cc +++ b/remoting/jingle_glue/jingle_info_request.cc @@ -5,7 +5,11 @@ #include "remoting/jingle_glue/jingle_info_request.h" #include "base/task.h" +#include "base/message_loop.h" +#include "base/stl_util.h" #include "base/string_number_conversions.h" +#include "net/base/net_util.h" +#include "remoting/jingle_glue/host_resolver.h" #include "remoting/jingle_glue/iq_request.h" #include "third_party/libjingle/source/talk/base/socketaddress.h" #include "third_party/libjingle/source/talk/xmllite/xmlelement.h" @@ -13,12 +17,17 @@ namespace remoting { -JingleInfoRequest::JingleInfoRequest(IqRequest* request) - : request_(request) { + +JingleInfoRequest::JingleInfoRequest(IqRequest* request, + HostResolverFactory* host_resolver_factory) + : host_resolver_factory_(host_resolver_factory), + request_(request) { request_->set_callback(NewCallback(this, &JingleInfoRequest::OnResponse)); } JingleInfoRequest::~JingleInfoRequest() { + STLDeleteContainerPointers(stun_dns_requests_.begin(), + stun_dns_requests_.end()); } void JingleInfoRequest::Send(const OnJingleInfoCallback& callback) { @@ -28,10 +37,6 @@ void JingleInfoRequest::Send(const OnJingleInfoCallback& callback) { } void JingleInfoRequest::OnResponse(const buzz::XmlElement* stanza) { - std::vector<std::string> relay_hosts; - std::vector<talk_base::SocketAddress> stun_hosts; - std::string relay_token; - const buzz::XmlElement* query = stanza->FirstNamed(buzz::QN_JINGLE_INFO_QUERY); if (query == NULL) { @@ -53,7 +58,12 @@ void JingleInfoRequest::OnResponse(const buzz::XmlElement* stanza) { if (!base::StringToInt(port_str, &port)) { LOG(WARNING) << "Unable to parse port in stanza" << stanza->Str(); } else { - stun_hosts.push_back(talk_base::SocketAddress(host, port)); + net::IPAddressNumber ip_number; + HostResolver* resolver = host_resolver_factory_->CreateHostResolver(); + stun_dns_requests_.insert(resolver); + resolver->SignalDone.connect( + this, &JingleInfoRequest::OnStunAddressResponse); + resolver->Resolve(talk_base::SocketAddress(host, port)); } } } @@ -61,19 +71,31 @@ void JingleInfoRequest::OnResponse(const buzz::XmlElement* stanza) { const buzz::XmlElement* relay = query->FirstNamed(buzz::QN_JINGLE_INFO_RELAY); if (relay) { - relay_token = relay->TextNamed(buzz::QN_JINGLE_INFO_TOKEN); + relay_token_ = relay->TextNamed(buzz::QN_JINGLE_INFO_TOKEN); for (const buzz::XmlElement* server = relay->FirstNamed(buzz::QN_JINGLE_INFO_SERVER); server != NULL; server = server->NextNamed(buzz::QN_JINGLE_INFO_SERVER)) { std::string host = server->Attr(buzz::QN_JINGLE_INFO_HOST); - if (host != buzz::STR_EMPTY) { - relay_hosts.push_back(host); - } + if (host != buzz::STR_EMPTY) + relay_hosts_.push_back(host); } } - on_jingle_info_cb_.Run(relay_token, relay_hosts, stun_hosts); + if (stun_dns_requests_.empty()) + on_jingle_info_cb_.Run(relay_token_, relay_hosts_, stun_hosts_); +} + +void JingleInfoRequest::OnStunAddressResponse( + HostResolver* resolver, const talk_base::SocketAddress& address) { + if (!address.IsNil()) + stun_hosts_.push_back(address); + + MessageLoop::current()->DeleteSoon(FROM_HERE, resolver); + stun_dns_requests_.erase(resolver); + + if (stun_dns_requests_.empty()) + on_jingle_info_cb_.Run(relay_token_, relay_hosts_, stun_hosts_); } } // namespace remoting diff --git a/remoting/jingle_glue/jingle_info_request.h b/remoting/jingle_glue/jingle_info_request.h index 9a63f82..df7552f 100644 --- a/remoting/jingle_glue/jingle_info_request.h +++ b/remoting/jingle_glue/jingle_info_request.h @@ -5,12 +5,14 @@ #ifndef REMOTING_JINGLE_GLUE_JINGLE_INFO_REQUEST_H_ #define REMOTING_JINGLE_GLUE_JINGLE_INFO_REQUEST_H_ +#include <set> #include <string> #include <vector> #include "base/basictypes.h" #include "base/callback.h" #include "base/memory/scoped_ptr.h" +#include "third_party/libjingle/source/talk/base/sigslot.h" class Task; @@ -25,6 +27,8 @@ class SocketAddress; namespace remoting { class IqRequest; +class HostResolver; +class HostResolverFactory; // JingleInfoRequest handles requesting STUN/Relay infromation from // the Google Talk network. The query is made when Send() is @@ -35,7 +39,7 @@ class IqRequest; // created on. // // TODO(ajwong): Add support for a timeout. -class JingleInfoRequest { +class JingleInfoRequest : public sigslot::has_slots<> { public: // Callback to receive the Jingle configuration settings. The argumetns are // passed by pointer so the receive may call swap on them. The receiver does @@ -45,17 +49,29 @@ class JingleInfoRequest { const std::string&, const std::vector<std::string>&, const std::vector<talk_base::SocketAddress>&)> OnJingleInfoCallback; - explicit JingleInfoRequest(IqRequest* request); - ~JingleInfoRequest(); + explicit JingleInfoRequest(IqRequest* request, + HostResolverFactory* host_resolver_factory); + virtual ~JingleInfoRequest(); void Send(const OnJingleInfoCallback& callback); private: + struct PendingDnsRequest; + void OnResponse(const buzz::XmlElement* stanza); + void OnStunAddressResponse(HostResolver* resolver, + const talk_base::SocketAddress& address); + HostResolverFactory* host_resolver_factory_; scoped_ptr<IqRequest> request_; OnJingleInfoCallback on_jingle_info_cb_; + std::vector<std::string> relay_hosts_; + std::vector<talk_base::SocketAddress> stun_hosts_; + std::string relay_token_; + + std::set<HostResolver*> stun_dns_requests_; + DISALLOW_COPY_AND_ASSIGN(JingleInfoRequest); }; diff --git a/remoting/protocol/connection_to_host.cc b/remoting/protocol/connection_to_host.cc index 580f198..26dca8b 100644 --- a/remoting/protocol/connection_to_host.cc +++ b/remoting/protocol/connection_to_host.cc @@ -8,6 +8,7 @@ #include "base/callback.h" #include "base/message_loop.h" #include "remoting/base/constants.h" +#include "remoting/jingle_glue/host_resolver.h" #include "remoting/jingle_glue/http_port_allocator.h" #include "remoting/jingle_glue/javascript_signal_strategy.h" #include "remoting/jingle_glue/xmpp_signal_strategy.h" @@ -28,11 +29,13 @@ ConnectionToHost::ConnectionToHost( MessageLoop* message_loop, talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, + HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* session_factory, bool allow_nat_traversal) : message_loop_(message_loop), network_manager_(network_manager), socket_factory_(socket_factory), + host_resolver_factory_(host_resolver_factory), port_allocator_session_factory_(session_factory), allow_nat_traversal_(allow_nat_traversal), state_(STATE_EMPTY), @@ -107,6 +110,7 @@ void ConnectionToHost::InitSession() { JingleSessionManager* session_manager = JingleSessionManager::CreateSandboxed( network_manager_.release(), socket_factory_.release(), + host_resolver_factory_.release(), port_allocator_session_factory_.release()); // TODO(ajwong): Make this a command switch when we're more stable. diff --git a/remoting/protocol/connection_to_host.h b/remoting/protocol/connection_to_host.h index 11a2e74..29bbb54 100644 --- a/remoting/protocol/connection_to_host.h +++ b/remoting/protocol/connection_to_host.h @@ -27,6 +27,7 @@ class PacketSocketFactory; namespace remoting { class JingleThread; +class HostResolverFactory; class PortAllocatorSessionFactory; class XmppProxy; class VideoPacket; @@ -76,6 +77,7 @@ class ConnectionToHost : public SignalStrategy::StatusObserver, ConnectionToHost(MessageLoop* network_message_loop, talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, + HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* session_factory, bool allow_nat_traversal); virtual ~ConnectionToHost(); @@ -131,6 +133,7 @@ class ConnectionToHost : public SignalStrategy::StatusObserver, MessageLoop* message_loop_; scoped_ptr<talk_base::NetworkManager> network_manager_; scoped_ptr<talk_base::PacketSocketFactory> socket_factory_; + scoped_ptr<HostResolverFactory> host_resolver_factory_; scoped_ptr<PortAllocatorSessionFactory> port_allocator_session_factory_; bool allow_nat_traversal_; diff --git a/remoting/protocol/jingle_session_manager.cc b/remoting/protocol/jingle_session_manager.cc index 9629285..60defc1 100644 --- a/remoting/protocol/jingle_session_manager.cc +++ b/remoting/protocol/jingle_session_manager.cc @@ -11,6 +11,7 @@ #include "base/string_util.h" #include "base/task.h" #include "remoting/base/constants.h" +#include "remoting/jingle_glue/host_resolver.h" #include "remoting/jingle_glue/http_port_allocator.h" #include "remoting/jingle_glue/jingle_info_request.h" #include "remoting/jingle_glue/jingle_signaling_connector.h" @@ -29,24 +30,28 @@ namespace protocol { // static JingleSessionManager* JingleSessionManager::CreateNotSandboxed() { - return new JingleSessionManager(NULL, NULL, NULL); + return new JingleSessionManager(NULL, NULL, NULL, NULL); } // static JingleSessionManager* JingleSessionManager::CreateSandboxed( talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, + HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* port_allocator_session_factory) { return new JingleSessionManager(network_manager, socket_factory, + host_resolver_factory, port_allocator_session_factory); } JingleSessionManager::JingleSessionManager( talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, + HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* port_allocator_session_factory) : network_manager_(network_manager), socket_factory_(socket_factory), + host_resolver_factory_(host_resolver_factory), port_allocator_session_factory_(port_allocator_session_factory), signal_strategy_(NULL), allow_nat_traversal_(false), @@ -121,7 +126,8 @@ void JingleSessionManager::Init( // If NAT traversal is enabled then we need to request STUN/Relay info. if (allow_nat_traversal) { jingle_info_request_.reset( - new JingleInfoRequest(signal_strategy_->CreateIqRequest())); + new JingleInfoRequest(signal_strategy_->CreateIqRequest(), + host_resolver_factory_.get())); jingle_info_request_->Send(base::Bind( &JingleSessionManager::OnJingleInfo, base::Unretained(this))); } else { diff --git a/remoting/protocol/jingle_session_manager.h b/remoting/protocol/jingle_session_manager.h index 06ce6c5..d7a1d28 100644 --- a/remoting/protocol/jingle_session_manager.h +++ b/remoting/protocol/jingle_session_manager.h @@ -22,6 +22,7 @@ class SessionManager; namespace remoting { +class HostResolverFactory; class HttpPortAllocator; class JingleInfoRequest; class JingleSignalingConnector; @@ -42,6 +43,7 @@ class JingleSessionManager static JingleSessionManager* CreateSandboxed( talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, + HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* port_allocator_session_factory); // SessionManager interface. @@ -81,6 +83,7 @@ class JingleSessionManager JingleSessionManager( talk_base::NetworkManager* network_manager, talk_base::PacketSocketFactory* socket_factory, + HostResolverFactory* host_resolver_factory, PortAllocatorSessionFactory* port_allocator_session_factory); // Called by JingleSession when a new connection is @@ -109,6 +112,7 @@ class JingleSessionManager scoped_ptr<talk_base::NetworkManager> network_manager_; scoped_ptr<talk_base::PacketSocketFactory> socket_factory_; + scoped_ptr<HostResolverFactory> host_resolver_factory_; scoped_ptr<PortAllocatorSessionFactory> port_allocator_session_factory_; std::string local_jid_; // Full jid for the local side of the session. diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 3524188..325313b 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -496,6 +496,8 @@ 'client/frame_consumer.h', 'client/input_handler.cc', 'client/input_handler.h', + 'client/ipc_host_resolver.cc', + 'client/ipc_host_resolver.h', 'client/rectangle_update_decoder.cc', 'client/rectangle_update_decoder.h', ], @@ -559,6 +561,8 @@ '../third_party/libjingle/libjingle.gyp:libjingle_p2p', ], 'sources': [ + 'jingle_glue/host_resolver.cc', + 'jingle_glue/host_resolver.h', 'jingle_glue/http_port_allocator.cc', 'jingle_glue/http_port_allocator.h', 'jingle_glue/iq_request.cc', |