001package ball.upnp.ssdp;
002/*-
003 * ##########################################################################
004 * UPnP/SSDP Implementation Classes
005 * %%
006 * Copyright (C) 2013 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.net.DatagramPacket;
022import java.net.InetSocketAddress;
023import java.net.SocketAddress;
024import java.net.URI;
025import java.util.List;
026import java.util.Objects;
027import java.util.function.Function;
028import java.util.stream.Stream;
029import org.apache.hc.core5.http.ParseException;
030import org.apache.hc.core5.http.message.BasicHttpResponse;
031import org.apache.hc.core5.http.message.BasicLineParser;
032import org.apache.hc.core5.http.message.StatusLine;
033import org.apache.hc.core5.util.CharArrayBuffer;
034
035import static java.util.stream.Collectors.joining;
036import static org.apache.commons.lang3.StringUtils.EMPTY;
037import static org.apache.commons.lang3.StringUtils.SPACE;
038
039/**
040 * SSDP {@link org.apache.hc.core5.http.HttpResponse} implementation.
041 *
042 * {@bean.info}
043 *
044 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
045 */
046public class SSDPResponse extends BasicHttpResponse implements SSDPMessage {
047    private static final long serialVersionUID = -5163694432470542459L;
048
049    /**
050     * Method to parse a {@link SSDPResponse} from a {@link DatagramPacket}.
051     *
052     * @param   packet          The {@link DatagramPacket}.
053     *
054     * @return  A new {@link SSDPResponse}.
055     *
056     * @throws  ParseException  If the {@link DatagramPacket} cannot be
057     *                          parsed.
058     */
059    public static SSDPResponse from(DatagramPacket packet) throws ParseException {
060        List<CharArrayBuffer> list = SSDPMessage.parse(packet);
061        StatusLine line = BasicLineParser.INSTANCE.parseStatusLine(list.remove(0));
062        SSDPResponse response = new SSDPResponse(line.getStatusCode(), line.getReasonPhrase());
063
064        for (CharArrayBuffer buffer : list) {
065            response.addHeader(BasicLineParser.INSTANCE.parseHeader(buffer));
066        }
067
068        return response;
069    }
070
071    /** @serial */ private SocketAddress address = null;
072    /** @serial */ private long timestamp = System.currentTimeMillis();
073    /** @serial */ private Long expiration = null;
074
075    /**
076     * Sole constructor.
077     *
078     * @param   code            The {@link SSDPRequest} {@code code}.
079     * @param   reason          The {@link SSDPRequest} reason.
080     */
081    protected SSDPResponse(int code, String reason) {
082        super(code, reason);
083    }
084
085    /**
086     * Method to get the {@link SocketAddress} from the
087     * {@link DatagramPacket} if {@link.this} {@link SSDPResponse} was
088     * parsed from a packet.
089     *
090     * @return  The {@link SocketAddress}.
091     */
092    public SocketAddress getSocketAddress() { return address; }
093
094    public String getStatusLine() {
095        String string =
096            Stream.of(getVersion(), getCode(), getReasonPhrase())
097            .filter(Objects::nonNull)
098            .map(Object::toString)
099            .collect(joining(SPACE));
100
101        return string;
102    }
103
104    /**
105     * {@link String} fluent header setter.
106     *
107     * @param   name            The header name.
108     * @param   value           The header value.
109     *
110     * @return  {@link.this}
111     */
112    public SSDPResponse header(String name, String value) {
113        setHeader(name, value);
114
115        return this;
116    }
117
118    /**
119     * {@link SocketAddress} fluent header setter.
120     *
121     * @param   name            The header name.
122     * @param   value           The header value.
123     *
124     * @return  {@link.this}
125     */
126    public SSDPResponse header(String name, SocketAddress value) {
127        return header(name, (InetSocketAddress) value);
128    }
129
130    /**
131     * {@link InetSocketAddress} fluent header setter.
132     *
133     * @param   name            The header name.
134     * @param   value           The header value.
135     *
136     * @return  {@link.this}
137     */
138    public SSDPResponse header(String name, InetSocketAddress value) {
139        return header(name, t -> String.format("%s:%d", value.getAddress().getHostAddress(), value.getPort()), value);
140    }
141
142    /**
143     * {@link Number} fluent header setter.
144     *
145     * @param   name            The header name.
146     * @param   value           The header value.
147     *
148     * @return  {@link.this}
149     */
150    public SSDPResponse header(String name, Number value) {
151        return header(name, Number::toString, value);
152    }
153
154    /**
155     * {@link URI} fluent header setter.
156     *
157     * @param   name            The header name.
158     * @param   value           The header value.
159     *
160     * @return  {@link.this}
161     */
162    public SSDPResponse header(String name, URI value) {
163        return header(name, URI::toASCIIString, value);
164    }
165
166    /**
167     * Fluent header setter.
168     *
169     * @param   <T>             The target type.
170     * @param   name            The header name.
171     * @param   value           The header value.
172     *
173     * @return  {@link.this}
174     */
175    public <T> SSDPResponse header(String name, Function<T,String> function, T value) {
176        setHeader(name, (value != null) ? function.apply(value) : null);
177
178        return this;
179    }
180
181    @Override
182    public long getExpiration() {
183        if (expiration == null) {
184            expiration = SSDPMessage.getExpiration(this, timestamp);
185        }
186
187        return expiration;
188    }
189
190    @Override
191    public String toString() {
192        String string =
193            Stream.concat(Stream.of(getStatusLine()), Stream.of(getHeaders()))
194            .filter(Objects::nonNull)
195            .map(Objects::toString)
196            .collect(joining(EOL, EMPTY, EOM));
197
198        return string;
199    }
200}