merge upstream changes v3.0

Mon, 24 Apr 2023 21:01:41 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 24 Apr 2023 21:01:41 +0200
changeset 67
5da2cb5aea6b
parent 66
1b12cf799fee (current diff)
parent 65
7dd4fd1e7071 (diff)
child 68
4d1ce98e09cc

merge upstream changes

.hgignore file | annotate | diff | comparison | revisions
Makefile file | annotate | diff | comparison | revisions
src/c2html.c file | annotate | diff | comparison | revisions
src/frontend.c file | annotate | diff | comparison | revisions
src/highlighter.c file | annotate | diff | comparison | revisions
src/highlighter.h file | annotate | diff | comparison | revisions
test/ctest.c file | annotate | diff | comparison | revisions
test/ctestfile.c file | annotate | diff | comparison | revisions
test/empty.c file | annotate | diff | comparison | revisions
test/golden-master/bigtest.html file | annotate | diff | comparison | revisions
test/golden-master/ctest.html file | annotate | diff | comparison | revisions
test/golden-master/empty.html file | annotate | diff | comparison | revisions
test/golden-master/javatest.html file | annotate | diff | comparison | revisions
test/golden-master/plain.html file | annotate | diff | comparison | revisions
     1.1 --- a/.hgignore	Mon Apr 24 20:54:38 2023 +0200
     1.2 +++ b/.hgignore	Mon Apr 24 21:01:41 2023 +0200
     1.3 @@ -5,4 +5,5 @@
     1.4  \.conflict\~$
     1.5  ^build/.*$
     1.6  ^nbproject/.*$
     1.7 -^.idea/.*$
     1.8 +.idea/
     1.9 +
     2.1 --- a/Makefile	Mon Apr 24 20:54:38 2023 +0200
     2.2 +++ b/Makefile	Mon Apr 24 21:01:41 2023 +0200
     2.3 @@ -49,18 +49,19 @@
     2.4  	$(MKDIR) $@
     2.5  	
     2.6  test: all
     2.7 -	./build/$(BIN) test/ctestfile.c -o build/ctest.html \
     2.8 +	./build/$(BIN) test/ctest.c -o build/ctest.html \
     2.9  	-H test/header.html -F test/footer.html
    2.10 -	./build/$(BIN) -j test/javatestfile.java -o build/javatest.html \
    2.11 +	./build/$(BIN) -j test/javatest.java -o build/javatest.html \
    2.12  	-H test/jheader.html -F test/footer.html
    2.13 -	./build/$(BIN) test/bigtestfile.c -o build/bigtest.html \
    2.14 +	./build/$(BIN) test/bigtest.c -o build/bigtest.html \
    2.15  	-H test/header.html -F test/footer.html
    2.16 -	./build/$(BIN) -p test/plain.csp -o build/plain.html \
    2.17 +	./build/$(BIN) -p test/plain.txt -o build/plain.html \
    2.18  	-H test/header.html -F test/footer.html
    2.19  	diff build/ctest.html test/gs/ctest.html && \
    2.20  	diff build/javatest.html test/gs/javatest.html && \
    2.21  	diff build/bigtest.html test/gs/bigtest.html && \
    2.22  	diff build/plain.html test/gs/plain.html
    2.23 +	@echo "Tests successful."
    2.24  	
    2.25  clean:
    2.26  	$(RM) $(RMFLAGS) build
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/test/bigtest.c	Mon Apr 24 21:01:41 2023 +0200
     3.3 @@ -0,0 +1,808 @@
     3.4 +/*
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 2014 Mike Becker. All rights reserved.
     3.8 + *
     3.9 + * Redistribution and use in source and binary forms, with or without
    3.10 + * modification, are permitted provided that the following conditions are met:
    3.11 + *
    3.12 + *   1. Redistributions of source code must retain the above copyright
    3.13 + *      notice, this list of conditions and the following disclaimer.
    3.14 + *
    3.15 + *   2. Redistributions in binary form must reproduce the above copyright
    3.16 + *      notice, this list of conditions and the following disclaimer in the
    3.17 + *      documentation and/or other materials provided with the distribution.
    3.18 + *
    3.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    3.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    3.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    3.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    3.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    3.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    3.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    3.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    3.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    3.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    3.29 + * POSSIBILITY OF SUCH DAMAGE.
    3.30 + *
    3.31 + */
    3.32 +
    3.33 +#include "rules.h"
    3.34 +#include "chess.h"
    3.35 +#include <string.h>
    3.36 +#include <stdlib.h>
    3.37 +#include <sys/time.h>
    3.38 +
    3.39 +static GameState gamestate_copy_sim(GameState *gamestate) {
    3.40 +    GameState simulation = *gamestate;
    3.41 +    if (simulation.lastmove) {
    3.42 +        MoveList *lastmovecopy = malloc(sizeof(MoveList));
    3.43 +        *lastmovecopy = *(simulation.lastmove);
    3.44 +        simulation.movelist = simulation.lastmove = lastmovecopy;
    3.45 +    }
    3.46 +
    3.47 +    return simulation;
    3.48 +}
    3.49 +
    3.50 +void gamestate_init(GameState *gamestate) {
    3.51 +    memset(gamestate, 0, sizeof(GameState));
    3.52 +    
    3.53 +    Board initboard = {
    3.54 +        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
    3.55 +        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
    3.56 +        {0,     0,       0,       0,      0,     0,       0,       0},
    3.57 +        {0,     0,       0,       0,      0,     0,       0,       0},
    3.58 +        {0,     0,       0,       0,      0,     0,       0,       0},
    3.59 +        {0,     0,       0,       0,      0,     0,       0,       0},
    3.60 +        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
    3.61 +        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
    3.62 +    };
    3.63 +    memcpy(gamestate->board, initboard, sizeof(Board));
    3.64 +}
    3.65 +
    3.66 +void gamestate_cleanup(GameState *gamestate) {
    3.67 +    MoveList *elem;
    3.68 +    elem = gamestate->movelist;
    3.69 +    while (elem) {
    3.70 +        MoveList *cur = elem;
    3.71 +        elem = elem->next;
    3.72 +        free(cur);
    3.73 +    };
    3.74 +}
    3.75 +
    3.76 +/* MUST be called IMMEDIATLY after applying a move to work correctly */
    3.77 +static void format_move(GameState *gamestate, Move *move) {
    3.78 +    char *string = move->string;
    3.79 +    
    3.80 +    /* at least 8 characters should be available, wipe them out */
    3.81 +    memset(string, 0, 8);
    3.82 +    
    3.83 +    /* special formats for castling */
    3.84 +    if ((move->piece&PIECE_MASK) == KING &&
    3.85 +            abs(move->tofile-move->fromfile) == 2) {
    3.86 +        if (move->tofile==fileidx('c')) {
    3.87 +            memcpy(string, "O-O-O", 5);
    3.88 +        } else {
    3.89 +            memcpy(string, "O-O", 3);
    3.90 +        }
    3.91 +    }
    3.92 +
    3.93 +    /* start by notating the piece character */
    3.94 +    string[0] = getpiecechr(move->piece);
    3.95 +    int idx = string[0] ? 1 : 0;
    3.96 +    
    3.97 +    /* find out how many source information we do need */
    3.98 +    uint8_t piece = move->piece & PIECE_MASK;
    3.99 +    if (piece == PAWN) {
   3.100 +        if (move->capture) {
   3.101 +            string[idx++] = filechr(move->fromfile);
   3.102 +        }
   3.103 +    } else if (piece != KING) {
   3.104 +        Move threats[16];
   3.105 +        uint8_t threatcount;
   3.106 +        get_real_threats(gamestate, move->torow, move->tofile,
   3.107 +            move->piece&COLOR_MASK, threats, &threatcount);
   3.108 +        if (threatcount > 1) {
   3.109 +            int ambrows = 0, ambfiles = 0;
   3.110 +            for (uint8_t i = 0 ; i < threatcount ; i++) {
   3.111 +                if (threats[i].fromrow == move->fromrow) {
   3.112 +                    ambrows++;
   3.113 +                }
   3.114 +                if (threats[i].fromfile == move->fromfile) {
   3.115 +                    ambfiles++;
   3.116 +                }
   3.117 +            }
   3.118 +            /* ambiguous row, name file */
   3.119 +            if (ambrows > 1) {
   3.120 +                string[idx++] = filechr(move->fromfile);
   3.121 +            }
   3.122 +            /* ambiguous file, name row */
   3.123 +            if (ambfiles > 1) {
   3.124 +                string[idx++] = filechr(move->fromrow);
   3.125 +            }
   3.126 +        }
   3.127 +    }
   3.128 +    
   3.129 +    /* capturing? */
   3.130 +    if (move->capture) {
   3.131 +        string[idx++] = 'x';
   3.132 +    }
   3.133 +    
   3.134 +    /* destination */
   3.135 +    string[idx++] = filechr(move->tofile);
   3.136 +    string[idx++] = rowchr(move->torow);
   3.137 +    
   3.138 +    /* promotion? */
   3.139 +    if (move->promotion) {
   3.140 +        string[idx++] = '=';
   3.141 +        string[idx++] = getpiecechr(move->promotion);
   3.142 +    }
   3.143 +    
   3.144 +    /* check? */
   3.145 +    if (move->check) {
   3.146 +        /* works only, if this function is called when applying the move */
   3.147 +        string[idx++] = gamestate->checkmate?'#':'+';
   3.148 +    }
   3.149 +}
   3.150 +
   3.151 +static void addmove(GameState* gamestate, Move *move) {
   3.152 +    MoveList *elem = malloc(sizeof(MoveList));
   3.153 +    elem->next = NULL;
   3.154 +    elem->move = *move;
   3.155 +    
   3.156 +    struct timeval curtimestamp;
   3.157 +    gettimeofday(&curtimestamp, NULL);
   3.158 +    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
   3.159 +    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
   3.160 +    
   3.161 +    if (gamestate->lastmove) {
   3.162 +        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
   3.163 +        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
   3.164 +        suseconds_t micros;
   3.165 +        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
   3.166 +            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
   3.167 +            sec--;
   3.168 +        } else {
   3.169 +            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
   3.170 +        }
   3.171 +        
   3.172 +        elem->move.movetime.tv_sec = sec;
   3.173 +        elem->move.movetime.tv_usec = micros;
   3.174 +        
   3.175 +        gamestate->lastmove->next = elem;
   3.176 +        gamestate->lastmove = elem;
   3.177 +    } else {
   3.178 +        elem->move.movetime.tv_usec = 0;
   3.179 +        elem->move.movetime.tv_sec = 0;
   3.180 +        gamestate->movelist = gamestate->lastmove = elem;
   3.181 +    }
   3.182 +}
   3.183 +
   3.184 +char getpiecechr(uint8_t piece) {
   3.185 +    switch (piece & PIECE_MASK) {
   3.186 +    case ROOK: return 'R';
   3.187 +    case KNIGHT: return 'N';
   3.188 +    case BISHOP: return 'B';
   3.189 +    case QUEEN: return 'Q';
   3.190 +    case KING: return 'K';
   3.191 +    default: return '\0';
   3.192 +    }
   3.193 +}
   3.194 +
   3.195 +uint8_t getpiece(char c) {
   3.196 +    switch (c) {
   3.197 +        case 'R': return ROOK;
   3.198 +        case 'N': return KNIGHT;
   3.199 +        case 'B': return BISHOP;
   3.200 +        case 'Q': return QUEEN;
   3.201 +        case 'K': return KING;
   3.202 +        default: return 0;
   3.203 +    }
   3.204 +}
   3.205 +
   3.206 +static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
   3.207 +    uint8_t piece = move->piece & PIECE_MASK;
   3.208 +    uint8_t color = move->piece & COLOR_MASK;
   3.209 +    
   3.210 +    /* en passant capture */
   3.211 +    if (move->capture && piece == PAWN &&
   3.212 +        mdst(gamestate->board, move) == 0) {
   3.213 +        gamestate->board[move->fromrow][move->tofile] = 0;
   3.214 +    }
   3.215 +    
   3.216 +    /* remove old en passant threats */
   3.217 +    for (uint8_t file = 0 ; file < 8 ; file++) {
   3.218 +        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
   3.219 +        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
   3.220 +    }
   3.221 +    
   3.222 +    /* add new en passant threat */
   3.223 +    if (piece == PAWN && (
   3.224 +        (move->fromrow == 1 && move->torow == 3) ||
   3.225 +        (move->fromrow == 6 && move->torow == 4))) {
   3.226 +        move->piece |= ENPASSANT_THREAT;
   3.227 +    }
   3.228 +    
   3.229 +    /* move (and maybe capture or promote) */
   3.230 +    msrc(gamestate->board, move) = 0;
   3.231 +    if (move->promotion) {
   3.232 +        mdst(gamestate->board, move) = move->promotion;
   3.233 +    } else {
   3.234 +        mdst(gamestate->board, move) = move->piece;
   3.235 +    }
   3.236 +    
   3.237 +    /* castling */
   3.238 +    if (piece == KING && move->fromfile == fileidx('e')) {
   3.239 +        
   3.240 +        if (move->tofile == fileidx('g')) {
   3.241 +            gamestate->board[move->torow][fileidx('h')] = 0;
   3.242 +            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
   3.243 +        } else if (move->tofile == fileidx('c')) {
   3.244 +            gamestate->board[move->torow][fileidx('a')] = 0;
   3.245 +            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
   3.246 +        }
   3.247 +    }
   3.248 +
   3.249 +    if (!simulate) {
   3.250 +        if (!move->string[0]) {
   3.251 +            format_move(gamestate, move);
   3.252 +        }
   3.253 +    }
   3.254 +    /* add move, even in simulation (checkmate test needs it) */
   3.255 +    addmove(gamestate, move);
   3.256 +}
   3.257 +
   3.258 +void apply_move(GameState *gamestate, Move *move) {
   3.259 +    apply_move_impl(gamestate, move, 0);
   3.260 +}
   3.261 +
   3.262 +static int validate_move_rules(GameState *gamestate, Move *move) {
   3.263 +    /* validate indices (don't trust opponent) */
   3.264 +    if (!chkidx(move)) {
   3.265 +        return INVALID_POSITION;
   3.266 +    }
   3.267 +    
   3.268 +    /* must move */
   3.269 +    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
   3.270 +        return INVALID_MOVE_SYNTAX;
   3.271 +    }
   3.272 +    
   3.273 +    /* does piece exist */
   3.274 +    if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
   3.275 +           != (move->piece&(PIECE_MASK|COLOR_MASK))) {
   3.276 +        return INVALID_POSITION;
   3.277 +    }
   3.278 +    
   3.279 +    /* can't capture own pieces */
   3.280 +    if ((mdst(gamestate->board, move) & COLOR_MASK)
   3.281 +            == (move->piece & COLOR_MASK)) {
   3.282 +        return RULES_VIOLATED;
   3.283 +    }
   3.284 +    
   3.285 +    /* must capture, if and only if destination is occupied */
   3.286 +    if ((mdst(gamestate->board, move) == 0 && move->capture) ||
   3.287 +            (mdst(gamestate->board, move) != 0 && !move->capture)) {
   3.288 +        return INVALID_MOVE_SYNTAX;
   3.289 +    }
   3.290 +    
   3.291 +    /* validate individual rules */
   3.292 +    _Bool chkrules;
   3.293 +    switch (move->piece & PIECE_MASK) {
   3.294 +    case PAWN:
   3.295 +        chkrules = pawn_chkrules(gamestate, move) &&
   3.296 +            !pawn_isblocked(gamestate, move);
   3.297 +        break;
   3.298 +    case ROOK:
   3.299 +        chkrules = rook_chkrules(move) &&
   3.300 +            !rook_isblocked(gamestate, move);
   3.301 +        break;
   3.302 +    case KNIGHT:
   3.303 +        chkrules = knight_chkrules(move); /* knight is never blocked */
   3.304 +        break;
   3.305 +    case BISHOP:
   3.306 +        chkrules = bishop_chkrules(move) &&
   3.307 +            !bishop_isblocked(gamestate, move);
   3.308 +        break;
   3.309 +    case QUEEN:
   3.310 +        chkrules = queen_chkrules(move) &&
   3.311 +            !queen_isblocked(gamestate, move);
   3.312 +        break;
   3.313 +    case KING:
   3.314 +        chkrules = king_chkrules(gamestate, move) &&
   3.315 +            !king_isblocked(gamestate, move);
   3.316 +        break;
   3.317 +    default:
   3.318 +        return INVALID_MOVE_SYNTAX;
   3.319 +    }
   3.320 +    
   3.321 +    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
   3.322 +}
   3.323 +
   3.324 +int validate_move(GameState *gamestate, Move *move) {
   3.325 +    
   3.326 +    int result = validate_move_rules(gamestate, move);
   3.327 +    
   3.328 +    /* cancel processing to save resources */
   3.329 +    if (result != VALID_MOVE_SEMANTICS) {
   3.330 +        return result;
   3.331 +    }
   3.332 +    
   3.333 +    /* find kings for check validation */
   3.334 +    uint8_t piececolor = (move->piece & COLOR_MASK);
   3.335 +    
   3.336 +    uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
   3.337 +    for (uint8_t row = 0 ; row < 8 ; row++) {
   3.338 +        for (uint8_t file = 0 ; file < 8 ; file++) {
   3.339 +            if (gamestate->board[row][file] ==
   3.340 +                    (piececolor == WHITE?WKING:BKING)) {
   3.341 +                mykingfile = file;
   3.342 +                mykingrow = row;
   3.343 +            } else if (gamestate->board[row][file] ==
   3.344 +                    (piececolor == WHITE?BKING:WKING)) {
   3.345 +                opkingfile = file;
   3.346 +                opkingrow = row;
   3.347 +            }
   3.348 +        }
   3.349 +    }
   3.350 +    
   3.351 +    /* simulate move for check validation */
   3.352 +    GameState simulation = gamestate_copy_sim(gamestate);
   3.353 +    Move simmove = *move;
   3.354 +    apply_move_impl(&simulation, &simmove, 1);
   3.355 +    
   3.356 +    /* don't move into or stay in check position */
   3.357 +    if (is_covered(&simulation, mykingrow, mykingfile,
   3.358 +        opponent_color(piececolor))) {
   3.359 +        
   3.360 +        gamestate_cleanup(&simulation);
   3.361 +        if ((move->piece & PIECE_MASK) == KING) {
   3.362 +            return KING_MOVES_INTO_CHECK;
   3.363 +        } else {
   3.364 +            /* last move is always not null in this case */
   3.365 +            return gamestate->lastmove->move.check ?
   3.366 +                KING_IN_CHECK : PIECE_PINNED;
   3.367 +        }
   3.368 +    }
   3.369 +    
   3.370 +    /* correct check and checkmate flags (move is still valid) */
   3.371 +    Move threats[16];
   3.372 +    uint8_t threatcount;
   3.373 +    move->check = get_threats(&simulation, opkingrow, opkingfile,
   3.374 +        piececolor, threats, &threatcount);
   3.375 +    
   3.376 +    if (move->check) {
   3.377 +        /* determine possible escape fields */
   3.378 +        _Bool canescape = 0;
   3.379 +        for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
   3.380 +            for (int df = -1 ; df <= 1 && !canescape ; df++) {
   3.381 +                if (!(dr == 0 && df == 0)  &&
   3.382 +                        isidx(opkingrow + dr) && isidx(opkingfile + df)) {
   3.383 +                    
   3.384 +                    /* escape field neither blocked nor covered */
   3.385 +                    if ((simulation.board[opkingrow + dr][opkingfile + df]
   3.386 +                            & COLOR_MASK) != opponent_color(piececolor)) {
   3.387 +                        canescape |= !is_covered(&simulation,
   3.388 +                            opkingrow + dr, opkingfile + df, piececolor);
   3.389 +                    }
   3.390 +                }
   3.391 +            }
   3.392 +        }
   3.393 +        /* can't escape, can he capture? */
   3.394 +        if (!canescape && threatcount == 1) {
   3.395 +            canescape = is_attacked(&simulation, threats[0].fromrow,
   3.396 +                threats[0].fromfile, opponent_color(piececolor));
   3.397 +        }
   3.398 +        
   3.399 +        /* can't capture, can he block? */
   3.400 +        if (!canescape && threatcount == 1) {
   3.401 +            Move *threat = &(threats[0]);
   3.402 +            uint8_t threatpiece = threat->piece & PIECE_MASK;
   3.403 +            
   3.404 +            /* knight, pawns and the king cannot be blocked */
   3.405 +            if (threatpiece == BISHOP || threatpiece == ROOK
   3.406 +                || threatpiece == QUEEN) {
   3.407 +                if (threat->fromrow == threat->torow) {
   3.408 +                    /* rook aspect (on row) */
   3.409 +                    int d = threat->tofile > threat->fromfile ? 1 : -1;
   3.410 +                    uint8_t file = threat->fromfile;
   3.411 +                    while (!canescape && file != threat->tofile - d) {
   3.412 +                        file += d;
   3.413 +                        canescape |= is_protected(&simulation,
   3.414 +                            threat->torow, file, opponent_color(piececolor));
   3.415 +                    }
   3.416 +                } else if (threat->fromfile == threat->tofile) {
   3.417 +                    /* rook aspect (on file) */
   3.418 +                    int d = threat->torow > threat->fromrow ? 1 : -1;
   3.419 +                    uint8_t row = threat->fromrow;
   3.420 +                    while (!canescape && row != threat->torow - d) {
   3.421 +                        row += d;
   3.422 +                        canescape |= is_protected(&simulation,
   3.423 +                            row, threat->tofile, opponent_color(piececolor));
   3.424 +                    }
   3.425 +                } else {
   3.426 +                    /* bishop aspect */
   3.427 +                    int dr = threat->torow > threat->fromrow ? 1 : -1;
   3.428 +                    int df = threat->tofile > threat->fromfile ? 1 : -1;
   3.429 +
   3.430 +                    uint8_t row = threat->fromrow;
   3.431 +                    uint8_t file = threat->fromfile;
   3.432 +                    while (!canescape && file != threat->tofile - df
   3.433 +                        && row != threat->torow - dr) {
   3.434 +                        row += dr;
   3.435 +                        file += df;
   3.436 +                        canescape |= is_protected(&simulation, row, file,
   3.437 +                            opponent_color(piececolor));
   3.438 +                    }
   3.439 +                }
   3.440 +            }
   3.441 +        }
   3.442 +            
   3.443 +        if (!canescape) {
   3.444 +            gamestate->checkmate = 1;
   3.445 +        }
   3.446 +    }
   3.447 +    
   3.448 +    gamestate_cleanup(&simulation);
   3.449 +    
   3.450 +    return VALID_MOVE_SEMANTICS;
   3.451 +}
   3.452 +
   3.453 +_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
   3.454 +        uint8_t color, Move *threats, uint8_t *threatcount) {
   3.455 +    Move candidates[32];
   3.456 +    int candidatecount = 0;
   3.457 +    for (uint8_t r = 0 ; r < 8 ; r++) {
   3.458 +        for (uint8_t f = 0 ; f < 8 ; f++) {
   3.459 +            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
   3.460 +                // non-capturing move
   3.461 +                memset(&(candidates[candidatecount]), 0, sizeof(Move));
   3.462 +                candidates[candidatecount].piece = gamestate->board[r][f];
   3.463 +                candidates[candidatecount].fromrow = r;
   3.464 +                candidates[candidatecount].fromfile = f;
   3.465 +                candidates[candidatecount].torow = row;
   3.466 +                candidates[candidatecount].tofile = file;
   3.467 +                candidatecount++;
   3.468 +
   3.469 +                // capturing move
   3.470 +                memcpy(&(candidates[candidatecount]),
   3.471 +                    &(candidates[candidatecount-1]), sizeof(Move));
   3.472 +                candidates[candidatecount].capture = 1;
   3.473 +                candidatecount++;
   3.474 +            }
   3.475 +        }
   3.476 +    }
   3.477 +
   3.478 +    if (threatcount) {
   3.479 +        *threatcount = 0;
   3.480 +    }
   3.481 +    
   3.482 +    
   3.483 +    _Bool result = 0;
   3.484 +    
   3.485 +    for (int i = 0 ; i < candidatecount ; i++) {
   3.486 +        if (validate_move_rules(gamestate, &(candidates[i]))
   3.487 +                == VALID_MOVE_SEMANTICS) {
   3.488 +            result = 1;
   3.489 +            if (threats && threatcount) {
   3.490 +                threats[(*threatcount)++] = candidates[i];
   3.491 +            }
   3.492 +        }
   3.493 +    }
   3.494 +    
   3.495 +    return result;
   3.496 +}
   3.497 +
   3.498 +_Bool is_pinned(GameState *gamestate, Move *move) {
   3.499 +    uint8_t color = move->piece & COLOR_MASK;
   3.500 +
   3.501 +    uint8_t kingfile = 0, kingrow = 0;
   3.502 +    for (uint8_t row = 0 ; row < 8 ; row++) {
   3.503 +        for (uint8_t file = 0 ; file < 8 ; file++) {
   3.504 +            if (gamestate->board[row][file] == (color|KING)) {
   3.505 +                kingfile = file;
   3.506 +                kingrow = row;
   3.507 +            }
   3.508 +        }
   3.509 +    }
   3.510 +
   3.511 +    GameState simulation = gamestate_copy_sim(gamestate);
   3.512 +    Move simmove = *move;
   3.513 +    apply_move(&simulation, &simmove);
   3.514 +    _Bool covered = is_covered(&simulation,
   3.515 +        kingrow, kingfile, opponent_color(color));
   3.516 +    gamestate_cleanup(&simulation);
   3.517 +    
   3.518 +    return covered;
   3.519 +}
   3.520 +
   3.521 +_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
   3.522 +        uint8_t color, Move *threats, uint8_t *threatcount) {
   3.523 +    
   3.524 +    if (threatcount) {
   3.525 +        *threatcount = 0;
   3.526 +    }
   3.527 +
   3.528 +    Move candidates[16];
   3.529 +    uint8_t candidatecount;
   3.530 +    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
   3.531 +        
   3.532 +        _Bool result = 0;
   3.533 +        uint8_t kingfile = 0, kingrow = 0;
   3.534 +        for (uint8_t row = 0 ; row < 8 ; row++) {
   3.535 +            for (uint8_t file = 0 ; file < 8 ; file++) {
   3.536 +                if (gamestate->board[row][file] == (color|KING)) {
   3.537 +                    kingfile = file;
   3.538 +                    kingrow = row;
   3.539 +                }
   3.540 +            }
   3.541 +        }
   3.542 +
   3.543 +        for (uint8_t i = 0 ; i < candidatecount ; i++) {
   3.544 +            GameState simulation = gamestate_copy_sim(gamestate);
   3.545 +            Move simmove = candidates[i];
   3.546 +            apply_move(&simulation, &simmove);
   3.547 +            if (!is_covered(&simulation, kingrow, kingfile,
   3.548 +                    opponent_color(color))) {
   3.549 +                result = 1;
   3.550 +                if (threats && threatcount) {
   3.551 +                    threats[(*threatcount)++] = candidates[i];
   3.552 +                }
   3.553 +            }
   3.554 +        }
   3.555 +        
   3.556 +        return result;
   3.557 +    } else {
   3.558 +        return 0;
   3.559 +    }
   3.560 +}
   3.561 +
   3.562 +static int getlocation(GameState *gamestate, Move *move) {   
   3.563 +
   3.564 +    uint8_t color = move->piece & COLOR_MASK;
   3.565 +    _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
   3.566 +    
   3.567 +    Move threats[16], *threat = NULL;
   3.568 +    uint8_t threatcount;
   3.569 +    
   3.570 +    if (get_threats(gamestate, move->torow, move->tofile, color,
   3.571 +            threats, &threatcount)) {
   3.572 +        
   3.573 +        int reason = INVALID_POSITION;
   3.574 +        
   3.575 +        // find threats for the specified position
   3.576 +        for (uint8_t i = 0 ; i < threatcount ; i++) {
   3.577 +            if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
   3.578 +                    == move->piece &&
   3.579 +                    (move->fromrow == POS_UNSPECIFIED ||
   3.580 +                    move->fromrow == threats[i].fromrow) &&
   3.581 +                    (move->fromfile == POS_UNSPECIFIED ||
   3.582 +                    move->fromfile == threats[i].fromfile)) {
   3.583 +
   3.584 +                if (threat) {
   3.585 +                    return AMBIGUOUS_MOVE;
   3.586 +                } else {
   3.587 +                    // found threat is no real threat
   3.588 +                    if (is_pinned(gamestate, &(threats[i]))) {
   3.589 +                        reason = incheck?KING_IN_CHECK:PIECE_PINNED;
   3.590 +                    } else {
   3.591 +                        threat = &(threats[i]);
   3.592 +                    }
   3.593 +                }
   3.594 +            }
   3.595 +        }
   3.596 +        
   3.597 +        // can't threaten specified position
   3.598 +        if (!threat) {
   3.599 +            return reason;
   3.600 +        }
   3.601 +
   3.602 +        memcpy(move, threat, sizeof(Move));
   3.603 +        return VALID_MOVE_SYNTAX;
   3.604 +    } else {
   3.605 +        return INVALID_POSITION;
   3.606 +    }
   3.607 +}
   3.608 +
   3.609 +int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
   3.610 +    memset(move, 0, sizeof(Move));
   3.611 +    move->fromfile = POS_UNSPECIFIED;
   3.612 +    move->fromrow = POS_UNSPECIFIED;
   3.613 +
   3.614 +    size_t len = strlen(mstr);
   3.615 +    if (len < 1 || len > 6) {
   3.616 +        return INVALID_MOVE_SYNTAX;
   3.617 +    }
   3.618 +    
   3.619 +    /* evaluate check/checkmate flags */
   3.620 +    if (mstr[len-1] == '+') {
   3.621 +        len--; mstr[len] = '\0';
   3.622 +        move->check = 1;
   3.623 +    } else if (mstr[len-1] == '#') {
   3.624 +        len--; mstr[len] = '\0';
   3.625 +        /* ignore - validation should set game state */
   3.626 +    }
   3.627 +    
   3.628 +    /* evaluate promotion */
   3.629 +    if (len > 3 && mstr[len-2] == '=') {
   3.630 +        move->promotion = getpiece(mstr[len-1]);
   3.631 +        if (!move->promotion) {
   3.632 +            return INVALID_MOVE_SYNTAX;
   3.633 +        } else {
   3.634 +            move->promotion |= color;
   3.635 +            len -= 2;
   3.636 +            mstr[len] = 0;
   3.637 +        }
   3.638 +    }
   3.639 +    
   3.640 +    if (len == 2) {
   3.641 +        /* pawn move (e.g. "e4") */
   3.642 +        move->piece = PAWN;
   3.643 +        move->tofile = fileidx(mstr[0]);
   3.644 +        move->torow = rowidx(mstr[1]);
   3.645 +    } else if (len == 3) {
   3.646 +        if (strcmp(mstr, "O-O") == 0) {
   3.647 +            /* king side castling */
   3.648 +            move->piece = KING;
   3.649 +            move->fromfile = fileidx('e');
   3.650 +            move->tofile = fileidx('g');
   3.651 +            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   3.652 +        } else {
   3.653 +            /* move (e.g. "Nf3") */
   3.654 +            move->piece = getpiece(mstr[0]);
   3.655 +            move->tofile = fileidx(mstr[1]);
   3.656 +            move->torow = rowidx(mstr[2]);
   3.657 +        }
   3.658 +    } else if (len == 4) {
   3.659 +        move->piece = getpiece(mstr[0]);
   3.660 +        if (!move->piece) {
   3.661 +            move->piece = PAWN;
   3.662 +            move->fromfile = fileidx(mstr[0]);
   3.663 +        }
   3.664 +        if (mstr[1] == 'x') {
   3.665 +            /* capture (e.g. "Nxf3", "dxe5") */
   3.666 +            move->capture = 1;
   3.667 +        } else {
   3.668 +            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   3.669 +            if (isfile(mstr[1])) {
   3.670 +                move->fromfile = fileidx(mstr[1]);
   3.671 +                if (move->piece == PAWN) {
   3.672 +                    move->piece = 0;
   3.673 +                }
   3.674 +            } else {
   3.675 +                move->fromrow = rowidx(mstr[1]);
   3.676 +            }
   3.677 +        }
   3.678 +        move->tofile = fileidx(mstr[2]);
   3.679 +        move->torow = rowidx(mstr[3]);
   3.680 +    } else if (len == 5) {
   3.681 +        if (strcmp(mstr, "O-O-O") == 0) {
   3.682 +            /* queen side castling "O-O-O" */
   3.683 +            move->piece = KING;
   3.684 +            move->fromfile = fileidx('e');
   3.685 +            move->tofile = fileidx('c');
   3.686 +            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   3.687 +        } else {
   3.688 +            move->piece = getpiece(mstr[0]);
   3.689 +            if (mstr[2] == 'x') {
   3.690 +                move->capture = 1;
   3.691 +                if (move->piece) {
   3.692 +                    /* capture (e.g. "Ndxf3") */
   3.693 +                    move->fromfile = fileidx(mstr[1]);
   3.694 +                } else {
   3.695 +                    /* long notation capture (e.g. "e5xf6") */
   3.696 +                    move->piece = PAWN;
   3.697 +                    move->fromfile = fileidx(mstr[0]);
   3.698 +                    move->fromrow = rowidx(mstr[1]);
   3.699 +                }
   3.700 +            } else {
   3.701 +                /* long notation move (e.g. "Nc5a4") */
   3.702 +                move->fromfile = fileidx(mstr[1]);
   3.703 +                move->fromrow = rowidx(mstr[2]);
   3.704 +            }
   3.705 +            move->tofile = fileidx(mstr[3]);
   3.706 +            move->torow = rowidx(mstr[4]);
   3.707 +        }
   3.708 +    } else if (len == 6) {
   3.709 +        /* long notation capture (e.g. "Nc5xf3") */
   3.710 +        if (mstr[3] == 'x') {
   3.711 +            move->capture = 1;
   3.712 +            move->piece = getpiece(mstr[0]);
   3.713 +            move->fromfile = fileidx(mstr[1]);
   3.714 +            move->fromrow = rowidx(mstr[2]);
   3.715 +            move->tofile = fileidx(mstr[4]);
   3.716 +            move->torow = rowidx(mstr[5]);
   3.717 +        }
   3.718 +    }
   3.719 +
   3.720 +    
   3.721 +    if (move->piece) {
   3.722 +        if (move->piece == PAWN
   3.723 +            && move->torow == (color==WHITE?7:0)
   3.724 +            && !move->promotion) {
   3.725 +            return NEED_PROMOTION;
   3.726 +        }
   3.727 +        
   3.728 +        move->piece |= color;
   3.729 +        if (move->fromfile == POS_UNSPECIFIED
   3.730 +            || move->fromrow == POS_UNSPECIFIED) {
   3.731 +            return getlocation(gamestate, move);
   3.732 +        } else {
   3.733 +            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   3.734 +        }
   3.735 +    } else {
   3.736 +        return INVALID_MOVE_SYNTAX;
   3.737 +    }
   3.738 +}
   3.739 +
   3.740 +_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
   3.741 +        uint8_t color) {
   3.742 +    
   3.743 +    Move threats[16];
   3.744 +    uint8_t threatcount;
   3.745 +    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
   3.746 +        for (int i = 0 ; i < threatcount ; i++) {
   3.747 +            if (threats[i].piece != (color|KING)) {
   3.748 +                return 1;
   3.749 +            }
   3.750 +        }
   3.751 +        return 0;
   3.752 +    } else {
   3.753 +        return 0;
   3.754 +    }
   3.755 +}
   3.756 +
   3.757 +uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
   3.758 +        uint8_t color) {
   3.759 +    if (!gameinfo->timecontrol) {
   3.760 +        return 0;
   3.761 +    }
   3.762 +    
   3.763 +    if (gamestate->movelist) {
   3.764 +        uint16_t time = gameinfo->time;
   3.765 +        suseconds_t micros = 0;
   3.766 +        
   3.767 +        MoveList *movelist = color == WHITE ?
   3.768 +            gamestate->movelist : gamestate->movelist->next;
   3.769 +        
   3.770 +        while (movelist) {
   3.771 +            time += gameinfo->addtime;
   3.772 +            
   3.773 +            struct movetimeval *movetime = &(movelist->move.movetime);
   3.774 +            if (movetime->tv_sec >= time) {
   3.775 +                return 0;
   3.776 +            }
   3.777 +            
   3.778 +            time -= movetime->tv_sec;
   3.779 +            micros += movetime->tv_usec;
   3.780 +            
   3.781 +            movelist = movelist->next ? movelist->next->next : NULL;
   3.782 +        }
   3.783 +        
   3.784 +        time_t sec;
   3.785 +        movelist = gamestate->lastmove;
   3.786 +        if ((movelist->move.piece & COLOR_MASK) != color) {
   3.787 +            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
   3.788 +            struct timeval currenttstamp;
   3.789 +            gettimeofday(&currenttstamp, NULL);
   3.790 +            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
   3.791 +            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
   3.792 +            if (sec >= time) {
   3.793 +                return 0;
   3.794 +            }
   3.795 +            
   3.796 +            time -= sec;
   3.797 +        }
   3.798 +        
   3.799 +        sec = micros / 1e6L;
   3.800 +        
   3.801 +        if (sec >= time) {
   3.802 +            return 0;
   3.803 +        }
   3.804 +
   3.805 +        time -= sec;
   3.806 +        
   3.807 +        return time;
   3.808 +    } else {
   3.809 +        return gameinfo->time;
   3.810 +    }
   3.811 +}
     4.1 --- a/test/bigtestfile.c	Mon Apr 24 20:54:38 2023 +0200
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,808 +0,0 @@
     4.4 -/*
     4.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 - *
     4.7 - * Copyright 2014 Mike Becker. All rights reserved.
     4.8 - *
     4.9 - * Redistribution and use in source and binary forms, with or without
    4.10 - * modification, are permitted provided that the following conditions are met:
    4.11 - *
    4.12 - *   1. Redistributions of source code must retain the above copyright
    4.13 - *      notice, this list of conditions and the following disclaimer.
    4.14 - *
    4.15 - *   2. Redistributions in binary form must reproduce the above copyright
    4.16 - *      notice, this list of conditions and the following disclaimer in the
    4.17 - *      documentation and/or other materials provided with the distribution.
    4.18 - *
    4.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    4.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    4.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    4.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    4.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    4.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    4.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    4.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    4.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    4.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    4.29 - * POSSIBILITY OF SUCH DAMAGE.
    4.30 - *
    4.31 - */
    4.32 -
    4.33 -#include "rules.h"
    4.34 -#include "chess.h"
    4.35 -#include <string.h>
    4.36 -#include <stdlib.h>
    4.37 -#include <sys/time.h>
    4.38 -
    4.39 -static GameState gamestate_copy_sim(GameState *gamestate) {
    4.40 -    GameState simulation = *gamestate;
    4.41 -    if (simulation.lastmove) {
    4.42 -        MoveList *lastmovecopy = malloc(sizeof(MoveList));
    4.43 -        *lastmovecopy = *(simulation.lastmove);
    4.44 -        simulation.movelist = simulation.lastmove = lastmovecopy;
    4.45 -    }
    4.46 -
    4.47 -    return simulation;
    4.48 -}
    4.49 -
    4.50 -void gamestate_init(GameState *gamestate) {
    4.51 -    memset(gamestate, 0, sizeof(GameState));
    4.52 -    
    4.53 -    Board initboard = {
    4.54 -        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
    4.55 -        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
    4.56 -        {0,     0,       0,       0,      0,     0,       0,       0},
    4.57 -        {0,     0,       0,       0,      0,     0,       0,       0},
    4.58 -        {0,     0,       0,       0,      0,     0,       0,       0},
    4.59 -        {0,     0,       0,       0,      0,     0,       0,       0},
    4.60 -        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
    4.61 -        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
    4.62 -    };
    4.63 -    memcpy(gamestate->board, initboard, sizeof(Board));
    4.64 -}
    4.65 -
    4.66 -void gamestate_cleanup(GameState *gamestate) {
    4.67 -    MoveList *elem;
    4.68 -    elem = gamestate->movelist;
    4.69 -    while (elem) {
    4.70 -        MoveList *cur = elem;
    4.71 -        elem = elem->next;
    4.72 -        free(cur);
    4.73 -    };
    4.74 -}
    4.75 -
    4.76 -/* MUST be called IMMEDIATLY after applying a move to work correctly */
    4.77 -static void format_move(GameState *gamestate, Move *move) {
    4.78 -    char *string = move->string;
    4.79 -    
    4.80 -    /* at least 8 characters should be available, wipe them out */
    4.81 -    memset(string, 0, 8);
    4.82 -    
    4.83 -    /* special formats for castling */
    4.84 -    if ((move->piece&PIECE_MASK) == KING &&
    4.85 -            abs(move->tofile-move->fromfile) == 2) {
    4.86 -        if (move->tofile==fileidx('c')) {
    4.87 -            memcpy(string, "O-O-O", 5);
    4.88 -        } else {
    4.89 -            memcpy(string, "O-O", 3);
    4.90 -        }
    4.91 -    }
    4.92 -
    4.93 -    /* start by notating the piece character */
    4.94 -    string[0] = getpiecechr(move->piece);
    4.95 -    int idx = string[0] ? 1 : 0;
    4.96 -    
    4.97 -    /* find out how many source information we do need */
    4.98 -    uint8_t piece = move->piece & PIECE_MASK;
    4.99 -    if (piece == PAWN) {
   4.100 -        if (move->capture) {
   4.101 -            string[idx++] = filechr(move->fromfile);
   4.102 -        }
   4.103 -    } else if (piece != KING) {
   4.104 -        Move threats[16];
   4.105 -        uint8_t threatcount;
   4.106 -        get_real_threats(gamestate, move->torow, move->tofile,
   4.107 -            move->piece&COLOR_MASK, threats, &threatcount);
   4.108 -        if (threatcount > 1) {
   4.109 -            int ambrows = 0, ambfiles = 0;
   4.110 -            for (uint8_t i = 0 ; i < threatcount ; i++) {
   4.111 -                if (threats[i].fromrow == move->fromrow) {
   4.112 -                    ambrows++;
   4.113 -                }
   4.114 -                if (threats[i].fromfile == move->fromfile) {
   4.115 -                    ambfiles++;
   4.116 -                }
   4.117 -            }
   4.118 -            /* ambiguous row, name file */
   4.119 -            if (ambrows > 1) {
   4.120 -                string[idx++] = filechr(move->fromfile);
   4.121 -            }
   4.122 -            /* ambiguous file, name row */
   4.123 -            if (ambfiles > 1) {
   4.124 -                string[idx++] = filechr(move->fromrow);
   4.125 -            }
   4.126 -        }
   4.127 -    }
   4.128 -    
   4.129 -    /* capturing? */
   4.130 -    if (move->capture) {
   4.131 -        string[idx++] = 'x';
   4.132 -    }
   4.133 -    
   4.134 -    /* destination */
   4.135 -    string[idx++] = filechr(move->tofile);
   4.136 -    string[idx++] = rowchr(move->torow);
   4.137 -    
   4.138 -    /* promotion? */
   4.139 -    if (move->promotion) {
   4.140 -        string[idx++] = '=';
   4.141 -        string[idx++] = getpiecechr(move->promotion);
   4.142 -    }
   4.143 -    
   4.144 -    /* check? */
   4.145 -    if (move->check) {
   4.146 -        /* works only, if this function is called when applying the move */
   4.147 -        string[idx++] = gamestate->checkmate?'#':'+';
   4.148 -    }
   4.149 -}
   4.150 -
   4.151 -static void addmove(GameState* gamestate, Move *move) {
   4.152 -    MoveList *elem = malloc(sizeof(MoveList));
   4.153 -    elem->next = NULL;
   4.154 -    elem->move = *move;
   4.155 -    
   4.156 -    struct timeval curtimestamp;
   4.157 -    gettimeofday(&curtimestamp, NULL);
   4.158 -    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
   4.159 -    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
   4.160 -    
   4.161 -    if (gamestate->lastmove) {
   4.162 -        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
   4.163 -        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
   4.164 -        suseconds_t micros;
   4.165 -        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
   4.166 -            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
   4.167 -            sec--;
   4.168 -        } else {
   4.169 -            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
   4.170 -        }
   4.171 -        
   4.172 -        elem->move.movetime.tv_sec = sec;
   4.173 -        elem->move.movetime.tv_usec = micros;
   4.174 -        
   4.175 -        gamestate->lastmove->next = elem;
   4.176 -        gamestate->lastmove = elem;
   4.177 -    } else {
   4.178 -        elem->move.movetime.tv_usec = 0;
   4.179 -        elem->move.movetime.tv_sec = 0;
   4.180 -        gamestate->movelist = gamestate->lastmove = elem;
   4.181 -    }
   4.182 -}
   4.183 -
   4.184 -char getpiecechr(uint8_t piece) {
   4.185 -    switch (piece & PIECE_MASK) {
   4.186 -    case ROOK: return 'R';
   4.187 -    case KNIGHT: return 'N';
   4.188 -    case BISHOP: return 'B';
   4.189 -    case QUEEN: return 'Q';
   4.190 -    case KING: return 'K';
   4.191 -    default: return '\0';
   4.192 -    }
   4.193 -}
   4.194 -
   4.195 -uint8_t getpiece(char c) {
   4.196 -    switch (c) {
   4.197 -        case 'R': return ROOK;
   4.198 -        case 'N': return KNIGHT;
   4.199 -        case 'B': return BISHOP;
   4.200 -        case 'Q': return QUEEN;
   4.201 -        case 'K': return KING;
   4.202 -        default: return 0;
   4.203 -    }
   4.204 -}
   4.205 -
   4.206 -static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
   4.207 -    uint8_t piece = move->piece & PIECE_MASK;
   4.208 -    uint8_t color = move->piece & COLOR_MASK;
   4.209 -    
   4.210 -    /* en passant capture */
   4.211 -    if (move->capture && piece == PAWN &&
   4.212 -        mdst(gamestate->board, move) == 0) {
   4.213 -        gamestate->board[move->fromrow][move->tofile] = 0;
   4.214 -    }
   4.215 -    
   4.216 -    /* remove old en passant threats */
   4.217 -    for (uint8_t file = 0 ; file < 8 ; file++) {
   4.218 -        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
   4.219 -        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
   4.220 -    }
   4.221 -    
   4.222 -    /* add new en passant threat */
   4.223 -    if (piece == PAWN && (
   4.224 -        (move->fromrow == 1 && move->torow == 3) ||
   4.225 -        (move->fromrow == 6 && move->torow == 4))) {
   4.226 -        move->piece |= ENPASSANT_THREAT;
   4.227 -    }
   4.228 -    
   4.229 -    /* move (and maybe capture or promote) */
   4.230 -    msrc(gamestate->board, move) = 0;
   4.231 -    if (move->promotion) {
   4.232 -        mdst(gamestate->board, move) = move->promotion;
   4.233 -    } else {
   4.234 -        mdst(gamestate->board, move) = move->piece;
   4.235 -    }
   4.236 -    
   4.237 -    /* castling */
   4.238 -    if (piece == KING && move->fromfile == fileidx('e')) {
   4.239 -        
   4.240 -        if (move->tofile == fileidx('g')) {
   4.241 -            gamestate->board[move->torow][fileidx('h')] = 0;
   4.242 -            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
   4.243 -        } else if (move->tofile == fileidx('c')) {
   4.244 -            gamestate->board[move->torow][fileidx('a')] = 0;
   4.245 -            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
   4.246 -        }
   4.247 -    }
   4.248 -
   4.249 -    if (!simulate) {
   4.250 -        if (!move->string[0]) {
   4.251 -            format_move(gamestate, move);
   4.252 -        }
   4.253 -    }
   4.254 -    /* add move, even in simulation (checkmate test needs it) */
   4.255 -    addmove(gamestate, move);
   4.256 -}
   4.257 -
   4.258 -void apply_move(GameState *gamestate, Move *move) {
   4.259 -    apply_move_impl(gamestate, move, 0);
   4.260 -}
   4.261 -
   4.262 -static int validate_move_rules(GameState *gamestate, Move *move) {
   4.263 -    /* validate indices (don't trust opponent) */
   4.264 -    if (!chkidx(move)) {
   4.265 -        return INVALID_POSITION;
   4.266 -    }
   4.267 -    
   4.268 -    /* must move */
   4.269 -    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
   4.270 -        return INVALID_MOVE_SYNTAX;
   4.271 -    }
   4.272 -    
   4.273 -    /* does piece exist */
   4.274 -    if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
   4.275 -           != (move->piece&(PIECE_MASK|COLOR_MASK))) {
   4.276 -        return INVALID_POSITION;
   4.277 -    }
   4.278 -    
   4.279 -    /* can't capture own pieces */
   4.280 -    if ((mdst(gamestate->board, move) & COLOR_MASK)
   4.281 -            == (move->piece & COLOR_MASK)) {
   4.282 -        return RULES_VIOLATED;
   4.283 -    }
   4.284 -    
   4.285 -    /* must capture, if and only if destination is occupied */
   4.286 -    if ((mdst(gamestate->board, move) == 0 && move->capture) ||
   4.287 -            (mdst(gamestate->board, move) != 0 && !move->capture)) {
   4.288 -        return INVALID_MOVE_SYNTAX;
   4.289 -    }
   4.290 -    
   4.291 -    /* validate individual rules */
   4.292 -    _Bool chkrules;
   4.293 -    switch (move->piece & PIECE_MASK) {
   4.294 -    case PAWN:
   4.295 -        chkrules = pawn_chkrules(gamestate, move) &&
   4.296 -            !pawn_isblocked(gamestate, move);
   4.297 -        break;
   4.298 -    case ROOK:
   4.299 -        chkrules = rook_chkrules(move) &&
   4.300 -            !rook_isblocked(gamestate, move);
   4.301 -        break;
   4.302 -    case KNIGHT:
   4.303 -        chkrules = knight_chkrules(move); /* knight is never blocked */
   4.304 -        break;
   4.305 -    case BISHOP:
   4.306 -        chkrules = bishop_chkrules(move) &&
   4.307 -            !bishop_isblocked(gamestate, move);
   4.308 -        break;
   4.309 -    case QUEEN:
   4.310 -        chkrules = queen_chkrules(move) &&
   4.311 -            !queen_isblocked(gamestate, move);
   4.312 -        break;
   4.313 -    case KING:
   4.314 -        chkrules = king_chkrules(gamestate, move) &&
   4.315 -            !king_isblocked(gamestate, move);
   4.316 -        break;
   4.317 -    default:
   4.318 -        return INVALID_MOVE_SYNTAX;
   4.319 -    }
   4.320 -    
   4.321 -    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
   4.322 -}
   4.323 -
   4.324 -int validate_move(GameState *gamestate, Move *move) {
   4.325 -    
   4.326 -    int result = validate_move_rules(gamestate, move);
   4.327 -    
   4.328 -    /* cancel processing to save resources */
   4.329 -    if (result != VALID_MOVE_SEMANTICS) {
   4.330 -        return result;
   4.331 -    }
   4.332 -    
   4.333 -    /* find kings for check validation */
   4.334 -    uint8_t piececolor = (move->piece & COLOR_MASK);
   4.335 -    
   4.336 -    uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
   4.337 -    for (uint8_t row = 0 ; row < 8 ; row++) {
   4.338 -        for (uint8_t file = 0 ; file < 8 ; file++) {
   4.339 -            if (gamestate->board[row][file] ==
   4.340 -                    (piececolor == WHITE?WKING:BKING)) {
   4.341 -                mykingfile = file;
   4.342 -                mykingrow = row;
   4.343 -            } else if (gamestate->board[row][file] ==
   4.344 -                    (piececolor == WHITE?BKING:WKING)) {
   4.345 -                opkingfile = file;
   4.346 -                opkingrow = row;
   4.347 -            }
   4.348 -        }
   4.349 -    }
   4.350 -    
   4.351 -    /* simulate move for check validation */
   4.352 -    GameState simulation = gamestate_copy_sim(gamestate);
   4.353 -    Move simmove = *move;
   4.354 -    apply_move_impl(&simulation, &simmove, 1);
   4.355 -    
   4.356 -    /* don't move into or stay in check position */
   4.357 -    if (is_covered(&simulation, mykingrow, mykingfile,
   4.358 -        opponent_color(piececolor))) {
   4.359 -        
   4.360 -        gamestate_cleanup(&simulation);
   4.361 -        if ((move->piece & PIECE_MASK) == KING) {
   4.362 -            return KING_MOVES_INTO_CHECK;
   4.363 -        } else {
   4.364 -            /* last move is always not null in this case */
   4.365 -            return gamestate->lastmove->move.check ?
   4.366 -                KING_IN_CHECK : PIECE_PINNED;
   4.367 -        }
   4.368 -    }
   4.369 -    
   4.370 -    /* correct check and checkmate flags (move is still valid) */
   4.371 -    Move threats[16];
   4.372 -    uint8_t threatcount;
   4.373 -    move->check = get_threats(&simulation, opkingrow, opkingfile,
   4.374 -        piececolor, threats, &threatcount);
   4.375 -    
   4.376 -    if (move->check) {
   4.377 -        /* determine possible escape fields */
   4.378 -        _Bool canescape = 0;
   4.379 -        for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
   4.380 -            for (int df = -1 ; df <= 1 && !canescape ; df++) {
   4.381 -                if (!(dr == 0 && df == 0)  &&
   4.382 -                        isidx(opkingrow + dr) && isidx(opkingfile + df)) {
   4.383 -                    
   4.384 -                    /* escape field neither blocked nor covered */
   4.385 -                    if ((simulation.board[opkingrow + dr][opkingfile + df]
   4.386 -                            & COLOR_MASK) != opponent_color(piececolor)) {
   4.387 -                        canescape |= !is_covered(&simulation,
   4.388 -                            opkingrow + dr, opkingfile + df, piececolor);
   4.389 -                    }
   4.390 -                }
   4.391 -            }
   4.392 -        }
   4.393 -        /* can't escape, can he capture? */
   4.394 -        if (!canescape && threatcount == 1) {
   4.395 -            canescape = is_attacked(&simulation, threats[0].fromrow,
   4.396 -                threats[0].fromfile, opponent_color(piececolor));
   4.397 -        }
   4.398 -        
   4.399 -        /* can't capture, can he block? */
   4.400 -        if (!canescape && threatcount == 1) {
   4.401 -            Move *threat = &(threats[0]);
   4.402 -            uint8_t threatpiece = threat->piece & PIECE_MASK;
   4.403 -            
   4.404 -            /* knight, pawns and the king cannot be blocked */
   4.405 -            if (threatpiece == BISHOP || threatpiece == ROOK
   4.406 -                || threatpiece == QUEEN) {
   4.407 -                if (threat->fromrow == threat->torow) {
   4.408 -                    /* rook aspect (on row) */
   4.409 -                    int d = threat->tofile > threat->fromfile ? 1 : -1;
   4.410 -                    uint8_t file = threat->fromfile;
   4.411 -                    while (!canescape && file != threat->tofile - d) {
   4.412 -                        file += d;
   4.413 -                        canescape |= is_protected(&simulation,
   4.414 -                            threat->torow, file, opponent_color(piececolor));
   4.415 -                    }
   4.416 -                } else if (threat->fromfile == threat->tofile) {
   4.417 -                    /* rook aspect (on file) */
   4.418 -                    int d = threat->torow > threat->fromrow ? 1 : -1;
   4.419 -                    uint8_t row = threat->fromrow;
   4.420 -                    while (!canescape && row != threat->torow - d) {
   4.421 -                        row += d;
   4.422 -                        canescape |= is_protected(&simulation,
   4.423 -                            row, threat->tofile, opponent_color(piececolor));
   4.424 -                    }
   4.425 -                } else {
   4.426 -                    /* bishop aspect */
   4.427 -                    int dr = threat->torow > threat->fromrow ? 1 : -1;
   4.428 -                    int df = threat->tofile > threat->fromfile ? 1 : -1;
   4.429 -
   4.430 -                    uint8_t row = threat->fromrow;
   4.431 -                    uint8_t file = threat->fromfile;
   4.432 -                    while (!canescape && file != threat->tofile - df
   4.433 -                        && row != threat->torow - dr) {
   4.434 -                        row += dr;
   4.435 -                        file += df;
   4.436 -                        canescape |= is_protected(&simulation, row, file,
   4.437 -                            opponent_color(piececolor));
   4.438 -                    }
   4.439 -                }
   4.440 -            }
   4.441 -        }
   4.442 -            
   4.443 -        if (!canescape) {
   4.444 -            gamestate->checkmate = 1;
   4.445 -        }
   4.446 -    }
   4.447 -    
   4.448 -    gamestate_cleanup(&simulation);
   4.449 -    
   4.450 -    return VALID_MOVE_SEMANTICS;
   4.451 -}
   4.452 -
   4.453 -_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
   4.454 -        uint8_t color, Move *threats, uint8_t *threatcount) {
   4.455 -    Move candidates[32];
   4.456 -    int candidatecount = 0;
   4.457 -    for (uint8_t r = 0 ; r < 8 ; r++) {
   4.458 -        for (uint8_t f = 0 ; f < 8 ; f++) {
   4.459 -            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
   4.460 -                // non-capturing move
   4.461 -                memset(&(candidates[candidatecount]), 0, sizeof(Move));
   4.462 -                candidates[candidatecount].piece = gamestate->board[r][f];
   4.463 -                candidates[candidatecount].fromrow = r;
   4.464 -                candidates[candidatecount].fromfile = f;
   4.465 -                candidates[candidatecount].torow = row;
   4.466 -                candidates[candidatecount].tofile = file;
   4.467 -                candidatecount++;
   4.468 -
   4.469 -                // capturing move
   4.470 -                memcpy(&(candidates[candidatecount]),
   4.471 -                    &(candidates[candidatecount-1]), sizeof(Move));
   4.472 -                candidates[candidatecount].capture = 1;
   4.473 -                candidatecount++;
   4.474 -            }
   4.475 -        }
   4.476 -    }
   4.477 -
   4.478 -    if (threatcount) {
   4.479 -        *threatcount = 0;
   4.480 -    }
   4.481 -    
   4.482 -    
   4.483 -    _Bool result = 0;
   4.484 -    
   4.485 -    for (int i = 0 ; i < candidatecount ; i++) {
   4.486 -        if (validate_move_rules(gamestate, &(candidates[i]))
   4.487 -                == VALID_MOVE_SEMANTICS) {
   4.488 -            result = 1;
   4.489 -            if (threats && threatcount) {
   4.490 -                threats[(*threatcount)++] = candidates[i];
   4.491 -            }
   4.492 -        }
   4.493 -    }
   4.494 -    
   4.495 -    return result;
   4.496 -}
   4.497 -
   4.498 -_Bool is_pinned(GameState *gamestate, Move *move) {
   4.499 -    uint8_t color = move->piece & COLOR_MASK;
   4.500 -
   4.501 -    uint8_t kingfile = 0, kingrow = 0;
   4.502 -    for (uint8_t row = 0 ; row < 8 ; row++) {
   4.503 -        for (uint8_t file = 0 ; file < 8 ; file++) {
   4.504 -            if (gamestate->board[row][file] == (color|KING)) {
   4.505 -                kingfile = file;
   4.506 -                kingrow = row;
   4.507 -            }
   4.508 -        }
   4.509 -    }
   4.510 -
   4.511 -    GameState simulation = gamestate_copy_sim(gamestate);
   4.512 -    Move simmove = *move;
   4.513 -    apply_move(&simulation, &simmove);
   4.514 -    _Bool covered = is_covered(&simulation,
   4.515 -        kingrow, kingfile, opponent_color(color));
   4.516 -    gamestate_cleanup(&simulation);
   4.517 -    
   4.518 -    return covered;
   4.519 -}
   4.520 -
   4.521 -_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
   4.522 -        uint8_t color, Move *threats, uint8_t *threatcount) {
   4.523 -    
   4.524 -    if (threatcount) {
   4.525 -        *threatcount = 0;
   4.526 -    }
   4.527 -
   4.528 -    Move candidates[16];
   4.529 -    uint8_t candidatecount;
   4.530 -    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
   4.531 -        
   4.532 -        _Bool result = 0;
   4.533 -        uint8_t kingfile = 0, kingrow = 0;
   4.534 -        for (uint8_t row = 0 ; row < 8 ; row++) {
   4.535 -            for (uint8_t file = 0 ; file < 8 ; file++) {
   4.536 -                if (gamestate->board[row][file] == (color|KING)) {
   4.537 -                    kingfile = file;
   4.538 -                    kingrow = row;
   4.539 -                }
   4.540 -            }
   4.541 -        }
   4.542 -
   4.543 -        for (uint8_t i = 0 ; i < candidatecount ; i++) {
   4.544 -            GameState simulation = gamestate_copy_sim(gamestate);
   4.545 -            Move simmove = candidates[i];
   4.546 -            apply_move(&simulation, &simmove);
   4.547 -            if (!is_covered(&simulation, kingrow, kingfile,
   4.548 -                    opponent_color(color))) {
   4.549 -                result = 1;
   4.550 -                if (threats && threatcount) {
   4.551 -                    threats[(*threatcount)++] = candidates[i];
   4.552 -                }
   4.553 -            }
   4.554 -        }
   4.555 -        
   4.556 -        return result;
   4.557 -    } else {
   4.558 -        return 0;
   4.559 -    }
   4.560 -}
   4.561 -
   4.562 -static int getlocation(GameState *gamestate, Move *move) {   
   4.563 -
   4.564 -    uint8_t color = move->piece & COLOR_MASK;
   4.565 -    _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
   4.566 -    
   4.567 -    Move threats[16], *threat = NULL;
   4.568 -    uint8_t threatcount;
   4.569 -    
   4.570 -    if (get_threats(gamestate, move->torow, move->tofile, color,
   4.571 -            threats, &threatcount)) {
   4.572 -        
   4.573 -        int reason = INVALID_POSITION;
   4.574 -        
   4.575 -        // find threats for the specified position
   4.576 -        for (uint8_t i = 0 ; i < threatcount ; i++) {
   4.577 -            if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
   4.578 -                    == move->piece &&
   4.579 -                    (move->fromrow == POS_UNSPECIFIED ||
   4.580 -                    move->fromrow == threats[i].fromrow) &&
   4.581 -                    (move->fromfile == POS_UNSPECIFIED ||
   4.582 -                    move->fromfile == threats[i].fromfile)) {
   4.583 -
   4.584 -                if (threat) {
   4.585 -                    return AMBIGUOUS_MOVE;
   4.586 -                } else {
   4.587 -                    // found threat is no real threat
   4.588 -                    if (is_pinned(gamestate, &(threats[i]))) {
   4.589 -                        reason = incheck?KING_IN_CHECK:PIECE_PINNED;
   4.590 -                    } else {
   4.591 -                        threat = &(threats[i]);
   4.592 -                    }
   4.593 -                }
   4.594 -            }
   4.595 -        }
   4.596 -        
   4.597 -        // can't threaten specified position
   4.598 -        if (!threat) {
   4.599 -            return reason;
   4.600 -        }
   4.601 -
   4.602 -        memcpy(move, threat, sizeof(Move));
   4.603 -        return VALID_MOVE_SYNTAX;
   4.604 -    } else {
   4.605 -        return INVALID_POSITION;
   4.606 -    }
   4.607 -}
   4.608 -
   4.609 -int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
   4.610 -    memset(move, 0, sizeof(Move));
   4.611 -    move->fromfile = POS_UNSPECIFIED;
   4.612 -    move->fromrow = POS_UNSPECIFIED;
   4.613 -
   4.614 -    size_t len = strlen(mstr);
   4.615 -    if (len < 1 || len > 6) {
   4.616 -        return INVALID_MOVE_SYNTAX;
   4.617 -    }
   4.618 -    
   4.619 -    /* evaluate check/checkmate flags */
   4.620 -    if (mstr[len-1] == '+') {
   4.621 -        len--; mstr[len] = '\0';
   4.622 -        move->check = 1;
   4.623 -    } else if (mstr[len-1] == '#') {
   4.624 -        len--; mstr[len] = '\0';
   4.625 -        /* ignore - validation should set game state */
   4.626 -    }
   4.627 -    
   4.628 -    /* evaluate promotion */
   4.629 -    if (len > 3 && mstr[len-2] == '=') {
   4.630 -        move->promotion = getpiece(mstr[len-1]);
   4.631 -        if (!move->promotion) {
   4.632 -            return INVALID_MOVE_SYNTAX;
   4.633 -        } else {
   4.634 -            move->promotion |= color;
   4.635 -            len -= 2;
   4.636 -            mstr[len] = 0;
   4.637 -        }
   4.638 -    }
   4.639 -    
   4.640 -    if (len == 2) {
   4.641 -        /* pawn move (e.g. "e4") */
   4.642 -        move->piece = PAWN;
   4.643 -        move->tofile = fileidx(mstr[0]);
   4.644 -        move->torow = rowidx(mstr[1]);
   4.645 -    } else if (len == 3) {
   4.646 -        if (strcmp(mstr, "O-O") == 0) {
   4.647 -            /* king side castling */
   4.648 -            move->piece = KING;
   4.649 -            move->fromfile = fileidx('e');
   4.650 -            move->tofile = fileidx('g');
   4.651 -            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   4.652 -        } else {
   4.653 -            /* move (e.g. "Nf3") */
   4.654 -            move->piece = getpiece(mstr[0]);
   4.655 -            move->tofile = fileidx(mstr[1]);
   4.656 -            move->torow = rowidx(mstr[2]);
   4.657 -        }
   4.658 -    } else if (len == 4) {
   4.659 -        move->piece = getpiece(mstr[0]);
   4.660 -        if (!move->piece) {
   4.661 -            move->piece = PAWN;
   4.662 -            move->fromfile = fileidx(mstr[0]);
   4.663 -        }
   4.664 -        if (mstr[1] == 'x') {
   4.665 -            /* capture (e.g. "Nxf3", "dxe5") */
   4.666 -            move->capture = 1;
   4.667 -        } else {
   4.668 -            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   4.669 -            if (isfile(mstr[1])) {
   4.670 -                move->fromfile = fileidx(mstr[1]);
   4.671 -                if (move->piece == PAWN) {
   4.672 -                    move->piece = 0;
   4.673 -                }
   4.674 -            } else {
   4.675 -                move->fromrow = rowidx(mstr[1]);
   4.676 -            }
   4.677 -        }
   4.678 -        move->tofile = fileidx(mstr[2]);
   4.679 -        move->torow = rowidx(mstr[3]);
   4.680 -    } else if (len == 5) {
   4.681 -        if (strcmp(mstr, "O-O-O") == 0) {
   4.682 -            /* queen side castling "O-O-O" */
   4.683 -            move->piece = KING;
   4.684 -            move->fromfile = fileidx('e');
   4.685 -            move->tofile = fileidx('c');
   4.686 -            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   4.687 -        } else {
   4.688 -            move->piece = getpiece(mstr[0]);
   4.689 -            if (mstr[2] == 'x') {
   4.690 -                move->capture = 1;
   4.691 -                if (move->piece) {
   4.692 -                    /* capture (e.g. "Ndxf3") */
   4.693 -                    move->fromfile = fileidx(mstr[1]);
   4.694 -                } else {
   4.695 -                    /* long notation capture (e.g. "e5xf6") */
   4.696 -                    move->piece = PAWN;
   4.697 -                    move->fromfile = fileidx(mstr[0]);
   4.698 -                    move->fromrow = rowidx(mstr[1]);
   4.699 -                }
   4.700 -            } else {
   4.701 -                /* long notation move (e.g. "Nc5a4") */
   4.702 -                move->fromfile = fileidx(mstr[1]);
   4.703 -                move->fromrow = rowidx(mstr[2]);
   4.704 -            }
   4.705 -            move->tofile = fileidx(mstr[3]);
   4.706 -            move->torow = rowidx(mstr[4]);
   4.707 -        }
   4.708 -    } else if (len == 6) {
   4.709 -        /* long notation capture (e.g. "Nc5xf3") */
   4.710 -        if (mstr[3] == 'x') {
   4.711 -            move->capture = 1;
   4.712 -            move->piece = getpiece(mstr[0]);
   4.713 -            move->fromfile = fileidx(mstr[1]);
   4.714 -            move->fromrow = rowidx(mstr[2]);
   4.715 -            move->tofile = fileidx(mstr[4]);
   4.716 -            move->torow = rowidx(mstr[5]);
   4.717 -        }
   4.718 -    }
   4.719 -
   4.720 -    
   4.721 -    if (move->piece) {
   4.722 -        if (move->piece == PAWN
   4.723 -            && move->torow == (color==WHITE?7:0)
   4.724 -            && !move->promotion) {
   4.725 -            return NEED_PROMOTION;
   4.726 -        }
   4.727 -        
   4.728 -        move->piece |= color;
   4.729 -        if (move->fromfile == POS_UNSPECIFIED
   4.730 -            || move->fromrow == POS_UNSPECIFIED) {
   4.731 -            return getlocation(gamestate, move);
   4.732 -        } else {
   4.733 -            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   4.734 -        }
   4.735 -    } else {
   4.736 -        return INVALID_MOVE_SYNTAX;
   4.737 -    }
   4.738 -}
   4.739 -
   4.740 -_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
   4.741 -        uint8_t color) {
   4.742 -    
   4.743 -    Move threats[16];
   4.744 -    uint8_t threatcount;
   4.745 -    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
   4.746 -        for (int i = 0 ; i < threatcount ; i++) {
   4.747 -            if (threats[i].piece != (color|KING)) {
   4.748 -                return 1;
   4.749 -            }
   4.750 -        }
   4.751 -        return 0;
   4.752 -    } else {
   4.753 -        return 0;
   4.754 -    }
   4.755 -}
   4.756 -
   4.757 -uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
   4.758 -        uint8_t color) {
   4.759 -    if (!gameinfo->timecontrol) {
   4.760 -        return 0;
   4.761 -    }
   4.762 -    
   4.763 -    if (gamestate->movelist) {
   4.764 -        uint16_t time = gameinfo->time;
   4.765 -        suseconds_t micros = 0;
   4.766 -        
   4.767 -        MoveList *movelist = color == WHITE ?
   4.768 -            gamestate->movelist : gamestate->movelist->next;
   4.769 -        
   4.770 -        while (movelist) {
   4.771 -            time += gameinfo->addtime;
   4.772 -            
   4.773 -            struct movetimeval *movetime = &(movelist->move.movetime);
   4.774 -            if (movetime->tv_sec >= time) {
   4.775 -                return 0;
   4.776 -            }
   4.777 -            
   4.778 -            time -= movetime->tv_sec;
   4.779 -            micros += movetime->tv_usec;
   4.780 -            
   4.781 -            movelist = movelist->next ? movelist->next->next : NULL;
   4.782 -        }
   4.783 -        
   4.784 -        time_t sec;
   4.785 -        movelist = gamestate->lastmove;
   4.786 -        if ((movelist->move.piece & COLOR_MASK) != color) {
   4.787 -            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
   4.788 -            struct timeval currenttstamp;
   4.789 -            gettimeofday(&currenttstamp, NULL);
   4.790 -            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
   4.791 -            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
   4.792 -            if (sec >= time) {
   4.793 -                return 0;
   4.794 -            }
   4.795 -            
   4.796 -            time -= sec;
   4.797 -        }
   4.798 -        
   4.799 -        sec = micros / 1e6L;
   4.800 -        
   4.801 -        if (sec >= time) {
   4.802 -            return 0;
   4.803 -        }
   4.804 -
   4.805 -        time -= sec;
   4.806 -        
   4.807 -        return time;
   4.808 -    } else {
   4.809 -        return gameinfo->time;
   4.810 -    }
   4.811 -}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/test/ctest.c	Mon Apr 24 21:01:41 2023 +0200
     5.3 @@ -0,0 +1,391 @@
     5.4 +/*
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2015 Olaf Wintermann. All rights reserved.
     5.8 + *
     5.9 + * Redistribution and use in source and binary forms, with or without
    5.10 + * modification, are permitted provided that the following conditions are met:
    5.11 + *
    5.12 + *   1. Redistributions of source code must retain the above copyright
    5.13 + *      notice, this list of conditions and the following disclaimer.
    5.14 + *
    5.15 + *   2. Redistributions in binary form must reproduce the above copyright
    5.16 + *      notice, this list of conditions and the following disclaimer in the
    5.17 + *      documentation and/or other materials provided with the distribution.
    5.18 + *
    5.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    5.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    5.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    5.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    5.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    5.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    5.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    5.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    5.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    5.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    5.29 + * POSSIBILITY OF SUCH DAMAGE.
    5.30 + */
    5.31 +
    5.32 +#include <time.h>
    5.33 +#include <stdio.h>
    5.34 +#include <stdlib.h>
    5.35 +#include <string.h>
    5.36 +#include <ucx/string.h>
    5.37 +#include <ucx/buffer.h>
    5.38 +#include <ucx/utils.h>
    5.39 +#include <libxml/tree.h>
    5.40 +#include <curl/curl.h>
    5.41 +
    5.42 +#include <openssl/sha.h>
    5.43 +#include <openssl/hmac.h>
    5.44 +#include <openssl/evp.h>
    5.45 +#include <openssl/bio.h>
    5.46 +#include <openssl/buffer.h>
    5.47 +#include <openssl/rand.h>
    5.48 +
    5.49 +#include "utils.h"
    5.50 +#include "crypto.h"
    5.51 +#include "webdav.h"
    5.52 +
    5.53 +#define MACRO1337 1337L
    5.54 +
    5.55 +/* -------------------- This is a testing file. -------------------------- */
    5.56 +/*
    5.57 +time_t util_parse_creationdate(char *str) {
    5.58 +    // example: 2012-11-29T21:35:35Z
    5.59 +    if(!str) {
    5.60 +        return 0;
    5.61 +    }
    5.62 +    // TODO
    5.63 +    return 0;
    5.64 +}
    5.65 +*/
    5.66 +time_t util_parse_lastmodified(char *str) {
    5.67 +    // example: Thu, 29 Nov 2012 21:35:35 GMT
    5.68 +    if(!str) {
    5.69 +        return 0;
    5.70 +    } else {
    5.71 +        return curl_getdate(str, NULL);
    5.72 +    }
    5.73 +}
    5.74 +
    5.75 +int util_getboolean(char *v) {
    5.76 +    if(v[0] == 'T' || v[0] == 't') {
    5.77 +        return 1;
    5.78 +    }
    5.79 +    return 0;
    5.80 +}
    5.81 +
    5.82 +int util_strtoint(char *str, int64_t *value) {
    5.83 +    char *end;
    5.84 +    int64_t val = strtoll(str, &end, 0);
    5.85 +    if(strlen(end) == 0) {
    5.86 +        *value = val;
    5.87 +        return 1;
    5.88 +    } else {
    5.89 +        return 0;
    5.90 +    }
    5.91 +}
    5.92 +
    5.93 +char* util_url_path(char *url) { 
    5.94 +    char *path = NULL;
    5.95 +    size_t len = strlen(url);
    5.96 +    int slashcount = 0;
    5.97 +    int slmax;
    5.98 +    if(len > 7 && !strncasecmp(url, "http://", 7)) {
    5.99 +        slmax = 3;
   5.100 +    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
   5.101 +        slmax = 3;
   5.102 +    } else {
   5.103 +        slmax = 1;
   5.104 +    }
   5.105 +    char c;
   5.106 +    for(int i=0;i<len;i++) {
   5.107 +        c = url[i];
   5.108 +        if(c == '/') {
   5.109 +            slashcount++;
   5.110 +            if(slashcount == slmax) {
   5.111 +                path = url + i;
   5.112 +                break;
   5.113 +            }
   5.114 +        }
   5.115 +    } 
   5.116 +    return path;
   5.117 +}
   5.118 +
   5.119 +char* util_url_decode(DavSession *sn, char *url) {
   5.120 +    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
   5.121 +    char *ret = strdup(unesc);
   5.122 +    curl_free(unesc);
   5.123 +    return ret;
   5.124 +}
   5.125 +
   5.126 +char* util_resource_name(char *url) {
   5.127 +    int si = 0;
   5.128 +    int osi = 0;
   5.129 +    int i = 0;
   5.130 +    int p = 0;
   5.131 +    char c;
   5.132 +    while((c = url[i]) != 0) {
   5.133 +        if(c == '/') {
   5.134 +            osi = si;
   5.135 +            si = i;
   5.136 +            p = 1;
   5.137 +        }
   5.138 +        i++;
   5.139 +    }
   5.140 +    
   5.141 +    char *name = url + si + p;
   5.142 +    if(name[0] == 0) {
   5.143 +        name = url + osi + p;
   5.144 +        if(name[0] == 0) {
   5.145 +            return url;
   5.146 +        }
   5.147 +    }
   5.148 +    
   5.149 +    return name;
   5.150 +}
   5.151 +
   5.152 +int util_mkdir(char *path, mode_t mode) {
   5.153 +#ifdef _WIN32
   5.154 +    return mkdir(path);
   5.155 +#else
   5.156 +    return mkdir(path, mode);
   5.157 +#endif
   5.158 +}
   5.159 +
   5.160 +char* util_concat_path(char *url_base, char *p) {
   5.161 +    sstr_t base = sstr(url_base);
   5.162 +    sstr_t path;
   5.163 +    if(p) {
   5.164 +        path = sstr(p);
   5.165 +    } else {
   5.166 +        path = sstrn("", 0);
   5.167 +    }
   5.168 +    
   5.169 +    int add_separator = 0;
   5.170 +    if(base.ptr[base.length-1] == '/') {
   5.171 +        if(path.ptr[0] == '/') {
   5.172 +            base.length--;
   5.173 +        }
   5.174 +    } else {
   5.175 +        if(path.length == 0 || path.ptr[0] != '/') {
   5.176 +            add_separator = 1;
   5.177 +        }
   5.178 +    }
   5.179 +    
   5.180 +    sstr_t url;
   5.181 +    if(add_separator) {
   5.182 +        url = sstrcat(3, base, sstr("/"), path);
   5.183 +    } else {
   5.184 +        url = sstrcat(2, base, path);
   5.185 +    }
   5.186 +    
   5.187 +    return url.ptr;
   5.188 +}
   5.189 +
   5.190 +void util_set_url(DavSession *sn, char *href) {
   5.191 +    sstr_t base = sstr(sn->base_url);
   5.192 +    sstr_t href_str = sstr(href);
   5.193 +    
   5.194 +    char *base_path = util_url_path(sn->base_url);
   5.195 +    base.length -= strlen(base_path);
   5.196 +    
   5.197 +    sstr_t url = sstrcat(2, base, href_str);
   5.198 +    
   5.199 +    curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr);
   5.200 +    free(url.ptr);
   5.201 +}
   5.202 +
   5.203 +char* util_path_to_url(DavSession *sn, char *path) {
   5.204 +    char *space = malloc(256);
   5.205 +    UcxBuffer *url = ucx_buffer_new(space, 256, CX_BUFFER_AUTO_EXTEND);
   5.206 +    
   5.207 +    // add base url
   5.208 +    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
   5.209 +    // remove trailing slash
   5.210 +    ucx_buffer_seek(url, -1, SEEK_CUR);
   5.211 +    
   5.212 +    sstr_t p = sstr(path);
   5.213 +    ssize_t ntk = 0;
   5.214 +    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
   5.215 +    
   5.216 +    for(int i=0;i<ntk;i++) {
   5.217 +        sstr_t node = tks[i];
   5.218 +        if(node.length > 0) {
   5.219 +            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
   5.220 +            ucx_buffer_putc(url, '/');
   5.221 +            ucx_buffer_write(esc, 1, strlen(esc), url);
   5.222 +            curl_free(esc);
   5.223 +        }
   5.224 +        free(node.ptr);
   5.225 +    }
   5.226 +    free(tks);
   5.227 +    if(path[p.length-1] == '/') {
   5.228 +        ucx_buffer_putc(url, '/');
   5.229 +    }
   5.230 +    ucx_buffer_putc(url, 0);
   5.231 +    
   5.232 +    space = url->space;
   5.233 +    ucx_buffer_free(url);
   5.234 +    
   5.235 +    return space;
   5.236 +}
   5.237 +
   5.238 +char* util_parent_path(char *path) {
   5.239 +    char *name = util_resource_name(path);
   5.240 +    size_t namelen = strlen(name);
   5.241 +    size_t pathlen = strlen(path);
   5.242 +    size_t parentlen = pathlen - namelen;
   5.243 +    char *parent = malloc(parentlen + 1);
   5.244 +    memcpy(parent, path, parentlen);
   5.245 +    parent[parentlen] = '\0';
   5.246 +    return parent;
   5.247 +}
   5.248 +
   5.249 +
   5.250 +char* util_xml_get_text(xmlNode *elm) {
   5.251 +    xmlNode *node = elm->children;
   5.252 +    while(node) {
   5.253 +        if(node->type == XML_TEXT_NODE) {
   5.254 +            return (char*)node->content;
   5.255 +        }
   5.256 +        node = node->next;
   5.257 +    }
   5.258 +    return NULL;
   5.259 +}
   5.260 +
   5.261 +
   5.262 +char* util_base64decode(char *in) {
   5.263 +    int len = 0;
   5.264 +    return util_base64decode_len(in, &len);
   5.265 +}
   5.266 +
   5.267 +char* util_base64decode_len(char* in, int *outlen) {
   5.268 +    size_t len = strlen(in);
   5.269 +    char *out = calloc(1, len);
   5.270 +    
   5.271 +    BIO* b = BIO_new_mem_buf(in, len);
   5.272 +    BIO *d = BIO_new(BIO_f_base64());
   5.273 +    BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL);
   5.274 +    b = BIO_push(d, b);
   5.275 +
   5.276 +    *outlen = BIO_read(b, out, len);
   5.277 +    BIO_free_all(b);
   5.278 +    
   5.279 +    return out;
   5.280 +}
   5.281 +
   5.282 +char* util_base64encode(char *in, size_t len) { 
   5.283 +    BIO *b;
   5.284 +    BIO *e;
   5.285 +    BUF_MEM *mem;
   5.286 +
   5.287 +    e = BIO_new(BIO_f_base64());
   5.288 +    b = BIO_new(BIO_s_mem());
   5.289 +    
   5.290 +    e = BIO_push(e, b);
   5.291 +    BIO_write(e, in, len);
   5.292 +    BIO_flush(e);
   5.293 +    
   5.294 +    BIO_get_mem_ptr(e, &mem);
   5.295 +    char *out = malloc(mem->length);
   5.296 +    memcpy(out, mem->data, mem->length -1);
   5.297 +    out[mem->length - 1] = '\0';
   5.298 +
   5.299 +    BIO_free_all(e);
   5.300 +
   5.301 +    return out;
   5.302 +}
   5.303 +
   5.304 +char* util_encrypt_str(DavSession *sn, char *str, char *key) {
   5.305 +    DavKey *k = dav_context_get_key(sn->context, key);
   5.306 +    if(!k) {
   5.307 +        // TODO: session error
   5.308 +        return NULL;
   5.309 +    }
   5.310 +    
   5.311 +    char *enc_str = aes_encrypt(str, k);
   5.312 +    char *ret_str = dav_session_strdup(sn, enc_str);
   5.313 +    free(enc_str);
   5.314 +    return ret_str;
   5.315 +}
   5.316 +
   5.317 +/* commented out for testing reasons */
   5.318 +/*
   5.319 +char* util_decrypt_str(DavSession *sn, char *str, char *key) {
   5.320 +    DavKey *k = dav_context_get_key(sn->context, key);
   5.321 +    if(!k) {
   5.322 +        // TODO: session error
   5.323 +        return NULL;
   5.324 +    }
   5.325 +    
   5.326 +    char *dec_str = aes_decrypt(str, k);
   5.327 +    char *ret_str = dav_session_strdup(sn, dec_str);
   5.328 +    free(dec_str);
   5.329 +    return ret_str;
   5.330 +}
   5.331 +*/
   5.332 +char* util_random_str() {
   5.333 +    unsigned char *str = malloc(25);
   5.334 +    str[24] = '\0';
   5.335 +    
   5.336 +    sstr_t t = S(
   5.337 +            "01234567890"
   5.338 +            "abcdefghijklmnopqrstuvwxyz"
   5.339 +            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
   5.340 +    const unsigned char *table = (const unsigned char*)t.ptr;
   5.341 +    
   5.342 +    RAND_pseudo_bytes(str, 24);
   5.343 +    for(int i=0;i<24;i++) {
   5.344 +        int c = str[i] % t.length;
   5.345 +        str[i] = table[c];
   5.346 +    }
   5.347 +    
   5.348 +    return (char*)str;
   5.349 +}
   5.350 +
   5.351 +/*
   5.352 + * gets a substring from 0 to the appearance of the token
   5.353 + * tokens are separated by space
   5.354 + * sets sub to the substring and returns the remaining string
   5.355 + */
   5.356 +sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
   5.357 +    int i;
   5.358 +    int token_start = -1;
   5.359 +    int token_end = -1;
   5.360 +    for(i=0;i<=str.length;i++) {
   5.361 +        int c;
   5.362 +        if(i == str.length) {
   5.363 +            c = ' ';
   5.364 +        } else {
   5.365 +            c = str.ptr[i];
   5.366 +        }
   5.367 +        if(c < 33) {
   5.368 +            if(token_start != -1) {
   5.369 +                token_end = i;
   5.370 +                size_t len = token_end - token_start;
   5.371 +                sstr_t tk = sstrsubsl(str, token_start, len);
   5.372 +                //printf("token: {%.*s}\n", token.length, token.ptr);
   5.373 +                if(!sstrcmp(tk, token)) {
   5.374 +                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
   5.375 +                    break;
   5.376 +                }
   5.377 +                token_start = -1;
   5.378 +                token_end = -1;
   5.379 +            }
   5.380 +        } else {
   5.381 +            if(token_start == -1) {
   5.382 +                token_start = i;
   5.383 +            }
   5.384 +        }
   5.385 +    }
   5.386 +    
   5.387 +    if(i < str.length) {
   5.388 +        return sstrtrim(sstrsubs(str, i));
   5.389 +    } else {
   5.390 +        str.ptr = NULL;
   5.391 +        str.length = 0;
   5.392 +        return str;
   5.393 +    }
   5.394 +}
     6.1 --- a/test/ctestfile.c	Mon Apr 24 20:54:38 2023 +0200
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,391 +0,0 @@
     6.4 -/*
     6.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     6.6 - *
     6.7 - * Copyright 2015 Olaf Wintermann. All rights reserved.
     6.8 - *
     6.9 - * Redistribution and use in source and binary forms, with or without
    6.10 - * modification, are permitted provided that the following conditions are met:
    6.11 - *
    6.12 - *   1. Redistributions of source code must retain the above copyright
    6.13 - *      notice, this list of conditions and the following disclaimer.
    6.14 - *
    6.15 - *   2. Redistributions in binary form must reproduce the above copyright
    6.16 - *      notice, this list of conditions and the following disclaimer in the
    6.17 - *      documentation and/or other materials provided with the distribution.
    6.18 - *
    6.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    6.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    6.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    6.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    6.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    6.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    6.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    6.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    6.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    6.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    6.29 - * POSSIBILITY OF SUCH DAMAGE.
    6.30 - */
    6.31 -
    6.32 -#include <time.h>
    6.33 -#include <stdio.h>
    6.34 -#include <stdlib.h>
    6.35 -#include <string.h>
    6.36 -#include <ucx/string.h>
    6.37 -#include <ucx/buffer.h>
    6.38 -#include <ucx/utils.h>
    6.39 -#include <libxml/tree.h>
    6.40 -#include <curl/curl.h>
    6.41 -
    6.42 -#include <openssl/sha.h>
    6.43 -#include <openssl/hmac.h>
    6.44 -#include <openssl/evp.h>
    6.45 -#include <openssl/bio.h>
    6.46 -#include <openssl/buffer.h>
    6.47 -#include <openssl/rand.h>
    6.48 -
    6.49 -#include "utils.h"
    6.50 -#include "crypto.h"
    6.51 -#include "webdav.h"
    6.52 -
    6.53 -#define MACRO1337 1337L
    6.54 -
    6.55 -/* -------------------- This is a testing file. -------------------------- */
    6.56 -/*
    6.57 -time_t util_parse_creationdate(char *str) {
    6.58 -    // example: 2012-11-29T21:35:35Z
    6.59 -    if(!str) {
    6.60 -        return 0;
    6.61 -    }
    6.62 -    // TODO
    6.63 -    return 0;
    6.64 -}
    6.65 -*/
    6.66 -time_t util_parse_lastmodified(char *str) {
    6.67 -    // example: Thu, 29 Nov 2012 21:35:35 GMT
    6.68 -    if(!str) {
    6.69 -        return 0;
    6.70 -    } else {
    6.71 -        return curl_getdate(str, NULL);
    6.72 -    }
    6.73 -}
    6.74 -
    6.75 -int util_getboolean(char *v) {
    6.76 -    if(v[0] == 'T' || v[0] == 't') {
    6.77 -        return 1;
    6.78 -    }
    6.79 -    return 0;
    6.80 -}
    6.81 -
    6.82 -int util_strtoint(char *str, int64_t *value) {
    6.83 -    char *end;
    6.84 -    int64_t val = strtoll(str, &end, 0);
    6.85 -    if(strlen(end) == 0) {
    6.86 -        *value = val;
    6.87 -        return 1;
    6.88 -    } else {
    6.89 -        return 0;
    6.90 -    }
    6.91 -}
    6.92 -
    6.93 -char* util_url_path(char *url) { 
    6.94 -    char *path = NULL;
    6.95 -    size_t len = strlen(url);
    6.96 -    int slashcount = 0;
    6.97 -    int slmax;
    6.98 -    if(len > 7 && !strncasecmp(url, "http://", 7)) {
    6.99 -        slmax = 3;
   6.100 -    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
   6.101 -        slmax = 3;
   6.102 -    } else {
   6.103 -        slmax = 1;
   6.104 -    }
   6.105 -    char c;
   6.106 -    for(int i=0;i<len;i++) {
   6.107 -        c = url[i];
   6.108 -        if(c == '/') {
   6.109 -            slashcount++;
   6.110 -            if(slashcount == slmax) {
   6.111 -                path = url + i;
   6.112 -                break;
   6.113 -            }
   6.114 -        }
   6.115 -    } 
   6.116 -    return path;
   6.117 -}
   6.118 -
   6.119 -char* util_url_decode(DavSession *sn, char *url) {
   6.120 -    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
   6.121 -    char *ret = strdup(unesc);
   6.122 -    curl_free(unesc);
   6.123 -    return ret;
   6.124 -}
   6.125 -
   6.126 -char* util_resource_name(char *url) {
   6.127 -    int si = 0;
   6.128 -    int osi = 0;
   6.129 -    int i = 0;
   6.130 -    int p = 0;
   6.131 -    char c;
   6.132 -    while((c = url[i]) != 0) {
   6.133 -        if(c == '/') {
   6.134 -            osi = si;
   6.135 -            si = i;
   6.136 -            p = 1;
   6.137 -        }
   6.138 -        i++;
   6.139 -    }
   6.140 -    
   6.141 -    char *name = url + si + p;
   6.142 -    if(name[0] == 0) {
   6.143 -        name = url + osi + p;
   6.144 -        if(name[0] == 0) {
   6.145 -            return url;
   6.146 -        }
   6.147 -    }
   6.148 -    
   6.149 -    return name;
   6.150 -}
   6.151 -
   6.152 -int util_mkdir(char *path, mode_t mode) {
   6.153 -#ifdef _WIN32
   6.154 -    return mkdir(path);
   6.155 -#else
   6.156 -    return mkdir(path, mode);
   6.157 -#endif
   6.158 -}
   6.159 -
   6.160 -char* util_concat_path(char *url_base, char *p) {
   6.161 -    sstr_t base = sstr(url_base);
   6.162 -    sstr_t path;
   6.163 -    if(p) {
   6.164 -        path = sstr(p);
   6.165 -    } else {
   6.166 -        path = sstrn("", 0);
   6.167 -    }
   6.168 -    
   6.169 -    int add_separator = 0;
   6.170 -    if(base.ptr[base.length-1] == '/') {
   6.171 -        if(path.ptr[0] == '/') {
   6.172 -            base.length--;
   6.173 -        }
   6.174 -    } else {
   6.175 -        if(path.length == 0 || path.ptr[0] != '/') {
   6.176 -            add_separator = 1;
   6.177 -        }
   6.178 -    }
   6.179 -    
   6.180 -    sstr_t url;
   6.181 -    if(add_separator) {
   6.182 -        url = sstrcat(3, base, sstr("/"), path);
   6.183 -    } else {
   6.184 -        url = sstrcat(2, base, path);
   6.185 -    }
   6.186 -    
   6.187 -    return url.ptr;
   6.188 -}
   6.189 -
   6.190 -void util_set_url(DavSession *sn, char *href) {
   6.191 -    sstr_t base = sstr(sn->base_url);
   6.192 -    sstr_t href_str = sstr(href);
   6.193 -    
   6.194 -    char *base_path = util_url_path(sn->base_url);
   6.195 -    base.length -= strlen(base_path);
   6.196 -    
   6.197 -    sstr_t url = sstrcat(2, base, href_str);
   6.198 -    
   6.199 -    curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr);
   6.200 -    free(url.ptr);
   6.201 -}
   6.202 -
   6.203 -char* util_path_to_url(DavSession *sn, char *path) {
   6.204 -    char *space = malloc(256);
   6.205 -    UcxBuffer *url = ucx_buffer_new(space, 256, CX_BUFFER_AUTO_EXTEND);
   6.206 -    
   6.207 -    // add base url
   6.208 -    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
   6.209 -    // remove trailing slash
   6.210 -    ucx_buffer_seek(url, -1, SEEK_CUR);
   6.211 -    
   6.212 -    sstr_t p = sstr(path);
   6.213 -    ssize_t ntk = 0;
   6.214 -    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
   6.215 -    
   6.216 -    for(int i=0;i<ntk;i++) {
   6.217 -        sstr_t node = tks[i];
   6.218 -        if(node.length > 0) {
   6.219 -            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
   6.220 -            ucx_buffer_putc(url, '/');
   6.221 -            ucx_buffer_write(esc, 1, strlen(esc), url);
   6.222 -            curl_free(esc);
   6.223 -        }
   6.224 -        free(node.ptr);
   6.225 -    }
   6.226 -    free(tks);
   6.227 -    if(path[p.length-1] == '/') {
   6.228 -        ucx_buffer_putc(url, '/');
   6.229 -    }
   6.230 -    ucx_buffer_putc(url, 0);
   6.231 -    
   6.232 -    space = url->space;
   6.233 -    ucx_buffer_free(url);
   6.234 -    
   6.235 -    return space;
   6.236 -}
   6.237 -
   6.238 -char* util_parent_path(char *path) {
   6.239 -    char *name = util_resource_name(path);
   6.240 -    size_t namelen = strlen(name);
   6.241 -    size_t pathlen = strlen(path);
   6.242 -    size_t parentlen = pathlen - namelen;
   6.243 -    char *parent = malloc(parentlen + 1);
   6.244 -    memcpy(parent, path, parentlen);
   6.245 -    parent[parentlen] = '\0';
   6.246 -    return parent;
   6.247 -}
   6.248 -
   6.249 -
   6.250 -char* util_xml_get_text(xmlNode *elm) {
   6.251 -    xmlNode *node = elm->children;
   6.252 -    while(node) {
   6.253 -        if(node->type == XML_TEXT_NODE) {
   6.254 -            return (char*)node->content;
   6.255 -        }
   6.256 -        node = node->next;
   6.257 -    }
   6.258 -    return NULL;
   6.259 -}
   6.260 -
   6.261 -
   6.262 -char* util_base64decode(char *in) {
   6.263 -    int len = 0;
   6.264 -    return util_base64decode_len(in, &len);
   6.265 -}
   6.266 -
   6.267 -char* util_base64decode_len(char* in, int *outlen) {
   6.268 -    size_t len = strlen(in);
   6.269 -    char *out = calloc(1, len);
   6.270 -    
   6.271 -    BIO* b = BIO_new_mem_buf(in, len);
   6.272 -    BIO *d = BIO_new(BIO_f_base64());
   6.273 -    BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL);
   6.274 -    b = BIO_push(d, b);
   6.275 -
   6.276 -    *outlen = BIO_read(b, out, len);
   6.277 -    BIO_free_all(b);
   6.278 -    
   6.279 -    return out;
   6.280 -}
   6.281 -
   6.282 -char* util_base64encode(char *in, size_t len) { 
   6.283 -    BIO *b;
   6.284 -    BIO *e;
   6.285 -    BUF_MEM *mem;
   6.286 -
   6.287 -    e = BIO_new(BIO_f_base64());
   6.288 -    b = BIO_new(BIO_s_mem());
   6.289 -    
   6.290 -    e = BIO_push(e, b);
   6.291 -    BIO_write(e, in, len);
   6.292 -    BIO_flush(e);
   6.293 -    
   6.294 -    BIO_get_mem_ptr(e, &mem);
   6.295 -    char *out = malloc(mem->length);
   6.296 -    memcpy(out, mem->data, mem->length -1);
   6.297 -    out[mem->length - 1] = '\0';
   6.298 -
   6.299 -    BIO_free_all(e);
   6.300 -
   6.301 -    return out;
   6.302 -}
   6.303 -
   6.304 -char* util_encrypt_str(DavSession *sn, char *str, char *key) {
   6.305 -    DavKey *k = dav_context_get_key(sn->context, key);
   6.306 -    if(!k) {
   6.307 -        // TODO: session error
   6.308 -        return NULL;
   6.309 -    }
   6.310 -    
   6.311 -    char *enc_str = aes_encrypt(str, k);
   6.312 -    char *ret_str = dav_session_strdup(sn, enc_str);
   6.313 -    free(enc_str);
   6.314 -    return ret_str;
   6.315 -}
   6.316 -
   6.317 -/* commented out for testing reasons */
   6.318 -/*
   6.319 -char* util_decrypt_str(DavSession *sn, char *str, char *key) {
   6.320 -    DavKey *k = dav_context_get_key(sn->context, key);
   6.321 -    if(!k) {
   6.322 -        // TODO: session error
   6.323 -        return NULL;
   6.324 -    }
   6.325 -    
   6.326 -    char *dec_str = aes_decrypt(str, k);
   6.327 -    char *ret_str = dav_session_strdup(sn, dec_str);
   6.328 -    free(dec_str);
   6.329 -    return ret_str;
   6.330 -}
   6.331 -*/
   6.332 -char* util_random_str() {
   6.333 -    unsigned char *str = malloc(25);
   6.334 -    str[24] = '\0';
   6.335 -    
   6.336 -    sstr_t t = S(
   6.337 -            "01234567890"
   6.338 -            "abcdefghijklmnopqrstuvwxyz"
   6.339 -            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
   6.340 -    const unsigned char *table = (const unsigned char*)t.ptr;
   6.341 -    
   6.342 -    RAND_pseudo_bytes(str, 24);
   6.343 -    for(int i=0;i<24;i++) {
   6.344 -        int c = str[i] % t.length;
   6.345 -        str[i] = table[c];
   6.346 -    }
   6.347 -    
   6.348 -    return (char*)str;
   6.349 -}
   6.350 -
   6.351 -/*
   6.352 - * gets a substring from 0 to the appearance of the token
   6.353 - * tokens are separated by space
   6.354 - * sets sub to the substring and returns the remaining string
   6.355 - */
   6.356 -sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
   6.357 -    int i;
   6.358 -    int token_start = -1;
   6.359 -    int token_end = -1;
   6.360 -    for(i=0;i<=str.length;i++) {
   6.361 -        int c;
   6.362 -        if(i == str.length) {
   6.363 -            c = ' ';
   6.364 -        } else {
   6.365 -            c = str.ptr[i];
   6.366 -        }
   6.367 -        if(c < 33) {
   6.368 -            if(token_start != -1) {
   6.369 -                token_end = i;
   6.370 -                size_t len = token_end - token_start;
   6.371 -                sstr_t tk = sstrsubsl(str, token_start, len);
   6.372 -                //printf("token: {%.*s}\n", token.length, token.ptr);
   6.373 -                if(!sstrcmp(tk, token)) {
   6.374 -                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
   6.375 -                    break;
   6.376 -                }
   6.377 -                token_start = -1;
   6.378 -                token_end = -1;
   6.379 -            }
   6.380 -        } else {
   6.381 -            if(token_start == -1) {
   6.382 -                token_start = i;
   6.383 -            }
   6.384 -        }
   6.385 -    }
   6.386 -    
   6.387 -    if(i < str.length) {
   6.388 -        return sstrtrim(sstrsubs(str, i));
   6.389 -    } else {
   6.390 -        str.ptr = NULL;
   6.391 -        str.length = 0;
   6.392 -        return str;
   6.393 -    }
   6.394 -}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/test/javatest.java	Mon Apr 24 21:01:41 2023 +0200
     7.3 @@ -0,0 +1,176 @@
     7.4 +/*
     7.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     7.6 + *
     7.7 + * Copyright 2014 Mike Becker. All rights reserved.
     7.8 + *
     7.9 + * Redistribution and use in source and binary forms, with or without
    7.10 + * modification, are permitted provided that the following conditions are met:
    7.11 + *
    7.12 + *   1. Redistributions of source code must retain the above copyright
    7.13 + *      notice, this list of conditions and the following disclaimer.
    7.14 + *
    7.15 + *   2. Redistributions in binary form must reproduce the above copyright
    7.16 + *      notice, this list of conditions and the following disclaimer in the
    7.17 + *      documentation and/or other materials provided with the distribution.
    7.18 + *
    7.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    7.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    7.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    7.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    7.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    7.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    7.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    7.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    7.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    7.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    7.29 + * POSSIBILITY OF SUCH DAMAGE.
    7.30 + *
    7.31 + */
    7.32 +
    7.33 +package de.uapcore.sigred.doc.base;
    7.34 +
    7.35 +import de.uapcore.sigred.doc.Resources;
    7.36 +import de.uapcore.sigrapi.impl.Digraph;
    7.37 +import de.uapcore.sigrapi.impl.Graph;
    7.38 +import de.uapcore.sigrapi.IGraph;
    7.39 +import java.io.IOException;
    7.40 +import java.io.InputStream;
    7.41 +import java.io.OutputStream;
    7.42 +import java.util.concurrent.atomic.AtomicBoolean;
    7.43 +import java.util.concurrent.atomic.AtomicReference;
    7.44 +import org.apache.xerces.impl.Constants;
    7.45 +import org.dom4j.Document;
    7.46 +import org.dom4j.DocumentException;
    7.47 +import org.dom4j.DocumentHelper;
    7.48 +import org.dom4j.Element;
    7.49 +import org.dom4j.Namespace;
    7.50 +import org.dom4j.QName;
    7.51 +import org.dom4j.io.OutputFormat;
    7.52 +import org.dom4j.io.SAXReader;
    7.53 +import org.dom4j.io.XMLWriter;
    7.54 +import org.xml.sax.ErrorHandler;
    7.55 +import org.xml.sax.SAXException;
    7.56 +import org.xml.sax.SAXParseException;
    7.57 +
    7.58 +public abstract class AbstractGraphDocument<T extends IGraph>
    7.59 +        extends FileBackedDocument {
    7.60 +    
    7.61 +    protected static final Namespace NAMESPACE = Namespace.get("sigred",
    7.62 +        "http://develop.uap-core.de/sigred/");
    7.63 +    
    7.64 +    private static final
    7.65 +        QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
    7.66 +    private static final
    7.67 +        QName TAG_GRAPH = QName.get("graph", NAMESPACE);
    7.68 +    private static final
    7.69 +        QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
    7.70 +    private static final
    7.71 +        QName TAG_METADATA = QName.get("metadata", NAMESPACE);
    7.72 +    
    7.73 +    protected final T graph;
    7.74 +    
    7.75 +    private final GraphDocumentMetadata metadata;
    7.76 +    
    7.77 +    public AbstractGraphDocument(Class<T> graphType) {
    7.78 +        T g;
    7.79 +        try {
    7.80 +            g = graphType.newInstance();
    7.81 +        } catch (ReflectiveOperationException e) {
    7.82 +            assert false;
    7.83 +            g = null; // for the compiler
    7.84 +        }
    7.85 +        graph = g;
    7.86 +        metadata = new GraphDocumentMetadata();
    7.87 +    }
    7.88 +
    7.89 +    public T getGraph() {
    7.90 +        return graph;
    7.91 +    }
    7.92 +    
    7.93 +    public GraphDocumentMetadata getMetadata() {
    7.94 +        return metadata;
    7.95 +    }
    7.96 +
    7.97 +    protected abstract void writeGraph(Element rootNode) throws IOException;
    7.98 +    protected abstract void readGraph(Element rootNode) throws IOException;
    7.99 +
   7.100 +    @Override
   7.101 +    public void writeTo(OutputStream out) throws IOException {
   7.102 +        Document doc = DocumentHelper.createDocument();
   7.103 +
   7.104 +        Element rootNode = doc.addElement(TAG_GRAPHDOC);
   7.105 +
   7.106 +        Element metadataNode = rootNode.addElement(TAG_METADATA);
   7.107 +
   7.108 +        metadata.write(metadataNode);
   7.109 +
   7.110 +        if (graph instanceof Graph) {
   7.111 +            writeGraph(rootNode.addElement(TAG_GRAPH));
   7.112 +        } else if (graph instanceof Digraph) {
   7.113 +            writeGraph(rootNode.addElement(TAG_DIGRAPH));
   7.114 +        } else {
   7.115 +            throw new IOException("unsupported graph type");
   7.116 +        }
   7.117 +
   7.118 +        XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
   7.119 +        writer.write(doc);
   7.120 +        writer.flush();
   7.121 +    }
   7.122 +
   7.123 +    @Override
   7.124 +    public void readFrom(InputStream in) throws IOException {
   7.125 +        try {
   7.126 +            SAXReader reader = new SAXReader(true);
   7.127 +            reader.setStripWhitespaceText(true);
   7.128 +            
   7.129 +            reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
   7.130 +                Constants.SCHEMA_VALIDATION_FEATURE, true);
   7.131 +            reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
   7.132 +                Constants.SCHEMA_LOCATION, String.format("%s %s",
   7.133 +                    NAMESPACE.getURI(), Resources.class.getResource(
   7.134 +                        "graph-document.xsd").toExternalForm()));
   7.135 +            
   7.136 +            final AtomicBoolean passed = new AtomicBoolean(true);
   7.137 +            final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
   7.138 +            // TODO: we should do more detailed error handling here
   7.139 +            reader.setErrorHandler(new ErrorHandler() {
   7.140 +                @Override
   7.141 +                public void warning(SAXParseException exception) throws SAXException {
   7.142 +                }
   7.143 +
   7.144 +                @Override
   7.145 +                public void error(SAXParseException exception) throws SAXException {
   7.146 +                    xmlerror.set(exception);
   7.147 +                    passed.set(false);
   7.148 +                }
   7.149 +
   7.150 +                @Override
   7.151 +                public void fatalError(SAXParseException exception) throws SAXException {
   7.152 +                    xmlerror.set(exception);
   7.153 +                    passed.set(false);
   7.154 +                }
   7.155 +                
   7.156 +            });
   7.157 +            Document doc = reader.read(in);
   7.158 +            if (!passed.get()) {
   7.159 +                // TODO: provide details (maybe via separate error object?)
   7.160 +                throw xmlerror.get();
   7.161 +            }
   7.162 +            
   7.163 +            doc.normalize();
   7.164 +            
   7.165 +            Element root = doc.getRootElement();
   7.166 +            metadata.read(root.element(TAG_METADATA));
   7.167 +            
   7.168 +            if (graph instanceof Graph) {
   7.169 +                readGraph(root.element(TAG_GRAPH));
   7.170 +            } else if (graph instanceof Digraph) {
   7.171 +                readGraph(root.element(TAG_DIGRAPH));
   7.172 +            } else {
   7.173 +                throw new IOException("unsupported graph type");
   7.174 +            }
   7.175 +        } catch (DocumentException | SAXException ex) {
   7.176 +            throw new IOException(ex);
   7.177 +        }
   7.178 +    }
   7.179 +}
     8.1 --- a/test/javatestfile.java	Mon Apr 24 20:54:38 2023 +0200
     8.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.3 @@ -1,176 +0,0 @@
     8.4 -/*
     8.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     8.6 - *
     8.7 - * Copyright 2014 Mike Becker. All rights reserved.
     8.8 - *
     8.9 - * Redistribution and use in source and binary forms, with or without
    8.10 - * modification, are permitted provided that the following conditions are met:
    8.11 - *
    8.12 - *   1. Redistributions of source code must retain the above copyright
    8.13 - *      notice, this list of conditions and the following disclaimer.
    8.14 - *
    8.15 - *   2. Redistributions in binary form must reproduce the above copyright
    8.16 - *      notice, this list of conditions and the following disclaimer in the
    8.17 - *      documentation and/or other materials provided with the distribution.
    8.18 - *
    8.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    8.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    8.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    8.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    8.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    8.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    8.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    8.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    8.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    8.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    8.29 - * POSSIBILITY OF SUCH DAMAGE.
    8.30 - *
    8.31 - */
    8.32 -
    8.33 -package de.uapcore.sigred.doc.base;
    8.34 -
    8.35 -import de.uapcore.sigred.doc.Resources;
    8.36 -import de.uapcore.sigrapi.impl.Digraph;
    8.37 -import de.uapcore.sigrapi.impl.Graph;
    8.38 -import de.uapcore.sigrapi.IGraph;
    8.39 -import java.io.IOException;
    8.40 -import java.io.InputStream;
    8.41 -import java.io.OutputStream;
    8.42 -import java.util.concurrent.atomic.AtomicBoolean;
    8.43 -import java.util.concurrent.atomic.AtomicReference;
    8.44 -import org.apache.xerces.impl.Constants;
    8.45 -import org.dom4j.Document;
    8.46 -import org.dom4j.DocumentException;
    8.47 -import org.dom4j.DocumentHelper;
    8.48 -import org.dom4j.Element;
    8.49 -import org.dom4j.Namespace;
    8.50 -import org.dom4j.QName;
    8.51 -import org.dom4j.io.OutputFormat;
    8.52 -import org.dom4j.io.SAXReader;
    8.53 -import org.dom4j.io.XMLWriter;
    8.54 -import org.xml.sax.ErrorHandler;
    8.55 -import org.xml.sax.SAXException;
    8.56 -import org.xml.sax.SAXParseException;
    8.57 -
    8.58 -public abstract class AbstractGraphDocument<T extends IGraph>
    8.59 -        extends FileBackedDocument {
    8.60 -    
    8.61 -    protected static final Namespace NAMESPACE = Namespace.get("sigred",
    8.62 -        "http://develop.uap-core.de/sigred/");
    8.63 -    
    8.64 -    private static final
    8.65 -        QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
    8.66 -    private static final
    8.67 -        QName TAG_GRAPH = QName.get("graph", NAMESPACE);
    8.68 -    private static final
    8.69 -        QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
    8.70 -    private static final
    8.71 -        QName TAG_METADATA = QName.get("metadata", NAMESPACE);
    8.72 -    
    8.73 -    protected final T graph;
    8.74 -    
    8.75 -    private final GraphDocumentMetadata metadata;
    8.76 -    
    8.77 -    public AbstractGraphDocument(Class<T> graphType) {
    8.78 -        T g;
    8.79 -        try {
    8.80 -            g = graphType.newInstance();
    8.81 -        } catch (ReflectiveOperationException e) {
    8.82 -            assert false;
    8.83 -            g = null; // for the compiler
    8.84 -        }
    8.85 -        graph = g;
    8.86 -        metadata = new GraphDocumentMetadata();
    8.87 -    }
    8.88 -
    8.89 -    public T getGraph() {
    8.90 -        return graph;
    8.91 -    }
    8.92 -    
    8.93 -    public GraphDocumentMetadata getMetadata() {
    8.94 -        return metadata;
    8.95 -    }
    8.96 -
    8.97 -    protected abstract void writeGraph(Element rootNode) throws IOException;
    8.98 -    protected abstract void readGraph(Element rootNode) throws IOException;
    8.99 -
   8.100 -    @Override
   8.101 -    public void writeTo(OutputStream out) throws IOException {
   8.102 -        Document doc = DocumentHelper.createDocument();
   8.103 -
   8.104 -        Element rootNode = doc.addElement(TAG_GRAPHDOC);
   8.105 -
   8.106 -        Element metadataNode = rootNode.addElement(TAG_METADATA);
   8.107 -
   8.108 -        metadata.write(metadataNode);
   8.109 -
   8.110 -        if (graph instanceof Graph) {
   8.111 -            writeGraph(rootNode.addElement(TAG_GRAPH));
   8.112 -        } else if (graph instanceof Digraph) {
   8.113 -            writeGraph(rootNode.addElement(TAG_DIGRAPH));
   8.114 -        } else {
   8.115 -            throw new IOException("unsupported graph type");
   8.116 -        }
   8.117 -
   8.118 -        XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
   8.119 -        writer.write(doc);
   8.120 -        writer.flush();
   8.121 -    }
   8.122 -
   8.123 -    @Override
   8.124 -    public void readFrom(InputStream in) throws IOException {
   8.125 -        try {
   8.126 -            SAXReader reader = new SAXReader(true);
   8.127 -            reader.setStripWhitespaceText(true);
   8.128 -            
   8.129 -            reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
   8.130 -                Constants.SCHEMA_VALIDATION_FEATURE, true);
   8.131 -            reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
   8.132 -                Constants.SCHEMA_LOCATION, String.format("%s %s",
   8.133 -                    NAMESPACE.getURI(), Resources.class.getResource(
   8.134 -                        "graph-document.xsd").toExternalForm()));
   8.135 -            
   8.136 -            final AtomicBoolean passed = new AtomicBoolean(true);
   8.137 -            final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
   8.138 -            // TODO: we should do more detailed error handling here
   8.139 -            reader.setErrorHandler(new ErrorHandler() {
   8.140 -                @Override
   8.141 -                public void warning(SAXParseException exception) throws SAXException {
   8.142 -                }
   8.143 -
   8.144 -                @Override
   8.145 -                public void error(SAXParseException exception) throws SAXException {
   8.146 -                    xmlerror.set(exception);
   8.147 -                    passed.set(false);
   8.148 -                }
   8.149 -
   8.150 -                @Override
   8.151 -                public void fatalError(SAXParseException exception) throws SAXException {
   8.152 -                    xmlerror.set(exception);
   8.153 -                    passed.set(false);
   8.154 -                }
   8.155 -                
   8.156 -            });
   8.157 -            Document doc = reader.read(in);
   8.158 -            if (!passed.get()) {
   8.159 -                // TODO: provide details (maybe via separate error object?)
   8.160 -                throw xmlerror.get();
   8.161 -            }
   8.162 -            
   8.163 -            doc.normalize();
   8.164 -            
   8.165 -            Element root = doc.getRootElement();
   8.166 -            metadata.read(root.element(TAG_METADATA));
   8.167 -            
   8.168 -            if (graph instanceof Graph) {
   8.169 -                readGraph(root.element(TAG_GRAPH));
   8.170 -            } else if (graph instanceof Digraph) {
   8.171 -                readGraph(root.element(TAG_DIGRAPH));
   8.172 -            } else {
   8.173 -                throw new IOException("unsupported graph type");
   8.174 -            }
   8.175 -        } catch (DocumentException | SAXException ex) {
   8.176 -            throw new IOException(ex);
   8.177 -        }
   8.178 -    }
   8.179 -}
     9.1 --- a/test/plain.csp	Mon Apr 24 20:54:38 2023 +0200
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,6 +0,0 @@
     9.4 -</body>
     9.5 -</html>
     9.6 -<!c
     9.7 -pblock_free(q);
     9.8 -!>
     9.9 -
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/test/plain.txt	Mon Apr 24 21:01:41 2023 +0200
    10.3 @@ -0,0 +1,6 @@
    10.4 +</body>
    10.5 +</html>
    10.6 +<!c
    10.7 +pblock_free(q);
    10.8 +!>
    10.9 +

mercurial