001package ball.upnp.ssdp; 002/*- 003 * ########################################################################## 004 * UPnP/SSDP Implementation Classes 005 * $Id: SSDPRequest.java 7215 2021-01-03 18:39:51Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-upnp/trunk/src/main/java/ball/upnp/ssdp/SSDPRequest.java $ 007 * %% 008 * Copyright (C) 2013 - 2021 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import java.net.DatagramPacket; 024import java.net.InetSocketAddress; 025import java.net.SocketAddress; 026import java.net.URI; 027import java.util.List; 028import java.util.Objects; 029import java.util.function.Function; 030import java.util.regex.Pattern; 031import java.util.stream.Stream; 032import org.apache.http.Header; 033import org.apache.http.HttpVersion; 034import org.apache.http.message.BasicHttpRequest; 035 036import static java.lang.Math.max; 037import static java.lang.Math.min; 038import static java.util.stream.Collectors.joining; 039import static org.apache.commons.lang3.StringUtils.EMPTY; 040import static org.apache.commons.lang3.math.NumberUtils.toInt; 041import static org.apache.http.message.BasicLineParser.INSTANCE; 042import static org.apache.http.message.BasicLineParser.parseHeader; 043import static org.apache.http.message.BasicLineParser.parseRequestLine; 044 045/** 046 * SSDP {@link org.apache.http.HttpRequest} implementation. 047 * 048 * {@bean.info} 049 * 050 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 051 * @version $Revision: 7215 $ 052 */ 053public class SSDPRequest extends BasicHttpRequest implements SSDPMessage { 054 055 /** 056 * {@link SSDPRequest} enumerated {@link Method}s. 057 */ 058 public enum Method { 059 NOTIFY(null), MSEARCH("M-SEARCH"); 060 061 private final String string; 062 063 Method(String string) { this.string = string; } 064 065 @Override 066 public String toString() { return (string != null) ? string : name(); } 067 068 /** 069 * Test if the argument method matches {@link.this}. 070 * 071 * @param method The method to test. 072 * 073 * @return {@code true} is match; {@code false} otherwise. 074 */ 075 public boolean is(String method) { 076 return Objects.equals(toString(), method); 077 } 078 } 079 080 /** 081 * Method to parse a {@link SSDPRequest} from a {@link DatagramPacket}. 082 * 083 * @param packet The {@link DatagramPacket}. 084 * 085 * @return A new {@link SSDPRequest}. 086 */ 087 public static SSDPRequest from(DatagramPacket packet) { 088 return new SSDPRequest(packet); 089 } 090 091 private SocketAddress address = null; 092 private long timestamp = System.currentTimeMillis(); 093 private Long expiration = null; 094 095 /** 096 * Sole non-private constructor. 097 * 098 * @param method The {@link SSDPRequest} {@link Method}. 099 */ 100 protected SSDPRequest(Method method) { 101 super(method.toString(), "*", HttpVersion.HTTP_1_1); 102 } 103 104 private SSDPRequest(DatagramPacket packet) { 105 this(packet, SSDPMessage.parse(packet)); 106 } 107 108 private SSDPRequest(DatagramPacket packet, List<String> lines) { 109 super(parseRequestLine(lines.remove(0), INSTANCE)); 110 111 lines.stream().forEach(t -> addHeader(parseHeader(t, INSTANCE))); 112 113 address = packet.getSocketAddress(); 114 } 115 116 /** 117 * Method to get the {@link org.apache.http.RequestLine} method. 118 * 119 * @return The method specified on the request line. 120 */ 121 public String getMethod() { return getRequestLine().getMethod(); } 122 123 /** 124 * Method to get the {@link SocketAddress} from the 125 * {@link DatagramPacket} if {@link.this} {@link SSDPRequest} was 126 * parsed from a packet. 127 * 128 * @return The {@link SocketAddress}. 129 */ 130 public SocketAddress getSocketAddress() { return address; } 131 132 /** 133 * Method to get the {@code MX} header value as an {@code int}. Returns 134 * {@code 120} if the header is not specified or the if the value is not 135 * in the range of {@code 1 <= mx <= 120}. 136 * 137 * @return The {@code MX} value. 138 */ 139 public int getMX() { 140 return getHeaderValue(t -> min(max(toInt(t, 120), 1), 120), MX); 141 } 142 143 /** 144 * {@link String} fluent header setter. 145 * 146 * @param name The header name. 147 * @param value The header value. 148 * 149 * @return {@link.this} 150 */ 151 public SSDPRequest header(String name, String value) { 152 setHeader(name, value); 153 154 return this; 155 } 156 157 /** 158 * {@link SocketAddress} fluent header setter. 159 * 160 * @param name The header name. 161 * @param value The header value. 162 * 163 * @return {@link.this} 164 */ 165 public SSDPRequest header(String name, SocketAddress value) { 166 return header(name, (InetSocketAddress) value); 167 } 168 169 /** 170 * {@link InetSocketAddress} fluent header setter. 171 * 172 * @param name The header name. 173 * @param value The header value. 174 * 175 * @return {@link.this} 176 */ 177 public SSDPRequest header(String name, InetSocketAddress value) { 178 return header(name, 179 t -> String.format("%s:%d", 180 value.getAddress().getHostAddress(), 181 value.getPort()), 182 value); 183 } 184 185 /** 186 * {@link Number} fluent header setter. 187 * 188 * @param name The header name. 189 * @param value The header value. 190 * 191 * @return {@link.this} 192 */ 193 public SSDPRequest header(String name, Number value) { 194 return header(name, Number::toString, value); 195 } 196 197 /** 198 * {@link URI} fluent header setter. 199 * 200 * @param name The header name. 201 * @param value The header value. 202 * 203 * @return {@link.this} 204 */ 205 public SSDPRequest header(String name, URI value) { 206 return header(name, URI::toASCIIString, value); 207 } 208 209 /** 210 * Fluent header setter. 211 * 212 * @param <T> The target type. 213 * @param name The header name. 214 * @param value The header value. 215 * 216 * @return {@link.this} 217 */ 218 public <T> SSDPRequest header(String name, 219 Function<T,String> function, T value) { 220 setHeader(name, (value != null) ? function.apply(value) : null); 221 222 return this; 223 } 224 225 @Override 226 public long getExpiration() { 227 if (expiration == null) { 228 expiration = SSDPMessage.getExpiration(this, timestamp); 229 } 230 231 return expiration; 232 } 233 234 @Override 235 public String toString() { 236 String string = 237 Stream.concat(Stream.of(getRequestLine()), 238 Stream.of(getAllHeaders())) 239 .filter(Objects::nonNull) 240 .map(Objects::toString) 241 .collect(joining(EOL, EMPTY, EOM)); 242 243 return string; 244 } 245}