Merge "Add MdnsPacketRepeater"
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.mdns;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests.
|
||||
*
|
||||
* TODO: implement
|
||||
*/
|
||||
public class MdnsAdvertiser {
|
||||
private static final String TAG = MdnsAdvertiser.class.getSimpleName();
|
||||
public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.mdns;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class holding data that can be included in a mDNS packet.
|
||||
*/
|
||||
public class MdnsPacket {
|
||||
public final int flags;
|
||||
public final List<MdnsRecord> questions;
|
||||
public final List<MdnsRecord> answers;
|
||||
public final List<MdnsRecord> authorityRecords;
|
||||
public final List<MdnsRecord> additionalRecords;
|
||||
|
||||
MdnsPacket(int flags,
|
||||
List<MdnsRecord> questions,
|
||||
List<MdnsRecord> answers,
|
||||
List<MdnsRecord> authorityRecords,
|
||||
List<MdnsRecord> additionalRecords) {
|
||||
this.flags = flags;
|
||||
this.questions = Collections.unmodifiableList(questions);
|
||||
this.answers = Collections.unmodifiableList(answers);
|
||||
this.authorityRecords = Collections.unmodifiableList(authorityRecords);
|
||||
this.additionalRecords = Collections.unmodifiableList(additionalRecords);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.mdns;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* A class used to send several packets at given time intervals.
|
||||
* @param <T> The type of the request providing packet repeating parameters.
|
||||
*/
|
||||
public abstract class MdnsPacketRepeater<T extends MdnsPacketRepeater.Request> {
|
||||
private static final boolean DBG = MdnsAdvertiser.DBG;
|
||||
@NonNull
|
||||
private final MdnsReplySender mReplySender;
|
||||
@NonNull
|
||||
protected final Handler mHandler;
|
||||
@Nullable
|
||||
private final PacketRepeaterCallback<T> mCb;
|
||||
|
||||
/**
|
||||
* Status callback from {@link MdnsPacketRepeater}.
|
||||
*
|
||||
* Callbacks are called on the {@link MdnsPacketRepeater} handler thread.
|
||||
* @param <T> The type of the request providing packet repeating parameters.
|
||||
*/
|
||||
public interface PacketRepeaterCallback<T extends MdnsPacketRepeater.Request> {
|
||||
/**
|
||||
* Called when a packet was sent.
|
||||
*/
|
||||
default void onSent(int index, @NonNull T info) {}
|
||||
|
||||
/**
|
||||
* Called when the {@link MdnsPacketRepeater} is done sending packets.
|
||||
*/
|
||||
default void onFinished(@NonNull T info) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to repeat packets.
|
||||
*
|
||||
* All methods are called in the looper thread.
|
||||
*/
|
||||
public interface Request {
|
||||
/**
|
||||
* Get a packet to send for one iteration.
|
||||
*/
|
||||
@NonNull
|
||||
MdnsPacket getPacket(int index);
|
||||
|
||||
/**
|
||||
* Get a set of destinations for the packet for one iteration.
|
||||
*/
|
||||
@NonNull
|
||||
Iterable<SocketAddress> getDestinations(int index);
|
||||
|
||||
/**
|
||||
* Get the delay in milliseconds until the next packet transmission.
|
||||
*/
|
||||
long getDelayMs(int nextIndex);
|
||||
|
||||
/**
|
||||
* Get the number of packets that should be sent.
|
||||
*/
|
||||
int getNumSends();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logging tag to use.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract String getTag();
|
||||
|
||||
private final class ProbeHandler extends Handler {
|
||||
ProbeHandler(@NonNull Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
final int index = msg.arg1;
|
||||
final T request = (T) msg.obj;
|
||||
|
||||
if (index >= request.getNumSends()) {
|
||||
if (mCb != null) {
|
||||
mCb.onFinished(request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final MdnsPacket packet = request.getPacket(index);
|
||||
final Iterable<SocketAddress> destinations = request.getDestinations(index);
|
||||
if (DBG) {
|
||||
Log.v(getTag(), "Sending packets to " + destinations + " for iteration "
|
||||
+ index + " out of " + request.getNumSends());
|
||||
}
|
||||
for (SocketAddress destination : destinations) {
|
||||
try {
|
||||
mReplySender.sendNow(packet, destination);
|
||||
} catch (IOException e) {
|
||||
Log.e(getTag(), "Error sending packet to " + destination, e);
|
||||
}
|
||||
}
|
||||
|
||||
int nextIndex = index + 1;
|
||||
// No need to go through the last handler loop if there's no callback to call
|
||||
if (nextIndex < request.getNumSends() || mCb != null) {
|
||||
// TODO: consider using AlarmManager / WakeupMessage to avoid missing sending during
|
||||
// deep sleep; but this would affect battery life, and discovered services are
|
||||
// likely not to be available since the device is in deep sleep anyway.
|
||||
final long delay = request.getDelayMs(nextIndex);
|
||||
sendMessageDelayed(obtainMessage(msg.what, nextIndex, 0, request), delay);
|
||||
if (DBG) Log.v(getTag(), "Scheduled next packet in " + delay + "ms");
|
||||
}
|
||||
|
||||
// Call onSent after scheduling the next run, to allow the callback to cancel it
|
||||
if (mCb != null) {
|
||||
mCb.onSent(index, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected MdnsPacketRepeater(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
|
||||
@Nullable PacketRepeaterCallback<T> cb) {
|
||||
mHandler = new ProbeHandler(looper);
|
||||
mReplySender = replySender;
|
||||
mCb = cb;
|
||||
}
|
||||
|
||||
protected void startSending(int id, @NonNull T request, long initialDelayMs) {
|
||||
if (DBG) {
|
||||
Log.v(getTag(), "Starting send with id " + id + ", request "
|
||||
+ request.getClass().getSimpleName() + ", delay " + initialDelayMs);
|
||||
}
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(id, 0, 0, request), initialDelayMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop sending the packets for the specified ID
|
||||
* @return true if probing was in progress, false if this was a no-op
|
||||
*/
|
||||
public boolean stop(int id) {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException("stop can only be called from the looper thread");
|
||||
}
|
||||
// Since this is run on the looper thread, messages cannot be currently processing and are
|
||||
// all in the handler queue; unless this method is called from a message, but the current
|
||||
// message cannot be cancelled.
|
||||
if (mHandler.hasMessages(id)) {
|
||||
if (DBG) {
|
||||
Log.v(getTag(), "Stopping send on id " + id);
|
||||
}
|
||||
mHandler.removeMessages(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import java.util.Map;
|
||||
public class MdnsPacketWriter {
|
||||
private static final int MDNS_POINTER_MASK = 0xC000;
|
||||
private final byte[] data;
|
||||
private final Map<Integer, String[]> labelDictionary;
|
||||
private final Map<Integer, String[]> labelDictionary = new HashMap<>();
|
||||
private int pos = 0;
|
||||
private int savedWritePos = -1;
|
||||
|
||||
@@ -44,7 +44,15 @@ public class MdnsPacketWriter {
|
||||
}
|
||||
|
||||
data = new byte[maxSize];
|
||||
labelDictionary = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a writer for a new packet.
|
||||
*
|
||||
* @param buffer The buffer to write to.
|
||||
*/
|
||||
public MdnsPacketWriter(byte[] buffer) {
|
||||
data = buffer;
|
||||
}
|
||||
|
||||
/** Returns the current write position. */
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.mdns;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* A class that handles sending mDNS replies to a {@link MulticastSocket}, possibly queueing them
|
||||
* to be sent after some delay.
|
||||
*
|
||||
* TODO: implement sending after a delay, combining queued replies and duplicate answer suppression
|
||||
*/
|
||||
public class MdnsReplySender {
|
||||
@NonNull
|
||||
private final MulticastSocket mSocket;
|
||||
@NonNull
|
||||
private final Looper mLooper;
|
||||
@NonNull
|
||||
private final byte[] mPacketCreationBuffer;
|
||||
|
||||
public MdnsReplySender(@NonNull Looper looper,
|
||||
@NonNull MulticastSocket socket, @NonNull byte[] packetCreationBuffer) {
|
||||
mLooper = looper;
|
||||
mSocket = socket;
|
||||
mPacketCreationBuffer = packetCreationBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet immediately.
|
||||
*
|
||||
* Must be called on the looper thread used by the {@link MdnsReplySender}.
|
||||
*/
|
||||
public void sendNow(@NonNull MdnsPacket packet, @NonNull SocketAddress destination)
|
||||
throws IOException {
|
||||
if (Thread.currentThread() != mLooper.getThread()) {
|
||||
throw new IllegalStateException("sendNow must be called in the handler thread");
|
||||
}
|
||||
|
||||
// TODO: support packets over size (send in multiple packets with TC bit set)
|
||||
final MdnsPacketWriter writer = new MdnsPacketWriter(mPacketCreationBuffer);
|
||||
|
||||
writer.writeUInt16(0); // Transaction ID (advertisement: 0)
|
||||
writer.writeUInt16(packet.flags); // Response, authoritative (rfc6762 18.4)
|
||||
writer.writeUInt16(packet.questions.size()); // questions count
|
||||
writer.writeUInt16(packet.answers.size()); // answers count
|
||||
writer.writeUInt16(packet.authorityRecords.size()); // authority entries count
|
||||
writer.writeUInt16(packet.additionalRecords.size()); // additional records count
|
||||
|
||||
for (MdnsRecord record : packet.questions) {
|
||||
record.write(writer, 0L);
|
||||
}
|
||||
for (MdnsRecord record : packet.answers) {
|
||||
record.write(writer, 0L);
|
||||
}
|
||||
for (MdnsRecord record : packet.authorityRecords) {
|
||||
record.write(writer, 0L);
|
||||
}
|
||||
for (MdnsRecord record : packet.additionalRecords) {
|
||||
record.write(writer, 0L);
|
||||
}
|
||||
|
||||
final int len = writer.getWritePosition();
|
||||
final byte[] outBuffer = new byte[len];
|
||||
System.arraycopy(mPacketCreationBuffer, 0, outBuffer, 0, len);
|
||||
|
||||
mSocket.send(new DatagramPacket(outBuffer, 0, len, destination));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user