Since the STEP format specification is expensive and difficult to obtain (at least it was the last time I looked), I have not obtained a copy or studied how it defines the representation of NURBS curves and surfaces, though I have studied the IGES format specification extensively. As I understand, NURBS in most formats are stored suboptimally, i.e., with an extra knot at each end of the knot vectors. That’s a shame, because the only reason for NURBS to exist at all is because they are a compressed format for representing rational Bezier curves and surfaces.
Anyway, it is essential that your NURBS code be consistent with your NURBS data; if one is suboptimal, then both must be, if one is optimal, then both must be. For you to write general NURBS code, you have to detect and compensate for the differences.
NURBS are nothing more than a complicated way of representing a sequence of the much simpler rational Beziers, so that a few bytes might possibly be saved.
One of the purposes of the knot vector is to define the amount of redundancy removed from each junction of rational Beziers in the NURBS representation. When the knot multiplicity equals the NURBS degree, then the junction that knot multiplicity represents has had no redundancy removed. That is either because the degree of continuity is zero at that junction of rational Beziers, or because not all the redundancy was removed in defining the NURBS. When the knot multiplicity is one less the the degree of the NURBS curve, then one level of redundancy has been removed, and the junction represented by that knot multiplicity has a degree of continuity of one (which will be higher if not all the redundancy was removed). Etc. The redundancy I refer to has to do with the control points.
For example, in your case you said that the order of your NURBS surface is four in both dimensions. That means the degree is three, so each of the NURBS curves defining your NURBS surface consists of one or more cubic rational Bezier curves. Cubic Bezier curves require four control points each. The control points of Bezier curves define the derivatives of the Bezier curve at its endpoints. The degree of continuity at the junction of two Bezier curves is the number of derivatives at that junction that match. NURBS exploit the degree of continuity and the matching derivatives to remove redundant control points, which is reflected in the knot vector. The control points aren’t necessarily simply removed, though, instead several may be replaced with a set of the appropriate intermediate control points as defined by the de Castlejau algorithm.
That, incidentally, is a presentation on NURBS curves unlike any you are likely to see anywhere else. It seems nearly everyone else likes to talk about NURBS curves in terms of basis functions, even though basis functions actually have nothing to do with anything, though they do just happen to be true.
The point of me writing all that is to say that there isn’t a simple correspondence between the knots in a knot vector and the control points. Really, the only reasonable way to deal with interior knots is to convert a NURBS curve to its sequence of equivalent rational Bezier curves. Then you don’t have any knot vector at all, and life becomes easy. It’s easy to exactly convert a NURBS curve into an equivalent sequence of rational Bezier curves with the de Boor blossom algorithm. However, all of this requires that your NURBS curves are defined consistent with the way your NURBS evaluator works, and this gets back to the knot vectors you have.
In any case, if the NURBS curves are defined correctly (disregarding the ends of the knot vectors), and it is just a question of whether or not the ends of the knot vectors are defined optimally/consistent with the NURBS evaluator code, then all you need to know is how to determine whether there are the curve’s order or degree number of knots required at the ends of the vector. In an optimally defined NURBS curve, there are as many control points as the number of knots plus one minus the degree of the curve. If there are two more knots than that, then you can assume that the NURBS curve has been defined suboptimally and the first and last knot of the vector is mathematically unnecessary. The easiest way to write NURBS code that works universally is to write it for the optimal definition, and then determine whether the NURBS curve is consistent with that or not (as described by the relation I provided above). If the NURBS curve has been defined suboptimally, begin indexing the knot vector from its second element rather than from its first element.
On the other hand, if you have to live with code that requires suboptimal NURBS curves/surfaces, and your NURBS has been defined optimally, then you need to insert a dummy value at each end of each knot vector so that it will be consistent with the algorithm. The dummy knot values must still be reasonable, however, and that means that each must be a duplicate of the value it precedes (at the start) or follows (at the end).
I know all this sounds confusing. Believe it or not, one of the main attractions of Bezier curves is their simplicity (in every aspect). Sometimes people lose sight of the point of something and in a desire to improve it, just make it more complicated instead. That’s what NURBS are. Maybe in the days when disk storage cost thousands of dollars per megabyte, saving a few bytes at the expense of increased complexity made sense. Today, with sub $100 terabyte hard disks, NURBS have outlived their usefulness (if they ever really had any).