diff --git a/cmd/client/main.go b/cmd/client/main.go new file mode 100644 index 0000000..3941c2f --- /dev/null +++ b/cmd/client/main.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net" + "time" + + "hash-of-wisdom/internal/pow/challenge" + "hash-of-wisdom/internal/pow/solver" + "hash-of-wisdom/internal/protocol" +) + +func main() { + serverAddr := flag.String("addr", "localhost:8080", "server address") + flag.Parse() + + fmt.Printf("Connecting to Word of Wisdom server at %s\n", *serverAddr) + + // Step 1: Get challenge + challengeResp, err := requestChallenge(*serverAddr) + if err != nil { + log.Fatalf("Failed to get challenge: %v", err) + } + + fmt.Printf("Received challenge with difficulty %d\n", challengeResp.Challenge.Difficulty) + + // Step 2: Solve challenge + fmt.Println("Solving challenge...") + start := time.Now() + + s := solver.NewSolver() + solution, err := s.Solve(context.Background(), challengeResp.Challenge) + if err != nil { + log.Fatalf("Failed to solve challenge: %v", err) + } + + solveTime := time.Since(start) + fmt.Printf("Challenge solved in %v with nonce %d\n", solveTime, solution.Nonce) + + // Step 3: Submit solution and get quote + err = submitSolution(*serverAddr, challengeResp.Challenge, solution.Nonce) + if err != nil { + log.Fatalf("Failed to submit solution: %v", err) + } +} + +func requestChallenge(serverAddr string) (*protocol.ChallengeResponse, error) { + // Connect with timeout + conn, err := net.DialTimeout("tcp", serverAddr, 5*time.Second) + if err != nil { + return nil, fmt.Errorf("failed to connect: %w", err) + } + defer conn.Close() + + // Request challenge + fmt.Println("Requesting challenge...") + challengeReq := &protocol.ChallengeRequest{} + if err := challengeReq.Encode(conn); err != nil { + return nil, fmt.Errorf("failed to send challenge request: %w", err) + } + + // Receive challenge + decoder := protocol.NewMessageDecoder() + msg, err := decoder.Decode(conn) + if err != nil { + return nil, fmt.Errorf("failed to receive challenge: %w", err) + } + + if msg.Type != protocol.ChallengeResponseType { + return nil, fmt.Errorf("unexpected response type: %v", msg.Type) + } + + // Parse challenge + challengeResp := &protocol.ChallengeResponse{} + if err := challengeResp.Decode(msg.PayloadStream); err != nil { + return nil, fmt.Errorf("failed to parse challenge: %w", err) + } + + return challengeResp, nil +} + +func submitSolution(serverAddr string, chall *challenge.Challenge, nonce uint64) error { + // Connect with timeout + conn, err := net.DialTimeout("tcp", serverAddr, 5*time.Second) + if err != nil { + return fmt.Errorf("failed to connect: %w", err) + } + defer conn.Close() + + // Submit solution + solutionReq := &protocol.SolutionRequest{ + Challenge: *chall, + Nonce: nonce, + } + + fmt.Println("Submitting solution...") + if err := solutionReq.Encode(conn); err != nil { + return fmt.Errorf("failed to send solution: %w", err) + } + + // Receive quote or error + decoder := protocol.NewMessageDecoder() + msg, err := decoder.Decode(conn) + if err != nil { + return fmt.Errorf("failed to receive response: %w", err) + } + + switch msg.Type { + case protocol.QuoteResponseType: + solutionResp := &protocol.SolutionResponse{} + if err := solutionResp.Decode(msg.PayloadStream); err != nil { + return fmt.Errorf("failed to parse quote: %w", err) + } + fmt.Printf("\nQuote received:\n\"%s\"\n— %s\n", solutionResp.Quote.Text, solutionResp.Quote.Author) + case protocol.ErrorResponseType: + errorResp := &protocol.ErrorResponse{} + if err := errorResp.Decode(msg.PayloadStream); err != nil { + fmt.Println("Error: Contact administrator") + return nil + } + if errorResp.Code == protocol.ErrServerError { + fmt.Println("Error: Contact administrator") + } else { + fmt.Printf("Error: %s\n", errorResp.Message) + } + default: + return fmt.Errorf("unexpected response type: %v", msg.Type) + } + + return nil +}