Android SSDP Service Discovery - java

I am implementing an java program that scans some of the smart devices that I have, using SSDP discovery. The code that I am using detects only 2 devices among 10 that are connected to my local network. I could not understand the reason is.
My question is, why I am not able to get all the devices that are in my local network.
This is the code that I am using
static final String HOST = "Host:" + SSDP.ADDRESS + ":" + SSDP.PORT;
static final String MAN = "Man: \"ssdp:discover\"";
static final String NEWLINE = "\r\n";
int mMX = 10; /* seconds to delay response */
String mST; /* Search target */
public SSDPSearchMsg(String ST) {
mST = ST;
}
public int getmMX() {
return mMX;
}
public void setmMX(int mMX) {
this.mMX = mMX;
}
public String getmST() {
return mST;
}
public void setmST(String mST) {
this.mST = mST;
}
#Override
public String toString() {
StringBuilder content = new StringBuilder();
content.append(SSDP.SL_MSEARCH).append(NEWLINE);
content.append(HOST).append(NEWLINE);
content.append(MAN).append(NEWLINE);
content.append(mST).append(NEWLINE);
content.append("MX:" + mMX).append(NEWLINE);
content.append(NEWLINE);
return content.toString();
}
Below is few constants
/* New line definition */
public static final String NEWLINE = "\r\n";
public static final String ADDRESS = "239.255.255.250";
public static final int PORT = 1900;
/* Definitions of start line */
public static final String SL_NOTIFY = "NOTIFY * HTTP/1.1";
public static final String SL_MSEARCH = "M-SEARCH * HTTP/1.1";
public static final String SL_OK = "HTTP/1.1 200 OK";
/* Definitions of search targets */
public static final String ST_RootDevice = "ST: upnp:rootdevice";
public static final String ST_ContentDirectory = "ST: urn:schemas-upnp-org:service:ContentDirectory:1";
public static final String ST_ALL="ST: ssdp:all";
public static final String ST_Media="ST:urn:schemas-upnp-org:device:MediaRenderer:1";
/* Definitions of notification sub type */
public static final String NTS_ALIVE = "NTS:ssdp:alive";
public static final String NTS_BYE = "NTS:ssdp:byebye";
public static final String NTS_UPDATE = "NTS:ssdp:update";
Main class
public class SSDPSocket {
SocketAddress mSSDPMulticastGroup;
MulticastSocket mSSDPSocket;
public SSDPSocket() throws IOException {
InetAddress localInAddress = InetAddress.getLocalHost();
System.out.println("Local address: " + localInAddress.getHostAddress());
mSSDPMulticastGroup = new InetSocketAddress(SSDP.ADDRESS, SSDP.PORT);
mSSDPSocket = new MulticastSocket(new InetSocketAddress(localInAddress,
0));
NetworkInterface netIf = NetworkInterface
.getByInetAddress(localInAddress);
mSSDPSocket.joinGroup(mSSDPMulticastGroup, netIf);
}
/* Used to send SSDP packet */
public void send(String data) throws IOException {
DatagramPacket dp = new DatagramPacket(data.getBytes(), data.length(),
mSSDPMulticastGroup);
mSSDPSocket.send(dp);
}
/* Used to receive SSDP packet */
public DatagramPacket receive() throws IOException {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
mSSDPSocket.receive(dp);
return dp;
}
public void close() {
if (mSSDPSocket != null) {
mSSDPSocket.close();
}
}
/* For test purpose */
public static void main(String[] args) {
SSDPSearchMsg search = new SSDPSearchMsg(SSDP.ST_Media);
System.out.println(search.toString());
SSDPSocket sock;
try {
sock = new SSDPSocket();
sock.send(search.toString());
while (true) {
DatagramPacket dp = sock.receive();
String c = new String(dp.getData());
System.out.println(c);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Can anyone please suggest a solution for this problem?

I just worked on an SSDP Java client and studied the spec carefully.
You are missing an "MX" header. If the header MX is not set, the SSDP Service "SHOULD" not respond to your request.
Therefore, try adding an "MX: 1" header and see where this goes.
The Schneider database was of good help for this issue.
Also, you can take a look at the ssdp client if you still are trying to use it.

Related

Using jsmpp for sending asynchronous messages to SMSC server

we have a message campaign where we send over 100k messages (SMS) a day. So we are a client of SMSC server. We have no influence on SMSC server code. Before some time, we had around 80-90 message per second, now frequency dropped to 15 messages per second, according to tcpdumps.
I have few information regarding this, so I will try to explain best as I can.
So we are using Spring Boot 2.7 and open source jsmpp (3.0.0) library for sending SMS messages (PDU commands) to SMSC.
While reading about protocol (page 40), I noticed that there is a way to send messages asynchronously by providing a seqence_number. The code example is here. But I am not sure if that is going to help...
The code:
#Component
public class ClientConfig {
#Autowired
private MessageReceiverListener msgListener;
#Autowired
private SessionStateListener sessionListener;
private SMPPSession session;
public String charset = "ISO-10646-UCS-2";
public long idleReceiveTimeout = 65000;
public long checkBindingTimeout = 12000;
public long timeout = 7000;
public int enquireLinkTimeout = 15000;
public String hostIp = "someIpAddress";
public int port = 5000;
public String final systemId = "someSystemId";
public String final password = "password";
public BindType bindType = BindType.BIND_TRX; //transceiver
public String systemType = null;
public String addressRange = null;
public TypeOfNumber addrTon = TypeOfNumber.UNKNOWN;
public NumberingPlanIndicator addrNpi = NumberingPlanIndicator.UNKNOWN;
protected synchronized void tryToConnectToSmsc() throws Exception {
try {
// Connect to host
BindParameter bp = new BindParameter(bindType, systemId, password, systemType, addrTon, addrNpi, addressRange);
session = new SMPPSession();
session.setEnquireLinkTimer(enquireLinkTimer);
session.connectAndBind(host, port, bp, timeout);
session.setMessageReceiverListener(msgListener);
session.addSessionStateListener(sessionListener);
}
// Main connection failed.
catch (Exception e) {
//log and re-attempt connection logic here
}
}
}
The listeners:
#Component
public class MySessionListenerImpl implements SessionStateListener {
#Override
public void onStateChange(SessionState newState, SessionState oldState, Session source) {
//TODO
}
}
#Service
public class SmsListenerImpl implements MessageReceiverListener {
#Override
public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException {
//TODO
}
#Override
public void onAcceptAlertNotification(AlertNotification alertNotification) {}
#Override
public DataSmResult onAcceptDataSm(DataSm dataSm, Session session) throws ProcessRequestException {
return null;
}
}
Message sending service:
#Service
public class MessageSendingServiceImpl extends ClientConfig implements MessageSendingService{
private final ESMClass esmClass = new ESMClass();
private final byte protocolId = (byte) 0;
private final byte priorityFlag = (byte) 1;
private final TimeFormatter formatter = new AbsoluteTimeFormatter();
private final byte defaultMsgId = (byte) 0;
public SmsAdapterServiceImpl() {
super();
}
#PostConstruct
public synchronized void init() throws Exception {
super.tryToConnectToSmsc();
}
#Override
public String send(DomainObject obj){ //DomainObject -> contains fields: id, to, from, text, delivery, validity;
String serviceType = null;
//source
TypeOfNumber sourceTON = TypeOfNumber.NATIONAL; //there is some logic here which determines if it is INTERNATIOANL, ALPHANUMERIC etc...
NumberPlaningIndicator sourceNpi = NumberPlaningIndicator.ISDN; //constant...
String sourcePhone = obj.getFrom();
//destination
TypeOfNumber destinationTON = TypeOfNumber.NATIONAL; //there is some logic here which determines if it is INTERNATIOANL, ALPHANUMERIC etc...
NumberPlaningIndicator destinationNpi = NumberPlaningIndicator.ISDN; //constant...
String destinationPhone = obj.getTo();
String scheduledDeliveryTime = null;
if (obj.getDelivery() != null) scheduledDeliveryTime = formatter.format(obj.getDelivery());
String validityPeriodTime = null;
if (obj.getValidity() != null) validityPeriodTime = formatter.format(obj.getValidity());
Map<Short, OptionalParameter> optionalParameters = new HashMap<>();
String text = obj.getText();
if ( text.length() > 89 ) { //set text as payload instead of message text
OctetString os = new OctetString(OptionalParameter.Tag.MESSAGE_PAYLOAD.code(), text, "ISO-10646-UCS-2"); //"ISO-10646-UCS-2" - encoding
optionalParameters.put(os.tag, os);
text = "";
}
String msgId =
session.submitShortMessage( serviceType ,
sourceTON ,
sourceNpi ,
sourcePhone ,
destinationTON ,
destinationNpi ,
destinationPhone ,
esmClass ,
protocolId ,
priorityFlag ,
scheduledDeliveryTime ,
validityPeriodTime ,
new RegisteredDelivery() ,
ReplaceIfPresentFlag.DEFAULT.value() ,
new GeneralDataCoding(Alphabet.ALPHA_UCS2) ,
defaultMsgId ,
text.getBytes("ISO-10646-UCS-2") ,
optionalParameters.values().toArray(new OptionalParameter[0]));
return msgId;
}
}
Client code which invokes the service (it is actually a scheduler job):
#Autowired private MessageSendingService messageSendingService;
#Scheduled(cron)
public void execute() {
List<DomainObject> messages = repository.findMessages(pageable, config.getBatch()); //up to several thousand
start(messages);
ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(getSchedulerConfiguration().getPoolSize(), new NamedThreadFactory("Factory"));
List<DomainObject> domainObjects = Collections.synchronizedList(messages);
List<List<DomainObject>> domainObjectsPartitioned = partition(domainObjects.size(), config.getPoolSize()); //pool size is 4
for (List<DomainObject> list: domainObjectsPartitioned ) {
executorService.execute(new Runnable() {
#Override
public void run() {
try {
start(list);
} catch (Exception e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
private void start(List<DomainObject> list){
for (DomainObject> obj : list) {
String mid = messageSendingService.send(obj);
//do smtg with id...
}
}

UDP Socket: java.net.SocketException: socket closed

MessageCreator: Encapsulate and resolve ports and device unique identifiers.
public class MessageCreator {
private static final String HEADER_PORT = "to port:";
private static final String HEADER_SN = "My sn:";
public static String buildWithPort(int port) {
return HEADER_PORT + port;
}
public static int parsePort(String data) {
if (data.startsWith(HEADER_PORT)) {
return Integer.parseInt(data.substring(HEADER_PORT.length()));
}
return -1;
}
public static String buildWithSn(String sn) {
return HEADER_SN + sn;
}
public static String parseSn(String data) {
if (data.startsWith(HEADER_SN)) {
return data.substring(HEADER_SN.length());
}
return null;
}
}
UdpProvider: Loop to listen to a specific port, then parse the received data, determine whether the data conforms to the predetermined format, get the sender's response port from it, and respond with the uniquely identified UUID value to the UDP searcher.
public class UdpProvider {
public static void main(String[] args) throws IOException {
String sn = UUID.randomUUID().toString();
Provider provider = new Provider(sn);
provider.start();
// Warning: Result of 'InputStream.read()' is ignored
System.in.read();
provider.exit();
}
private static class Provider extends Thread {
private DatagramSocket ds = null;
private boolean done = false;
private final String sn;
public Provider(String sn) {
super();
this.sn = sn;
}
#Override
public void run() {
super.run();
try {
ds = new DatagramSocket(20000);
while (!done) {
final byte[] buf = new byte[512];
DatagramPacket receivePak = new DatagramPacket(buf, buf.length);
ds.receive(receivePak);
String ip = receivePak.getAddress().getHostAddress();
int port = receivePak.getPort();
byte[] receivePakData = receivePak.getData();
String receiveData = new String(receivePakData, 0, /*receivePakData.length*/receivePak.getLength());
System.out.println("received from -> ip: " + ip + ", port: " + port + ", data: " + receiveData);
int responsePort = MessageCreator.parsePort(receiveData.trim());
if (responsePort != -1) {
String responseData = MessageCreator.buildWithSn(sn);
byte[] bytes = responseData.getBytes(StandardCharsets.UTF_8);
DatagramPacket responsePak = new DatagramPacket(bytes, bytes.length,
/*InetAddress.getLocalHost()*/
receivePak.getAddress(),
responsePort);
ds.send(responsePak);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close();
}
}
public void exit() {
done = true;
close();
}
public void close() {
if (ds != null) {
ds.close();
ds = null;
}
}
}
}
UdpSearcher: Listening to a specific port and sending a LAN broadcast, sending a broadcast sets the listening port in the data, so you need to turn on listening first to finish before you can send a broadcast, and once you receive the response data, you can parse the device information
public class UdpSearcher {
private static final int LISTENER_PORT = 30000;
public static void main(String[] args) throws IOException, InterruptedException {
Listener listener = listen();
sendBroadcast();
// Warning: Result of 'InputStream.read()' is ignored
System.in.read();
List<Device> deviceList = listener.getDevicesAndClose();
for (Device device : deviceList) {
System.out.println(device);
}
}
public static void sendBroadcast() throws IOException {
DatagramSocket ds = new DatagramSocket();
String sendData = MessageCreator.buildWithPort(LISTENER_PORT);
byte[] sendDataBytes = sendData.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPak = new DatagramPacket(sendDataBytes, sendDataBytes.length);
sendPak.setAddress(InetAddress.getByName("255.255.255.255"));
sendPak.setPort(20000);
ds.send(sendPak);
ds.close();
}
public static Listener listen() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
Listener listener = new Listener(LISTENER_PORT, countDownLatch);
listener.start();
countDownLatch.await();
return listener;
}
private static class Listener extends Thread {
private final int listenPort;
private DatagramSocket ds = null;
private boolean done = false;
private final CountDownLatch countDownLatch;
private List<Device> devices = new ArrayList<>();
public Listener(int listenPort, CountDownLatch countDownLatch) {
super();
this.listenPort = listenPort;
this.countDownLatch = countDownLatch;
}
#Override
public void run() {
super.run();
countDownLatch.countDown();
try {
ds = new DatagramSocket(listenPort);
while (!done) {
final byte[] buf = new byte[512];
DatagramPacket receivePak = new DatagramPacket(buf, buf.length);
ds.receive(receivePak);
String ip = receivePak.getAddress().getHostAddress();
int port = receivePak.getPort();
byte[] data = receivePak.getData();
String receiveData = new String(data, 0, /*data.length*/receivePak.getLength());
String sn = MessageCreator.parseSn(receiveData);
System.out.println("received from -> ip: " + ip + ", port: " + port + ", data: " + receiveData);
if (sn != null) {
Device device = new Device(ip, port, sn);
devices.add(device);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close();
}
}
public void close() {
if (ds != null) {
ds.close();
ds = null;
}
}
public List<Device> getDevicesAndClose() {
done = true;
close();
return devices;
}
}
private static class Device {
private String ip;
private int port;
private String sn;
public Device(String ip, int port, String sn) {
this.ip = ip;
this.port = port;
this.sn = sn;
}
#Override
public String toString() {
return "Device{" +
"ip='" + ip + '\'' +
", port=" + port +
", sn='" + sn + '\'' +
'}';
}
}
}
UdpProvider and UdpSearcher worked fine and printed corrresponding data until I input a char sequence from keyboard follwed by pressing Enter key on each console window, both threw an exception on this line ds.receive(receivePak); :

Read rcon command response

I want to send rcon command to server using java, to do this I'm using the following library https://github.com/Kronos666/rkon-core
When i run command like this
Rcon rcon = new Rcon("127.0.0.1", 27015, "mypassword".getBytes());
// Example: On a minecraft server this will list the connected players
String result = rcon.command("list");
// Display the result in the console
System.out.println(result);
My server show response in console Gc connection established from... and so on
but in java app i have the empty result, it's not null, it's just empty
String result = rcon.command("list");
How can i take response from server using rcon protocol?
Try this:
try {
Rcon rcon = new Rcon("127.0.0.1", 27015, "mypassword".getBytes());
String result = rcon.command("list");
System.out.println(result);
} catch (AuthenticationException e) {
String result = "Authentication failed";
}
Finally I write my own implementation:
public final class RconClient implements AutoCloseable {
private static final int MAX_SIZE = 4096;
private final Socket socket;
private final RconData data;
private static final Logger LOG = LoggerFactory.getLogger(RconClient.class);
#SuppressWarnings("ConstructorShouldNotThrowExceptions")
public RconClient(final String host,
final int port,
final byte[] password) throws IOException {
this.socket = new Socket(host, port);
final RconData requst = request(new RconData(RconData.AUTH, password));
if (requst.getId() == -1) {
LOG.error("Wrong password or ip to connect to rcon");
throw new LoginException(host, port);
}
this.data = read();
}
public String command(String command) throws IOException {
command = "get5_status";
final RconData response = request(new RconData(command.getBytes()));
return new String(response.getPayload(), Charset.forName("UTF-8"));
}
public RconData request(RconData packet) throws IOException {
try {
write(packet);
return read();
} catch (final SocketException exception) {
socket.close();
throw exception;
}
}
private void write(RconData packet) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(packet.getPayload().length + 14);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(packet.getPayload().length + 10);
buffer.putInt(packet.getId());
buffer.putInt(packet.getType());
buffer.put(packet.getPayload());
buffer.put((byte)0);
buffer.put((byte)0);
socket.getOutputStream().write(buffer.array());
socket.getOutputStream().flush();
}
private RconData read() throws IOException {
byte[] packet = new byte[MAX_SIZE];
int packetSize = this.socket.getInputStream().read(packet);
ByteBuffer buffer = ByteBuffer.wrap(packet, 0, packetSize);
buffer.order(ByteOrder.LITTLE_ENDIAN);
if (buffer.remaining() < 4) {
throw new WrongPacketException();
}
int size = buffer.getInt();
if (buffer.remaining() < size) {
throw new WrongPacketException();
}
int id = buffer.getInt();
int type = buffer.getInt();
byte[] payload = new byte[size - 10];
buffer.get(payload);
buffer.get(new byte[2]);
return new RconData(id, type, payload);
}
#Override
public void close() throws IOException {
this.socket.close();
}
}
Where RconData it's simple POJO with byte[] password property,

java client and C# server working with unicode

I'm trying to build a tcp client and a server that will work with unicode. the server is in C# and looks like that:
static void Main(string[] args)
{
// tcp setup
TcpListener serverSocket = new TcpListener(8888);
TcpClient clientSocket = default(TcpClient);
serverSocket.Start();
// waiting for client to connect
clientSocket = serverSocket.AcceptTcpClient();
// client comunication
string resAscii = Recv(clientSocket);
Send(clientSocket, "got from you: " + resAscii);
string resUnicode = Recv(clientSocket);
Send(clientSocket, "קיבלתי ממך: " + resUnicode);
Console.ReadKey();
}
public static void Send(TcpClient client, string msg)
{
Byte[] sendtBytes = Encoding.Unicode.GetBytes(msg + "$");
client.GetStream().Write(sendtBytes, 0, sendtBytes.Length);
client.GetStream().Flush();
}
public static string Recv(TcpClient client)
{
byte[] recvBytes = new byte[65537];
client.GetStream().Read(recvBytes, 0, (int)client.ReceiveBufferSize);
string dataFromClient = System.Text.Encoding.Unicode.GetString(recvBytes);
dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf('$'));
return dataFromClient;
}
and the client is in java (android):
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
#Override
public void run() {
try {
Socket s = new Socket("192.168.0.102", 8888);
Send(s, "this is ascii");
String asciiString = Recv(s);
Send(s, "זה יוניקוד");
String unicodeString = Recv(s);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private String Recv(Socket s) throws IOException {
byte[] b = new byte[10080];
int read = s.getInputStream().read(b, 0, 1000);
String ret = new String(b, "UTF16");
ret = ret.substring(0, ret.indexOf('$'));
return ret;
}
private void Send(Socket s, String msg) throws IOException {
s.getOutputStream().write((msg + "$").getBytes("UTF16"));
}
When I'm sending from the client to the server I get the message. But when the server sending to the client I get only Chinese letters:
client (android) error
how can I fix it?
As #flakes said that was the answer.
change this line in the Recv function:
String ret = new String(b, "UTF16");
with this line:
String ret = new String(b, StandardCharsets.UTF_16LE);
and
s.getOutputStream().write((msg + "$").getBytes("UTF16"));
with this line in the Send file:
s.getOutputStream().write((msg + "$").getBytes(StandardCharsets.UTF_16LE));

Unable to receive proper UDP packets using SSDP

I'm trying to implement a very simple SSDP functionality into my android app taken from here.
My application sends some UDP packets containing a relevant M-SEARCH message to the broadcasting address without any issue. The problem is, I should be getting a proper response back from other devices that's running a UPNP server. For some reason, I'm only receiving the exact same packets back I sent from my android device.
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
WifiManager.MulticastLock multicastLock = wm.createMulticastLock("multicastLock");
multicastLock.setReferenceCounted(true);
multicastLock.acquire();
setContentView(R.layout.activity_main);
((Button)this.findViewById(R.id.btnSendSSDPSearch)).setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSendSSDPSearch:
new Thread(new Runnable() {
#Override
public void run() {
SendMSearchMessage();
}
}).start();
default:
break;
}
}
private void SendMSearchMessage() {
SSDPSearchMsg searchContentDirectory = new SSDPSearchMsg(SSDPConstants.ST_ContentDirectory);
SSDPSearchMsg searchAVTransport = new SSDPSearchMsg(SSDPConstants.ST_AVTransport);
SSDPSearchMsg searchProduct = new SSDPSearchMsg(SSDPConstants.ST_Product);
SSDPSocket sock;
try {
sock = new SSDPSocket();
for (int i = 0; i < 2; i++) {
sock.send(searchContentDirectory.toString());
sock.send(searchAVTransport.toString());
sock.send(searchProduct.toString());
}
while (true) {
DatagramPacket dp = sock.receive(); //Here, I only receive the same packets I initially sent above
String c = new String(dp.getData());
System.out.println(c);
}
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e("M-SEARCH", e.getMessage());
}
SSDPSocket.java where the UDP packet transmission is actually done
public class SSDPSocket {
SocketAddress mSSDPMulticastGroup;
MulticastSocket mSSDPSocket;
InetAddress broadcastAddress;
public SSDPSocket() throws IOException {
mSSDPSocket = new MulticastSocket(55325); //Bind some random port for receiving datagram
broadcastAddress = InetAddress.getByName(SSDPConstants.ADDRESS);
mSSDPSocket.joinGroup(broadcastAddress);
}
/* Used to send SSDP packet */
public void send(String data) throws IOException {
DatagramPacket dp = new DatagramPacket(data.getBytes(), data.length(),
broadcastAddress,SSDPConstants.PORT);
mSSDPSocket.send(dp);
}
/* Used to receive SSDP packet */
public DatagramPacket receive() throws IOException {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
mSSDPSocket.receive(dp);
return dp;
}
public void close() {
if (mSSDPSocket != null) {
mSSDPSocket.close();
}
}
}
SSDPSearchMsg.java for constructing SSDP Broadcast string
(Probably unrelated to the problem I'm experiencing but just in case)
public class SSDPSearchMsg {
static final String HOST = "Host:" + SSDP.ADDRESS + ":" + SSDP.PORT;
static final String MAN = "Man:ssdp:discover";
static final String NEWLINE = System.getProperty("line.separator");
int mMX = 3; /* seconds to delay response */
String mST; /* Search target */
public SSDPSearchMsg(String ST) {
mST = ST;
}
public int getmMX() {
return mMX;
}
public void setmMX(int mMX) {
this.mMX = mMX;
}
public String getmST() {
return mST;
}
public void setmST(String mST) {
this.mST = mST;
}
#Override
public String toString() {
StringBuilder content = new StringBuilder();
content.append(SSDP.SL_MSEARCH).append(NEWLINE);
content.append(HOST).append(NEWLINE);
content.append(MAN).append(NEWLINE);
content.append(mST).append(NEWLINE);
content.append("MX:" + mMX).append(NEWLINE);
content.append(NEWLINE);
return content.toString();
}
}
SSDPConstants.java
public class SSDPConstants {
/* New line definition */
public static final String NEWLINE = "\r\n";
public static final String ADDRESS = "239.255.255.250";
public static final int PORT = 1900;
/* Definitions of start line */
public static final String SL_NOTIFY = "NOTIFY * HTTP/1.1";
public static final String SL_MSEARCH = "M-SEARCH * HTTP/1.1";
public static final String SL_OK = "HTTP/1.1 200 OK";
/* Definitions of search targets */
public static final String ST_RootDevice = "St: rootdevice";
public static final String ST_ContentDirectory = "St: urn:schemas-upnp-org:service:ContentDirectory:1";
public static final String ST_AVTransport = "St: urn:schemas-upnp-org:service:AVTransport:1";
public static final String ST_Product = "St: urn:av-openhome-org:service:Product:1";
/* Definitions of notification sub type */
public static final String NTS_ALIVE = "NTS:ssdp:alive";
public static final String NTS_BYE = "NTS:ssdp:byebye";
public static final String NTS_UPDATE = "NTS:ssdp:update";
}
I also made sure that the manifest includes relevant permissions:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
I'm testing the app on an actual device, not on emulator.
Any assistance would be appreciated.
Edit upon comment:
Multicast itself should work without issue. I've downloaded an app called BubbleUPNP to test the SSDP functionality. Sure enough, wireshark properly captures all messages sent from the phone to the broadcasting address in SSDP Protocol:
M-SEARCH * HTTP/1.1
Man: "ssdp:discover"
Mx: 3
Host: 239.255.255.250:1900
St: urn:schemas-upnp-org:service:AVTransport:1
And the response
HTTP/1.1 200 OK
ST:urn:schemas-upnp-org:service:ContentDirectory:1
USN:uuid:d5829e90-73ce-4213-9ad1-4e75dbdd0232::urn:schemas-upnp-org:service:ContentDirectory:1
Location:http://10.175.95.4:2869/upnphost/udhisapi.dll?content=uuid:d5829e90-73ce-4213-9ad1-4e75dbdd0232
OPT:"http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS:05f3dd08b4b4b5aafa1fe983fa447f49
Cache-Control:max-age=900
Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0
So yeah, this is without a doubt, an implementation issue. Nothing wrong with the device.
Weird. I fixed the issue but I'm genuinely not sure what made it work.
Here are some changes that I made:
Instead of assigning a fixed port, I made it dynamically allocate an available port.
public class SSDPSocket {
SocketAddress mSSDPMulticastGroup;
MulticastSocket mSSDPSocket;
InetAddress broadcastAddress;
public SSDPSocket() throws IOException {
mSSDPSocket = new MulticastSocket();
broadcastAddress = InetAddress.getByName(SSDPConstants.ADDRESS);
mSSDPSocket.joinGroup(broadcastAddress);
}
...
}
I also changed the M-Search message structure, including its order.
public class SSDPSearchMsg {
static final String HOST = "Host: " + SSDPConstants.ADDRESS + ":" + SSDPConstants.PORT;
static final String MAN = "Man: \"ssdp:discover\"";
static final String NEWLINE = "\r\n";
int mMX = 3; /* seconds to delay response */
String mST; /* Search target */
public SSDPSearchMsg(String ST) {
mST = ST;
}
public int getmMX() {
return mMX;
}
public void setmMX(int mMX) {
this.mMX = mMX;
}
public String getmST() {
return mST;
}
public void setmST(String mST) {
this.mST = mST;
}
#Override
public String toString() {
StringBuilder content = new StringBuilder();
content.append(SSDPConstants.SL_MSEARCH).append(NEWLINE);
content.append(MAN).append(NEWLINE);
content.append("Mx: " + mMX).append(NEWLINE);
content.append(HOST).append(NEWLINE);
content.append(mST).append(NEWLINE);
content.append(NEWLINE);
return content.toString();
}
}
And everything suddenly works. Why it works is beyond me. My previous implementation follows the SSDP protocol as far as I can tell.
On possible answer is that you may have an "old" device. Apparently multicast (from Java) is broken before Android 2.3.7
Reference: https://stackoverflow.com/a/9836464/139985
Another possibility is that it is a device-specific problem; e.g. like this: https://stackoverflow.com/a/3714848/139985. (I'm not saying it is that specific problem ...)
Another is that multicast is disabled in the kernel configs: http://code.google.com/p/android/issues/detail?id=51195
It seems like there are a range of different causes for multicast not working on various Android devices ...
I followed your code and change some words to upper case. It works.
static final String HOST = "HOST: " + SSDPConstants.ADDRESS + ":" + SSDPConstants.PORT;
static final String MAN = "MAN: \"ssdp:discover\"";
Don't need to change the order in MSEARCH message.

Categories