1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cronet_bidirectional_stream_adapter.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "base/functional/bind.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/cronet/android/cronet_context_adapter.h"
16 #include "components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h"
17 #include "components/cronet/android/io_buffer_with_byte_buffer.h"
18 #include "components/cronet/android/url_request_error.h"
19 #include "components/cronet/metrics_util.h"
20 #include "net/base/http_user_agent_settings.h"
21 #include "net/base/net_errors.h"
22 #include "net/base/request_priority.h"
23 #include "net/http/bidirectional_stream_request_info.h"
24 #include "net/http/http_network_session.h"
25 #include "net/http/http_response_headers.h"
26 #include "net/http/http_status_code.h"
27 #include "net/http/http_transaction_factory.h"
28 #include "net/http/http_util.h"
29 #include "net/ssl/ssl_info.h"
30 #include "net/third_party/quiche/src/quiche/quic/core/quic_packets.h"
31 #include "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.h"
32 #include "net/url_request/url_request_context.h"
33 #include "url/gurl.h"
34
35 using base::android::ConvertUTF8ToJavaString;
36 using base::android::ConvertJavaStringToUTF8;
37 using base::android::JavaRef;
38 using base::android::ScopedJavaLocalRef;
39
40 namespace cronet {
41
42 namespace {
43
44 // As |GetArrayLength| makes no guarantees about the returned value (e.g., it
45 // may be -1 if |array| is not a valid Java array), provide a safe wrapper
46 // that always returns a valid, non-negative size.
47 template <typename JavaArrayType>
SafeGetArrayLength(JNIEnv * env,JavaArrayType jarray)48 size_t SafeGetArrayLength(JNIEnv* env, JavaArrayType jarray) {
49 DCHECK(jarray);
50 jsize length = env->GetArrayLength(jarray);
51 DCHECK_GE(length, 0) << "Invalid array length: " << length;
52 return static_cast<size_t>(std::max(0, length));
53 }
54
55 } // namespace
56
PendingWriteData(JNIEnv * env,const JavaRef<jobjectArray> & jwrite_buffer_list,const JavaRef<jintArray> & jwrite_buffer_pos_list,const JavaRef<jintArray> & jwrite_buffer_limit_list,jboolean jwrite_end_of_stream)57 PendingWriteData::PendingWriteData(
58 JNIEnv* env,
59 const JavaRef<jobjectArray>& jwrite_buffer_list,
60 const JavaRef<jintArray>& jwrite_buffer_pos_list,
61 const JavaRef<jintArray>& jwrite_buffer_limit_list,
62 jboolean jwrite_end_of_stream) {
63 this->jwrite_buffer_list.Reset(jwrite_buffer_list);
64 this->jwrite_buffer_pos_list.Reset(jwrite_buffer_pos_list);
65 this->jwrite_buffer_limit_list.Reset(jwrite_buffer_limit_list);
66 this->jwrite_end_of_stream = jwrite_end_of_stream;
67 }
68
~PendingWriteData()69 PendingWriteData::~PendingWriteData() {
70 // Reset global references.
71 jwrite_buffer_list.Reset();
72 jwrite_buffer_pos_list.Reset();
73 jwrite_buffer_limit_list.Reset();
74 }
75
JNI_CronetBidirectionalStream_CreateBidirectionalStream(JNIEnv * env,const base::android::JavaParamRef<jobject> & jbidi_stream,jlong jurl_request_context_adapter,jboolean jsend_request_headers_automatically,jboolean jtraffic_stats_tag_set,jint jtraffic_stats_tag,jboolean jtraffic_stats_uid_set,jint jtraffic_stats_uid,jlong jnetwork_handle)76 static jlong JNI_CronetBidirectionalStream_CreateBidirectionalStream(
77 JNIEnv* env,
78 const base::android::JavaParamRef<jobject>& jbidi_stream,
79 jlong jurl_request_context_adapter,
80 jboolean jsend_request_headers_automatically,
81 jboolean jtraffic_stats_tag_set,
82 jint jtraffic_stats_tag,
83 jboolean jtraffic_stats_uid_set,
84 jint jtraffic_stats_uid,
85 jlong jnetwork_handle) {
86 CronetContextAdapter* context_adapter =
87 reinterpret_cast<CronetContextAdapter*>(jurl_request_context_adapter);
88 DCHECK(context_adapter);
89
90 CronetBidirectionalStreamAdapter* adapter =
91 new CronetBidirectionalStreamAdapter(
92 context_adapter, env, jbidi_stream,
93 jsend_request_headers_automatically, jtraffic_stats_tag_set,
94 jtraffic_stats_tag, jtraffic_stats_uid_set, jtraffic_stats_uid,
95 jnetwork_handle);
96
97 return reinterpret_cast<jlong>(adapter);
98 }
99
CronetBidirectionalStreamAdapter(CronetContextAdapter * context,JNIEnv * env,const base::android::JavaParamRef<jobject> & jbidi_stream,bool send_request_headers_automatically,bool traffic_stats_tag_set,int32_t traffic_stats_tag,bool traffic_stats_uid_set,int32_t traffic_stats_uid,net::handles::NetworkHandle network)100 CronetBidirectionalStreamAdapter::CronetBidirectionalStreamAdapter(
101 CronetContextAdapter* context,
102 JNIEnv* env,
103 const base::android::JavaParamRef<jobject>& jbidi_stream,
104 bool send_request_headers_automatically,
105 bool traffic_stats_tag_set,
106 int32_t traffic_stats_tag,
107 bool traffic_stats_uid_set,
108 int32_t traffic_stats_uid,
109 net::handles::NetworkHandle network)
110 : context_(context),
111 owner_(env, jbidi_stream),
112 send_request_headers_automatically_(send_request_headers_automatically),
113 traffic_stats_tag_set_(traffic_stats_tag_set),
114 traffic_stats_tag_(traffic_stats_tag),
115 traffic_stats_uid_set_(traffic_stats_uid_set),
116 traffic_stats_uid_(traffic_stats_uid),
117 network_(network),
118 stream_failed_(false) {}
119
~CronetBidirectionalStreamAdapter()120 CronetBidirectionalStreamAdapter::~CronetBidirectionalStreamAdapter() {
121 DCHECK(context_->IsOnNetworkThread());
122 }
123
SendRequestHeaders(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller)124 void CronetBidirectionalStreamAdapter::SendRequestHeaders(
125 JNIEnv* env,
126 const base::android::JavaParamRef<jobject>& jcaller) {
127 context_->PostTaskToNetworkThread(
128 FROM_HERE,
129 base::BindOnce(
130 &CronetBidirectionalStreamAdapter::SendRequestHeadersOnNetworkThread,
131 base::Unretained(this)));
132 }
133
Start(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,const base::android::JavaParamRef<jstring> & jurl,jint jpriority,const base::android::JavaParamRef<jstring> & jmethod,const base::android::JavaParamRef<jobjectArray> & jheaders,jboolean jend_of_stream)134 jint CronetBidirectionalStreamAdapter::Start(
135 JNIEnv* env,
136 const base::android::JavaParamRef<jobject>& jcaller,
137 const base::android::JavaParamRef<jstring>& jurl,
138 jint jpriority,
139 const base::android::JavaParamRef<jstring>& jmethod,
140 const base::android::JavaParamRef<jobjectArray>& jheaders,
141 jboolean jend_of_stream) {
142 // Prepare request info here to be able to return the error.
143 std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info(
144 new net::BidirectionalStreamRequestInfo());
145 request_info->url = GURL(ConvertJavaStringToUTF8(env, jurl));
146 request_info->priority = static_cast<net::RequestPriority>(jpriority);
147 // Http method is a token, just as header name.
148 request_info->method = ConvertJavaStringToUTF8(env, jmethod);
149 if (!net::HttpUtil::IsValidHeaderName(request_info->method))
150 return -1;
151
152 std::vector<std::string> headers;
153 base::android::AppendJavaStringArrayToStringVector(env, jheaders, &headers);
154 for (size_t i = 0; i < headers.size(); i += 2) {
155 std::string name(headers[i]);
156 std::string value(headers[i + 1]);
157 if (!net::HttpUtil::IsValidHeaderName(name) ||
158 !net::HttpUtil::IsValidHeaderValue(value)) {
159 return i + 1;
160 }
161 request_info->extra_headers.SetHeader(name, value);
162 }
163 request_info->end_stream_on_headers = jend_of_stream;
164 if (traffic_stats_tag_set_ || traffic_stats_uid_set_) {
165 request_info->socket_tag = net::SocketTag(
166 traffic_stats_uid_set_ ? traffic_stats_uid_ : net::SocketTag::UNSET_UID,
167 traffic_stats_tag_set_ ? traffic_stats_tag_
168 : net::SocketTag::UNSET_TAG);
169 }
170
171 context_->PostTaskToNetworkThread(
172 FROM_HERE,
173 base::BindOnce(&CronetBidirectionalStreamAdapter::StartOnNetworkThread,
174 base::Unretained(this), std::move(request_info)));
175 return 0;
176 }
177
ReadData(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,const base::android::JavaParamRef<jobject> & jbyte_buffer,jint jposition,jint jlimit)178 jboolean CronetBidirectionalStreamAdapter::ReadData(
179 JNIEnv* env,
180 const base::android::JavaParamRef<jobject>& jcaller,
181 const base::android::JavaParamRef<jobject>& jbyte_buffer,
182 jint jposition,
183 jint jlimit) {
184 DCHECK_LT(jposition, jlimit);
185
186 void* data = env->GetDirectBufferAddress(jbyte_buffer);
187 if (!data)
188 return JNI_FALSE;
189
190 scoped_refptr<IOBufferWithByteBuffer> read_buffer(
191 new IOBufferWithByteBuffer(env, jbyte_buffer, data, jposition, jlimit));
192
193 int remaining_capacity = jlimit - jposition;
194
195 context_->PostTaskToNetworkThread(
196 FROM_HERE,
197 base::BindOnce(&CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread,
198 base::Unretained(this), read_buffer, remaining_capacity));
199 return JNI_TRUE;
200 }
201
WritevData(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,const base::android::JavaParamRef<jobjectArray> & jbyte_buffers,const base::android::JavaParamRef<jintArray> & jbyte_buffers_pos,const base::android::JavaParamRef<jintArray> & jbyte_buffers_limit,jboolean jend_of_stream)202 jboolean CronetBidirectionalStreamAdapter::WritevData(
203 JNIEnv* env,
204 const base::android::JavaParamRef<jobject>& jcaller,
205 const base::android::JavaParamRef<jobjectArray>& jbyte_buffers,
206 const base::android::JavaParamRef<jintArray>& jbyte_buffers_pos,
207 const base::android::JavaParamRef<jintArray>& jbyte_buffers_limit,
208 jboolean jend_of_stream) {
209 size_t buffers_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
210 size_t pos_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
211 size_t limit_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
212 if (buffers_array_size != pos_array_size ||
213 buffers_array_size != limit_array_size) {
214 DLOG(ERROR) << "Illegal arguments.";
215 return JNI_FALSE;
216 }
217
218 std::unique_ptr<PendingWriteData> pending_write_data;
219 pending_write_data.reset(
220 new PendingWriteData(env, jbyte_buffers, jbyte_buffers_pos,
221 jbyte_buffers_limit, jend_of_stream));
222 for (size_t i = 0; i < buffers_array_size; ++i) {
223 ScopedJavaLocalRef<jobject> jbuffer(
224 env, env->GetObjectArrayElement(
225 pending_write_data->jwrite_buffer_list.obj(), i));
226 void* data = env->GetDirectBufferAddress(jbuffer.obj());
227 if (!data)
228 return JNI_FALSE;
229 jint pos;
230 env->GetIntArrayRegion(pending_write_data->jwrite_buffer_pos_list.obj(), i,
231 1, &pos);
232 jint limit;
233 env->GetIntArrayRegion(pending_write_data->jwrite_buffer_limit_list.obj(),
234 i, 1, &limit);
235 DCHECK_LE(pos, limit);
236 scoped_refptr<net::WrappedIOBuffer> write_buffer =
237 base::MakeRefCounted<net::WrappedIOBuffer>(
238 static_cast<char*>(data) + pos, limit - pos);
239 pending_write_data->write_buffer_list.push_back(write_buffer);
240 pending_write_data->write_buffer_len_list.push_back(limit - pos);
241 }
242
243 context_->PostTaskToNetworkThread(
244 FROM_HERE,
245 base::BindOnce(
246 &CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread,
247 base::Unretained(this), std::move(pending_write_data)));
248 return JNI_TRUE;
249 }
250
Destroy(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,jboolean jsend_on_canceled)251 void CronetBidirectionalStreamAdapter::Destroy(
252 JNIEnv* env,
253 const base::android::JavaParamRef<jobject>& jcaller,
254 jboolean jsend_on_canceled) {
255 // Destroy could be called from any thread, including network thread (if
256 // posting task to executor throws an exception), but is posted, so |this|
257 // is valid until calling task is complete. Destroy() is always called from
258 // within a synchronized java block that guarantees no future posts to the
259 // network thread with the adapter pointer.
260 context_->PostTaskToNetworkThread(
261 FROM_HERE,
262 base::BindOnce(&CronetBidirectionalStreamAdapter::DestroyOnNetworkThread,
263 base::Unretained(this), jsend_on_canceled));
264 }
265
OnStreamReady(bool request_headers_sent)266 void CronetBidirectionalStreamAdapter::OnStreamReady(
267 bool request_headers_sent) {
268 DCHECK(context_->IsOnNetworkThread());
269 JNIEnv* env = base::android::AttachCurrentThread();
270 cronet::Java_CronetBidirectionalStream_onStreamReady(
271 env, owner_, request_headers_sent ? JNI_TRUE : JNI_FALSE);
272 }
273
OnHeadersReceived(const spdy::Http2HeaderBlock & response_headers)274 void CronetBidirectionalStreamAdapter::OnHeadersReceived(
275 const spdy::Http2HeaderBlock& response_headers) {
276 DCHECK(context_->IsOnNetworkThread());
277 JNIEnv* env = base::android::AttachCurrentThread();
278 // Get http status code from response headers.
279 jint http_status_code = 0;
280 const auto http_status_header = response_headers.find(":status");
281 if (http_status_header != response_headers.end()) {
282 base::StringToInt(http_status_header->second, &http_status_code);
283 }
284
285 std::string protocol;
286 switch (bidi_stream_->GetProtocol()) {
287 case net::kProtoHTTP2:
288 protocol = "h2";
289 break;
290 case net::kProtoQUIC:
291 protocol = "quic/1+spdy/3";
292 break;
293 default:
294 break;
295 }
296
297 cronet::Java_CronetBidirectionalStream_onResponseHeadersReceived(
298 env, owner_, http_status_code, ConvertUTF8ToJavaString(env, protocol),
299 GetHeadersArray(env, response_headers),
300 bidi_stream_->GetTotalReceivedBytes());
301 }
302
OnDataRead(int bytes_read)303 void CronetBidirectionalStreamAdapter::OnDataRead(int bytes_read) {
304 DCHECK(context_->IsOnNetworkThread());
305 JNIEnv* env = base::android::AttachCurrentThread();
306 cronet::Java_CronetBidirectionalStream_onReadCompleted(
307 env, owner_, read_buffer_->byte_buffer(), bytes_read,
308 read_buffer_->initial_position(), read_buffer_->initial_limit(),
309 bidi_stream_->GetTotalReceivedBytes());
310 // Free the read buffer. This lets the Java ByteBuffer be freed, if the
311 // embedder releases it, too.
312 read_buffer_ = nullptr;
313 }
314
OnDataSent()315 void CronetBidirectionalStreamAdapter::OnDataSent() {
316 DCHECK(context_->IsOnNetworkThread());
317 DCHECK(pending_write_data_);
318
319 JNIEnv* env = base::android::AttachCurrentThread();
320 // Call into Java.
321 cronet::Java_CronetBidirectionalStream_onWritevCompleted(
322 env, owner_, pending_write_data_->jwrite_buffer_list,
323 pending_write_data_->jwrite_buffer_pos_list,
324 pending_write_data_->jwrite_buffer_limit_list,
325 pending_write_data_->jwrite_end_of_stream);
326 // Free the java objects. This lets the Java ByteBuffers be freed, if the
327 // embedder releases it, too.
328 pending_write_data_.reset();
329 }
330
OnTrailersReceived(const spdy::Http2HeaderBlock & response_trailers)331 void CronetBidirectionalStreamAdapter::OnTrailersReceived(
332 const spdy::Http2HeaderBlock& response_trailers) {
333 DCHECK(context_->IsOnNetworkThread());
334 JNIEnv* env = base::android::AttachCurrentThread();
335 cronet::Java_CronetBidirectionalStream_onResponseTrailersReceived(
336 env, owner_, GetHeadersArray(env, response_trailers));
337 }
338
OnFailed(int error)339 void CronetBidirectionalStreamAdapter::OnFailed(int error) {
340 DCHECK(context_->IsOnNetworkThread());
341 stream_failed_ = true;
342 JNIEnv* env = base::android::AttachCurrentThread();
343 net::NetErrorDetails net_error_details;
344 bidi_stream_->PopulateNetErrorDetails(&net_error_details);
345 cronet::Java_CronetBidirectionalStream_onError(
346 env, owner_, NetErrorToUrlRequestError(error), error,
347 net_error_details.quic_connection_error,
348 ConvertUTF8ToJavaString(env, net::ErrorToString(error)),
349 bidi_stream_->GetTotalReceivedBytes());
350 }
351
StartOnNetworkThread(std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info)352 void CronetBidirectionalStreamAdapter::StartOnNetworkThread(
353 std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info) {
354 DCHECK(context_->IsOnNetworkThread());
355 DCHECK(!bidi_stream_);
356
357 request_info->detect_broken_connection =
358 context_->cronet_url_request_context()
359 ->bidi_stream_detect_broken_connection();
360 request_info->heartbeat_interval =
361 context_->cronet_url_request_context()->heartbeat_interval();
362 request_info->extra_headers.SetHeaderIfMissing(
363 net::HttpRequestHeaders::kUserAgent,
364 context_->GetURLRequestContext(network_)
365 ->http_user_agent_settings()
366 ->GetUserAgent());
367 bidi_stream_.reset(
368 new net::BidirectionalStream(std::move(request_info),
369 context_->GetURLRequestContext(network_)
370 ->http_transaction_factory()
371 ->GetSession(),
372 send_request_headers_automatically_, this));
373 }
374
SendRequestHeadersOnNetworkThread()375 void CronetBidirectionalStreamAdapter::SendRequestHeadersOnNetworkThread() {
376 DCHECK(context_->IsOnNetworkThread());
377 DCHECK(!send_request_headers_automatically_);
378
379 if (stream_failed_) {
380 // If stream failed between the time when SendRequestHeaders is invoked and
381 // SendRequestHeadersOnNetworkThread is executed, do not call into
382 // |bidi_stream_| since the underlying stream might have been destroyed.
383 // Do not invoke Java callback either, since onError is posted when
384 // |stream_failed_| is set to true.
385 return;
386 }
387 bidi_stream_->SendRequestHeaders();
388 }
389
ReadDataOnNetworkThread(scoped_refptr<IOBufferWithByteBuffer> read_buffer,int buffer_size)390 void CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread(
391 scoped_refptr<IOBufferWithByteBuffer> read_buffer,
392 int buffer_size) {
393 DCHECK(context_->IsOnNetworkThread());
394 DCHECK(read_buffer);
395 DCHECK(!read_buffer_);
396
397 read_buffer_ = read_buffer;
398
399 int bytes_read = bidi_stream_->ReadData(read_buffer_.get(), buffer_size);
400 // If IO is pending, wait for the BidirectionalStream to call OnDataRead.
401 if (bytes_read == net::ERR_IO_PENDING)
402 return;
403
404 if (bytes_read < 0) {
405 OnFailed(bytes_read);
406 return;
407 }
408 OnDataRead(bytes_read);
409 }
410
WritevDataOnNetworkThread(std::unique_ptr<PendingWriteData> pending_write_data)411 void CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread(
412 std::unique_ptr<PendingWriteData> pending_write_data) {
413 DCHECK(context_->IsOnNetworkThread());
414 DCHECK(pending_write_data);
415 DCHECK(!pending_write_data_);
416
417 if (stream_failed_) {
418 // If stream failed between the time when WritevData is invoked and
419 // WritevDataOnNetworkThread is executed, do not call into |bidi_stream_|
420 // since the underlying stream might have been destroyed. Do not invoke
421 // Java callback either, since onError is posted when |stream_failed_| is
422 // set to true.
423 return;
424 }
425
426 pending_write_data_ = std::move(pending_write_data);
427 bool end_of_stream = pending_write_data_->jwrite_end_of_stream == JNI_TRUE;
428 bidi_stream_->SendvData(pending_write_data_->write_buffer_list,
429 pending_write_data_->write_buffer_len_list,
430 end_of_stream);
431 }
432
DestroyOnNetworkThread(bool send_on_canceled)433 void CronetBidirectionalStreamAdapter::DestroyOnNetworkThread(
434 bool send_on_canceled) {
435 DCHECK(context_->IsOnNetworkThread());
436 if (send_on_canceled) {
437 JNIEnv* env = base::android::AttachCurrentThread();
438 cronet::Java_CronetBidirectionalStream_onCanceled(env, owner_);
439 }
440 MaybeReportMetrics();
441 delete this;
442 }
443
444 base::android::ScopedJavaLocalRef<jobjectArray>
GetHeadersArray(JNIEnv * env,const spdy::Http2HeaderBlock & header_block)445 CronetBidirectionalStreamAdapter::GetHeadersArray(
446 JNIEnv* env,
447 const spdy::Http2HeaderBlock& header_block) {
448 DCHECK(context_->IsOnNetworkThread());
449
450 std::vector<std::string> headers;
451 for (const auto& header : header_block) {
452 auto value = std::string(header.second);
453 size_t start = 0;
454 size_t end = 0;
455 // The do loop will split headers by '\0' so that applications can skip it.
456 do {
457 end = value.find('\0', start);
458 std::string split_value;
459 if (end != value.npos) {
460 split_value = value.substr(start, end - start);
461 } else {
462 split_value = value.substr(start);
463 }
464 headers.push_back(std::string(header.first));
465 headers.push_back(split_value);
466 start = end + 1;
467 } while (end != value.npos);
468 }
469 return base::android::ToJavaArrayOfStrings(env, headers);
470 }
471
MaybeReportMetrics()472 void CronetBidirectionalStreamAdapter::MaybeReportMetrics() {
473 if (!bidi_stream_)
474 return;
475 net::LoadTimingInfo load_timing_info;
476 bidi_stream_->GetLoadTimingInfo(&load_timing_info);
477 JNIEnv* env = base::android::AttachCurrentThread();
478 base::Time start_time = load_timing_info.request_start_time;
479 base::TimeTicks start_ticks = load_timing_info.request_start;
480 cronet::Java_CronetBidirectionalStream_onMetricsCollected(
481 env, owner_,
482 metrics_util::ConvertTime(start_ticks, start_ticks, start_time),
483 metrics_util::ConvertTime(
484 load_timing_info.connect_timing.domain_lookup_start, start_ticks,
485 start_time),
486 metrics_util::ConvertTime(
487 load_timing_info.connect_timing.domain_lookup_end, start_ticks,
488 start_time),
489 metrics_util::ConvertTime(load_timing_info.connect_timing.connect_start,
490 start_ticks, start_time),
491 metrics_util::ConvertTime(load_timing_info.connect_timing.connect_end,
492 start_ticks, start_time),
493 metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_start,
494 start_ticks, start_time),
495 metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_end,
496 start_ticks, start_time),
497 metrics_util::ConvertTime(load_timing_info.send_start, start_ticks,
498 start_time),
499 metrics_util::ConvertTime(load_timing_info.send_end, start_ticks,
500 start_time),
501 metrics_util::ConvertTime(load_timing_info.push_start, start_ticks,
502 start_time),
503 metrics_util::ConvertTime(load_timing_info.push_end, start_ticks,
504 start_time),
505 metrics_util::ConvertTime(load_timing_info.receive_headers_end,
506 start_ticks, start_time),
507 metrics_util::ConvertTime(base::TimeTicks::Now(), start_ticks,
508 start_time),
509 load_timing_info.socket_reused, bidi_stream_->GetTotalSentBytes(),
510 bidi_stream_->GetTotalReceivedBytes());
511 }
512
513 } // namespace cronet
514