001/*
002 * Copyright 2015-2024 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.dnsqueryresult.DnsQueryResult;
021import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
022import org.minidns.dnssec.DnssecResultNotAuthenticException;
023import org.minidns.dnssec.DnssecUnverifiedReason;
024import org.minidns.record.Data;
025
026public class ResolverResult<D extends Data> {
027
028    protected final Question question;
029    private final RESPONSE_CODE responseCode;
030    private final Set<D> data;
031    private final boolean isAuthenticData;
032    protected final Set<DnssecUnverifiedReason> unverifiedReasons;
033    protected final DnsMessage answer;
034    protected final DnsQueryResult result;
035
036    ResolverResult(Question question, DnsQueryResult result, Set<DnssecUnverifiedReason> unverifiedReasons) throws NullResultException {
037        // TODO: Is this null check still needed?
038        if (result == null) {
039            throw new MiniDnsException.NullResultException(question.asMessageBuilder().build());
040        }
041
042        this.result = result;
043
044        DnsMessage answer = result.response;
045        this.question = question;
046        this.responseCode = answer.responseCode;
047        this.answer = answer;
048
049        Set<D> r = answer.getAnswersFor(question);
050        if (r == null) {
051            this.data = Collections.emptySet();
052        } else {
053            this.data = Collections.unmodifiableSet(r);
054        }
055
056        if (unverifiedReasons == null) {
057            this.unverifiedReasons = null;
058            isAuthenticData = false;
059        } else {
060            this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons);
061            isAuthenticData = this.unverifiedReasons.isEmpty();
062        }
063    }
064
065    public boolean wasSuccessful() {
066        return responseCode == RESPONSE_CODE.NO_ERROR;
067    }
068
069    public Set<D> getAnswers() {
070        throwIseIfErrorResponse();
071        return data;
072    }
073
074    public Set<D> getAnswersOrEmptySet() {
075        return data;
076    }
077
078    public RESPONSE_CODE getResponseCode() {
079        return responseCode;
080    }
081
082    public boolean isAuthenticData() {
083        throwIseIfErrorResponse();
084        return isAuthenticData;
085    }
086
087    /**
088     * Get the reasons the result could not be verified if any exists.
089     *
090     * @return The reasons the result could not be verified or <code>null</code>.
091     */
092    public Set<DnssecUnverifiedReason> getUnverifiedReasons() {
093        throwIseIfErrorResponse();
094        return unverifiedReasons;
095    }
096
097    public Question getQuestion() {
098        return question;
099    }
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}