• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ebourg / jsign / #418

23 Jul 2026 12:50PM UTC coverage: 81.28% (+0.5%) from 80.829%
#418

push

ebourg
CodeQL workflow

5397 of 6640 relevant lines covered (81.28%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.53
/jsign-core/src/main/java/net/jsign/ChannelUtils.java
1
/*
2
 * Copyright 2021 Emmanuel Bourg
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package net.jsign;
18

19
import java.io.ByteArrayOutputStream;
20
import java.io.File;
21
import java.io.IOException;
22
import java.nio.ByteBuffer;
23
import java.nio.channels.ByteChannel;
24
import java.nio.channels.SeekableByteChannel;
25
import java.nio.channels.WritableByteChannel;
26
import java.nio.file.FileSystemException;
27
import java.nio.file.Files;
28
import java.nio.file.StandardOpenOption;
29
import java.security.MessageDigest;
30

31
/**
32
 * Helper class for Channel operations.
33
 *
34
 * @since 4.0
35
 */
36
public class ChannelUtils {
×
37

38
    /**
39
     * Opens a channel for the specified file with read/write access, falling back to read-only access
40
     * if the file is not writable or locked.
41
     *
42
     * @since 8.0
43
     */
44
    public static SeekableByteChannel openReadWriteOrReadOnly(File file) throws IOException {
45
        try {
46
            return Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE);
1✔
47
        } catch (FileSystemException e) {
1✔
48
            return Files.newByteChannel(file.toPath(), StandardOpenOption.READ);
1✔
49
        }
50
    }
51

52
    public static void copy(SeekableByteChannel src, WritableByteChannel dest) throws IOException {
53
        ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
1✔
54
        src.position(0);
1✔
55

56
        while (src.position() < src.size()) {
1✔
57
            buffer.clear();
1✔
58
            src.read(buffer);
1✔
59
            buffer.flip();
1✔
60
            dest.write(buffer);
1✔
61
        }
62
    }
1✔
63

64
    public static void copy(SeekableByteChannel src, SeekableByteChannel dest, long length) throws IOException {
65
        ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
1✔
66
        long remaining = length;
1✔
67
        long destOffset = dest.position();
1✔
68
        long srcOffset = src.position();
1✔
69
        while (remaining > 0) {
1✔
70
            int avail = (int) Math.min(remaining, buffer.capacity());
1✔
71
            buffer.clear();
1✔
72
            buffer.limit(avail);
1✔
73

74
            src.position(srcOffset);
1✔
75
            src.read(buffer);
1✔
76
            buffer.flip();
1✔
77

78
            dest.position(destOffset);
1✔
79
            dest.write(buffer);
1✔
80
            remaining -= buffer.position();
1✔
81
            srcOffset += buffer.position();
1✔
82
            destOffset += buffer.position();
1✔
83
        }
1✔
84
    }
1✔
85

86
    /**
87
     * Insert data into a SeekableByteChannel at the specified position,
88
     * shifting the data after the insertion point.
89
     */
90
    public static void insert(SeekableByteChannel channel, long position, byte[] data) throws IOException {
91
        if (position > channel.size()) {
1✔
92
            throw new IOException("Cannot insert data after the end of the file");
×
93
        }
94

95
        File backupFile = File.createTempFile("jsign", ".tmp");
1✔
96
        try (SeekableByteChannel backupChannel = Files.newByteChannel(backupFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE)) {
1✔
97
            copy(channel, backupChannel);
1✔
98

99
            channel.position(position);
1✔
100
            channel.write(ByteBuffer.wrap(data));
1✔
101

102
            backupChannel.position(position);
1✔
103
            copy(backupChannel, channel, backupChannel.size() - position);
1✔
104
        } finally {
105
            backupFile.delete();
1✔
106
        }
107
    }
1✔
108

109
    /**
110
     * Remove data from a SeekableByteChannel, shifting the data after the deletion point.
111
     */
112
    public static void delete(SeekableByteChannel channel, long position, long length) throws IOException {
113
        delete(channel, position, length, 1024 * 1024);
1✔
114
    }
1✔
115

116
    public static void delete(SeekableByteChannel channel, long position, long length, int bufferSize) throws IOException {
117
        if (position + length > channel.size()) {
1✔
118
            throw new IOException("Cannot delete data after the end of the file");
1✔
119
        }
120

121
        ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
1✔
122
        long remaining = channel.size() - position - length;
1✔
123
        while (remaining > 0) {
1✔
124
            buffer.clear();
1✔
125
            buffer.limit((int) Math.min(remaining, buffer.capacity()));
1✔
126

127
            channel.position(position + length);
1✔
128
            channel.read(buffer);
1✔
129
            buffer.flip();
1✔
130

131
            channel.position(position);
1✔
132
            channel.write(buffer);
1✔
133
            remaining -= buffer.position();
1✔
134
            position += buffer.position();
1✔
135
        }
136
        
137
        channel.truncate(channel.size() - length);
1✔
138
    }
1✔
139

140
    /**
141
     * Update the specified digest by reading the SeekableByteChannel
142
     * from the start offset included to the end offset excluded.
143
     *
144
     * @param digest      the message digest to update
145
     * @param startOffset the start offset
146
     * @param endOffset   the end offset
147
     * @throws IOException if an I/O error occurs
148
     */
149
    public static void updateDigest(SeekableByteChannel channel, MessageDigest digest, long startOffset, long endOffset) throws IOException {
150
        channel.position(startOffset);
1✔
151

152
        ByteBuffer buffer = ByteBuffer.allocate(65536);
1✔
153

154
        long position = startOffset;
1✔
155
        while (position < endOffset) {
1✔
156
            buffer.clear();
1✔
157
            buffer.limit((int) Math.min(buffer.capacity(), endOffset - position));
1✔
158
            channel.read(buffer);
1✔
159
            buffer.rewind();
1✔
160

161
            digest.update(buffer);
1✔
162

163
            position += buffer.limit();
1✔
164
        }
165
    }
1✔
166

167
    /**
168
     * Read a null terminated string from the specified channel.
169
     */
170
    public static byte[] readNullTerminatedString(ByteChannel channel) throws IOException {
171
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
1✔
172
            byte singleChar;
173
            ByteBuffer buffer = ByteBuffer.allocate(1);
1✔
174
            do {
175
                buffer.clear();
1✔
176
                buffer.limit(1);
1✔
177
                if (channel.read(buffer) < 0) {
1✔
178
                    throw new IOException("End of file reached, cannot read null terminated string");
1✔
179
                }
180
                buffer.flip();
1✔
181
                singleChar = buffer.array()[0];
1✔
182
                bos.write(singleChar);
1✔
183
            } while (singleChar != 0);
1✔
184
            return bos.toByteArray();
1✔
185
        }
186
    }
187
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc