diff --git a/analysis/frequency.go b/analysis/frequency.go new file mode 100644 index 0000000..2791936 --- /dev/null +++ b/analysis/frequency.go @@ -0,0 +1,30 @@ +package analysis + +// scoreText evaluates the likelihood that a given byte slice (input) is +// meaningful or coherent English text. It does so by scoring each byte +// according to its frequency of appearance in English, with a higher score +// indicating a higher likelihood of the input being English. +func ScoreText(input []byte) float64 { + // englishFreq maps English letters and the space character to their + // relative frequencies in English text + englishFreq := map[byte]float64{ + 'a': 8.167, 'b': 1.492, 'c': 2.782, 'd': 4.253, 'e': 12.702, + 'f': 2.228, 'g': 2.015, 'h': 6.094, 'i': 6.966, 'j': 0.153, + 'k': 0.772, 'l': 4.025, 'm': 2.406, 'n': 6.749, 'o': 7.507, + 'p': 1.929, 'q': 0.095, 'r': 5.987, 's': 6.327, 't': 9.056, + 'u': 2.758, 'v': 0.978, 'w': 2.360, 'x': 0.150, 'y': 1.974, + 'z': 0.074, ' ': 13.000, + } + + score := 0.0 + + for _, b := range input { + // If the byte is found in the englishFreq map,add its frequency value + // to the total score. + if val, ok := englishFreq[b]; ok { + score += val + } + } + + return score +} diff --git a/analysis/frequency_test.go b/analysis/frequency_test.go new file mode 100644 index 0000000..e34c68c --- /dev/null +++ b/analysis/frequency_test.go @@ -0,0 +1,27 @@ +package analysis + +import ( + "testing" +) + +func TestScoreText(t *testing.T) { + tests := []struct { + name string + text string + score float64 + }{ + {name: "Valid score", text: "What Do You Want From Me", score: 158.66299999999998}, + {name: "No text", text: "", score: 0.0}, + {name: "Single letter", text: "a", score: 8.167}, + {name: "Space", text: " ", score: 13.000}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ScoreText([]byte(tt.text)) + if got != tt.score { + t.Fatalf("expected: %f, got: %f", tt.score, got) + } + }) + } +} diff --git a/encoding/base64.go b/encoding/base64.go new file mode 100644 index 0000000..d7a472c --- /dev/null +++ b/encoding/base64.go @@ -0,0 +1,19 @@ +package encoding + +import "encoding/base64" + +// Decodes Base64 encoded string. Returns bytes. +func DecodeBase64(encoded string) ([]byte, error) { + return base64.StdEncoding.DecodeString(encoded) +} + +// isBase64Encoded checks if the input string is Base64 encoded. +// This function performs a basic check to see if the input is decodable from Base64. +func isBase64Encoded(input string) bool { + if input == "" { + return false + } + // Attempt to decode the input string from Base64 + _, err := base64.StdEncoding.DecodeString(input) + return err == nil +} diff --git a/encoding/base64_test.go b/encoding/base64_test.go new file mode 100644 index 0000000..83f590a --- /dev/null +++ b/encoding/base64_test.go @@ -0,0 +1,24 @@ +package encoding + +import "testing" + +func TestIsBase64Encoded(t *testing.T) { + tests := []struct { + name string + input string + want bool + }{ + {name: "Valid Base64", input: "dGVzdCBpbnB1dA==", want: true}, + {name: "Invalid Base64", input: "test input", want: false}, + {name: "Empty String", input: "", want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isBase64Encoded(tt.input) + if got != tt.want { + t.Errorf("want: %v, got: %v", tt.want, got) + } + }) + } +} diff --git a/encoding/decode.go b/encoding/decode.go new file mode 100644 index 0000000..ebd84a2 --- /dev/null +++ b/encoding/decode.go @@ -0,0 +1,26 @@ +package encoding + +func Decode(encodedData string) ([]byte, error) { + var verifiedData []byte + var err error + if isHexEncoded(encodedData) { + verifiedData, err = DecodeHex(encodedData) + if err != nil { + return nil, err + } + return verifiedData, nil + } + + if isBase64Encoded(encodedData) { + verifiedData, err = DecodeBase64(encodedData) + if err != nil { + return nil, err + } + return verifiedData, nil + } + + // plaintext, or some other encoded data type not checked for. + verifiedData = []byte(encodedData) + + return verifiedData, nil +} diff --git a/encoding/decode_test.go b/encoding/decode_test.go new file mode 100644 index 0000000..303d591 --- /dev/null +++ b/encoding/decode_test.go @@ -0,0 +1,29 @@ +package encoding + +import "testing" + +func TestDecode(t *testing.T) { + tests := []struct { + name string + encoded string + want string + wantErr bool + }{ + {name: "Hex Encoded Text", encoded: "7465737420696e707574", want: "test input", wantErr: false}, + {name: "Non-Encoded Text", encoded: "not encoded text", want: "not encoded text", wantErr: false}, + {name: "Base64 Encoded Text", encoded: "dGVzdCBpbnB1dA==", want: "test input", wantErr: false}, + {name: "Invalid Hex", encoded: "7465737420696e70757", want: "", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Decode(tt.encoded) + if err == nil && tt.wantErr { + t.Errorf("expected error and didn't get one") + } + if string(got) != tt.want { + t.Errorf("want: %v, got: %v", tt.want, got) + } + }) + } +} diff --git a/encoding/hex.go b/encoding/hex.go new file mode 100644 index 0000000..5d6817a --- /dev/null +++ b/encoding/hex.go @@ -0,0 +1,28 @@ +package encoding + +import ( + "encoding/hex" + "fmt" + "regexp" +) + +// Decodes hex encoded string. Returns bytes. +func DecodeHex(encoded string) ([]byte, error) { + return hex.DecodeString(encoded) +} + +// isHexEncoded checks if the input string is hex encoded. +// This function uses a regex pattern to ensure the string consists only of hexadecimal characters. +func isHexEncoded(input string) bool { + if input == "" { + return false + } + // Hex regex pattern to match valid hexadecimal characters + hexPattern := `^[0-9A-Fa-f]+$` + matched, err := regexp.MatchString(hexPattern, input) + if err != nil { + fmt.Println("Regex match error:", err) + return false + } + return matched +} diff --git a/encoding/hex_test.go b/encoding/hex_test.go new file mode 100644 index 0000000..2da1d21 --- /dev/null +++ b/encoding/hex_test.go @@ -0,0 +1,23 @@ +package encoding + +import "testing" + +func TestIsHexEncoded(t *testing.T) { + tests := []struct { + name string + input string + want bool + }{ + {name: "Valid Hex", input: "7465737420696e707574", want: true}, + {name: "Invalid Hex", input: "test input", want: false}, + {name: "Empty String", input: "", want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isHexEncoded(tt.input) + if got != tt.want { + t.Errorf("want: %v, got: %v", tt.want, got) + } + }) + } +} diff --git a/main.go b/main.go index 081fabb..ee8a880 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,7 @@ import ( "time" "github.com/nronzel/xoracle/pkg/handlers" - limiter "github.com/nronzel/xoracle/pkg/rate_limiter" + mw "github.com/nronzel/xoracle/pkg/middleware" ) func main() { @@ -16,7 +16,7 @@ func main() { mux.HandleFunc("GET /", handlers.HandlerRoot) mux.HandleFunc("POST /decrypt", handlers.HandlerDecrypt) - rl := limiter.NewRateLimiter(1, 3) + rl := mw.NewRateLimiter(1, 3) server := &http.Server{ Addr: ":8080", diff --git a/pkg/decryption/decryption.go b/pkg/decryption/decryption.go index 1059040..07fbf09 100644 --- a/pkg/decryption/decryption.go +++ b/pkg/decryption/decryption.go @@ -2,9 +2,10 @@ package decryption import ( "fmt" - "github.com/nronzel/xoracle/utils" "math" "sort" + + "github.com/nronzel/xoracle/analysis" ) // singleByteXORCipher attempts to decrypt a message that has been XOR'ed @@ -30,7 +31,7 @@ func singleByteXORCipher(encoded []byte) (byte, []byte) { } // Evaluate the decrypted message using a scoring function. - score := utils.ScoreText(decoded) + score := analysis.ScoreText(decoded) // If the current message's score is higher than the highest score found // so far - update maxScore, key, and message with the current values. diff --git a/pkg/decryption/types.go b/pkg/decryption/decryption_types.go similarity index 100% rename from pkg/decryption/types.go rename to pkg/decryption/decryption_types.go diff --git a/pkg/decryption/dc_util.go b/pkg/decryption/score.go similarity index 100% rename from pkg/decryption/dc_util.go rename to pkg/decryption/score.go diff --git a/pkg/decryption/dc_util_test.go b/pkg/decryption/score_test.go similarity index 100% rename from pkg/decryption/dc_util_test.go rename to pkg/decryption/score_test.go diff --git a/pkg/handlers/handlerDecrypt.go b/pkg/handlers/handlerDecrypt.go index 4d5df28..1da8c50 100644 --- a/pkg/handlers/handlerDecrypt.go +++ b/pkg/handlers/handlerDecrypt.go @@ -6,8 +6,8 @@ import ( "net/http" "strings" + en "github.com/nronzel/xoracle/encoding" dc "github.com/nronzel/xoracle/pkg/decryption" - "github.com/nronzel/xoracle/utils" ) func HandlerDecrypt(w http.ResponseWriter, r *http.Request) { @@ -23,7 +23,7 @@ func HandlerDecrypt(w http.ResponseWriter, r *http.Request) { // Checks if data is Base64 or Hex encoded, and decodes, otherwise just // returns the data as is. It's either plaintext, or some other encoding // not checked for. - verifiedData, err := utils.Decode(encodedData) + verifiedData, err := en.Decode(encodedData) if err != nil { w.Header().Set("Content-Type", "text/plaintext") http.Error(w, "problem decoding data", http.StatusInternalServerError) diff --git a/pkg/rate_limiter/limit.go b/pkg/middleware/rate_limit.go similarity index 100% rename from pkg/rate_limiter/limit.go rename to pkg/middleware/rate_limit.go diff --git a/pkg/rate_limiter/limit_test.go b/pkg/middleware/rate_limit_test.go similarity index 100% rename from pkg/rate_limiter/limit_test.go rename to pkg/middleware/rate_limit_test.go diff --git a/scripts/buildmac.sh b/scripts/buildmac.sh new file mode 100755 index 0000000..82c2b26 --- /dev/null +++ b/scripts/buildmac.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o xoracle-macos-amd64 +CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o xoracle-macos-arm64 diff --git a/scripts/buildwindows.sh b/scripts/buildwindows.sh new file mode 100755 index 0000000..cb2e292 --- /dev/null +++ b/scripts/buildwindows.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o xoracle.exe diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index 75d6ced..0000000 --- a/utils/utils.go +++ /dev/null @@ -1,99 +0,0 @@ -package utils - -import ( - "encoding/base64" - "encoding/hex" - "fmt" - "regexp" -) - -// Decodes hex encoded string. Returns bytes. -func DecodeHex(encoded string) ([]byte, error) { - return hex.DecodeString(encoded) -} - -// Decodes Base64 encoded string. Returns bytes. -func DecodeBase64(encoded string) ([]byte, error) { - return base64.StdEncoding.DecodeString(encoded) -} - -// scoreText evaluates the likelihood that a given byte slice (input) is -// meaningful or coherent English text. It does so by scoring each byte -// according to its frequency of appearance in English, with a higher score -// indicating a higher likelihood of the input being English. -func ScoreText(input []byte) float64 { - // englishFreq maps English letters and the space character to their - // relative frequencies in English text - englishFreq := map[byte]float64{ - 'a': 8.167, 'b': 1.492, 'c': 2.782, 'd': 4.253, 'e': 12.702, - 'f': 2.228, 'g': 2.015, 'h': 6.094, 'i': 6.966, 'j': 0.153, - 'k': 0.772, 'l': 4.025, 'm': 2.406, 'n': 6.749, 'o': 7.507, - 'p': 1.929, 'q': 0.095, 'r': 5.987, 's': 6.327, 't': 9.056, - 'u': 2.758, 'v': 0.978, 'w': 2.360, 'x': 0.150, 'y': 1.974, - 'z': 0.074, ' ': 13.000, - } - - score := 0.0 - - for _, b := range input { - // If the byte is found in the englishFreq map,add its frequency value - // to the total score. - if val, ok := englishFreq[b]; ok { - score += val - } - } - - return score -} - -// isBase64Encoded checks if the input string is Base64 encoded. -// This function performs a basic check to see if the input is decodable from Base64. -func isBase64Encoded(input string) bool { - if input == "" { - return false - } - // Attempt to decode the input string from Base64 - _, err := base64.StdEncoding.DecodeString(input) - return err == nil -} - -// isHexEncoded checks if the input string is hex encoded. -// This function uses a regex pattern to ensure the string consists only of hexadecimal characters. -func isHexEncoded(input string) bool { - if input == "" { - return false - } - // Hex regex pattern to match valid hexadecimal characters - hexPattern := `^[0-9A-Fa-f]+$` - matched, err := regexp.MatchString(hexPattern, input) - if err != nil { - fmt.Println("Regex match error:", err) - return false - } - return matched -} - -func Decode(encodedData string) ([]byte, error) { - var verifiedData []byte - var err error - if isHexEncoded(encodedData) { - verifiedData, err = DecodeHex(encodedData) - if err != nil { - return nil, err - } - return verifiedData, nil - } - - if isBase64Encoded(encodedData) { - verifiedData, err = DecodeBase64(encodedData) - if err != nil { - return nil, err - } - return verifiedData, nil - } - - // plaintext, or some other encoded data type not checked for. - verifiedData = []byte(encodedData) - - return verifiedData, nil -} diff --git a/utils/utils_test.go b/utils/utils_test.go deleted file mode 100644 index 41c9478..0000000 --- a/utils/utils_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package utils - -import ( - "testing" -) - -func TestScoreText(t *testing.T) { - tests := []struct { - name string - text string - score float64 - }{ - {name: "Valid score", text: "What Do You Want From Me", score: 158.66299999999998}, - {name: "No text", text: "", score: 0.0}, - {name: "Single letter", text: "a", score: 8.167}, - {name: "Space", text: " ", score: 13.000}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := ScoreText([]byte(tt.text)) - if got != tt.score { - t.Fatalf("expected: %f, got: %f", tt.score, got) - } - }) - } -} - -func TestIsBase64Encoded(t *testing.T) { - tests := []struct { - name string - input string - want bool - }{ - {name: "Valid Base64", input: "dGVzdCBpbnB1dA==", want: true}, - {name: "Invalid Base64", input: "test input", want: false}, - {name: "Empty String", input: "", want: false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isBase64Encoded(tt.input) - if got != tt.want { - t.Errorf("want: %v, got: %v", tt.want, got) - } - }) - } -} - -func TestIsHexEncoded(t *testing.T) { - tests := []struct { - name string - input string - want bool - }{ - {name: "Valid Hex", input: "7465737420696e707574", want: true}, - {name: "Invalid Hex", input: "test input", want: false}, - {name: "Empty String", input: "", want: false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isHexEncoded(tt.input) - if got != tt.want { - t.Errorf("want: %v, got: %v", tt.want, got) - } - }) - } -} - -func TestDecode(t *testing.T) { - tests := []struct { - name string - encoded string - want string - wantErr bool - }{ - {name: "Hex Encoded Text", encoded: "7465737420696e707574", want: "test input", wantErr: false}, - {name: "Non-Encoded Text", encoded: "not encoded text", want: "not encoded text", wantErr: false}, - {name: "Base64 Encoded Text", encoded: "dGVzdCBpbnB1dA==", want: "test input", wantErr: false}, - {name: "Invalid Hex", encoded: "7465737420696e70757", want: "", wantErr: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := Decode(tt.encoded) - if err == nil && tt.wantErr { - t.Errorf("expected error and didn't get one") - } - if string(got) != tt.want { - t.Errorf("want: %v, got: %v", tt.want, got) - } - }) - } -}