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.BasicHttpRequest; 031import org.apache.hc.core5.http.message.BasicLineParser; 032import org.apache.hc.core5.http.message.RequestLine; 033import org.apache.hc.core5.util.CharArrayBuffer; 034 035import static java.lang.Math.max; 036import static java.lang.Math.min; 037import static java.util.stream.Collectors.joining; 038import static org.apache.commons.lang3.StringUtils.EMPTY; 039import static org.apache.commons.lang3.StringUtils.SPACE; 040import static org.apache.commons.lang3.math.NumberUtils.toInt; 041import static org.apache.hc.core5.http.HttpVersion.HTTP_1_1; 042 043/** 044 * SSDP {@link org.apache.hc.core5.http.HttpRequest} implementation. 045 * 046 * {@bean.info} 047 * 048 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 049 */ 050public class SSDPRequest extends BasicHttpRequest implements SSDPMessage { 051 private static final long serialVersionUID = -8886375187855815595L; 052 053 /** 054 * {@link SSDPRequest} enumerated {@link Method}s. 055 */ 056 public enum Method { 057 NOTIFY(null), MSEARCH("M-SEARCH"); 058 059 private final String string; 060 061 Method(String string) { this.string = string; } 062 063 @Override 064 public String toString() { return (string != null) ? string : name(); } 065 066 /** 067 * Test if the argument method matches {@link.this}. 068 * 069 * @param method The method to test. 070 * 071 * @return {@code true} is match; {@code false} otherwise. 072 */ 073 public boolean is(String method) { 074 return Objects.equals(toString(), method); 075 } 076 } 077 078 /** 079 * Method to parse a {@link SSDPRequest} from a {@link DatagramPacket}. 080 * 081 * @param packet The {@link DatagramPacket}. 082 * 083 * @return A new {@link SSDPRequest}. 084 * 085 * @throws ParseException If the {@link DatagramPacket} cannot be 086 * parsed. 087 */ 088 public static SSDPRequest from(DatagramPacket packet) throws ParseException { 089 List<CharArrayBuffer> list = SSDPMessage.parse(packet); 090 RequestLine line = BasicLineParser.INSTANCE.parseRequestLine(list.remove(0)); 091 SSDPRequest request = new SSDPRequest(line.getMethod(), line.getUri()); 092 093 request.setVersion(line.getProtocolVersion()); 094 095 for (CharArrayBuffer buffer : list) { 096 request.addHeader(BasicLineParser.INSTANCE.parseHeader(buffer)); 097 } 098 099 request.address = packet.getSocketAddress(); 100 101 return request; 102 } 103 104 /** @serial */ private SocketAddress address = null; 105 /** @serial */ private long timestamp = System.currentTimeMillis(); 106 /** @serial */ private Long expiration = null; 107 108 /** 109 * Sole non-private constructor. 110 * 111 * @param method The {@link SSDPRequest} {@link Method}. 112 */ 113 protected SSDPRequest(Method method) { 114 super(method.toString(), "*"); 115 116 setVersion(HTTP_1_1); 117 } 118 119 private SSDPRequest(String method, String uri) { super(method, uri); } 120 121 /** 122 * Method to get the {@link SocketAddress} from the 123 * {@link DatagramPacket} if {@link.this} {@link SSDPRequest} was 124 * parsed from a packet. 125 * 126 * @return The {@link SocketAddress}. 127 */ 128 public SocketAddress getSocketAddress() { return address; } 129 130 public String getRequestLine() { 131 String string = 132 Stream.of(getMethod(), getPath(), getVersion()) 133 .filter(Objects::nonNull) 134 .map(Object::toString) 135 .collect(joining(SPACE)); 136 137 return string; 138 } 139 140 /** 141 * Method to get the {@code MX} header value as an {@code int}. Returns 142 * {@code 120} if the header is not specified or the if the value is not 143 * in the range of {@code 1 <= mx <= 120}. 144 * 145 * @return The {@code MX} value. 146 */ 147 public int getMX() { 148 return getHeaderValue(t -> min(max(toInt(t, 120), 1), 120), MX); 149 } 150 151 /** 152 * {@link String} fluent header setter. 153 * 154 * @param name The header name. 155 * @param value The header value. 156 * 157 * @return {@link.this} 158 */ 159 public SSDPRequest header(String name, String value) { 160 setHeader(name, value); 161 162 return this; 163 } 164 165 /** 166 * {@link SocketAddress} fluent header setter. 167 * 168 * @param name The header name. 169 * @param value The header value. 170 * 171 * @return {@link.this} 172 */ 173 public SSDPRequest header(String name, SocketAddress value) { 174 return header(name, (InetSocketAddress) value); 175 } 176 177 /** 178 * {@link InetSocketAddress} fluent header setter. 179 * 180 * @param name The header name. 181 * @param value The header value. 182 * 183 * @return {@link.this} 184 */ 185 public SSDPRequest header(String name, InetSocketAddress value) { 186 return header(name, t -> String.format("%s:%d", value.getAddress().getHostAddress(), value.getPort()), value); 187 } 188 189 /** 190 * {@link Number} fluent header setter. 191 * 192 * @param name The header name. 193 * @param value The header value. 194 * 195 * @return {@link.this} 196 */ 197 public SSDPRequest header(String name, Number value) { 198 return header(name, Number::toString, value); 199 } 200 201 /** 202 * {@link URI} fluent header setter. 203 * 204 * @param name The header name. 205 * @param value The header value. 206 * 207 * @return {@link.this} 208 */ 209 public SSDPRequest header(String name, URI value) { 210 return header(name, URI::toASCIIString, value); 211 } 212 213 /** 214 * Fluent header setter. 215 * 216 * @param <T> The target type. 217 * @param name The header name. 218 * @param value The header value. 219 * 220 * @return {@link.this} 221 */ 222 public <T> SSDPRequest header(String name, Function<T,String> function, T value) { 223 setHeader(name, (value != null) ? function.apply(value) : null); 224 225 return this; 226 } 227 228 @Override 229 public long getExpiration() { 230 if (expiration == null) { 231 expiration = SSDPMessage.getExpiration(this, timestamp); 232 } 233 234 return expiration; 235 } 236 237 @Override 238 public String toString() { 239 String string = 240 Stream.concat(Stream.of(getRequestLine()), Stream.of(getHeaders())) 241 .filter(Objects::nonNull) 242 .map(Objects::toString) 243 .collect(joining(EOL, EMPTY, EOM)); 244 245 return string; 246 } 247}