1- use super :: auth:: RemoteControlConnectionAuth ;
21use super :: pairing_unavailable_error;
3- use super :: protocol:: EnrollRemoteServerRequest ;
4- use super :: protocol:: EnrollRemoteServerResponse ;
5- use super :: protocol:: RefreshRemoteServerRequest ;
62use super :: protocol:: RemoteControlPairingStatusRequest ;
73use super :: protocol:: RemoteControlPairingStatusResponse as BackendRemoteControlPairingStatusResponse ;
84use super :: protocol:: RemoteControlTarget ;
@@ -14,24 +10,20 @@ use codex_app_server_protocol::RemoteControlPairingStatusResponse;
1410use codex_login:: default_client:: build_reqwest_client;
1511use codex_state:: RemoteControlEnrollmentRecord ;
1612use codex_state:: StateRuntime ;
17- use serde:: Serialize ;
18- use serde:: de:: DeserializeOwned ;
1913use std:: io;
2014use std:: io:: ErrorKind ;
2115use time:: OffsetDateTime ;
2216use time:: format_description:: well_known:: Rfc3339 ;
2317use tracing:: info;
2418use tracing:: warn;
2519
26- const REMOTE_CONTROL_ENROLL_TIMEOUT : std:: time:: Duration = std:: time:: Duration :: from_secs ( 30 ) ;
2720const REMOTE_CONTROL_PAIRING_TIMEOUT : std:: time:: Duration = std:: time:: Duration :: from_secs ( 30 ) ;
2821const REMOTE_CONTROL_RESPONSE_BODY_MAX_BYTES : usize = 4096 ;
29- const REMOTE_CONTROL_SERVER_TOKEN_REFRESH_SKEW_SECS : i64 = 30 ;
22+ const REMOTE_CONTROL_SERVER_TOKEN_REFRESH_SKEW_SECS : i64 = 5 * 60 ;
3023
3124const REQUEST_ID_HEADER : & str = "x-request-id" ;
3225const OAI_REQUEST_ID_HEADER : & str = "x-oai-request-id" ;
3326const CF_RAY_HEADER : & str = "cf-ray" ;
34- pub ( super ) const REMOTE_CONTROL_INSTALLATION_ID_HEADER : & str = "x-codex-installation-id" ;
3527
3628#[ derive( Debug , Clone , PartialEq , Eq ) ]
3729pub ( super ) struct RemoteControlEnrollment {
@@ -42,14 +34,24 @@ pub(super) struct RemoteControlEnrollment {
4234 pub ( super ) server_name : String ,
4335 pub ( super ) remote_control_token : Option < String > ,
4436 pub ( super ) expires_at : Option < OffsetDateTime > ,
37+ pub ( super ) next_refresh_at : Option < OffsetDateTime > ,
38+ }
39+
40+ #[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
41+ pub ( super ) enum RemoteControlServerTokenRefreshRequirement {
42+ Required ,
43+ Proactive ,
44+ NotNeeded ,
4545}
4646
4747impl RemoteControlEnrollment {
4848 pub ( super ) async fn start_pairing (
4949 & self ,
5050 request : StartRemoteControlPairingRequest ,
5151 ) -> io:: Result < RemoteControlPairingStartResponse > {
52- if self . should_refresh_server_token ( ) {
52+ if self . server_token_refresh_requirement ( )
53+ == RemoteControlServerTokenRefreshRequirement :: Required
54+ {
5355 return Err ( pairing_unavailable_error ( ) ) ;
5456 }
5557 let remote_control_token = self
@@ -142,7 +144,9 @@ impl RemoteControlEnrollment {
142144 & self ,
143145 request : RemoteControlPairingStatusRequest ,
144146 ) -> io:: Result < RemoteControlPairingStatusResponse > {
145- if self . should_refresh_server_token ( ) {
147+ if self . server_token_refresh_requirement ( )
148+ == RemoteControlServerTokenRefreshRequirement :: Required
149+ {
146150 return Err ( pairing_unavailable_error ( ) ) ;
147151 }
148152 let remote_control_token = self
@@ -201,13 +205,35 @@ impl RemoteControlEnrollment {
201205 } )
202206 }
203207
208+ pub ( super ) fn server_token_refresh_requirement (
209+ & self ,
210+ ) -> RemoteControlServerTokenRefreshRequirement {
211+ self . server_token_refresh_requirement_at ( OffsetDateTime :: now_utc ( ) )
212+ }
213+
204214 pub ( super ) fn should_refresh_server_token ( & self ) -> bool {
205- self . remote_control_token . is_none ( )
206- || self . expires_at . is_none_or ( |expires_at| {
207- expires_at. unix_timestamp ( )
208- <= OffsetDateTime :: now_utc ( ) . unix_timestamp ( )
209- + REMOTE_CONTROL_SERVER_TOKEN_REFRESH_SKEW_SECS
210- } )
215+ self . server_token_refresh_requirement ( )
216+ != RemoteControlServerTokenRefreshRequirement :: NotNeeded
217+ }
218+
219+ pub ( super ) fn server_token_refresh_requirement_at (
220+ & self ,
221+ now : OffsetDateTime ,
222+ ) -> RemoteControlServerTokenRefreshRequirement {
223+ let Some ( expires_at) = self . remote_control_token . as_ref ( ) . and ( self . expires_at ) else {
224+ return RemoteControlServerTokenRefreshRequirement :: Required ;
225+ } ;
226+ if expires_at <= now {
227+ return RemoteControlServerTokenRefreshRequirement :: Required ;
228+ }
229+ if expires_at > now + time:: Duration :: seconds ( REMOTE_CONTROL_SERVER_TOKEN_REFRESH_SKEW_SECS )
230+ || self
231+ . next_refresh_at
232+ . is_some_and ( |next_refresh_at| next_refresh_at > now)
233+ {
234+ return RemoteControlServerTokenRefreshRequirement :: NotNeeded ;
235+ }
236+ RemoteControlServerTokenRefreshRequirement :: Proactive
211237 }
212238
213239 pub ( super ) fn clear_server_token ( & mut self ) {
@@ -267,6 +293,7 @@ pub(super) async fn load_persisted_remote_control_enrollment(
267293 server_name : enrollment. server_name ,
268294 remote_control_token : None ,
269295 expires_at : None ,
296+ next_refresh_at : None ,
270297 } ) )
271298 }
272299 None => {
@@ -398,164 +425,12 @@ pub(crate) fn format_headers(headers: &HeaderMap) -> String {
398425 format ! ( "request-id: {request_id_str}, cf-ray: {cf_ray_str}" )
399426}
400427
401- pub ( super ) async fn enroll_remote_control_server (
402- remote_control_target : & RemoteControlTarget ,
403- auth : & RemoteControlConnectionAuth ,
404- installation_id : & str ,
405- server_name : & str ,
406- ) -> io:: Result < RemoteControlEnrollment > {
407- let enroll_url = & remote_control_target. enroll_url ;
408- let request = EnrollRemoteServerRequest {
409- name : server_name. to_string ( ) ,
410- os : std:: env:: consts:: OS ,
411- arch : std:: env:: consts:: ARCH ,
412- app_server_version : env ! ( "CARGO_PKG_VERSION" ) ,
413- installation_id : installation_id. to_string ( ) ,
414- } ;
415- let enrollment_response = send_remote_control_server_request :: < _ , EnrollRemoteServerResponse > (
416- enroll_url,
417- auth,
418- installation_id,
419- & request,
420- "enroll" ,
421- "server enrollment" ,
422- )
423- . await ?;
424- let mut enrollment = RemoteControlEnrollment {
425- remote_control_target : remote_control_target. clone ( ) ,
426- account_id : auth. account_id . clone ( ) ,
427- environment_id : enrollment_response. environment_id ,
428- server_id : enrollment_response. server_id ,
429- server_name : server_name. to_string ( ) ,
430- remote_control_token : None ,
431- expires_at : None ,
432- } ;
433- update_remote_control_server_token (
434- & mut enrollment,
435- enroll_url,
436- enrollment_response. remote_control_token ,
437- enrollment_response. expires_at ,
438- ) ?;
439- Ok ( enrollment)
440- }
441-
442- pub ( super ) async fn refresh_remote_control_server (
443- auth : & RemoteControlConnectionAuth ,
444- installation_id : & str ,
445- enrollment : & mut RemoteControlEnrollment ,
446- ) -> io:: Result < ( ) > {
447- let refresh_url = enrollment. remote_control_target . refresh_url . clone ( ) ;
448- let request = RefreshRemoteServerRequest {
449- server_id : enrollment. server_id . clone ( ) ,
450- installation_id : installation_id. to_string ( ) ,
451- } ;
452- let refreshed = send_remote_control_server_request :: < _ , EnrollRemoteServerResponse > (
453- & refresh_url,
454- auth,
455- installation_id,
456- & request,
457- "refresh" ,
458- "server refresh" ,
459- )
460- . await ?;
461- if refreshed. server_id != enrollment. server_id
462- || refreshed. environment_id != enrollment. environment_id
463- {
464- return Err ( io:: Error :: other ( format ! (
465- "remote control server refresh returned mismatched enrollment: expected server_id={}, environment_id={}; got server_id={}, environment_id={}" ,
466- enrollment. server_id,
467- enrollment. environment_id,
468- refreshed. server_id,
469- refreshed. environment_id
470- ) ) ) ;
471- }
472-
473- update_remote_control_server_token (
474- enrollment,
475- & refresh_url,
476- refreshed. remote_control_token ,
477- refreshed. expires_at ,
478- )
479- }
480-
481- async fn send_remote_control_server_request < Request , Response > (
482- url : & str ,
483- auth : & RemoteControlConnectionAuth ,
484- installation_id : & str ,
485- request : & Request ,
486- action : & str ,
487- response_kind : & str ,
488- ) -> io:: Result < Response >
489- where
490- Request : Serialize ,
491- Response : DeserializeOwned ,
492- {
493- let client = build_reqwest_client ( ) ;
494- let auth_headers = auth. request_headers ( ) ?;
495- let response = client
496- . post ( url)
497- . timeout ( REMOTE_CONTROL_ENROLL_TIMEOUT )
498- . headers ( auth_headers)
499- . header ( REMOTE_CONTROL_INSTALLATION_ID_HEADER , installation_id)
500- . json ( request)
501- . send ( )
502- . await
503- . map_err ( |err| {
504- io:: Error :: other ( format ! (
505- "failed to {action} remote control server at `{url}`: {err}"
506- ) )
507- } ) ?;
508- let headers = response. headers ( ) . clone ( ) ;
509- let status = response. status ( ) ;
510- let body = response. bytes ( ) . await . map_err ( |err| {
511- io:: Error :: other ( format ! (
512- "failed to read remote control {response_kind} response from `{url}`: {err}"
513- ) )
514- } ) ?;
515- let body_preview = preview_remote_control_response_body ( & body) ;
516- if !status. is_success ( ) {
517- let headers_str = format_headers ( & headers) ;
518- let error_kind = match status. as_u16 ( ) {
519- 401 | 403 => ErrorKind :: PermissionDenied ,
520- 404 => ErrorKind :: NotFound ,
521- _ => ErrorKind :: Other ,
522- } ;
523- return Err ( io:: Error :: new (
524- error_kind,
525- format ! (
526- "remote control {response_kind} failed at `{url}`: HTTP {status}, {headers_str}, body: {body_preview}"
527- ) ,
528- ) ) ;
529- }
530-
531- serde_json:: from_slice :: < Response > ( & body) . map_err ( |err| {
532- let headers_str = format_headers ( & headers) ;
533- io:: Error :: other ( format ! (
534- "failed to parse remote control {response_kind} response from `{url}`: HTTP {status}, {headers_str}, body: {body_preview}, decode error: {err}"
535- ) )
536- } )
537- }
538-
539- fn update_remote_control_server_token (
540- enrollment : & mut RemoteControlEnrollment ,
541- url : & str ,
542- token : String ,
543- expires_at : String ,
544- ) -> io:: Result < ( ) > {
545- let expires_at = OffsetDateTime :: parse ( & expires_at, & Rfc3339 ) . map_err ( |err| {
546- io:: Error :: other ( format ! (
547- "failed to parse remote control server token expiry from `{url}`: {err}"
548- ) )
549- } ) ?;
550- enrollment. remote_control_token = Some ( token) ;
551- enrollment. expires_at = Some ( expires_at) ;
552- Ok ( ( ) )
553- }
554-
555428#[ cfg( test) ]
556429mod tests {
557430 use super :: * ;
431+ use crate :: transport:: remote_control:: auth:: RemoteControlConnectionAuth ;
558432 use crate :: transport:: remote_control:: protocol:: normalize_remote_control_url;
433+ use crate :: transport:: remote_control:: server_api:: enroll_remote_control_server;
559434 use codex_state:: StateRuntime ;
560435 use pretty_assertions:: assert_eq;
561436 use serde_json:: json;
@@ -575,28 +450,6 @@ mod tests {
575450 . expect ( "state runtime should initialize" )
576451 }
577452
578- #[ test]
579- fn remote_control_enrollment_refreshes_server_token_before_expiry ( ) {
580- let expires_soon = RemoteControlEnrollment {
581- remote_control_target : normalize_remote_control_url ( "http://localhost/backend-api/" )
582- . expect ( "target should normalize" ) ,
583- account_id : "account-a" . to_string ( ) ,
584- environment_id : "env_first" . to_string ( ) ,
585- server_id : "srv_e_first" . to_string ( ) ,
586- server_name : "first-server" . to_string ( ) ,
587- remote_control_token : Some ( "expires-soon" . to_string ( ) ) ,
588- expires_at : Some ( OffsetDateTime :: now_utc ( ) + time:: Duration :: seconds ( 29 ) ) ,
589- } ;
590- let expires_later = RemoteControlEnrollment {
591- expires_at : Some ( OffsetDateTime :: now_utc ( ) + time:: Duration :: seconds ( 31 ) ) ,
592- remote_control_token : Some ( "expires-later" . to_string ( ) ) ,
593- ..expires_soon. clone ( )
594- } ;
595-
596- assert ! ( expires_soon. should_refresh_server_token( ) ) ;
597- assert ! ( !expires_later. should_refresh_server_token( ) ) ;
598- }
599-
600453 #[ test]
601454 fn preview_remote_control_response_body_redacts_server_token ( ) {
602455 assert_eq ! (
@@ -630,6 +483,7 @@ mod tests {
630483 server_name : "first-server" . to_string ( ) ,
631484 remote_control_token : None ,
632485 expires_at : None ,
486+ next_refresh_at : None ,
633487 } ;
634488 let second_enrollment = RemoteControlEnrollment {
635489 remote_control_target : second_target. clone ( ) ,
@@ -639,6 +493,7 @@ mod tests {
639493 server_name : "second-server" . to_string ( ) ,
640494 remote_control_token : None ,
641495 expires_at : None ,
496+ next_refresh_at : None ,
642497 } ;
643498
644499 update_persisted_remote_control_enrollment (
@@ -714,6 +569,7 @@ mod tests {
714569 server_name : "first-server" . to_string ( ) ,
715570 remote_control_token : None ,
716571 expires_at : None ,
572+ next_refresh_at : None ,
717573 } ;
718574 let second_enrollment = RemoteControlEnrollment {
719575 remote_control_target : second_target. clone ( ) ,
@@ -723,6 +579,7 @@ mod tests {
723579 server_name : "second-server" . to_string ( ) ,
724580 remote_control_token : None ,
725581 expires_at : None ,
582+ next_refresh_at : None ,
726583 } ;
727584
728585 update_persisted_remote_control_enrollment (
0 commit comments