package exchange

import (
	
	
	

	
	

	
	
	
	
)

// Run runs client-side flow.
func ( ClientExchange) ( context.Context) (ClientExchangeResult, error) {
	// 1. DH exchange initiation.
	,  := crypto.RandInt128(.rand)
	if  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "client nonce generation")
	}
	 := new(bin.Buffer)

	.log.Debug("Sending ReqPqMultiRequest")
	if  := .writeUnencrypted(, , &mt.ReqPqMultiRequest{Nonce: });  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "write ReqPqMultiRequest")
	}

	// 2. Server sends response of the form
	// resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector long = ResPQ;
	var  mt.ResPQ
	if  := .readUnencrypted(, , &);  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "read ResPQ response")
	}
	.log.Debug("Received server ResPQ")
	if .Nonce !=  {
		return ClientExchangeResult{}, errors.New("ResPQ nonce mismatch")
	}
	 := .ServerNonce

	// Selecting first public key that match fingerprint.
	var  PublicKey
:
	for ,  := range .keys {
		 := .Fingerprint()

		for ,  := range .ServerPublicKeyFingerprints {
			if  ==  {
				 = 
				break 
			}
		}
	}
	if .Zero() {
		return ClientExchangeResult{}, ErrKeyFingerprintNotFound
	}

	// The pq is a representation of a natural number (in binary big endian format).
	// SetBytes is also big endian.
	 := big.NewInt(0).SetBytes(.Pq)
	// Normally pq is less than or equal to 2^63-1.
	 := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(63), nil)
	if .Cmp() > 0 {
		return ClientExchangeResult{}, errors.New("server provided bad pq")
	}

	 := .clock.Now()
	// 3. Client decomposes pq into prime factors such that p < q.
	// Performing proof of work.
	, ,  := crypto.DecomposePQ(, .rand)
	if  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "decompose pq")
	}
	.log.Debug("PQ decomposing complete", zap.Duration("took", .clock.Now().Sub()))
	// Make a copy of p and q values to reduce allocations.
	 := .Bytes()
	 := .Bytes()

	// 4. Client sends query to server.
	// req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string
	//   public_key_fingerprint:long encrypted_data:string = Server_DH_Params
	,  := crypto.RandInt256(.rand)
	if  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "generate new nonce")
	}

	var  []byte
	 := &mt.PQInnerDataDC{
		Pq:          .Pq,
		Nonce:       ,
		NewNonce:    ,
		ServerNonce: ,
		P:           ,
		Q:           ,
		DC:          .dc,
	}
	.Reset()
	if  := .Encode();  != nil {
		return ClientExchangeResult{}, 
	}

	// `encrypted_data := RSA_PAD(data, server_public_key);`
	,  := crypto.RSAPad(.Buf, .RSA, .rand)
	if  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "encrypted_data generation")
	}

	 = 

	 := &mt.ReqDHParamsRequest{
		Nonce:                ,
		ServerNonce:          ,
		P:                    ,
		Q:                    ,
		PublicKeyFingerprint: .Fingerprint(),
		EncryptedData:        ,
	}
	.log.Debug("Sending ReqDHParamsRequest")
	if  := .writeUnencrypted(, , );  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "write ReqDHParamsRequest")
	}

	// 5. Server responds with Server_DH_Params.
	if  := .conn.Recv(, );  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "read ServerDHParams message")
	}
	.log.Debug("Received server ServerDHParams")

	var  proto.UnencryptedMessage
	if  := .Decode();  != nil {
		return ClientExchangeResult{}, errors.Wrap(, "decode ServerDHParams message")
	}

	.ResetTo(.MessageData)
	,  := mt.DecodeServerDHParams()
	if  != nil {
		return ClientExchangeResult{}, 
	}
	switch p := .(type) {
	case *mt.ServerDHParamsOk:
		// Success.
		if .Nonce !=  {
			return ClientExchangeResult{}, errors.New("ServerDHParamsOk nonce mismatch")
		}
		if .ServerNonce !=  {
			return ClientExchangeResult{}, errors.New("ServerDHParamsOk server nonce mismatch")
		}

		,  := crypto.TempAESKeys(.BigInt(), .BigInt())
		// Decrypting inner data.
		,  := crypto.DecryptExchangeAnswer(.EncryptedAnswer, , )
		if  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "exchange answer decrypt")
		}
		.ResetTo()

		 := mt.ServerDHInnerData{}
		if  := .Decode();  != nil {
			return ClientExchangeResult{}, 
		}
		if .Nonce !=  {
			return ClientExchangeResult{}, errors.New("ServerDHInnerData nonce mismatch")
		}
		if .ServerNonce !=  {
			return ClientExchangeResult{}, errors.New("ServerDHInnerData server nonce mismatch")
		}

		 := big.NewInt(0).SetBytes(.DhPrime)
		 := big.NewInt(int64(.G))
		if  := crypto.CheckDH(.G, );  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "check DH params")
		}
		 := big.NewInt(0).SetBytes(.GA)

		// 6. Random number b is computed:
		 := big.NewInt(0).SetBit(big.NewInt(0), crypto.RSAKeyBits, 1)
		,  := rand.Int(.rand, )
		if  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "number b generation")
		}
		// g_b = g^b mod dh_prime
		 := big.NewInt(0).Exp(, , )

		// Checking key exchange parameters.
		if  := crypto.CheckDHParams(, , , );  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "key exchange failed: invalid params")
		}

		 := mt.ClientDHInnerData{
			ServerNonce: .ServerNonce,
			Nonce:       .Nonce,
			GB:          .Bytes(),
			// first attempt
			RetryID: 0,
		}
		.Reset()
		if  := .Encode();  != nil {
			return ClientExchangeResult{}, 
		}
		,  := crypto.EncryptExchangeAnswer(.rand, .Buf, , )
		if  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "exchange answer encrypt")
		}

		 := &mt.SetClientDHParamsRequest{
			Nonce:         ,
			ServerNonce:   .ServerNonce,
			EncryptedData: ,
		}
		.log.Debug("Sending SetClientDHParamsRequest")
		if  := .writeUnencrypted(, , );  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "write SetClientDHParamsRequest")
		}

		// 7. Computing auth_key using formula (g_a)^b mod dh_prime
		 := big.NewInt(0).Exp(, , )

		.Reset()
		if  := .conn.Recv(, );  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "read DhGen message")
		}
		.log.Debug("Received server DhGen")

		if  := .Decode();  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "decode DhGen message")
		}
		.ResetTo(.MessageData)
		,  := mt.DecodeSetClientDHParamsAnswer()
		if  != nil {
			return ClientExchangeResult{}, errors.Wrap(, "decode DhGen answer")
		}
		switch v := .(type) {
		case *mt.DhGenOk: // dh_gen_ok#3bcbf734
			if .Nonce !=  {
				return ClientExchangeResult{}, errors.New("DhGenOk nonce mismatch")
			}
			if .ServerNonce !=  {
				return ClientExchangeResult{}, errors.New("DhGenOk server nonce mismatch")
			}

			var  crypto.Key
			.FillBytes([:])
			 := .ID()

			// Checking received hash.
			 := crypto.NonceHash1(, )
			 := crypto.ServerSalt(, .ServerNonce)

			if  != .NewNonceHash1 {
				return ClientExchangeResult{}, errors.New("key exchange verification failed: hash mismatch")
			}

			// Generating new session id and salt.
			,  := crypto.NewSessionID(.rand)
			if  != nil {
				return ClientExchangeResult{}, 
			}

			return ClientExchangeResult{
				AuthKey:    crypto.AuthKey{Value: , ID: },
				SessionID:  ,
				ServerSalt: ,
			}, nil
		case *mt.DhGenRetry: // dh_gen_retry#46dc1fb9
			return ClientExchangeResult{}, errors.Errorf("retry required: %x", .NewNonceHash2)
		case *mt.DhGenFail: // dh_gen_fail#a69dae02
			return ClientExchangeResult{}, errors.Errorf("dh_hen_fail: %x", .NewNonceHash3)
		default:
			return ClientExchangeResult{}, errors.Errorf("unexpected SetClientDHParamsRequest result %T", )
		}
	case *mt.ServerDHParamsFail:
		return ClientExchangeResult{}, errors.New("server respond with server_DH_params_fail")
	default:
		return ClientExchangeResult{}, errors.Errorf("unexpected ReqDHParamsRequest result %T", )
	}
}