commit
						81f6c8f2e9
					
				@ -0,0 +1,2 @@ | 
				
			|||||||
 | 
					/cmake-build-debug/ | 
				
			||||||
 | 
					.idea | 
				
			||||||
@ -0,0 +1,12 @@ | 
				
			|||||||
 | 
					cmake_minimum_required(VERSION 3.23) | 
				
			||||||
 | 
					project(modbus C) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set(CMAKE_C_STANDARD 99) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_executable(modbus | 
				
			||||||
 | 
					    src/pp/payload_builder.c | 
				
			||||||
 | 
					    src/pp/payload_parser.c | 
				
			||||||
 | 
					    src/modbus.c | 
				
			||||||
 | 
					    src/main.c) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_include_directories(modbus PRIVATE src) | 
				
			||||||
									
										Binary file not shown.
									
								
							
						@ -0,0 +1,8 @@ | 
				
			|||||||
 | 
					#include <stdio.h> | 
				
			||||||
 | 
					#include "modbus.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main() | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    printf("Hello, World!\n"); | 
				
			||||||
 | 
					    return 0; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,412 @@ | 
				
			|||||||
 | 
					#include <stdint.h> | 
				
			||||||
 | 
					#include <stdbool.h> | 
				
			||||||
 | 
					#include "pp/payload_parser.h" | 
				
			||||||
 | 
					#include "pp/payload_builder.h" | 
				
			||||||
 | 
					#include "modbus.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Function to calculate MODBUS CRC. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * https://github.com/starnight/MODBUS-CRC/blob/master/modbuscrc.c
 | 
				
			||||||
 | 
					 **/ | 
				
			||||||
 | 
					static uint16_t crc16_update(uint16_t crc, uint8_t a) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    int i; | 
				
			||||||
 | 
					    crc ^= (uint16_t) a; | 
				
			||||||
 | 
					    for (i = 0; i < 8; ++i) { | 
				
			||||||
 | 
					        if (crc & 1) { | 
				
			||||||
 | 
					            crc = (crc >> 1) ^ 0xA001; | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            crc = (crc >> 1); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return crc; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline uint16_t crc16_init() | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return 0xFFFF; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handle a modbus request | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					ModbusError_t mb_handleRequest( | 
				
			||||||
 | 
					        ModbusSlave_t *ms, | 
				
			||||||
 | 
					        const uint8_t *req, | 
				
			||||||
 | 
					        size_t req_size, | 
				
			||||||
 | 
					        uint8_t *resp, | 
				
			||||||
 | 
					        size_t resp_capacity, | 
				
			||||||
 | 
					        size_t *resp_size | 
				
			||||||
 | 
					) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    uint16_t txn_id, msglen, protocol_id, ref, count, ref2, count2, value, and_mask, or_mask; | 
				
			||||||
 | 
					    bool bvalue; | 
				
			||||||
 | 
					    ModbusException_t exc = MB_EXCEPTION_OK; | 
				
			||||||
 | 
					    size_t numbytes; | 
				
			||||||
 | 
					    uint8_t fcx, bytecount, bitcnt, scratch; | 
				
			||||||
 | 
					    const size_t TCP_RESP_OVERHEAD = 8; | 
				
			||||||
 | 
					    const size_t RTU_RESP_OVERHEAD = 4; | 
				
			||||||
 | 
					    const size_t overhead = ms->proto == MB_PROTO_TCP ? TCP_RESP_OVERHEAD : RTU_RESP_OVERHEAD; | 
				
			||||||
 | 
					    pb_mark_t resp_fc_mark, resp_len_mark, resp_pld_start_mark; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool tcp = ms->proto == MB_PROTO_TCP; | 
				
			||||||
 | 
					    PayloadParser pp = pp_start_le(req, req_size, NULL); | 
				
			||||||
 | 
					    PayloadBuilder pb = pb_start_le(resp, resp_capacity, NULL); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (tcp) { | 
				
			||||||
 | 
					        /* Parse header */ | 
				
			||||||
 | 
					        txn_id = pp_u16(&pp); | 
				
			||||||
 | 
					        protocol_id = pp_u16(&pp); | 
				
			||||||
 | 
					        msglen = pp_u16(&pp); | 
				
			||||||
 | 
					        if (protocol_id != 0) { | 
				
			||||||
 | 
					            return MB_ERR_BADPROTO; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					        msglen = req_size; | 
				
			||||||
 | 
					        /* check  CRC */ | 
				
			||||||
 | 
					        uint16_t crc = crc16_init(); | 
				
			||||||
 | 
					        for (int pos = 0; pos < req_size - 2 /* size of CRC */; pos++) { | 
				
			||||||
 | 
					            crc = crc16_update(crc, pp_u8(&pp)); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (crc != pp_u16(&pp)) { | 
				
			||||||
 | 
					            return MB_ERR_CHECKSUM; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        pp_rewind(&pp); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint8_t slave_id = pp_u8(&pp); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* check addressing (don't check for TCP - UnitID is used e.g. for gateway target devices) */ | 
				
			||||||
 | 
					    if (!tcp && slave_id != ms->addr) { | 
				
			||||||
 | 
					        return MB_ERR_NOTFORME; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fcx = pp_u8(&pp); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					        return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* start building the response */ | 
				
			||||||
 | 
					    if (tcp) { | 
				
			||||||
 | 
					        pb_u16(&pb, txn_id); | 
				
			||||||
 | 
					        pb_u16(&pb, protocol_id); | 
				
			||||||
 | 
					        resp_len_mark = pb_save(&pb); | 
				
			||||||
 | 
					        pb_u16(&pb, 0); // Placeholder for LEN
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    pb_u8(&pb, slave_id); | 
				
			||||||
 | 
					    resp_fc_mark = pb_save(&pb); | 
				
			||||||
 | 
					    pb_u8(&pb, fcx); // Exceptions will add 0x80 to this
 | 
				
			||||||
 | 
					    resp_pld_start_mark = pb_save(&pb); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ms->startOfAccess) { | 
				
			||||||
 | 
					        exc = ms->startOfAccess(ms, fcx, slave_id); | 
				
			||||||
 | 
					        if (exc != 0) { | 
				
			||||||
 | 
					            goto exception; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (fcx) { | 
				
			||||||
 | 
					        case FC05_WRITE_SINGLE_COIL: | 
				
			||||||
 | 
					            if (!ms->writeCoil) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            value = pp_u16(&pp); | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (resp_capacity < 4 + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (value == 0xFF00) { | 
				
			||||||
 | 
					                exc = ms->writeCoil(ms, ref, true); | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                exc = ms->writeCoil(ms, ref, false); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (exc != 0) { | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            pb_u16(&pb, ref); | 
				
			||||||
 | 
					            pb_u16(&pb, value); | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case FC01_READ_COILS: | 
				
			||||||
 | 
					        case FC02_READ_DISCRETES: | 
				
			||||||
 | 
					            /* check we have the needed function */ | 
				
			||||||
 | 
					            if (fcx == FC01_READ_COILS) { | 
				
			||||||
 | 
					                if (!ms->readCoil) { | 
				
			||||||
 | 
					                    exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                if (!ms->readDiscrete) { | 
				
			||||||
 | 
					                    exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            count = pp_u16(&pp); | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            bytecount = (count + 7) / 8; | 
				
			||||||
 | 
					            if (count > 255 * 8 || resp_capacity < bytecount + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            pb_u8(&pb, bytecount); | 
				
			||||||
 | 
					            bitcnt = 0; | 
				
			||||||
 | 
					            scratch = 0; | 
				
			||||||
 | 
					            while (count-- > 0) { | 
				
			||||||
 | 
					                if (fcx == FC01_READ_COILS) { | 
				
			||||||
 | 
					                    exc = ms->readCoil(ms, ref++, &bvalue); | 
				
			||||||
 | 
					                } else { | 
				
			||||||
 | 
					                    exc = ms->readDiscrete(ms, ref++, &bvalue); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                if (exc != 0) { | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                scratch = (scratch << 1) | bvalue; | 
				
			||||||
 | 
					                bitcnt++; | 
				
			||||||
 | 
					                if (bitcnt == 8) { | 
				
			||||||
 | 
					                    pb_u8(&pb, scratch); | 
				
			||||||
 | 
					                    scratch = 0; | 
				
			||||||
 | 
					                    bitcnt = 0; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case FC03_READ_HOLDING_REGISTERS: | 
				
			||||||
 | 
					        case FC04_READ_INPUT_REGISTERS: | 
				
			||||||
 | 
					            /* check we have the needed function */ | 
				
			||||||
 | 
					            if (fcx == FC03_READ_HOLDING_REGISTERS) { | 
				
			||||||
 | 
					                if (!ms->readHolding) { | 
				
			||||||
 | 
					                    exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                if (!ms->readInput) { | 
				
			||||||
 | 
					                    exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            count = pp_u16(&pp); | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (count > 255 || resp_capacity < count * 2 + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            while (count-- > 0) { | 
				
			||||||
 | 
					                if (fcx == FC03_READ_HOLDING_REGISTERS) { | 
				
			||||||
 | 
					                    exc = ms->readHolding(ms, ref++, &value); | 
				
			||||||
 | 
					                } else { | 
				
			||||||
 | 
					                    exc = ms->readInput(ms, ref++, &value); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                if (exc != 0) { | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                pb_u16(&pb, value); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case FC06_WRITE_SINGLE_REGISTER: | 
				
			||||||
 | 
					            if (!ms->writeHolding) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            value = pp_u16(&pp); | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (resp_capacity < 4 + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            exc = ms->writeHolding(ms, ref, value); | 
				
			||||||
 | 
					            if (exc != 0) { | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            pb_u16(&pb, ref); | 
				
			||||||
 | 
					            pb_u16(&pb, value); | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case FC16_WRITE_MULTIPLE_REGISTERS: | 
				
			||||||
 | 
					            if (!ms->writeHolding) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            count = pp_u16(&pp); | 
				
			||||||
 | 
					            pp_skip(&pp, 1); // this is always count * 2
 | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (count > 255 || resp_capacity < 4 + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (pp_remains(&pp) < count * 2 + (tcp ? 0 : 2)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            while (count-- > 0) { | 
				
			||||||
 | 
					                value = pp_u16(&pp); | 
				
			||||||
 | 
					                exc = ms->writeHolding(ms, ref++, value); | 
				
			||||||
 | 
					                if (exc != 0) { | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            pb_u16(&pb, ref); | 
				
			||||||
 | 
					            pb_u16(&pb, count); | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case FC15_WRITE_MULTIPLE_COILS: | 
				
			||||||
 | 
					            if (!ms->writeCoil) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            count = pp_u16(&pp); | 
				
			||||||
 | 
					            bytecount = pp_u8(&pp); | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (count > 255 || resp_capacity < 4 + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (pp_remains(&pp) < bytecount + (tcp ? 0 : 2)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            bitcnt = 8; | 
				
			||||||
 | 
					            scratch = 0; | 
				
			||||||
 | 
					            while (count-- > 0) { | 
				
			||||||
 | 
					                if (bitcnt == 8) { | 
				
			||||||
 | 
					                    scratch = pp_u8(&pp); | 
				
			||||||
 | 
					                    bitcnt = 0; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                exc = ms->writeCoil(ms, ref++, (scratch >> bitcnt) & 1); | 
				
			||||||
 | 
					                bitcnt++; | 
				
			||||||
 | 
					                if (exc != 0) { | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            pb_u16(&pb, ref); | 
				
			||||||
 | 
					            pb_u16(&pb, count); | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case FC22_MASK_WRITE_REGISTER: | 
				
			||||||
 | 
					            if (!ms->writeHolding || !ms->readHolding) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            and_mask = pp_u16(&pp); | 
				
			||||||
 | 
					            or_mask = pp_u16(&pp); | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (resp_capacity < 4 + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            exc = ms->readHolding(ms, ref, &value); | 
				
			||||||
 | 
					            if (exc != 0) { | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            value = (value & and_mask) | (or_mask & ~and_mask); | 
				
			||||||
 | 
					            exc = ms->writeHolding(ms, ref, value); | 
				
			||||||
 | 
					            if (exc != 0) { | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            pb_u16(&pb, ref); | 
				
			||||||
 | 
					            pb_u16(&pb, and_mask); | 
				
			||||||
 | 
					            pb_u16(&pb, or_mask); | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case FC23_READ_WRITE_MULTIPLE_REGISTERS: | 
				
			||||||
 | 
					            if (!ms->writeHolding || !ms->readHolding) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            // read
 | 
				
			||||||
 | 
					            ref = pp_u16(&pp); | 
				
			||||||
 | 
					            count = pp_u16(&pp); | 
				
			||||||
 | 
					            // write
 | 
				
			||||||
 | 
					            ref2 = pp_u16(&pp); | 
				
			||||||
 | 
					            count2 = pp_u16(&pp); | 
				
			||||||
 | 
					            pp_skip(&pp, 1); // qty of bytes
 | 
				
			||||||
 | 
					            if (!pp_ok(&pp)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (count > 255 || count2 > 255 || resp_capacity < count * 2 + overhead) { | 
				
			||||||
 | 
					                exc = MB_EXCEPTION_ILLEGAL_DATA_ADDRESS; // TODO?
 | 
				
			||||||
 | 
					                goto exception; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            if (pp_remains(&pp) < count2 * 2 + (tcp ? 0 : 2)) { | 
				
			||||||
 | 
					                return MB_ERR_NEEDMORE; | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            // First, write
 | 
				
			||||||
 | 
					            while (count-- > 0) { | 
				
			||||||
 | 
					                value = pp_u16(&pp); | 
				
			||||||
 | 
					                exc = ms->writeHolding(ms, ref++, value); | 
				
			||||||
 | 
					                if (exc != 0) { | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            // Second, read
 | 
				
			||||||
 | 
					            pb_u8(&pb, 2 * count); | 
				
			||||||
 | 
					            while (count-- > 0) { | 
				
			||||||
 | 
					                exc = ms->readHolding(ms, ref++, &value); | 
				
			||||||
 | 
					                if (exc != 0) { | 
				
			||||||
 | 
					                    goto exception; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                pb_u16(&pb, value); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default: | 
				
			||||||
 | 
					            exc = MB_EXCEPTION_ILLEGAL_FUNCTION; | 
				
			||||||
 | 
					            goto exception; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    goto checksum; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    exception:; | 
				
			||||||
 | 
					    *resp_fc_mark |= 0x80; | 
				
			||||||
 | 
					    pb_restore(&pb, resp_pld_start_mark); | 
				
			||||||
 | 
					    pb_u8(&pb, (uint8_t) exc); | 
				
			||||||
 | 
					    goto checksum; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    checksum: | 
				
			||||||
 | 
					    numbytes = pb_length(&pb); | 
				
			||||||
 | 
					    if (tcp) { | 
				
			||||||
 | 
					        pb_mark_t end = pb_save(&pb); | 
				
			||||||
 | 
					        pb_restore(&pb, resp_len_mark); | 
				
			||||||
 | 
					        pb_u16(&pb, numbytes - 6); | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					        uint8_t *m = pb.start; | 
				
			||||||
 | 
					        uint16_t crc = crc16_init(); | 
				
			||||||
 | 
					        while (numbytes > 0) { | 
				
			||||||
 | 
					            crc = crc16_update(crc, *m++); | 
				
			||||||
 | 
					            numbytes--; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        pb_u16(&pb, crc); | 
				
			||||||
 | 
					        numbytes += 2; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    *resp_size = numbytes; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ms->endOfAccess) { | 
				
			||||||
 | 
					        ms->endOfAccess(ms); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return MB_OK; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,73 @@ | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * TODO file description | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef MODBUS_H | 
				
			||||||
 | 
					#define MODBUS_H | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h> | 
				
			||||||
 | 
					#include <stdbool.h> | 
				
			||||||
 | 
					#include <sys/types.h> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum ModbusException { | 
				
			||||||
 | 
					  MB_EXCEPTION_OK = 0, | 
				
			||||||
 | 
					  MB_EXCEPTION_ILLEGAL_FUNCTION = 1, | 
				
			||||||
 | 
					  MB_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2, | 
				
			||||||
 | 
					  MB_EXCEPTION_ILLEGAL_DATA_VALUE = 3, | 
				
			||||||
 | 
					  MB_EXCEPTION_SLAVE_DEVICE_FAILURE = 4, | 
				
			||||||
 | 
					  // other codes exist but are not meaningful for simple slave devices
 | 
				
			||||||
 | 
					} ModbusException_t; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum ModbusFunction { | 
				
			||||||
 | 
					  FC01_READ_COILS = 1, | 
				
			||||||
 | 
					  FC02_READ_DISCRETES = 2, | 
				
			||||||
 | 
					  FC03_READ_HOLDING_REGISTERS = 3, | 
				
			||||||
 | 
					  FC04_READ_INPUT_REGISTERS = 4, | 
				
			||||||
 | 
					  FC05_WRITE_SINGLE_COIL = 5, | 
				
			||||||
 | 
					  FC06_WRITE_SINGLE_REGISTER = 6, | 
				
			||||||
 | 
					  FC15_WRITE_MULTIPLE_COILS = 15, | 
				
			||||||
 | 
					  FC16_WRITE_MULTIPLE_REGISTERS = 16, | 
				
			||||||
 | 
					  FC22_MASK_WRITE_REGISTER = 22, | 
				
			||||||
 | 
					  FC23_READ_WRITE_MULTIPLE_REGISTERS = 23, | 
				
			||||||
 | 
					} ModbusFunction_t; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum ModbusError { | 
				
			||||||
 | 
					  MB_OK = 0, | 
				
			||||||
 | 
					  MB_ERROR = 1, | 
				
			||||||
 | 
					  MB_ERR_NOTFORME, | 
				
			||||||
 | 
					  MB_ERR_NEEDMORE, | 
				
			||||||
 | 
					  MB_ERR_CHECKSUM, | 
				
			||||||
 | 
					  MB_ERR_BADPROTO, | 
				
			||||||
 | 
					} ModbusError_t; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum ModbusProtocol { | 
				
			||||||
 | 
					  MB_PROTO_RTU, | 
				
			||||||
 | 
					  MB_PROTO_TCP | 
				
			||||||
 | 
					} ModbusProtocol_t; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct ModbusSlave ModbusSlave_t; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ModbusSlave { | 
				
			||||||
 | 
					  uint8_t addr; | 
				
			||||||
 | 
					  ModbusProtocol_t proto; | 
				
			||||||
 | 
					  void *userData; | 
				
			||||||
 | 
					  enum ModbusException (*startOfAccess)(ModbusSlave_t *ms, ModbusFunction_t fcx, uint8_t slave_id); | 
				
			||||||
 | 
					  void (*endOfAccess)(ModbusSlave_t *ms); | 
				
			||||||
 | 
					  enum ModbusException (*readCoil)(ModbusSlave_t *ms, uint16_t reference, bool *value); | 
				
			||||||
 | 
					  enum ModbusException (*readDiscrete)(ModbusSlave_t *ms, uint16_t reference, bool *value); | 
				
			||||||
 | 
					  enum ModbusException (*readHolding)(ModbusSlave_t *ms, uint16_t reference, uint16_t *value); | 
				
			||||||
 | 
					  enum ModbusException (*readInput)(ModbusSlave_t *ms, uint16_t reference, uint16_t *value); | 
				
			||||||
 | 
					  enum ModbusException (*writeCoil)(ModbusSlave_t *ms, uint16_t reference, bool value); | 
				
			||||||
 | 
					  enum ModbusException (*writeHolding)(ModbusSlave_t *ms, uint16_t reference, uint16_t value); | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ModbusError_t mb_handleRequest( | 
				
			||||||
 | 
					        ModbusSlave_t *ms, | 
				
			||||||
 | 
					        const uint8_t *req, | 
				
			||||||
 | 
					        size_t req_size, | 
				
			||||||
 | 
					        uint8_t* resp, | 
				
			||||||
 | 
					        size_t resp_capacity, | 
				
			||||||
 | 
					        size_t *resp_size | 
				
			||||||
 | 
					); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif //MODBUS_H
 | 
				
			||||||
@ -0,0 +1,87 @@ | 
				
			|||||||
 | 
					#include <string.h> | 
				
			||||||
 | 
					#include "payload_builder.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define pb_check_capacity(pb, needed)                                                   \ | 
				
			||||||
 | 
					    if ((pb)->current + (needed) > (pb)->end) {                                         \
 | 
				
			||||||
 | 
					        if ((pb)->full_handler == NULL || !(pb)->full_handler((pb), (needed))) {        \
 | 
				
			||||||
 | 
					            (pb)->ok = 0;                                                               \
 | 
				
			||||||
 | 
					        }                                                                               \
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write from a buffer */ | 
				
			||||||
 | 
					bool pb_buf(PayloadBuilder *pb, const uint8_t *buf, uint32_t len) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pb_check_capacity(pb, len); | 
				
			||||||
 | 
					    if (!pb->ok) { return false; } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    memcpy(pb->current, buf, len); | 
				
			||||||
 | 
					    pb->current += len; | 
				
			||||||
 | 
					    return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write s zero terminated string */ | 
				
			||||||
 | 
					bool pb_string(PayloadBuilder *pb, const char *str) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    uint32_t len = (uint32_t) strlen(str); | 
				
			||||||
 | 
					    pb_check_capacity(pb, len + 1); | 
				
			||||||
 | 
					    if (!pb->ok) { | 
				
			||||||
 | 
					        return false; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    memcpy(pb->current, str, len + 1); | 
				
			||||||
 | 
					    pb->current += len + 1; | 
				
			||||||
 | 
					    return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write uint8_t to the buffer */ | 
				
			||||||
 | 
					bool pb_u8(PayloadBuilder *pb, uint8_t byte) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pb_check_capacity(pb, 1); | 
				
			||||||
 | 
					    if (!pb->ok) { | 
				
			||||||
 | 
					        return false; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    *pb->current++ = byte; | 
				
			||||||
 | 
					    return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write uint16_t to the buffer. */ | 
				
			||||||
 | 
					bool pb_u16(PayloadBuilder *pb, uint16_t word) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pb_check_capacity(pb, 2); | 
				
			||||||
 | 
					    if (!pb->ok) { | 
				
			||||||
 | 
					        return false; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (pb->bigendian) { | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 8) & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) (word & 0xFF); | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) (word & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 8) & 0xFF); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write uint32_t to the buffer. */ | 
				
			||||||
 | 
					bool pb_u32(PayloadBuilder *pb, uint32_t word) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pb_check_capacity(pb, 4); | 
				
			||||||
 | 
					    if (!pb->ok) { | 
				
			||||||
 | 
					        return false; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (pb->bigendian) { | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 24) & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 16) & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 8) & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) (word & 0xFF); | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) (word & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 8) & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 16) & 0xFF); | 
				
			||||||
 | 
					        *pb->current++ = (uint8_t) ((word >> 24) & 0xFF); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,126 @@ | 
				
			|||||||
 | 
					#ifndef PAYLOAD_BUILDER_H | 
				
			||||||
 | 
					#define PAYLOAD_BUILDER_H | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * PayloadBuilder, part of the TinyFrame utilities collection | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * (c) Ondřej Hruška, 2014-2017. MIT license. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The builder supports big and little endian which is selected when | 
				
			||||||
 | 
					 * initializing it or by accessing the bigendian struct field. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * This module helps you with building payloads (not only for TinyFrame) | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The builder performs bounds checking and calls the provided handler when | 
				
			||||||
 | 
					 * the requested write wouldn't fit. Use the handler to realloc / flush the buffer | 
				
			||||||
 | 
					 * or report an error. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h> | 
				
			||||||
 | 
					#include <stdbool.h> | 
				
			||||||
 | 
					#include <stddef.h> | 
				
			||||||
 | 
					#include "type_coerce.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct PayloadBuilder_ PayloadBuilder; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Full buffer handler. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * 'needed' more bytes should be written but the end of the buffer was reached. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Return true if the problem was solved (e.g. buffer was flushed and the | 
				
			||||||
 | 
					 * 'current' pointer moved to the beginning). | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * If false is returned, the 'ok' flag on the struct is set to false | 
				
			||||||
 | 
					 * and all following writes are discarded. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					typedef bool (*pb_full_handler)(PayloadBuilder *pb, uint32_t needed); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PayloadBuilder_ { | 
				
			||||||
 | 
					  uint8_t *start;   //!< Pointer to the beginning of the buffer
 | 
				
			||||||
 | 
					  uint8_t *current; //!< Pointer to the next byte to be read
 | 
				
			||||||
 | 
					  uint8_t *end;     //!< Pointer to the end of the buffer (start + length)
 | 
				
			||||||
 | 
					  pb_full_handler full_handler; //!< Callback for buffer overrun
 | 
				
			||||||
 | 
					  bool bigendian;   //!< Flag to use big-endian parsing
 | 
				
			||||||
 | 
					  bool ok;          //!< Indicates that all reads were successful
 | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- initializer helper macros ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the builder. */ | 
				
			||||||
 | 
					#define pb_start_e(buf, capacity, bigendian, full_handler) ((PayloadBuilder){buf, buf, (buf)+(capacity), full_handler, bigendian, 1}) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the builder in big-endian mode */ | 
				
			||||||
 | 
					#define pb_start_be(buf, capacity, full_handler) pb_start_e(buf, capacity, 1, full_handler) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the builder in little-endian mode */ | 
				
			||||||
 | 
					#define pb_start_le(buf, capacity, full_handler) pb_start_e(buf, capacity, 0, full_handler) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the parser in little-endian mode (default) */ | 
				
			||||||
 | 
					#define pb_start(buf, capacity, full_handler) pb_start_le(buf, capacity, full_handler) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- utilities ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Get already used bytes count */ | 
				
			||||||
 | 
					#define pb_length(pb) ((pb)->current - (pb)->start) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Reset the current pointer to start */ | 
				
			||||||
 | 
					#define pb_rewind(pb) do { (pb)->current = (pb)->start; } while (0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** save & restore position */ | 
				
			||||||
 | 
					typedef uint8_t* pb_mark_t; | 
				
			||||||
 | 
					#define pb_save(pb) ((pb)->current) | 
				
			||||||
 | 
					#define pb_restore(pb, mark) do { (pb)->current = (mark); } while (0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write from a buffer */ | 
				
			||||||
 | 
					bool pb_buf(PayloadBuilder *pb, const uint8_t *buf, uint32_t len); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write a zero terminated string */ | 
				
			||||||
 | 
					bool pb_string(PayloadBuilder *pb, const char *str); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write uint8_t to the buffer */ | 
				
			||||||
 | 
					bool pb_u8(PayloadBuilder *pb, uint8_t byte); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write boolean to the buffer. */ | 
				
			||||||
 | 
					static inline bool pb_bool(PayloadBuilder *pb, bool b) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pb_u8(pb, (uint8_t) b); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write uint16_t to the buffer. */ | 
				
			||||||
 | 
					bool pb_u16(PayloadBuilder *pb, uint16_t word); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write uint32_t to the buffer. */ | 
				
			||||||
 | 
					bool pb_u32(PayloadBuilder *pb, uint32_t word); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write int8_t to the buffer. */ | 
				
			||||||
 | 
					static inline bool pb_i8(PayloadBuilder *pb, int8_t byte) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pb_u8(pb, ((union conv8) {.i8 = byte}).u8); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write char (int8_t) to the buffer. */ | 
				
			||||||
 | 
					static inline bool pb_char(PayloadBuilder *pb, char c) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pb_i8(pb, c); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write int16_t to the buffer. */ | 
				
			||||||
 | 
					static inline bool pb_i16(PayloadBuilder *pb, int16_t word) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pb_u16(pb, ((union conv16) {.i16 = word}).u16); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write int32_t to the buffer. */ | 
				
			||||||
 | 
					static inline bool pb_i32(PayloadBuilder *pb, int32_t word) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pb_u32(pb, ((union conv32) {.i32 = word}).u32); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Write 4-byte float to the buffer. */ | 
				
			||||||
 | 
					static inline bool pb_float(PayloadBuilder *pb, float f) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pb_u32(pb, ((union conv32) {.f32 = f}).u32); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // PAYLOAD_BUILDER_H
 | 
				
			||||||
@ -0,0 +1,104 @@ | 
				
			|||||||
 | 
					#include "payload_parser.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define pp_check_capacity(pp, needed)                                               \ | 
				
			||||||
 | 
					    if ((pp)->current + (needed) > (pp)->end) {                                     \
 | 
				
			||||||
 | 
					        if ((pp)->empty_handler == NULL || !(pp)->empty_handler((pp), (needed))) {  \
 | 
				
			||||||
 | 
					            (pp)->ok = 0;                                                           \
 | 
				
			||||||
 | 
					        }                                                                           \
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t pp_u8(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pp_check_capacity(pp, 1); | 
				
			||||||
 | 
					    if (!pp->ok) { | 
				
			||||||
 | 
					        return 0; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return *pp->current++; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint16_t pp_u16(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pp_check_capacity(pp, 2); | 
				
			||||||
 | 
					    if (!pp->ok) { | 
				
			||||||
 | 
					        return 0; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint16_t x = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (pp->bigendian) { | 
				
			||||||
 | 
					        x |= *pp->current++ << 8; | 
				
			||||||
 | 
					        x |= *pp->current++; | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					        x |= *pp->current++; | 
				
			||||||
 | 
					        x |= *pp->current++ << 8; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return x; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint32_t pp_u32(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pp_check_capacity(pp, 4); | 
				
			||||||
 | 
					    if (!pp->ok) { | 
				
			||||||
 | 
					        return 0; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t x = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (pp->bigendian) { | 
				
			||||||
 | 
					        x |= (uint32_t) (*pp->current++ << 24); | 
				
			||||||
 | 
					        x |= (uint32_t) (*pp->current++ << 16); | 
				
			||||||
 | 
					        x |= (uint32_t) (*pp->current++ << 8); | 
				
			||||||
 | 
					        x |= *pp->current++; | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					        x |= *pp->current++; | 
				
			||||||
 | 
					        x |= (uint32_t) (*pp->current++ << 8); | 
				
			||||||
 | 
					        x |= (uint32_t) (*pp->current++ << 16); | 
				
			||||||
 | 
					        x |= (uint32_t) (*pp->current++ << 24); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return x; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t *pp_tail(PayloadParser *pp, uint32_t *length) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    int32_t len = (int) (pp->end - pp->current); | 
				
			||||||
 | 
					    if (!pp->ok || len <= 0) { | 
				
			||||||
 | 
					        if (length != NULL) { | 
				
			||||||
 | 
					            *length = 0; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        return NULL; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (length != NULL) { | 
				
			||||||
 | 
					        *length = (uint32_t) len; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return pp->current; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read a zstring */ | 
				
			||||||
 | 
					uint32_t pp_string(PayloadParser *pp, char *buffer, uint32_t maxlen) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pp_check_capacity(pp, 1); | 
				
			||||||
 | 
					    uint32_t len = 0; | 
				
			||||||
 | 
					    while (len < maxlen - 1 && pp->current != pp->end) { | 
				
			||||||
 | 
					        char c = *buffer++ = (char) *pp->current++; | 
				
			||||||
 | 
					        if (c == 0) { | 
				
			||||||
 | 
					            break; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        len++; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    *buffer = 0; | 
				
			||||||
 | 
					    return len; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read a buffer */ | 
				
			||||||
 | 
					uint32_t pp_buf(PayloadParser *pp, uint8_t *buffer, uint32_t maxlen) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    uint32_t len = 0; | 
				
			||||||
 | 
					    while (len < maxlen && pp->current != pp->end) { | 
				
			||||||
 | 
					        *buffer++ = *pp->current++; | 
				
			||||||
 | 
					        len++; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return len; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,167 @@ | 
				
			|||||||
 | 
					#ifndef PAYLOAD_PARSER_H | 
				
			||||||
 | 
					#define PAYLOAD_PARSER_H | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * PayloadParser, part of the TinyFrame utilities collection | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * (c) Ondřej Hruška, 2016-2017. MIT license. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * This module helps you with parsing payloads (not only from TinyFrame). | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The parser supports big and little-endian which is selected when | 
				
			||||||
 | 
					 * initializing it or by accessing the bigendian struct field. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The parser performs bounds checking and calls the provided handler when | 
				
			||||||
 | 
					 * the requested read doesn't have enough data. Use the callback to take | 
				
			||||||
 | 
					 * appropriate action, e.g. report an error. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * If the handler function is not defined, the pb->ok flag is set to false | 
				
			||||||
 | 
					 * (use this to check for success), and further reads won't have any effect | 
				
			||||||
 | 
					 * and always result in 0 or empty array. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h> | 
				
			||||||
 | 
					#include <stddef.h> | 
				
			||||||
 | 
					#include <stdbool.h> | 
				
			||||||
 | 
					#include "type_coerce.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct PayloadParser_ PayloadParser; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Empty buffer handler. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * 'needed' more bytes should be read but the end was reached. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Return true if the problem was solved (e.g. new data loaded into | 
				
			||||||
 | 
					 * the buffer and the 'current' pointer moved to the beginning). | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * If false is returned, the 'ok' flag on the struct is set to false | 
				
			||||||
 | 
					 * and all following reads will fail / return 0. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					typedef bool (*pp_empty_handler)(PayloadParser *pp, uint32_t needed); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PayloadParser_ { | 
				
			||||||
 | 
					    const uint8_t *start;   //!< Pointer to the beginning of the buffer
 | 
				
			||||||
 | 
					    const uint8_t *current; //!< Pointer to the next byte to be read
 | 
				
			||||||
 | 
					    const uint8_t *end;     //!< Pointer to the end of the buffer (start + length)
 | 
				
			||||||
 | 
					    pp_empty_handler empty_handler; //!< Callback for buffer underrun
 | 
				
			||||||
 | 
					    bool bigendian;   //!< Flag to use big-endian parsing
 | 
				
			||||||
 | 
					    bool ok;          //!< Indicates that all reads were successful
 | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- initializer helper macros ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the parser. */ | 
				
			||||||
 | 
					#define pp_start_e(buf, length, bigendian, empty_handler) ((PayloadParser){buf, buf, (buf)+(length), empty_handler, bigendian, 1}) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the parser in big-endian mode */ | 
				
			||||||
 | 
					#define pp_start_be(buf, length, empty_handler) pp_start_e(buf, length, 1, empty_handler) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the parser in little-endian mode */ | 
				
			||||||
 | 
					#define pp_start_le(buf, length, empty_handler) pp_start_e(buf, length, 0, empty_handler) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Start the parser in little-endian mode (default) */ | 
				
			||||||
 | 
					#define pp_start(buf, length, empty_handler) pp_start_le(buf, length, empty_handler) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- utilities ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Get remaining length */ | 
				
			||||||
 | 
					#define pp_remains(pp) ((pp)->end - (pp)->current) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Reset the current pointer to start */ | 
				
			||||||
 | 
					#define pp_rewind(pp) do { (pp)->current = (pp)->start; } while (0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** save & restore position */ | 
				
			||||||
 | 
					typedef const uint8_t* pp_mark_t; | 
				
			||||||
 | 
					#define pp_save(pp) ((pp)->current) | 
				
			||||||
 | 
					#define pp_restore(pp, mark) do { (pp)->current = (mark); } while (0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Returns true if the parser is still in OK state (not overrun) */ | 
				
			||||||
 | 
					#define pp_ok(pp) ((pp)->ok) | 
				
			||||||
 | 
					/** Returns true if end was reached */ | 
				
			||||||
 | 
					#define pp_end(pp) ((pp)->end == (pp)->current) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Get the remainder of the buffer. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Returns NULL and sets 'length' to 0 if there are no bytes left. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * @param pp | 
				
			||||||
 | 
					 * @param length : here the buffer length will be stored. NULL to do not store. | 
				
			||||||
 | 
					 * @return the remaining portion of the input buffer | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					const uint8_t *pp_tail(PayloadParser *pp, uint32_t *length); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read uint8_t from the payload. */ | 
				
			||||||
 | 
					uint8_t pp_u8(PayloadParser *pp); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read bool from the payload. */ | 
				
			||||||
 | 
					static inline bool pp_bool(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pp_u8(pp) != 0; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Skip bytes */ | 
				
			||||||
 | 
					static inline void pp_skip(PayloadParser *pp, uint32_t num) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    pp->current += num; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read uint16_t from the payload. */ | 
				
			||||||
 | 
					uint16_t pp_u16(PayloadParser *pp); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read uint32_t from the payload. */ | 
				
			||||||
 | 
					uint32_t pp_u32(PayloadParser *pp); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read int8_t from the payload. */ | 
				
			||||||
 | 
					static inline int8_t pp_i8(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return ((union conv8) {.u8 = pp_u8(pp)}).i8; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read char (int8_t) from the payload. */ | 
				
			||||||
 | 
					static inline int8_t pp_char(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return pp_i8(pp); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read int16_t from the payload. */ | 
				
			||||||
 | 
					static inline int16_t pp_i16(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return ((union conv16) {.u16 = pp_u16(pp)}).i16; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read int32_t from the payload. */ | 
				
			||||||
 | 
					static inline int32_t pp_i32(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return ((union conv32) {.u32 = pp_u32(pp)}).i32; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Read 4-byte float from the payload. */ | 
				
			||||||
 | 
					static inline float pp_float(PayloadParser *pp) | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    return ((union conv32) {.u32 = pp_u32(pp)}).f32; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Parse a zero-terminated string | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * @param pp - parser | 
				
			||||||
 | 
					 * @param buffer - target buffer | 
				
			||||||
 | 
					 * @param maxlen - buffer size | 
				
			||||||
 | 
					 * @return actual number of bytes, excluding terminator | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					uint32_t pp_string(PayloadParser *pp, char *buffer, uint32_t maxlen); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Parse a buffer | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * @param pp - parser | 
				
			||||||
 | 
					 * @param buffer - target buffer | 
				
			||||||
 | 
					 * @param maxlen - buffer size | 
				
			||||||
 | 
					 * @return actual number of bytes, excluding terminator | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					uint32_t pp_buf(PayloadParser *pp, uint8_t *buffer, uint32_t maxlen); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // PAYLOAD_PARSER_H
 | 
				
			||||||
@ -0,0 +1,32 @@ | 
				
			|||||||
 | 
					#ifndef TYPE_COERCE_H | 
				
			||||||
 | 
					#define TYPE_COERCE_H | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Structs for conversion between types, | 
				
			||||||
 | 
					 * part of the TinyFrame utilities collection | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * (c) Ondřej Hruška, 2016-2017. MIT license. | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * This is a support header file for PayloadParser and PayloadBuilder. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h> | 
				
			||||||
 | 
					#include <stddef.h> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					union conv8 { | 
				
			||||||
 | 
					    uint8_t u8; | 
				
			||||||
 | 
					    int8_t i8; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					union conv16 { | 
				
			||||||
 | 
					    uint16_t u16; | 
				
			||||||
 | 
					    int16_t i16; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					union conv32 { | 
				
			||||||
 | 
					    uint32_t u32; | 
				
			||||||
 | 
					    int32_t i32; | 
				
			||||||
 | 
					    float f32; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // TYPE_COERCE_H
 | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue