• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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