Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion wled00/data/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ function sendDDP(ws, start, len, colors) {
let pkt = new Uint8Array(11 + dLen); // DDP header is 10 bytes, plus 1 byte for WLED websocket protocol indicator
pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1
pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0
pkt[2] = 0x00; // reserved
pkt[2] = 0x00; // upper nibble is reserved, lower nibble is sequence number, if set to 0 no sequence checking is done (if enabled)
pkt[3] = 0x0B; // RGB, 8bit per channel
pkt[4] = 0x01; // destination id (not used but 0x01 is default output)
pkt[5] = (off >> 24) & 255; // DDP protocol 4-7 is offset
Expand Down
48 changes: 33 additions & 15 deletions wled00/e131.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#define MAX_CHANNELS_PER_UNIVERSE 512

// forward declarations
static void handleDDPPacket(e131_packet_t* p);
static void handleDDPPacket(e131_packet_t* p, size_t packetLen);
static void handleArtnetPollReply(IPAddress ipAddress);
static void prepareArtnetPollReply(ArtPollReply *reply);
static void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t portAddress);
Expand All @@ -17,20 +17,31 @@ static void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16

//DDP protocol support, called by handleE131Packet
//handles RGB data only
static void handleDDPPacket(e131_packet_t* p) {
static void handleDDPPacket(e131_packet_t* p, size_t packetLen) {
static bool ddpSeenPush = false; // have we seen a push yet?
int lastPushSeq = e131LastSequenceNumber[0];

if (packetLen < DDP_HEADER_LEN) return; // too short to safely read any DDP header fields

// reject unsupported color data types (only RGB and RGBW are supported)
uint8_t maskedType = p->dataType & 0x3F; // mask out custom and reserved flags, only type bits are relevant
if (maskedType != DDP_TYPE_RGB24 && maskedType != DDP_TYPE_RGBW32) return;
//uint8_t maskedType = p->dataType & 0x3F; // mask out custom and reserved flags, only type bits are relevant
//if (maskedType != DDP_TYPE_RGB24 && maskedType != DDP_TYPE_RGBW32) return;

// note: for maximum compatibility we do not reject unknonw or malformed data types but simply default to RGB24 and check there is enough data available in the packet to do so
// also we assume 8bit per channel and currently do not support other bit depths

// reject control, status and config packets (not implemented)
if (p->destination == DDP_ID_CONTROL || p->destination == DDP_ID_STATUS || p->destination == DDP_ID_CONFIG) return;

// reject query and response packets (not implemented)
if (p->flags & (DDP_FLAGS_QUERY | DDP_FLAGS_REPLY)) return;

// reject status and config packets (not implemented)
if (p->destination == DDP_ID_STATUS || p->destination == DDP_ID_CONFIG) return;
bool push = p->flags & DDP_FLAGS_PUSH; // push flag means "render now"
if (!push && (p->flags & DDP_FLAGS_STORAGE)) return; // reject "from storage" flag but still let the push flag pass if set along with it

//reject late packets belonging to previous frame (assuming 4 packets max. before push)
//reject late packets belonging to previous frame (assuming 4 packets max. before push, if more are used and packets are very late, they are still accepted)
if (e131SkipOutOfSequence && lastPushSeq) {
int sn = p->sequenceNum & 0xF;
int sn = p->sequenceNum & 0xF; // sequence number is 4 bits, 1-15, 0 means unused
if (sn) {
if (lastPushSeq > 5) {
if (sn > (lastPushSeq -5) && sn < lastPushSeq) return;
Expand All @@ -40,7 +51,8 @@ static void handleDDPPacket(e131_packet_t* p) {
}
}

unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel)
unsigned ddpChannelsPerLed = 3; // default to RGB
if ((p->dataType & 0b00111000)>>3 == 0b011) ddpChannelsPerLed = 4; // RGBW data type (see DDP protocol definition)

uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
start += DMXAddress / ddpChannelsPerLed;
Expand All @@ -50,8 +62,14 @@ static void handleDDPPacket(e131_packet_t* p) {
unsigned c = 0;
if (p->flags & DDP_FLAGS_TIME) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later

// ensure the received packet is at least as long as the header claims
if (packetLen < DDP_HEADER_LEN + c + dataLen) {
DEBUG_PRINTLN(F("DDP packet incomplete"));
return;
}

unsigned numLeds = stop - start; // stop >= start is guaranteed
unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
unsigned maxDataIndex = numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
if (maxDataIndex > dataLen) {
DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting."));
return;
Expand All @@ -66,7 +84,6 @@ static void handleDDPPacket(e131_packet_t* p) {
}
}

bool push = p->flags & DDP_FLAGS_PUSH;
ddpSeenPush |= push;
if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display
e131NewData = true;
Expand All @@ -76,14 +93,15 @@ static void handleDDPPacket(e131_packet_t* p) {
}

//E1.31 and Art-Net protocol support
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen){

int uni = 0, dmxChannels = 0;
uint8_t* e131_data = nullptr;
int seq = 0, mde = REALTIME_MODE_E131;

if (protocol == P_ARTNET)
{
if (packetLen < 10) return; // need at least art_opcode (offset 8, 2 bytes)
if (p->art_opcode == ARTNET_OPCODE_OPPOLL) {
handleArtnetPollReply(clientIP);
return;
Expand All @@ -96,9 +114,9 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
} else if (protocol == P_E131) {
// Ignore PREVIEW data (E1.31: 6.2.6)
if ((p->options & 0x80) != 0) return;
dmxChannels = htons(p->property_value_count) - 1;
dmxChannels = htons(p->property_value_count) - 1; // on malformed packets, this can become negative, checked below
// DMX level data is zero start code. Ignore everything else. (E1.11: 8.5)
if (dmxChannels == 0 || p->property_values[0] != 0) return;
if (dmxChannels <= 0 || p->property_values[0] != 0) return;
uni = htons(p->universe);
e131_data = p->property_values;
seq = p->sequence_number;
Expand All @@ -110,7 +128,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
}
} else { //DDP
realtimeIP = clientIP;
handleDDPPacket(p);
handleDDPPacket(p, packetLen);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ void initDMXInput();
void handleDMXInput();

//e131.cpp
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol);
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen);
void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses);
// void handleArtnetPollReply(IPAddress ipAddress); // local function, only used in e131.cpp
// void prepareArtnetPollReply(ArtPollReply* reply); // local function, only used in e131.cpp
Expand Down
19 changes: 11 additions & 8 deletions wled00/src/dependencies/e131/ESPAsyncE131.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
#include "../network/Network.h"
#include <string.h>

// E1.17 ACN Packet Identifier
// E1.17 ACN Packet Identifier "ASC-E1.17"
const byte ESPAsyncE131::ACN_ID[12] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };

// Art-Net Packet Identifier
// Art-Net Packet Identifier "Art-Net"
const byte ESPAsyncE131::ART_ID[8] = { 0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00 };

// Constructor
Expand Down Expand Up @@ -100,13 +100,16 @@ bool ESPAsyncE131::initMulticast(uint16_t port, uint16_t universe, uint8_t n) {
void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
bool error = false;
uint8_t protocol = P_E131;
const size_t pktLen = _packet.length();

e131_packet_t *sbuff = reinterpret_cast<e131_packet_t *>(_packet.data());

//E1.31 packet identifier ("ACS-E1.17")
if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
protocol = P_ARTNET;


// E1.31 packet identifier (ACN_ID = "ASC-E1.17"), need at least 16 bytes to safely read acn_id (offset 4, length 12).
if (pktLen >= 16) {
if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
protocol = P_ARTNET;
}

if (protocol == P_ARTNET) {
if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id)))
error = true; //not "Art-Net"
Expand All @@ -129,6 +132,6 @@ void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
}

