xref: /aosp_15_r20/external/cronet/components/cronet/android/cronet_bidirectional_stream_adapter.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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     auto write_buffer = base::MakeRefCounted<net::WrappedIOBuffer>(
236         base::make_span(static_cast<char*>(data), static_cast<size_t>(limit))
237             .subspan(pos));
238     pending_write_data->write_buffer_list.push_back(write_buffer);
239     pending_write_data->write_buffer_len_list.push_back(write_buffer->size());
240   }
241 
242   context_->PostTaskToNetworkThread(
243       FROM_HERE,
244       base::BindOnce(
245           &CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread,
246           base::Unretained(this), std::move(pending_write_data)));
247   return JNI_TRUE;
248 }
249 
Destroy(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,jboolean jsend_on_canceled)250 void CronetBidirectionalStreamAdapter::Destroy(
251     JNIEnv* env,
252     const base::android::JavaParamRef<jobject>& jcaller,
253     jboolean jsend_on_canceled) {
254   // Destroy could be called from any thread, including network thread (if
255   // posting task to executor throws an exception), but is posted, so |this|
256   // is valid until calling task is complete. Destroy() is always called from
257   // within a synchronized java block that guarantees no future posts to the
258   // network thread with the adapter pointer.
259   context_->PostTaskToNetworkThread(
260       FROM_HERE,
261       base::BindOnce(&CronetBidirectionalStreamAdapter::DestroyOnNetworkThread,
262                      base::Unretained(this), jsend_on_canceled));
263 }
264 
OnStreamReady(bool request_headers_sent)265 void CronetBidirectionalStreamAdapter::OnStreamReady(
266     bool request_headers_sent) {
267   DCHECK(context_->IsOnNetworkThread());
268   JNIEnv* env = base::android::AttachCurrentThread();
269   cronet::Java_CronetBidirectionalStream_onStreamReady(
270       env, owner_, request_headers_sent ? JNI_TRUE : JNI_FALSE);
271 }
272 
OnHeadersReceived(const spdy::Http2HeaderBlock & response_headers)273 void CronetBidirectionalStreamAdapter::OnHeadersReceived(
274     const spdy::Http2HeaderBlock& response_headers) {
275   DCHECK(context_->IsOnNetworkThread());
276   JNIEnv* env = base::android::AttachCurrentThread();
277   // Get http status code from response headers.
278   jint http_status_code = 0;
279   const auto http_status_header = response_headers.find(":status");
280   if (http_status_header != response_headers.end()) {
281     base::StringToInt(http_status_header->second, &http_status_code);
282   }
283 
284   std::string protocol;
285   switch (bidi_stream_->GetProtocol()) {
286     case net::kProtoHTTP2:
287       protocol = "h2";
288       break;
289     case net::kProtoQUIC:
290       protocol = "quic/1+spdy/3";
291       break;
292     default:
293       break;
294   }
295 
296   cronet::Java_CronetBidirectionalStream_onResponseHeadersReceived(
297       env, owner_, http_status_code, ConvertUTF8ToJavaString(env, protocol),
298       GetHeadersArray(env, response_headers),
299       bidi_stream_->GetTotalReceivedBytes());
300 }
301 
OnDataRead(int bytes_read)302 void CronetBidirectionalStreamAdapter::OnDataRead(int bytes_read) {
303   DCHECK(context_->IsOnNetworkThread());
304   JNIEnv* env = base::android::AttachCurrentThread();
305   cronet::Java_CronetBidirectionalStream_onReadCompleted(
306       env, owner_, read_buffer_->byte_buffer(), bytes_read,
307       read_buffer_->initial_position(), read_buffer_->initial_limit(),
308       bidi_stream_->GetTotalReceivedBytes());
309   // Free the read buffer. This lets the Java ByteBuffer be freed, if the
310   // embedder releases it, too.
311   read_buffer_ = nullptr;
312 }
313 
OnDataSent()314 void CronetBidirectionalStreamAdapter::OnDataSent() {
315   DCHECK(context_->IsOnNetworkThread());
316   DCHECK(pending_write_data_);
317 
318   JNIEnv* env = base::android::AttachCurrentThread();
319   // Call into Java.
320   cronet::Java_CronetBidirectionalStream_onWritevCompleted(
321       env, owner_, pending_write_data_->jwrite_buffer_list,
322       pending_write_data_->jwrite_buffer_pos_list,
323       pending_write_data_->jwrite_buffer_limit_list,
324       pending_write_data_->jwrite_end_of_stream);
325   // Free the java objects. This lets the Java ByteBuffers be freed, if the
326   // embedder releases it, too.
327   pending_write_data_.reset();
328 }
329 
OnTrailersReceived(const spdy::Http2HeaderBlock & response_trailers)330 void CronetBidirectionalStreamAdapter::OnTrailersReceived(
331     const spdy::Http2HeaderBlock& response_trailers) {
332   DCHECK(context_->IsOnNetworkThread());
333   JNIEnv* env = base::android::AttachCurrentThread();
334   cronet::Java_CronetBidirectionalStream_onResponseTrailersReceived(
335       env, owner_, GetHeadersArray(env, response_trailers));
336 }
337 
OnFailed(int error)338 void CronetBidirectionalStreamAdapter::OnFailed(int error) {
339   DCHECK(context_->IsOnNetworkThread());
340   stream_failed_ = true;
341   JNIEnv* env = base::android::AttachCurrentThread();
342   net::NetErrorDetails net_error_details;
343   bidi_stream_->PopulateNetErrorDetails(&net_error_details);
344   cronet::Java_CronetBidirectionalStream_onError(
345       env, owner_, NetErrorToUrlRequestError(error), error,
346       net_error_details.quic_connection_error,
347       ConvertUTF8ToJavaString(env, net::ErrorToString(error)),
348       bidi_stream_->GetTotalReceivedBytes());
349 }
350 
StartOnNetworkThread(std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info)351 void CronetBidirectionalStreamAdapter::StartOnNetworkThread(
352     std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info) {
353   DCHECK(context_->IsOnNetworkThread());
354   DCHECK(!bidi_stream_);
355 
356   request_info->detect_broken_connection =
357       context_->cronet_url_request_context()
358           ->bidi_stream_detect_broken_connection();
359   request_info->heartbeat_interval =
360       context_->cronet_url_request_context()->heartbeat_interval();
361   request_info->extra_headers.SetHeaderIfMissing(
362       net::HttpRequestHeaders::kUserAgent,
363       context_->GetURLRequestContext(network_)
364           ->http_user_agent_settings()
365           ->GetUserAgent());
366   bidi_stream_.reset(
367       new net::BidirectionalStream(std::move(request_info),
368                                    context_->GetURLRequestContext(network_)
369                                        ->http_transaction_factory()
370                                        ->GetSession(),
371                                    send_request_headers_automatically_, this));
372 }
373 
SendRequestHeadersOnNetworkThread()374 void CronetBidirectionalStreamAdapter::SendRequestHeadersOnNetworkThread() {
375   DCHECK(context_->IsOnNetworkThread());
376   DCHECK(!send_request_headers_automatically_);
377 
378   if (stream_failed_) {
379     // If stream failed between the time when SendRequestHeaders is invoked and
380     // SendRequestHeadersOnNetworkThread is executed, do not call into
381     // |bidi_stream_| since the underlying stream might have been destroyed.
382     // Do not invoke Java callback either, since onError is posted when
383     // |stream_failed_| is set to true.
384     return;
385   }
386   bidi_stream_->SendRequestHeaders();
387 }
388 
ReadDataOnNetworkThread(scoped_refptr<IOBufferWithByteBuffer> read_buffer,int buffer_size)389 void CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread(
390     scoped_refptr<IOBufferWithByteBuffer> read_buffer,
391     int buffer_size) {
392   DCHECK(context_->IsOnNetworkThread());
393   DCHECK(read_buffer);
394   DCHECK(!read_buffer_);
395 
396   read_buffer_ = read_buffer;
397 
398   int bytes_read = bidi_stream_->ReadData(read_buffer_.get(), buffer_size);
399   // If IO is pending, wait for the BidirectionalStream to call OnDataRead.
400   if (bytes_read == net::ERR_IO_PENDING)
401     return;
402 
403   if (bytes_read < 0) {
404     OnFailed(bytes_read);
405     return;
406   }
407   OnDataRead(bytes_read);
408 }
409 
WritevDataOnNetworkThread(std::unique_ptr<PendingWriteData> pending_write_data)410 void CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread(
411     std::unique_ptr<PendingWriteData> pending_write_data) {
412   DCHECK(context_->IsOnNetworkThread());
413   DCHECK(pending_write_data);
414   DCHECK(!pending_write_data_);
415 
416   if (stream_failed_) {
417     // If stream failed between the time when WritevData is invoked and
418     // WritevDataOnNetworkThread is executed, do not call into |bidi_stream_|
419     // since the underlying stream might have been destroyed. Do not invoke
420     // Java callback either, since onError is posted when |stream_failed_| is
421     // set to true.
422     return;
423   }
424 
425   pending_write_data_ = std::move(pending_write_data);
426   bool end_of_stream = pending_write_data_->jwrite_end_of_stream == JNI_TRUE;
427   bidi_stream_->SendvData(pending_write_data_->write_buffer_list,
428                           pending_write_data_->write_buffer_len_list,
429                           end_of_stream);
430 }
431 
DestroyOnNetworkThread(bool send_on_canceled)432 void CronetBidirectionalStreamAdapter::DestroyOnNetworkThread(
433     bool send_on_canceled) {
434   DCHECK(context_->IsOnNetworkThread());
435   if (send_on_canceled) {
436     JNIEnv* env = base::android::AttachCurrentThread();
437     cronet::Java_CronetBidirectionalStream_onCanceled(env, owner_);
438   }
439   MaybeReportMetrics();
440   delete this;
441 }
442 
443 base::android::ScopedJavaLocalRef<jobjectArray>
GetHeadersArray(JNIEnv * env,const spdy::Http2HeaderBlock & header_block)444 CronetBidirectionalStreamAdapter::GetHeadersArray(
445     JNIEnv* env,
446     const spdy::Http2HeaderBlock& header_block) {
447   DCHECK(context_->IsOnNetworkThread());
448 
449   std::vector<std::string> headers;
450   for (const auto& header : header_block) {
451     auto value = std::string(header.second);
452     size_t start = 0;
453     size_t end = 0;
454     // The do loop will split headers by '\0' so that applications can skip it.
455     do {
456       end = value.find('\0', start);
457       std::string split_value;
458       if (end != value.npos) {
459         split_value = value.substr(start, end - start);
460       } else {
461         split_value = value.substr(start);
462       }
463       headers.push_back(std::string(header.first));
464       headers.push_back(split_value);
465       start = end + 1;
466     } while (end != value.npos);
467   }
468   return base::android::ToJavaArrayOfStrings(env, headers);
469 }
470 
MaybeReportMetrics()471 void CronetBidirectionalStreamAdapter::MaybeReportMetrics() {
472   if (!bidi_stream_)
473     return;
474   net::LoadTimingInfo load_timing_info;
475   bidi_stream_->GetLoadTimingInfo(&load_timing_info);
476   JNIEnv* env = base::android::AttachCurrentThread();
477   base::Time start_time = load_timing_info.request_start_time;
478   base::TimeTicks start_ticks = load_timing_info.request_start;
479   cronet::Java_CronetBidirectionalStream_onMetricsCollected(
480       env, owner_,
481       metrics_util::ConvertTime(start_ticks, start_ticks, start_time),
482       metrics_util::ConvertTime(
483           load_timing_info.connect_timing.domain_lookup_start, start_ticks,
484           start_time),
485       metrics_util::ConvertTime(
486           load_timing_info.connect_timing.domain_lookup_end, start_ticks,
487           start_time),
488       metrics_util::ConvertTime(load_timing_info.connect_timing.connect_start,
489                                 start_ticks, start_time),
490       metrics_util::ConvertTime(load_timing_info.connect_timing.connect_end,
491                                 start_ticks, start_time),
492       metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_start,
493                                 start_ticks, start_time),
494       metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_end,
495                                 start_ticks, start_time),
496       metrics_util::ConvertTime(load_timing_info.send_start, start_ticks,
497                                 start_time),
498       metrics_util::ConvertTime(load_timing_info.send_end, start_ticks,
499                                 start_time),
500       metrics_util::ConvertTime(load_timing_info.push_start, start_ticks,
501                                 start_time),
502       metrics_util::ConvertTime(load_timing_info.push_end, start_ticks,
503                                 start_time),
504       metrics_util::ConvertTime(load_timing_info.receive_headers_end,
505                                 start_ticks, start_time),
506       metrics_util::ConvertTime(base::TimeTicks::Now(), start_ticks,
507                                 start_time),
508       load_timing_info.socket_reused, bidi_stream_->GetTotalSentBytes(),
509       bidi_stream_->GetTotalReceivedBytes());
510 }
511 
512 }  // namespace cronet
513