2323use std:: net:: SocketAddr ;
2424use std:: sync:: Arc ;
2525
26- use axum:: { routing:: get, Router } ;
26+ use axum:: { extract :: State , routing:: { get, post } , Json , Router } ;
2727use clap:: Parser ;
2828use tokio:: io:: { AsyncReadExt , AsyncWriteExt } ;
2929use tokio:: net:: { TcpListener , TcpStream } ;
3030use tokio:: sync:: { Mutex , RwLock } ;
31- use tracing:: { error, info, warn, Level } ;
31+ use tracing:: { debug , error, info, warn, Level } ;
3232use tracing_subscriber:: FmtSubscriber ;
3333
3434use wharf_core:: config:: { ConfigLoader , YachtAgentConfig } ;
3535use wharf_core:: db_policy:: { DatabasePolicy , PolicyEngine , QueryAction } ;
36+ use wharf_core:: mooring:: {
37+ self , CommitRequest , CommitResponse , MooringInitRequest , MooringInitResponse ,
38+ MooringSession , SessionState , VerifyRequest , VerifyResponse , YachtStatus ,
39+ MOORING_PROTOCOL_VERSION ,
40+ } ;
3641use wharf_core:: types:: HeaderPolicy ;
3742
3843mod ebpf;
@@ -150,6 +155,15 @@ struct AgentState {
150155 /// The expected filesystem hashes (from Wharf)
151156 integrity_hashes : std:: collections:: HashMap < String , String > ,
152157
158+ /// Active mooring sessions
159+ mooring_sessions : std:: collections:: HashMap < String , MooringSession > ,
160+
161+ /// Allowed Wharf public keys (Ed25519, hex-encoded)
162+ allowed_wharf_keys : Vec < String > ,
163+
164+ /// Last successful mooring timestamp
165+ last_mooring_time : Option < u64 > ,
166+
153167 /// Statistics
154168 queries_allowed : u64 ,
155169 queries_blocked : u64 ,
@@ -162,10 +176,33 @@ impl AgentState {
162176 header_policy : HeaderPolicy :: default ( ) ,
163177 moored : false ,
164178 integrity_hashes : std:: collections:: HashMap :: new ( ) ,
179+ mooring_sessions : std:: collections:: HashMap :: new ( ) ,
180+ allowed_wharf_keys : vec ! [ ] ,
181+ last_mooring_time : None ,
165182 queries_allowed : 0 ,
166183 queries_blocked : 0 ,
167184 }
168185 }
186+
187+ /// Check if a Wharf public key is allowed
188+ fn is_wharf_key_allowed ( & self , pubkey : & str ) -> bool {
189+ // In development mode, accept any key if no keys are configured
190+ if self . allowed_wharf_keys . is_empty ( ) {
191+ return true ;
192+ }
193+ self . allowed_wharf_keys . contains ( & pubkey. to_string ( ) )
194+ }
195+
196+ /// Get the current yacht status
197+ fn get_status ( & self ) -> YachtStatus {
198+ YachtStatus {
199+ ready : true ,
200+ load : 0 , // TODO: Calculate actual load
201+ connections : 0 , // TODO: Track connections
202+ last_mooring : self . last_mooring_time ,
203+ not_ready_reason : None ,
204+ }
205+ }
169206}
170207
171208// =============================================================================
@@ -339,7 +376,11 @@ async fn main() -> anyhow::Result<()> {
339376 let mut app = Router :: new ( )
340377 . route ( "/health" , get ( health_check) )
341378 . route ( "/status" , get ( status) )
342- . route ( "/stats" , get ( stats) ) ;
379+ . route ( "/stats" , get ( stats) )
380+ // Mooring protocol endpoints
381+ . route ( "/mooring/init" , post ( mooring_init) )
382+ . route ( "/mooring/verify" , post ( mooring_verify) )
383+ . route ( "/mooring/commit" , post ( mooring_commit) ) ;
343384
344385 // Add metrics endpoint if enabled
345386 if args. metrics_enabled {
@@ -594,6 +635,169 @@ yacht_integrity_status 1
594635 )
595636}
596637
638+ // =============================================================================
639+ // MOORING ENDPOINTS
640+ // =============================================================================
641+
642+ /// Type alias for shared state
643+ type SharedState = Arc < RwLock < AgentState > > ;
644+
645+ /// Initialize a mooring session
646+ async fn mooring_init (
647+ State ( state) : State < SharedState > ,
648+ Json ( request) : Json < MooringInitRequest > ,
649+ ) -> Json < serde_json:: Value > {
650+ debug ! ( "Mooring init request from key: {}" , request. wharf_pubkey) ;
651+
652+ // Version check
653+ if request. version != MOORING_PROTOCOL_VERSION {
654+ return Json ( serde_json:: json!( {
655+ "error" : "version_mismatch" ,
656+ "expected" : MOORING_PROTOCOL_VERSION ,
657+ "actual" : request. version
658+ } ) ) ;
659+ }
660+
661+ let mut state_guard = state. write ( ) . await ;
662+
663+ // Key authorization check
664+ if !state_guard. is_wharf_key_allowed ( & request. wharf_pubkey ) {
665+ warn ! ( "Mooring attempt from unknown key: {}" , request. wharf_pubkey) ;
666+ return Json ( serde_json:: json!( {
667+ "error" : "unauthorized" ,
668+ "message" : "Wharf public key not in allow list"
669+ } ) ) ;
670+ }
671+
672+ // TODO: Verify Ed25519 signature
673+
674+ // Create session
675+ let session_id = mooring:: generate_session_id ( ) ;
676+ let now = mooring:: current_timestamp ( ) ;
677+ let expires_at = now + 3600 ; // 1 hour session
678+
679+ let session = MooringSession {
680+ session_id : session_id. clone ( ) ,
681+ wharf_pubkey : request. wharf_pubkey . clone ( ) ,
682+ created_at : now,
683+ expires_at,
684+ requested_layers : request. layers . clone ( ) ,
685+ committed_layers : vec ! [ ] ,
686+ state : SessionState :: Initiated ,
687+ } ;
688+
689+ state_guard. mooring_sessions . insert ( session_id. clone ( ) , session) ;
690+ let yacht_status = state_guard. get_status ( ) ;
691+ drop ( state_guard) ;
692+
693+ info ! ( "Mooring session initiated: {}" , session_id) ;
694+
695+ let response = MooringInitResponse {
696+ session_id,
697+ version : MOORING_PROTOCOL_VERSION . to_string ( ) ,
698+ yacht_pubkey : "TODO_YACHT_PUBKEY" . to_string ( ) , // TODO: Generate/load yacht keypair
699+ accepted_layers : request. layers ,
700+ expires_at,
701+ status : yacht_status,
702+ } ;
703+
704+ Json ( serde_json:: to_value ( response) . unwrap ( ) )
705+ }
706+
707+ /// Verify a layer against expected manifest
708+ async fn mooring_verify (
709+ State ( state) : State < SharedState > ,
710+ Json ( request) : Json < VerifyRequest > ,
711+ ) -> Json < serde_json:: Value > {
712+ debug ! ( "Mooring verify request for session: {}" , request. session_id) ;
713+
714+ let state_guard = state. read ( ) . await ;
715+
716+ // Find session
717+ let session = match state_guard. mooring_sessions . get ( & request. session_id ) {
718+ Some ( s) => s,
719+ None => {
720+ return Json ( serde_json:: json!( {
721+ "error" : "session_not_found" ,
722+ "session_id" : request. session_id
723+ } ) ) ;
724+ }
725+ } ;
726+
727+ // Check session validity
728+ let now = mooring:: current_timestamp ( ) ;
729+ if now > session. expires_at {
730+ return Json ( serde_json:: json!( {
731+ "error" : "session_expired" ,
732+ "session_id" : request. session_id
733+ } ) ) ;
734+ }
735+
736+ drop ( state_guard) ;
737+
738+ // TODO: Actually verify files on disk against manifest
739+ // For MVP, just return success
740+ let response = VerifyResponse {
741+ verified : true ,
742+ matched_files : request. expected_manifest . file_count ,
743+ differing_files : vec ! [ ] ,
744+ missing_files : vec ! [ ] ,
745+ extra_files : vec ! [ ] ,
746+ } ;
747+
748+ Json ( serde_json:: to_value ( response) . unwrap ( ) )
749+ }
750+
751+ /// Commit transferred layers
752+ async fn mooring_commit (
753+ State ( state) : State < SharedState > ,
754+ Json ( request) : Json < CommitRequest > ,
755+ ) -> Json < serde_json:: Value > {
756+ debug ! ( "Mooring commit request for session: {}" , request. session_id) ;
757+
758+ let mut state_guard = state. write ( ) . await ;
759+
760+ // Find and update session
761+ let session = match state_guard. mooring_sessions . get_mut ( & request. session_id ) {
762+ Some ( s) => s,
763+ None => {
764+ return Json ( serde_json:: json!( {
765+ "error" : "session_not_found" ,
766+ "session_id" : request. session_id
767+ } ) ) ;
768+ }
769+ } ;
770+
771+ // Check session validity
772+ let now = mooring:: current_timestamp ( ) ;
773+ if now > session. expires_at {
774+ return Json ( serde_json:: json!( {
775+ "error" : "session_expired" ,
776+ "session_id" : request. session_id
777+ } ) ) ;
778+ }
779+
780+ // Update session state
781+ session. state = SessionState :: Committed ;
782+ session. committed_layers = request. layers . clone ( ) ;
783+
784+ // Update agent state
785+ state_guard. moored = true ;
786+ state_guard. last_mooring_time = Some ( now) ;
787+
788+ info ! ( "Mooring commit successful for session: {}" , request. session_id) ;
789+
790+ let response = CommitResponse {
791+ success : true ,
792+ committed_layers : request. layers ,
793+ files_modified : 0 , // TODO: Track actual changes
794+ snapshot_id : Some ( format ! ( "snap-{}" , now) ) ,
795+ error : None ,
796+ } ;
797+
798+ Json ( serde_json:: to_value ( response) . unwrap ( ) )
799+ }
800+
597801// =============================================================================
598802// FIREWALL SETUP
599803// =============================================================================
0 commit comments