/*
* Copyright (C) 2015 SoftIndex LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.datakernel.dns;
import io.datakernel.async.ResultCallback;
import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.eventloop.AsyncUdpSocket;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.eventloop.ScheduledRunnable;
import io.datakernel.eventloop.UdpPacket;
import io.datakernel.exception.AsyncTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
final class DnsClientConnection implements AsyncUdpSocket.EventHandler {
@SuppressWarnings("ThrowableInstanceNeverThrown")
private static final AsyncTimeoutException TIMEOUT_EXCEPTION = new AsyncTimeoutException();
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Map<String, Set<ResultCallback<DnsQueryResult>>> resultHandlers = new HashMap<>();
private final Eventloop eventloop;
private final AsyncUdpSocket socket;
private DnsClientConnection(Eventloop eventloop, AsyncUdpSocket socket) {
this.eventloop = eventloop;
this.socket = socket;
this.socket.setEventHandler(this);
}
public static DnsClientConnection create(Eventloop eventloop, AsyncUdpSocket udpSocket) {
return new DnsClientConnection(eventloop, udpSocket);
}
public void resolve4(String domainName, InetSocketAddress dnsServerAddress, long timeout, ResultCallback<DnsQueryResult> callback) {
resolve(domainName, dnsServerAddress, timeout, callback, false);
}
public void resolve6(String domainName, InetSocketAddress dnsServerAddress, long timeout, ResultCallback<DnsQueryResult> callback) {
resolve(domainName, dnsServerAddress, timeout, callback, true);
}
private void resolve(final String domainName, InetSocketAddress dnsServerAddress, final long timeout,
final ResultCallback<DnsQueryResult> callback, boolean ipv6) {
final ResultCallback<DnsQueryResult> callbackWithTimeout = new ResultCallback<DnsQueryResult>() {
private final ScheduledRunnable timeouter = eventloop.schedule(eventloop.currentTimeMillis() + timeout, new Runnable() {
@Override
public void run() {
final Set<ResultCallback<DnsQueryResult>> callbacks = resultHandlers.get(domainName);
callbacks.remove(getThisCallback());
if (callbacks.isEmpty()) resultHandlers.remove(domainName);
setException(TIMEOUT_EXCEPTION);
}
});
private ResultCallback<DnsQueryResult> getThisCallback() {
return this;
}
@Override
protected void onResult(DnsQueryResult result) {
if (!timeouter.isCancelled() && !timeouter.isComplete()) {
timeouter.cancel();
callback.setResult(result);
}
}
@Override
protected void onException(Exception e) {
if (!timeouter.isCancelled() && !timeouter.isComplete()) {
timeouter.cancel();
callback.setException(e);
}
}
};
Set<ResultCallback<DnsQueryResult>> callbacks = resultHandlers.get(domainName);
if (callbacks == null) {
callbacks = new HashSet<>();
resultHandlers.put(domainName, callbacks);
}
callbacks.add(callbackWithTimeout);
ByteBuf query = DnsMessage.newQuery(domainName, ipv6);
UdpPacket queryPacket = UdpPacket.of(query, dnsServerAddress);
socket.send(queryPacket);
socket.read();
}
public void close() {
socket.close();
}
public int getNumberOfRequestsInProgress() {
return resultHandlers.size();
}
public String[] getDomainNamesBeingResolved() {
Set<String> domainNamesBeingResolved = resultHandlers.keySet();
return domainNamesBeingResolved.toArray(new String[domainNamesBeingResolved.size()]);
}
public boolean allRequestsCompleted() {
return resultHandlers.size() == 0;
}
// region AsyncUdpSocket.EventHandler implementation
@Override
public void onRegistered() {
}
@Override
public void onRead(UdpPacket packet) {
byte[] response = packet.getBuf().array();
try {
DnsQueryResult dnsQueryResult = DnsMessage.getQueryResult(response);
String domainName = dnsQueryResult.getDomainName();
final Set<ResultCallback<DnsQueryResult>> callbacks = resultHandlers.remove(domainName);
if (callbacks != null) {
if (dnsQueryResult.isSuccessful()) {
for (ResultCallback<DnsQueryResult> callback : callbacks) {
callback.setResult(dnsQueryResult);
}
} else {
final DnsException exception = dnsQueryResult.getException();
for (ResultCallback<DnsQueryResult> callback : callbacks) {
callback.setException(exception);
}
}
}
} catch (DnsResponseParseException e) {
logger.info("Received packet cannot be parsed as DNS server response.", e);
} finally {
packet.recycle();
}
}
@Override
public void onSent() {
}
@Override
public void onClosedWithError(Exception e) {
}
// endregion
}