다음 C11 예제는 인코딩과 수신기를 함께 보여줍니다. 수신기는 시작 END를 기다리고, ESC 뒤에 허용되지 않은 값이 오거나 8바이트 예제 버퍼를 넘으면 해당 프레임을 끝 END까지 버립니다. 이 엄격한 오류 처리와 8바이트 한도는 이 예제의 설계 정책입니다. 실제 SLIP/IP 패킷에 8바이트 버퍼를 사용하지 마세요. RFC 1055는 기존 구현의 1006바이트 수신 권고를 설명합니다. END 뒤에서 잘린 ESC도 손상 프레임으로 처리합니다.
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
enum { END = 0xC0, ESC = 0xDB, ESC_END = 0xDC, ESC_ESC = 0xDD };
/* Returns bytes written, or zero when the output buffer is too small. */
static size_t slip_encode(const uint8_t *src, size_t count,
uint8_t *dst, size_t capacity) {
size_t used = 0;
if (capacity == 0) return 0;
dst[used++] = END; /* discard any prior line noise */
for (size_t i = 0; i < count; ++i) {
if (src[i] == END || src[i] == ESC) {
if (capacity - used < 2) return 0;
dst[used++] = ESC;
dst[used++] = src[i] == END ? ESC_END : ESC_ESC;
} else {
if (capacity == used) return 0;
dst[used++] = src[i];
}
}
if (capacity == used) return 0;
dst[used++] = END;
return used;
}
typedef struct {
uint8_t data[8]; /* choose a bound for your protocol */
size_t used;
bool started, escaped, discard;
} SlipRx;
/* Feed one UART byte: 1 = packet, 0 = pending, -1 = damaged/oversize. */
static int slip_feed(SlipRx *rx, uint8_t byte,
uint8_t *packet, size_t *packet_len) {
if (byte == END) {
int event = 0;
if (rx->started) {
if (rx->discard || rx->escaped) event = -1;
else if (rx->used != 0) {
memcpy(packet, rx->data, rx->used);
*packet_len = rx->used;
event = 1;
}
}
rx->started = true;
rx->used = 0;
rx->escaped = false;
rx->discard = false;
return event;
}
if (!rx->started || rx->discard) return 0;
if (rx->escaped) {
rx->escaped = false;
if (byte == ESC_END) byte = END;
else if (byte == ESC_ESC) byte = ESC;
else { rx->discard = true; return 0; }
} else if (byte == ESC) {
rx->escaped = true;
return 0;
}
if (rx->used == sizeof rx->data) {
rx->discard = true;
return 0;
}
rx->data[rx->used++] = byte;
return 0;
}
int main(void) {
const uint8_t payload[] = {0x11, END, ESC, 0x22};
const uint8_t wire_expected[] = {END, 0x11, ESC, ESC_END,
ESC, ESC_ESC, 0x22, END};
uint8_t wire[16], packet[8];
size_t packet_len = 0;
size_t wire_len = slip_encode(payload, sizeof payload, wire, sizeof wire);
assert(wire_len == sizeof wire_expected);
assert(memcmp(wire, wire_expected, wire_len) == 0);
assert(slip_encode(payload, sizeof payload, wire, 7) == 0);
SlipRx rx = {0};
assert(slip_feed(&rx, 0x55, packet, &packet_len) == 0);
for (size_t i = 0; i + 1 < wire_len; ++i)
assert(slip_feed(&rx, wire[i], packet, &packet_len) == 0);
assert(slip_feed(&rx, wire[wire_len - 1], packet, &packet_len) == 1);
assert(packet_len == sizeof payload);
assert(memcmp(packet, payload, packet_len) == 0);
printf("frame: 11 C0 DB 22\n");
const uint8_t invalid[] = {END, ESC, 0x01, END};
int event = 0;
for (size_t i = 0; i < sizeof invalid; ++i)
event = slip_feed(&rx, invalid[i], packet, &packet_len);
assert(event == -1);
printf("bad escape: dropped\n");
assert(slip_feed(&rx, END, packet, &packet_len) == 0);
for (int i = 0; i < 9; ++i)
assert(slip_feed(&rx, 0x42, packet, &packet_len) == 0);
assert(slip_feed(&rx, END, packet, &packet_len) == -1);
printf("oversize: dropped\n");
return 0;
}
SLIP 자체에는 오류 검출, 패킷 종류, 재전송 기능이 없습니다. 데이터 무결성과 재시도 정책이 필요하다면 상위 프로토콜에서 별도로 정의해야 합니다. 이 코드는 PC에서 프레임 변환을 검증한 것이며, 특정 MCU의 UART 타이밍이나 전기적 오류를 시험한 결과는 아닙니다.
댓글
첫 댓글을 남겨 보세요.