001/*
002 * Copyright 2015-2018 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.hla;
012
013import java.util.Collections;
014import java.util.Set;
015
016import org.minidns.MiniDnsException;
017import org.minidns.MiniDnsException.NullResultException;
018import org.minidns.dnsmessage.DnsMessage;
019import org.minidns.dnsmessage.Question;
020import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
021import org.minidns.dnssec.DnssecResultNotAuthenticException;
022import org.minidns.dnssec.UnverifiedReason;
023import org.minidns.record.Data;
024
025public class ResolverResult<D extends Data> {
026
027    protected final Question question;
028    private final RESPONSE_CODE responseCode;
029    private final Set<D> data;
030    private final boolean isAuthenticData;
031    protected final Set<UnverifiedReason> unverifiedReasons;
032    protected final DnsMessage answer;
033
034    ResolverResult(Question question , DnsMessage answer, Set<UnverifiedReason> unverifiedReasons) throws NullResultException {
035        if (answer == null) {
036            throw new MiniDnsException.NullResultException(question.asMessageBuilder().build());
037        }
038
039        this.question = question;
040        this.responseCode = answer.responseCode;
041        this.answer = answer;
042
043        Set<D> r = answer.getAnswersFor(question);
044        if (r == null) {
045            this.data = Collections.emptySet();
046        } else {
047            this.data = Collections.unmodifiableSet(r);
048        }
049
050        if (unverifiedReasons == null) {
051            this.unverifiedReasons = null;
052            isAuthenticData = false;
053        } else {
054            this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons);
055            isAuthenticData = this.unverifiedReasons.isEmpty();
056        }
057    }
058
059    public boolean wasSuccessful() {
060        return responseCode == RESPONSE_CODE.NO_ERROR;
061    }
062
063    public Set<D> getAnswers() {
064        throwIseIfErrorResponse();
065        return data;
066    }
067
068    public Set<D> getAnswersOrEmptySet() {
069        return data;
070    }
071
072    public RESPONSE_CODE getResponseCode() {
073        return responseCode;
074    }
075
076    public boolean isAuthenticData() {
077        throwIseIfErrorResponse();
078        return isAuthenticData;
079    }
080
081    /**
082     * Get the reasons the result could not be verified if any exists.
083     *
084     * @return The reasons the result could not be verified or <code>null</code>.
085     */
086    public Set<UnverifiedReason> getUnverifiedReasons() {
087        throwIseIfErrorResponse();
088        return unverifiedReasons;
089    }
090
091    public Question getQuestion() {
092        return question;
093    }
094
095    public void throwIfErrorResponse() throws ResolutionUnsuccessfulException {
096        ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
097        if (resolutionUnsuccessfulException != null) throw resolutionUnsuccessfulException;
098    }
099
100    private ResolutionUnsuccessfulException resolutionUnsuccessfulException;
101
102    public ResolutionUnsuccessfulException getResolutionUnsuccessfulException() {
103        if (wasSuccessful()) return null;
104
105        if (resolutionUnsuccessfulException == null) {
106            resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);
107        }
108
109        return resolutionUnsuccessfulException;
110    }
111
112    private DnssecResultNotAuthenticException dnssecResultNotAuthenticException;
113
114    public DnssecResultNotAuthenticException getDnssecResultNotAuthenticException() {
115        if (!wasSuccessful())
116            return null;
117        if (isAuthenticData)
118            return null;
119
120        if (dnssecResultNotAuthenticException == null) {
121            dnssecResultNotAuthenticException = DnssecResultNotAuthenticException.from(getUnverifiedReasons());
122        }
123
124        return dnssecResultNotAuthenticException;
125    }
126
127    /**
128     * Get the raw answer DNS message we received. <b>This is likely not what you want</b>, try {@link #getAnswers()} instead.
129     *
130     * @return the raw answer DNS Message.
131     * @see #getAnswers()
132     */
133    public DnsMessage getRawAnswer() {
134        return answer;
135    }
136
137    @Override
138    public final String toString() {
139        StringBuilder sb = new StringBuilder();
140
141        sb.append(getClass().getName()).append('\n')
142               .append("Question: ").append(question).append('\n')
143               .append("Response Code: ").append(responseCode).append('\n');
144
145        if (responseCode == RESPONSE_CODE.NO_ERROR) {
146            if (isAuthenticData) {
147                sb.append("Results verified via DNSSEC\n");
148            }
149            if (hasUnverifiedReasons()) {
150                sb.append(unverifiedReasons).append('\n');
151            }
152            sb.append(answer.answerSection);
153        }
154
155        return sb.toString();
156    }
157
158    boolean hasUnverifiedReasons() {
159        return unverifiedReasons != null && !unverifiedReasons.isEmpty();
160    }
161
162    protected void throwIseIfErrorResponse() {
163        ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
164        if (resolutionUnsuccessfulException != null)
165            throw new IllegalStateException("Can not perform operation because the DNS resolution was unsuccessful",
166                    resolutionUnsuccessfulException);
167    }
168}