if (!error) {
_callback(sbuff, _packet.remoteIP(), protocol);
_callback(sbuff, _packet.remoteIP(), protocol, pktLen);
}
}
19 changes: 12 additions & 7 deletions wled00/src/dependencies/e131/ESPAsyncE131.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,24 @@ typedef struct ip_addr ip4_addr_t;
#define DDP_FLAGS_VER 0xc0 // version mask
#define DDP_FLAGS_VER1 0x40 // version=1
#define DDP_FLAGS_PUSH 0x01
#define DDP_FLAGS_QUERY 0x02
#define DDP_FLAGS_REPLY 0x04
#define DDP_FLAGS_STORAGE 0x08
#define DDP_FLAGS_QUERY 0x02 // unsupported - used by XLights for auto-discovery
#define DDP_FLAGS_REPLY 0x04 // unsupported - response packet from another display
#define DDP_FLAGS_STORAGE 0x08 // unsupported - show data from a storage unit instead of from packet data field. Data field defines storage unit (by name, number, URL or whatever mechanism wanted).
#define DDP_FLAGS_TIME 0x10

#define DDP_CHANNELS_PER_PACKET 1440 // 480 leds

#define DDP_TYPE_RGB24 0x0B // 00 001 011 (RGB , 8 bits per channel, 3 channels)
#define DDP_TYPE_RGBW32 0x1B // 00 011 011 (RGBW, 8 bits per channel, 4 channels)
#define DDP_TYPE_LEGACY 0x01 // 00 000 001 legacy RGB 8bit definition
#define DDP_TYPE_UNDEF 0x00 // type and bit depth undefined

