1/*
  2 * Copyright 2015-2022 the original author or authors
  3 *
  4 * This software is licensed under the Apache License, Version 2.0,
  5 * the GNU Lesser General Public License version 2 or later ("LGPL")
  6 * and the WTFPL.
  7 * You may choose either license to govern your use of this software only
  8 * upon the condition that you accept all of the terms of either
  9 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
 10 */
 11package de.gultsch.minidns;
 12
 13import java.util.Collections;
 14import java.util.Set;
 15
 16import org.minidns.MiniDnsException;
 17import org.minidns.MiniDnsException.NullResultException;
 18import org.minidns.dnsmessage.DnsMessage;
 19import org.minidns.dnsmessage.Question;
 20import org.minidns.dnsqueryresult.DnsQueryResult;
 21import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
 22import org.minidns.dnssec.DnssecResultNotAuthenticException;
 23import org.minidns.dnssec.DnssecUnverifiedReason;
 24import org.minidns.record.Data;
 25
 26public class ResolverResult<D extends Data> {
 27
 28    protected final Question question;
 29    private final RESPONSE_CODE responseCode;
 30    private final Set<D> data;
 31    private final boolean isAuthenticData;
 32    protected final Set<DnssecUnverifiedReason> unverifiedReasons;
 33    protected final DnsMessage answer;
 34    protected final DnsQueryResult result;
 35
 36    public ResolverResult(Question question, DnsQueryResult result, Set<DnssecUnverifiedReason> unverifiedReasons) throws NullResultException {
 37        // TODO: Is this null check still needed?
 38        if (result == null) {
 39            throw new MiniDnsException.NullResultException(question.asMessageBuilder().build());
 40        }
 41
 42        this.result = result;
 43
 44        DnsMessage answer = result.response;
 45        this.question = question;
 46        this.responseCode = answer.responseCode;
 47        this.answer = answer;
 48
 49        Set<D> r = answer.getAnswersFor(question);
 50        if (r == null) {
 51            this.data = Collections.emptySet();
 52        } else {
 53            this.data = Collections.unmodifiableSet(r);
 54        }
 55
 56        if (unverifiedReasons == null) {
 57            this.unverifiedReasons = null;
 58            isAuthenticData = false;
 59        } else {
 60            this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons);
 61            isAuthenticData = this.unverifiedReasons.isEmpty();
 62        }
 63    }
 64
 65    public boolean wasSuccessful() {
 66        return responseCode == RESPONSE_CODE.NO_ERROR;
 67    }
 68
 69    public Set<D> getAnswers() {
 70        throwIseIfErrorResponse();
 71        return data;
 72    }
 73
 74    public Set<D> getAnswersOrEmptySet() {
 75        return data;
 76    }
 77
 78    public RESPONSE_CODE getResponseCode() {
 79        return responseCode;
 80    }
 81
 82    public boolean isAuthenticData() {
 83        throwIseIfErrorResponse();
 84        return isAuthenticData;
 85    }
 86
 87    /**
 88     * Get the reasons the result could not be verified if any exists.
 89     *
 90     * @return The reasons the result could not be verified or <code>null</code>.
 91     */
 92    public Set<DnssecUnverifiedReason> getUnverifiedReasons() {
 93        throwIseIfErrorResponse();
 94        return unverifiedReasons;
 95    }
 96
 97    public Question getQuestion() {
 98        return question;
 99    }
100
101    public void throwIfErrorResponse() throws ResolutionUnsuccessfulException {
102        ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
103        if (resolutionUnsuccessfulException != null) throw resolutionUnsuccessfulException;
104    }
105
106    private ResolutionUnsuccessfulException resolutionUnsuccessfulException;
107
108    public ResolutionUnsuccessfulException getResolutionUnsuccessfulException() {
109        if (wasSuccessful()) return null;
110
111        if (resolutionUnsuccessfulException == null) {
112            resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);
113        }
114
115        return resolutionUnsuccessfulException;
116    }
117
118    private DnssecResultNotAuthenticException dnssecResultNotAuthenticException;
119
120    public DnssecResultNotAuthenticException getDnssecResultNotAuthenticException() {
121        if (!wasSuccessful())
122            return null;
123        if (isAuthenticData)
124            return null;
125
126        if (dnssecResultNotAuthenticException == null) {
127            dnssecResultNotAuthenticException = DnssecResultNotAuthenticException.from(getUnverifiedReasons());
128        }
129
130        return dnssecResultNotAuthenticException;
131    }
132
133    /**
134     * Get the raw answer DNS message we received. <b>This is likely not what you want</b>, try {@link #getAnswers()} instead.
135     *
136     * @return the raw answer DNS Message.
137     * @see #getAnswers()
138     */
139    public DnsMessage getRawAnswer() {
140        return answer;
141    }
142
143    public DnsQueryResult getDnsQueryResult() {
144        return result;
145    }
146
147    @Override
148    public final String toString() {
149        StringBuilder sb = new StringBuilder();
150
151        sb.append(getClass().getName()).append('\n')
152               .append("Question: ").append(question).append('\n')
153               .append("Response Code: ").append(responseCode).append('\n');
154
155        if (responseCode == RESPONSE_CODE.NO_ERROR) {
156            if (isAuthenticData) {
157                sb.append("Results verified via DNSSEC\n");
158            }
159            if (hasUnverifiedReasons()) {
160                sb.append(unverifiedReasons).append('\n');
161            }
162            sb.append(answer.answerSection);
163        }
164
165        return sb.toString();
166    }
167
168    boolean hasUnverifiedReasons() {
169        return unverifiedReasons != null && !unverifiedReasons.isEmpty();
170    }
171
172    protected void throwIseIfErrorResponse() {
173        ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
174        if (resolutionUnsuccessfulException != null)
175            throw new IllegalStateException("Can not perform operation because the DNS resolution was unsuccessful",
176                    resolutionUnsuccessfulException);
177    }
178}