@@ -3307,12 +3307,146 @@ static void test_wolfSSH_SCP_ReKey_ToServer_NonBlock(void)
33073307 scp_rekey_test (1 , 1 );
33083308}
33093309
3310+ /* A send callback that returns 0 bytes on its first
3311+ * WOLFSSH_SCP_SINGLE_FILE_REQUEST (metadata now, data on the following call)
3312+ * must not make the server send the file header twice. The offset is static
3313+ * because the one-shot echoserver runs a single transfer. */
3314+ static byte scpZeroFirstData [SCP_REKEY_FILE_SZ ];
3315+ static word32 scpZeroFirstOffset ;
3316+
3317+ static int scpSendZeroFirst (WOLFSSH * ssh , int state , const char * peerRequest ,
3318+ char * fileName , word32 fileNameSz , word64 * mTime , word64 * aTime ,
3319+ int * fileMode , word32 fileOffset , word32 * totalFileSz ,
3320+ byte * buf , word32 bufSz , void * ctx )
3321+ {
3322+ word32 remain , n ;
3323+
3324+ (void )ssh ;
3325+ (void )peerRequest ;
3326+ (void )fileOffset ;
3327+ (void )ctx ;
3328+
3329+ switch (state ) {
3330+ case WOLFSSH_SCP_NEW_REQUEST :
3331+ return WS_SUCCESS ;
3332+
3333+ case WOLFSSH_SCP_SINGLE_FILE_REQUEST :
3334+ /* fill metadata, but hand back zero data bytes on this first call */
3335+ WSTRNCPY (fileName , "scp_hdr_zero.txt" , fileNameSz );
3336+ if (totalFileSz != NULL ) * totalFileSz = SCP_REKEY_FILE_SZ ;
3337+ if (mTime != NULL ) * mTime = 0 ;
3338+ if (aTime != NULL ) * aTime = 0 ;
3339+ if (fileMode != NULL ) * fileMode = 0644 ;
3340+ scpZeroFirstOffset = 0 ;
3341+ return 0 ;
3342+
3343+ case WOLFSSH_SCP_CONTINUE_FILE_TRANSFER :
3344+ remain = SCP_REKEY_FILE_SZ - scpZeroFirstOffset ;
3345+ if (remain == 0 )
3346+ return WS_SCP_COMPLETE ;
3347+ n = (remain < bufSz ) ? remain : bufSz ;
3348+ WMEMCPY (buf , scpZeroFirstData + scpZeroFirstOffset , n );
3349+ scpZeroFirstOffset += n ;
3350+ return (int )n ;
3351+
3352+ default :
3353+ return WS_SCP_ABORT ;
3354+ }
3355+ }
3356+
3357+ static void test_wolfSSH_SCP_SendZeroFirst (void )
3358+ {
3359+ func_args ser ;
3360+ tcp_ready ready ;
3361+ int argsCount ;
3362+ int ret ;
3363+ word32 i ;
3364+ WS_SOCKET_T clientFd ;
3365+ #ifdef USE_WINDOWS_API
3366+ DWORD rcvTimeout = 20000 ;
3367+ #else
3368+ struct timeval rcvTimeout ;
3369+ #endif
3370+ const char * args [6 ];
3371+ WOLFSSH_CTX * ctx = NULL ;
3372+ WOLFSSH * ssh = NULL ;
3373+ const char * srcName = "./scp_hdr_src.txt" ;
3374+ const char * fromName = "./scp_hdr_from.txt" ;
3375+ char srcBuf [32 ];
3376+ char fromBuf [32 ];
3377+ char cmd [64 ];
3378+ THREAD_TYPE serThread ;
3379+
3380+ WSTRNCPY (srcBuf , srcName , sizeof (srcBuf ));
3381+ WSTRNCPY (fromBuf , fromName , sizeof (fromBuf ));
3382+
3383+ for (i = 0 ; i < SCP_REKEY_FILE_SZ ; i ++ )
3384+ scpZeroFirstData [i ] = (byte )((i * 7 + 1 ) & 0xff );
3385+ /* The on-disk file only satisfies the server's base-path parsing; the
3386+ * custom callback supplies the actual bytes, so a duplicated header shows
3387+ * up as a content mismatch below rather than a missing file. */
3388+ AssertIntEQ (scpWriteTestFile (srcName , scpZeroFirstData , SCP_REKEY_FILE_SZ ),
3389+ 0 );
3390+
3391+ WMEMSET (& ser , 0 , sizeof (func_args ));
3392+ argsCount = 0 ;
3393+ args [argsCount ++ ] = "." ;
3394+ args [argsCount ++ ] = "-1" ;
3395+ args [argsCount ++ ] = "-p" ;
3396+ args [argsCount ++ ] = "0" ;
3397+ ser .argv = (char * * )args ;
3398+ ser .argc = argsCount ;
3399+ ser .signal = & ready ;
3400+ ser .scp_send = scpSendZeroFirst ;
3401+ InitTcpReady (ser .signal );
3402+ ThreadStart (echoserver_test , (void * )& ser , & serThread );
3403+ WaitTcpReady (& ready );
3404+
3405+ WSNPRINTF (cmd , sizeof (cmd ), "scp -f %s" , srcName );
3406+ scp_client_connect (& ctx , & ssh , ready .port , cmd );
3407+ AssertNotNull (ctx );
3408+ AssertNotNull (ssh );
3409+
3410+ /* bound the recv so a regression fails the match assert below, not CI */
3411+ clientFd = wolfSSH_get_fd (ssh );
3412+ #ifdef USE_WINDOWS_API
3413+ (void )setsockopt (clientFd , SOL_SOCKET , SO_RCVTIMEO ,
3414+ (const char * )& rcvTimeout , sizeof (rcvTimeout ));
3415+ #else
3416+ rcvTimeout .tv_sec = 20 ;
3417+ rcvTimeout .tv_usec = 0 ;
3418+ (void )setsockopt (clientFd , SOL_SOCKET , SO_RCVTIMEO ,
3419+ & rcvTimeout , sizeof (rcvTimeout ));
3420+ #endif
3421+
3422+ ret = wolfSSH_SCP_from (ssh , srcBuf , fromBuf );
3423+ AssertIntEQ (ret , WS_SUCCESS );
3424+
3425+ ret = wolfSSH_shutdown (ssh );
3426+ (void )ret ;
3427+
3428+ clientFd = wolfSSH_get_fd (ssh );
3429+ WCLOSESOCKET (clientFd );
3430+ wolfSSH_free (ssh );
3431+ wolfSSH_CTX_free (ctx );
3432+ ThreadJoin (serThread );
3433+ FreeTcpReady (& ready );
3434+
3435+ /* a duplicate header would corrupt the stream; an exact match proves the
3436+ * header was sent once */
3437+ AssertIntEQ (scpFilesMatch (fromName , scpZeroFirstData , SCP_REKEY_FILE_SZ ), 0 );
3438+
3439+ WREMOVE (NULL , srcName );
3440+ WREMOVE (NULL , fromName );
3441+ }
3442+
33103443#else /* WOLFSSH_SCP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED &&
33113444 * !NO_FILESYSTEM && !WOLFSSH_SCP_USER_CALLBACKS && !WOLFSSH_ZEPHYR */
33123445static void test_wolfSSH_SCP_ReKey (void ) { ; }
33133446static void test_wolfSSH_SCP_ReKey_NonBlock (void ) { ; }
33143447static void test_wolfSSH_SCP_ReKey_ToServer (void ) { ; }
33153448static void test_wolfSSH_SCP_ReKey_ToServer_NonBlock (void ) { ; }
3449+ static void test_wolfSSH_SCP_SendZeroFirst (void ) { ; }
33163450#endif
33173451
33183452
@@ -4346,6 +4480,7 @@ int wolfSSH_ApiTest(int argc, char** argv)
43464480 test_wolfSSH_SCP_ReKey_NonBlock ();
43474481 test_wolfSSH_SCP_ReKey_ToServer ();
43484482 test_wolfSSH_SCP_ReKey_ToServer_NonBlock ();
4483+ test_wolfSSH_SCP_SendZeroFirst ();
43494484
43504485 /* SFTP tests */
43514486 test_wolfSSH_SFTP_SendReadPacket ();
0 commit comments