#define DDP_ID_DISPLAY 1
#define DDP_ID_CONFIG 250
#define DDP_ID_STATUS 251
// DDP Source or Destination ID (header byte 3)
#define DDP_ID_DISPLAY 1 // default output device
#define DDP_ID_CONTROL 246 // JSON control (not implemented)
#define DDP_ID_CONFIG 250 // JSON config (not implemented)
#define DDP_ID_STATUS 251 // JSON status (not implemented)
#define DDP_ID_ALL 255 // all devices

#define ARTNET_OPCODE_OPDMX 0x5000
#define ARTNET_OPCODE_OPPOLL 0x2000
Expand Down Expand Up @@ -212,7 +217,7 @@ typedef union {
} ArtPollReply;

// new packet callback
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol);
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen);

class ESPAsyncE131 {
private:
Expand Down
1 change: 1 addition & 0 deletions wled00/udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const

// write the header
/*0*/ddpUdp.write(flags);
// TODO: sequence number should be 1-15 as 0 means "unused", it has no bad consequences other than out of sequence packet may be accepted
/*1*/ddpUdp.write(sequenceNumber++ & 0x0F); // sequence may be unnecessary unless we are sending twice (as requested in Sync settings)
/*2*/ddpUdp.write(isRGBW ? DDP_TYPE_RGBW32 : DDP_TYPE_RGB24);
/*3*/ddpUdp.write(DDP_ID_DISPLAY);
Expand Down
12 changes: 3 additions & 9 deletions wled00/ws.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,13 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if (!data || len < offset+1) return; // catch invalid / single-byte payload
switch (data[0]) {
case BINARY_PROTOCOL_E131:
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_E131);
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_E131, len - offset);
break;
case BINARY_PROTOCOL_ARTNET:
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_ARTNET);
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_ARTNET, len - offset);
break;
case BINARY_PROTOCOL_DDP:
if (len < 10 + offset) return; // DDP header is 10 bytes (+1 protocol byte)
size_t ddpDataLen = (data[8+offset] << 8) | data[9+offset]; // data length in bytes from DDP header
uint8_t flags = data[0+offset];
if ((flags & DDP_FLAGS_TIME) ) ddpDataLen += 4; // timecode flag adds 4 bytes to data length
if (len < (10 + offset + ddpDataLen)) return; // not enough data, prevent out of bounds read
// could be a valid DDP packet, forward to handler
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_DDP);
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_DDP, len - offset);
}
}
} else {
Expand Down
Loading