Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {

testImplementation 'io.mockk:mockk:1.9'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.1'
testCompile 'junit:junit:4.12'
testCompile 'com.github.stefanbirkner:system-rules:1.19.0'

Expand Down
15 changes: 8 additions & 7 deletions src/main/java/com/github/sikandar/faktory/FaktoryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ public class FaktoryClient {
private final FaktoryConnection connection;
public static String url = "tcp://localhost:7419";

public FaktoryClient(final String urlParam) {
public FaktoryClient(final String urlParam, final String password) {
url = urlParam == null ? url : urlParam;
connection = new FaktoryConnection(url);
connection = new FaktoryConnection(url, password);
}

public FaktoryClient() {
this(System.getenv("FAKTORY_URL"));
public FaktoryClient(final String urlParam) {
this(urlParam, null);
}

public FaktoryClient() {
this(System.getenv("FAKTORY_URL"), System.getenv("FAKTORY_PASSWORD"));
}

public void push(final FaktoryJob job) throws IOException {
final String payload = new Gson().toJson(job);
connection.connect();
connection.send("PUSH " + payload);
connection.close();
}


}
}
76 changes: 71 additions & 5 deletions src/main/java/com/github/sikandar/faktory/FaktoryConnection.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.github.sikandar.faktory;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.regex.Pattern;

/**
Expand All @@ -20,29 +25,34 @@ public class FaktoryConnection {

private static final Pattern HI_RECEIVED = Pattern.compile("\\+HI\\s\\{\"v\":\\d}");
private static final Pattern OK_RECEIVED = Pattern.compile("\\+OK");
private static final String HELLO_WITH_NO_PASSWORD = "HELLO {\"v\":2}";
private static final String HELLO_WITH_PASSWORD = "HELLO {\"pwdhash\":\"%s\",\"v\":2}";

private final URI url;
private final String password;
private Socket socket;
private BufferedReader fromServer;
private DataOutputStream toServer;

public FaktoryConnection(String url) {
this.url = URI.create(url);
this.password = null;
}

public FaktoryConnection(String url, String password) {
this.url = URI.create(url);
this.password = password;
}

/**
* Method used to connect to Faktory using a Socket.
*/

public void connect() throws IOException {
socket = openSocket();
toServer = new DataOutputStream(socket.getOutputStream());
fromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));

if (!HI_RECEIVED.matcher(readFromSocket()).matches()) {
throw new FaktoryException("Invalid +HI, Expecting:" + HI_RECEIVED);
}
send("HELLO {\"v\":2}");
send(getHelloMessage(readFromSocket()));
}

public void send(String message) throws IOException {
Expand Down Expand Up @@ -71,4 +81,60 @@ private String readFromSocket() throws IOException {
private void writeToSocket(String content) throws IOException {
toServer.writeBytes(content + "\n");
}

private String getHelloMessage(String hiMessage) throws IOException {
// If password not set in faktory you will receive "+HI {"v":2}"
if (password == null || password.length() == 0) {
if (!HI_RECEIVED.matcher(hiMessage).matches()) {
throw new FaktoryException("Invalid +HI, Expecting:" + HI_RECEIVED);
}
return HELLO_WITH_NO_PASSWORD;
}

// If password set in faktory you will receive payload like
// "+HI {"v":2,"i":5171,"s":"5fb7c632793578c7"}"
// Parse the message to find version, salt and number of iteration
String jsonPayload = hiMessage.substring(hiMessage.indexOf('{'));
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> hiMap = objectMapper.readValue(jsonPayload, Map.class);
String salt = (String) hiMap.get("s");
Integer iterations = (Integer) hiMap.get("i");

if (salt == null || iterations == null) {
throw new FaktoryException("Salt/Iterations cannot be null if password is set.");
}

return String.format(HELLO_WITH_PASSWORD, hashPassword(password, salt, iterations));
}

private String hashPassword(String password, String salt, int iterations) {
try {
// Combine password and salt
String input = password + salt;
byte[] bytes = input.getBytes("UTF-8");

// Initialize SHA-256 MessageDigest
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(bytes);

// Perform multiple iterations
for (int i = 1; i < iterations; i++) {
hash = digest.digest(hash);
}

// Convert byte array to hexadecimal string
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}

return hexString.toString();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
throw new RuntimeException("Error while hashing password", e);
}
}
}
6 changes: 6 additions & 0 deletions src/test/java/com/github/sikandar/faktory/FaktoryClientT.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public void initializeClientWithoutUriAndWithoutEnvVars() {
@Test
public void initializeClientWithoutUriButWithEnvVars() {
environmentVariables.set("FAKTORY_URL", "tcp://192.168.0.2:7419");
environmentVariables.set("FAKTORY_PASSWORD", "password2Faktory");
FaktoryClient anotherClient = new FaktoryClient();

Assert.assertEquals("tcp://192.168.0.2:7419", anotherClient.url);
Expand All @@ -33,6 +34,11 @@ public void initializeClientWithACustomUri() {
Assert.assertEquals("tcp://192.168.0.1:7419", client.url);
}

@Test
public void initializeClientWithACustomUriAndCustomPassword() {
FaktoryClient client = new FaktoryClient("tcp://192.168.0.1:7419", "password2Faktory");
Assert.assertEquals("tcp://192.168.0.1:7419", client.url);
}

@Test(expected = FaktoryException.class)
public void pushFaktoryJobWithoutConnection() throws Exception {
Expand Down